transaction system deploying

This commit is contained in:
2025-09-09 15:15:04 +03:30
parent 5307a27d20
commit 9678bf2c21
11 changed files with 241 additions and 32 deletions

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.0 on 2025-09-09 08:55
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0072_alter_quota_base_price_cooperative_and_more'),
('warehouse', '0016_inventoryquotasaletransaction_additional_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='InventoryQuotaSaleItem',
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)),
('weight', models.PositiveBigIntegerField(default=0)),
('unit_price', models.PositiveBigIntegerField(default=0)),
('total_price', 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)),
('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)),
('product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_items', to='product.product')),
('transaction', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='warehouse.inventoryquotasaletransaction')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.0 on 2025-09-09 08:55
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0017_inventoryquotasaleitem'),
]
operations = [
migrations.RemoveField(
model_name='inventoryquotasaletransaction',
name='product',
),
migrations.RemoveField(
model_name='inventoryquotasaletransaction',
name='weight',
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.0 on 2025-09-09 11:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0072_alter_quota_base_price_cooperative_and_more'),
('warehouse', '0018_remove_inventoryquotasaletransaction_product_and_more'),
]
operations = [
migrations.AddField(
model_name='inventoryquotasaleitem',
name='quota_distribution',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_items', to='product.quotadistribution'),
),
migrations.AddField(
model_name='inventoryquotasaletransaction',
name='items_total_weight',
field=models.PositiveBigIntegerField(default=0),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.0 on 2025-09-09 11:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0019_inventoryquotasaleitem_quota_distribution_and_more'),
]
operations = [
migrations.RemoveField(
model_name='inventoryquotasaletransaction',
name='items_total_weight',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0 on 2025-09-09 11:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0020_remove_inventoryquotasaletransaction_items_total_weight'),
]
operations = [
migrations.AddField(
model_name='inventoryquotasaletransaction',
name='weight',
field=models.PositiveBigIntegerField(default=0),
),
]

View File

@@ -88,14 +88,8 @@ class InventoryQuotaSaleTransaction(BaseModel):
related_name='inventory_sales',
null=True
)
weight = models.DecimalField(max_digits=12, decimal_places=2, null=True)
weight = models.PositiveBigIntegerField(default=0)
delivery_address = models.TextField(blank=True, null=True)
product = models.ForeignKey(
product_models.Product,
on_delete=models.CASCADE,
related_name='sale_transactions',
null=True
)
transaction_price = models.PositiveBigIntegerField(default=0)
price_paid = models.PositiveBigIntegerField(default=0)
type_of_price = (
@@ -121,17 +115,43 @@ class InventoryQuotaSaleTransaction(BaseModel):
payer_cart = models.CharField(max_length=50, null=True)
additional = models.JSONField(default=dict)
def buyers_count(self):
""" number of buyers from specific inventory """
unique_buyers_count = self.objects.filter(
inventory_entry=self.inventory_entry
).values('buyer_user').distinct().count()
return unique_buyers_count
@property
def total_weight(self):
""" summation of total sold product weight """
return sum(item.weight for item in self.items.all)
def __str__(self):
return f"Inventory Sale: {self.transaction_id}-{self.quota_distribution.distribution_id}"
def save(self, *args, **kwargs):
super(InventoryQuotaSaleTransaction, self).save(*args, **kwargs)
class InventoryQuotaSaleItem(BaseModel):
transaction = models.ForeignKey(
InventoryQuotaSaleTransaction,
on_delete=models.CASCADE,
related_name='items',
null=True
)
quota_distribution = models.ForeignKey(
product_models.QuotaDistribution,
on_delete=models.CASCADE,
related_name='sale_items',
null=True
)
product = models.ForeignKey(
product_models.Product,
on_delete=models.CASCADE,
related_name='sale_items',
null=True
)
weight = models.PositiveBigIntegerField(default=0)
unit_price = models.PositiveBigIntegerField(default=0)
total_price = models.PositiveBigIntegerField(default=0)
def __str__(self):
return f'Item {self.product} - {self.weight} Kg - {self.total_price}'
def save(self, *args, **kwargs):
return super(InventoryQuotaSaleItem, self).save(*args, **kwargs)

View File

@@ -84,7 +84,7 @@ class InventoryEntryViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDevice
class InventoryQuotaSaleTransactionViewSet(viewsets.ModelViewSet, DynamicSearchMixin, POSDeviceMixin):
queryset = warehouse_models.InventoryQuotaSaleTransaction.objects.all()
queryset = warehouse_models.InventoryQuotaSaleTransaction.objects.all().prefetch_related('items')
serializer_class = warehouse_serializers.InventoryQuotaSaleTransactionSerializer
permission_classes = [AllowAny]
search_fields = [

View File

@@ -8,11 +8,12 @@ from apps.product.services.services import (
from apps.pos_device.services.services import pos_organizations_sharing_information
from apps.pos_device.pos.api.v1.serializers.device import DeviceSerializer
from apps.herd.pos.api.v1.serializers import RancherSerializer
from apps.warehouse import models as warehouse_models
from apps.warehouse.exceptions import (
TotalInventorySaleException
)
from apps.warehouse import models as warehouse_models
from rest_framework import serializers
from apps.herd.models import Rancher
from django.db import models
@@ -95,25 +96,62 @@ class InventoryEntrySerializer(serializers.ModelSerializer):
class InventoryQuotaSaleTransactionSerializer(serializers.ModelSerializer):
rancher_national_code = serializers.CharField(max_length=50, required=False)
class Meta: # noqa
model = warehouse_models.InventoryQuotaSaleTransaction
fields = '__all__'
depth = 0
def create(self, validated_data):
items_data = self.context['request'].data['items']
# get rancher with national code
rancher = Rancher.objects.get(national_code=validated_data.pop('rancher_national_code'))
validated_data.update({'rancher': rancher})
# if transaction exists, update transaction status
transaction = self.Meta.model.objects.filter(
transaction_id=validated_data.get('transaction_id')
)
if transaction.exists():
obj = transaction.first()
obj.transaction_status = validated_data.get('transaction_status')
obj.save(update_fields=['transaction_status'])
return obj
# create transaction record
transaction = warehouse_models.InventoryQuotaSaleTransaction.objects.create(
**validated_data
)
# calculate total price of product items in shopping cart
total_price = 0
for item_data in items_data:
item = warehouse_models.InventoryQuotaSaleItem.objects.create(
transaction=transaction,
**item_data
)
total_price += item.total_price
transaction.transaction_price = total_price
transaction.save()
return transaction
def validate(self, attrs):
"""
validate total inventory sale should be fewer than
inventory entry from distribution
"""
inventory_entry = attrs['inventory_entry']
distribution = attrs['quota_distribution']
if 'quota_distribution' in attrs.keys():
distribution = attrs['quota_distribution']
total_sale_weight = inventory_entry.inventory_sales.aggregate(
total=models.Sum('weight')
)['total'] or 0
total_sale_weight = distribution.inventory_sales.aggregate(
total=models.Sum('weight')
)['total'] or 0
if total_sale_weight + attrs['weight'] > distribution.warehouse_balance:
raise TotalInventorySaleException()
if total_sale_weight + attrs['weight'] > distribution.warehouse_balance:
raise TotalInventorySaleException()
return attrs
@@ -122,10 +160,29 @@ class InventoryQuotaSaleTransactionSerializer(serializers.ModelSerializer):
representation = super().to_representation(instance)
representation['rancher'] = RancherSerializer(instance.rancher).data
if instance.rancher:
representation['rancher'] = RancherSerializer(instance.rancher).data
representation['pos_device'] = DeviceSerializer(instance.pos_device).data
representation['seller_organization'] = instance.seller_organization.name
representation['inventory_entry'] = InventoryEntrySerializer(instance.inventory_entry).data
if instance.seller_organization:
representation['seller_organization'] = instance.seller_organization.name
if instance.inventory_entry:
representation['inventory_entry'] = InventoryEntrySerializer(instance.inventory_entry).data
return representation
class InventoryQuotaSaleItemSerializer(serializers.ModelSerializer):
product_name = serializers.CharField(source='product.name', read_only=True)
class Meta:
model = warehouse_models.InventoryQuotaSaleItem
fields = [
'id',
"transaction",
"product",
"product_name",
"weight",
"unit_price",
"total_price",
]

View File

@@ -34,4 +34,7 @@ def update_distribution_warehouse_entry(sender, instance, **kwargs):
@receiver(post_save, sender=InventoryQuotaSaleTransaction)
@receiver(post_delete, sender=InventoryQuotaSaleTransaction)
def update_distribution_warehouse_sold_and_balance(sender, instance, **kwargs):
warehouse_sold_and_balance(instance.quota_distribution)
if instance.quota_distribution:
warehouse_sold_and_balance(instance.quota_distribution)
else:
print("quota distribution is null - warehouse app signals")