diff --git a/apps/product/migrations/0076_incentiveplanrancher.py b/apps/product/migrations/0076_incentiveplanrancher.py new file mode 100644 index 0000000..79340a9 --- /dev/null +++ b/apps/product/migrations/0076_incentiveplanrancher.py @@ -0,0 +1,39 @@ +# Generated by Django 5.0 on 2025-09-23 12:12 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('herd', '0018_rancher_dhi_state'), + ('livestock', '0015_livestocktype_en_name_alter_livestocktype_name'), + ('product', '0075_historicalquotadistribution_free_sale_balance_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='IncentivePlanRancher', + 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)), + ('allowed_quantity', models.PositiveBigIntegerField(default=0)), + ('used_quantity', models.PositiveBigIntegerField(default=0)), + ('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)), + ('livestock_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rancher_plans', to='livestock.livestocktype')), + ('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)), + ('plan', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rancher_plans', to='product.incentiveplan')), + ('rancher', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plans', to='herd.rancher')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index ae45a74..e080e6a 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -6,6 +6,7 @@ from apps.authorization.models import UserRelations from apps.authentication.models import OrganizationType, Organization from django.contrib.postgres.fields import ArrayField from apps.livestock.models import LiveStockType +from apps.herd.models import Rancher from datetime import datetime import jdatetime @@ -312,6 +313,35 @@ class IncentivePlan(BaseModel): return super(IncentivePlan, self).save(*args, **kwargs) +class IncentivePlanRancher(BaseModel): + plan = models.ForeignKey( + IncentivePlan, + on_delete=models.CASCADE, + related_name='rancher_plans', + null=True + ) + rancher = models.ForeignKey( + Rancher, + on_delete=models.CASCADE, + related_name='plans', + null=True + ) + livestock_type = models.ForeignKey( + LiveStockType, + on_delete=models.CASCADE, + related_name='rancher_plans', + null=True + ) + allowed_quantity = models.PositiveBigIntegerField(default=0) + used_quantity = models.PositiveBigIntegerField(default=0) + + def __str__(self): + return f'{self.plan.name}-{self.rancher.first_name}-{self.livestock_type.name}' + + def save(self, *args, **kwargs): + return super(IncentivePlanRancher, self).save(*args, **kwargs) + + class Quota(BaseModel): """ quota for product with some conditions """ @@ -428,7 +458,7 @@ class QuotaStats(BaseModel): def __str__(self): return f'Quota: {self.quota.quota_id} stats' - + def save(self, *args, **kwargs): return super(QuotaStats, self).save(*args, **kwargs) diff --git a/apps/product/web/api/v1/serializers/product_serializers.py b/apps/product/web/api/v1/serializers/product_serializers.py index 284cb56..634b958 100644 --- a/apps/product/web/api/v1/serializers/product_serializers.py +++ b/apps/product/web/api/v1/serializers/product_serializers.py @@ -155,3 +155,9 @@ class IncentivePlanSerializer(serializers.ModelSerializer): # noqa class Meta: model = product_models.IncentivePlan fields = '__all__' + + +class IncentivePlanRancherSerializer(serializers.ModelSerializer): + class Meta: + model = product_models.IncentivePlanRancher + fields = '__all__' diff --git a/apps/product/web/api/v1/urls.py b/apps/product/web/api/v1/urls.py index 8d800f8..2282430 100644 --- a/apps/product/web/api/v1/urls.py +++ b/apps/product/web/api/v1/urls.py @@ -14,6 +14,7 @@ router.register(r'attribute_value', product_api.AttributeValueViewSet, basename= router.register(r'broker', product_api.BrokerViewSet, basename='broker') router.register(r'sale_unit', product_api.SaleUnitViewSet, basename='sale_unit') router.register(r'incentive_plan', product_api.IncentivePlanViewSet, basename='incentive_plan') +router.register(r'rancher_incentive_plan', product_api.IncentivePlanRancherViewSet, basename='rancher_incentive_plan') router.register(r'stats', product_api.ProductStatsViewSet, basename='stats') router.register(r'quota', quota_api.QuotaViewSet, basename='quota') router.register(r'quota_distribution', distribution_apis.QuotaDistributionViewSet, basename='quota_distribution') diff --git a/apps/product/web/api/v1/viewsets/product_api.py b/apps/product/web/api/v1/viewsets/product_api.py index 6294386..811f574 100644 --- a/apps/product/web/api/v1/viewsets/product_api.py +++ b/apps/product/web/api/v1/viewsets/product_api.py @@ -2,12 +2,14 @@ import datetime from apps.product.web.api.v1.serializers import product_serializers as product_serializers from apps.product.web.api.v1.serializers import quota_serializers from apps.core.mixins.soft_delete_mixin import SoftDeleteMixin +from apps.core.mixins.search_mixin import DynamicSearchMixin from common.helpers import get_organization_by_user from rest_framework.exceptions import APIException from apps.product import models as product_models from rest_framework.response import Response from rest_framework.decorators import action from rest_framework import viewsets, filters +from common.tools import CustomOperations from rest_framework import status from django.db import transaction from django.db.models import Q @@ -27,7 +29,7 @@ def delete(queryset, pk): obj.delete() -class ProductCategoryViewSet(viewsets.ModelViewSet, SoftDeleteMixin): +class ProductCategoryViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): queryset = product_models.ProductCategory.objects.all() serializer_class = product_serializers.ProductCategorySerializer filter_backends = [filters.SearchFilter] @@ -65,7 +67,7 @@ class ProductCategoryViewSet(viewsets.ModelViewSet, SoftDeleteMixin): return Response(e, status=status.HTTP_204_NO_CONTENT) -class ProductViewSet(viewsets.ModelViewSet, SoftDeleteMixin): +class ProductViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): queryset = product_models.Product.objects.all() serializer_class = product_serializers.ProductSerializer filter_backends = [filters.SearchFilter] @@ -144,7 +146,7 @@ class ProductViewSet(viewsets.ModelViewSet, SoftDeleteMixin): return Response(e, status=status.HTTP_204_NO_CONTENT) -class ProductStatsViewSet(viewsets.ModelViewSet, SoftDeleteMixin): +class ProductStatsViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): """ product statistics by its quotas """ queryset = product_models.ProductStats.objects.all() @@ -175,7 +177,7 @@ class ProductStatsViewSet(viewsets.ModelViewSet, SoftDeleteMixin): raise e -class AttributeViewSet(viewsets.ModelViewSet, SoftDeleteMixin): +class AttributeViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): """ attributes of reference product """ # queryset = product_models.Attribute.objects.select_related('product').all() @@ -233,7 +235,7 @@ class AttributeViewSet(viewsets.ModelViewSet, SoftDeleteMixin): return Response(e, status=status.HTTP_204_NO_CONTENT) -class AttributeValueViewSet(viewsets.ModelViewSet, SoftDeleteMixin): +class AttributeValueViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): """ apis for attribute values of child products """ # noqa queryset = product_models.AttributeValue.objects.all() @@ -271,7 +273,7 @@ class AttributeValueViewSet(viewsets.ModelViewSet, SoftDeleteMixin): return Response(e, status=status.HTTP_204_NO_CONTENT) -class BrokerViewSet(viewsets.ModelViewSet, SoftDeleteMixin): +class BrokerViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): """ apis of product brokers """ # noqa queryset = product_models.Broker.objects.all() @@ -311,7 +313,7 @@ class BrokerViewSet(viewsets.ModelViewSet, SoftDeleteMixin): return Response(e, status=status.HTTP_204_NO_CONTENT) -class SaleUnitViewSet(viewsets.ModelViewSet, SoftDeleteMixin): +class SaleUnitViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): """ apis of unit of sale for products """ # noqa queryset = product_models.SaleUnit.objects.all() @@ -351,7 +353,7 @@ class SaleUnitViewSet(viewsets.ModelViewSet, SoftDeleteMixin): return Response(e, status=status.HTTP_204_NO_CONTENT) -class IncentivePlanViewSet(viewsets.ModelViewSet, SoftDeleteMixin): # noqa +class IncentivePlanViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): # noqa """ apis for incentive plan """ queryset = product_models.IncentivePlan.objects.all() @@ -439,3 +441,8 @@ class IncentivePlanViewSet(viewsets.ModelViewSet, SoftDeleteMixin): # noqa return Response(status=status.HTTP_200_OK) except APIException as e: return Response(e, status=status.HTTP_204_NO_CONTENT) + + +class IncentivePlanRancherViewSet(viewsets.ModelViewSet, SoftDeleteMixin, DynamicSearchMixin): + queryset = product_models.IncentivePlanRancher.objects.all() + serializer_class = product_serializers.IncentivePlanRancherSerializer