add purchace limitations system for rancher - add incentive plans to rancher entries data

This commit is contained in:
2025-08-26 12:18:10 +03:30
parent b5dfcebafe
commit c0b6b8ddca
12 changed files with 142 additions and 14 deletions

View File

@@ -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

View File

@@ -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

View 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,
},
),
]

View File

@@ -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

View File

@@ -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__'

View File

@@ -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 = [

View 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'),
),
]

View File

@@ -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}'

View File

@@ -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()

View File

@@ -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

View File

@@ -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,

View File

@@ -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']