add purchace limitations system for rancher - add incentive plans to rancher entries data
This commit is contained in:
@@ -9,3 +9,11 @@ class TokenBlackListedException(APIException):
|
|||||||
status_code = status.HTTP_401_UNAUTHORIZED
|
status_code = status.HTTP_401_UNAUTHORIZED
|
||||||
default_detail = _('unauthorized')
|
default_detail = _('unauthorized')
|
||||||
default_code = 'unauthorized'
|
default_code = 'unauthorized'
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationBankAccountException(APIException):
|
||||||
|
""" if organization does not have bank account """
|
||||||
|
|
||||||
|
status_code = status.HTTP_403_FORBIDDEN
|
||||||
|
default_detail = "برای این سازمان حساب بانکی تعریف نشده است, ابتدا حساب بانکی تعریف کنید" # noqa
|
||||||
|
default_code = "برای این سازمان حساب بانکی تعریف نشده است" # noqa
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
from rest_framework import viewsets
|
from apps.core.serializers import MobileTestSerializer, SystemConfigSerializer
|
||||||
from apps.core.models import MobileTest
|
from apps.core.models import MobileTest, SystemConfig
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from apps.core.serializers import MobileTestSerializer
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
|
||||||
class MobileTestViewSet(viewsets.ModelViewSet):
|
class MobileTestViewSet(viewsets.ModelViewSet):
|
||||||
queryset = MobileTest.objects.all()
|
queryset = MobileTest.objects.all()
|
||||||
serializer_class = MobileTestSerializer
|
serializer_class = MobileTestSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SystemConfigViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = SystemConfig.objects.all()
|
||||||
|
serializer_class = SystemConfigSerializer
|
||||||
|
|||||||
34
apps/core/migrations/0007_systemconfig.py
Normal file
34
apps/core/migrations/0007_systemconfig.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-08-26 07:49
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0006_mobiletest_creator_info_mobiletest_modifier_info'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SystemConfig',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('creator_info', models.CharField(max_length=100, null=True)),
|
||||||
|
('modifier_info', models.CharField(max_length=100, null=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('key', models.CharField(max_length=100, null=True)),
|
||||||
|
('value', models.CharField(max_length=100, null=True)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -44,3 +44,15 @@ class MobileTest(BaseModel):
|
|||||||
longitude = models.DecimalField(max_digits=22, decimal_places=16)
|
longitude = models.DecimalField(max_digits=22, decimal_places=16)
|
||||||
count = models.IntegerField(default=0)
|
count = models.IntegerField(default=0)
|
||||||
time = models.DateTimeField(auto_now_add=True)
|
time = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
|
||||||
|
class SystemConfig(BaseModel):
|
||||||
|
key = models.CharField(max_length=100, null=True)
|
||||||
|
value = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, key, default=None):
|
||||||
|
try:
|
||||||
|
return cls.objects.get(key=key).value
|
||||||
|
except cls.DoesNotExist:
|
||||||
|
return default
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from apps.core.models import MobileTest
|
from apps.core.models import MobileTest, SystemConfig
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
@@ -6,3 +6,9 @@ class MobileTestSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = MobileTest
|
model = MobileTest
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SystemConfigSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SystemConfig
|
||||||
|
fields = '__all__'
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
from apps.core.api import MobileTestViewSet, SystemConfigViewSet
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from apps.core.api import MobileTestViewSet
|
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register('mobile_test', MobileTestViewSet, basename='mobile_test')
|
router.register('mobile_test', MobileTestViewSet, basename='mobile_test')
|
||||||
|
router.register('system_config', SystemConfigViewSet, basename='system_config')
|
||||||
app_name = "core"
|
app_name = "core"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
18
apps/herd/migrations/0017_rancher_ignore_purchase_limit.py
Normal file
18
apps/herd/migrations/0017_rancher_ignore_purchase_limit.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-08-26 07:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('herd', '0016_rancher_activity_rancher_heavy_livestock_number_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rancher',
|
||||||
|
name='ignore_purchase_limit',
|
||||||
|
field=models.BooleanField(default=False, help_text='if its true rancher has not buy limitations'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -127,6 +127,9 @@ class Rancher(BaseModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
without_herd = models.BooleanField(default=False)
|
without_herd = models.BooleanField(default=False)
|
||||||
|
ignore_purchase_limit = models.BooleanField(
|
||||||
|
default=False, help_text="if its true rancher has not buy limitations"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'rancher: {self.first_name} {self.last_name}'
|
return f'rancher: {self.first_name} {self.last_name}'
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from apps.pos_device.web.api.v1.serilaizers import device as device_serializer
|
from apps.pos_device.web.api.v1.serilaizers import device as device_serializer
|
||||||
|
from apps.authentication.exceptions import OrganizationBankAccountException
|
||||||
from apps.authorization.api.v1.serializers import UserRelationSerializer
|
from apps.authorization.api.v1.serializers import UserRelationSerializer
|
||||||
from apps.pos_device.web.api.v1.viewsets.client import POSClientViewSet
|
from apps.pos_device.web.api.v1.viewsets.client import POSClientViewSet
|
||||||
from apps.authentication.api.v1.api import (
|
from apps.authentication.api.v1.api import (
|
||||||
UserViewSet,
|
|
||||||
Organization,
|
Organization,
|
||||||
|
BankAccountInformation,
|
||||||
OrganizationSerializer
|
OrganizationSerializer
|
||||||
)
|
)
|
||||||
from apps.core.mixins.search_mixin import DynamicSearchMixin
|
from apps.core.mixins.search_mixin import DynamicSearchMixin
|
||||||
@@ -176,10 +177,17 @@ class DeviceAssignmentViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
# if client will be an organization
|
# if client will be an organization
|
||||||
if request.data['client_data']['is_organization']:
|
if request.data['client_data']['is_organization']:
|
||||||
|
|
||||||
|
# check if organization have bank account or raise exception
|
||||||
|
if not BankAccountInformation.objects.filter(
|
||||||
|
organization_id=request.data['client_data']['organization']
|
||||||
|
).exists():
|
||||||
|
raise OrganizationBankAccountException()
|
||||||
|
|
||||||
|
# check if organization is a client before
|
||||||
client = pos_models.POSClient.objects.filter(
|
client = pos_models.POSClient.objects.filter(
|
||||||
organization_id=request.data['client_data']['organization']
|
organization_id=request.data['client_data']['organization']
|
||||||
)
|
)
|
||||||
|
|
||||||
if client.exists():
|
if client.exists():
|
||||||
request.data.update({'client': client.first().id})
|
request.data.update({'client': client.first().id})
|
||||||
|
|
||||||
@@ -276,6 +284,13 @@ class StakeHoldersViewSet(viewsets.ModelViewSet, DynamicSearchMixin):
|
|||||||
|
|
||||||
stakeholders_data = []
|
stakeholders_data = []
|
||||||
for stakeholder in request.data['stakeholders']:
|
for stakeholder in request.data['stakeholders']:
|
||||||
|
|
||||||
|
# check if organization have bank account or raise exception
|
||||||
|
if not BankAccountInformation.objects.filter(
|
||||||
|
organization_id=stakeholder['organization']
|
||||||
|
).exists():
|
||||||
|
raise OrganizationBankAccountException()
|
||||||
|
|
||||||
serializer = self.serializer_class(data=stakeholder)
|
serializer = self.serializer_class(data=stakeholder)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
|||||||
@@ -22,10 +22,25 @@ def quota_live_stock_allocation_info(quota: Quota) -> typing.Any:
|
|||||||
|
|
||||||
allocations = quota.livestock_allocations.filter(quota=quota)
|
allocations = quota.livestock_allocations.filter(quota=quota)
|
||||||
|
|
||||||
allocations_list = [{
|
if allocations:
|
||||||
"name": alloc.livestock_type.name,
|
allocations_list = [{
|
||||||
"quantity": alloc.quantity_kg
|
"name": alloc.livestock_type.name,
|
||||||
} for alloc in allocations]
|
"quantity": alloc.quantity_kg
|
||||||
|
} for alloc in allocations]
|
||||||
|
|
||||||
return allocations_list
|
return allocations_list
|
||||||
|
|
||||||
|
|
||||||
|
def quota_incentive_plans_info(quota: Quota) -> typing.Any:
|
||||||
|
""" information of quota incentive plans """
|
||||||
|
|
||||||
|
incentive_plans = quota.incentive_assignments.all()
|
||||||
|
|
||||||
|
if incentive_plans:
|
||||||
|
incentive_plans_list = [{
|
||||||
|
'name': plan.incentive_plan.name,
|
||||||
|
'heavy_value': plan.heavy_value,
|
||||||
|
'light_value': plan.light_value
|
||||||
|
} for plan in incentive_plans]
|
||||||
|
|
||||||
|
return incentive_plans_list
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
from apps.herd.services.services import get_rancher_statistics, rancher_quota_weight
|
from apps.herd.services.services import get_rancher_statistics, rancher_quota_weight
|
||||||
from apps.product.services.services import quota_live_stock_allocation_info
|
from apps.product.services.services import (
|
||||||
|
quota_live_stock_allocation_info,
|
||||||
|
quota_incentive_plans_info
|
||||||
|
)
|
||||||
from apps.pos_device.pos.api.v1.serializers.device import DeviceSerializer
|
from apps.pos_device.pos.api.v1.serializers.device import DeviceSerializer
|
||||||
from apps.herd.pos.api.v1.serializers import RancherSerializer
|
from apps.herd.pos.api.v1.serializers import RancherSerializer
|
||||||
from apps.warehouse.exceptions import (
|
from apps.warehouse.exceptions import (
|
||||||
@@ -46,7 +49,8 @@ class InventoryEntrySerializer(serializers.ModelSerializer):
|
|||||||
'quota_weight': instance.distribution.quota.quota_weight,
|
'quota_weight': instance.distribution.quota.quota_weight,
|
||||||
'quota_livestock_allocations': quota_live_stock_allocation_info(
|
'quota_livestock_allocations': quota_live_stock_allocation_info(
|
||||||
instance.distribution.quota
|
instance.distribution.quota
|
||||||
)
|
),
|
||||||
|
'quota_incentive_plans': quota_incentive_plans_info(instance.distribution.quota)
|
||||||
}
|
}
|
||||||
representation['product'] = {
|
representation['product'] = {
|
||||||
'name': instance.distribution.quota.product.name,
|
'name': instance.distribution.quota.product.name,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from apps.warehouse.models import InventoryEntry, InventoryQuotaSaleTransaction
|
from apps.warehouse.models import InventoryEntry, InventoryQuotaSaleTransaction
|
||||||
from apps.herd.services.services import rancher_quota_weight, get_rancher_statistics
|
from apps.herd.services.services import rancher_quota_weight, get_rancher_statistics
|
||||||
|
from apps.core.models import SystemConfig
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
|
|
||||||
|
|
||||||
@@ -17,6 +18,12 @@ def get_total_sold(inventory_entry, rancher):
|
|||||||
def can_buy_from_inventory(rancher, inventory_entry: InventoryEntry):
|
def can_buy_from_inventory(rancher, inventory_entry: InventoryEntry):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
|
if SystemConfig.get("IGNORE_ALL_RANCHER_PURCHASE_LIMITS") == "true":
|
||||||
|
return True
|
||||||
|
|
||||||
|
if rancher.ignore_purchase_limit:
|
||||||
|
return True
|
||||||
|
|
||||||
quota_weight = rancher_quota_weight(rancher, inventory_entry) # {total, by_type}
|
quota_weight = rancher_quota_weight(rancher, inventory_entry) # {total, by_type}
|
||||||
total_allowed = quota_weight['total']
|
total_allowed = quota_weight['total']
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user