some first models of pos & add required to attributes

This commit is contained in:
2025-07-21 09:39:31 +03:30
parent c87204c134
commit ebc79a7dbd
14 changed files with 446 additions and 17 deletions

View File

@@ -254,6 +254,15 @@ CACHES = {
} }
} }
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis://:ydnW4hwzuDRYcTX3FWCHgQ1f@apo.liara.cloud:33740/0")],
},
},
}
REST_CAPTCHA = { REST_CAPTCHA = {
'CAPTCHA_CACHE': 'default', 'CAPTCHA_CACHE': 'default',
'CAPTCHA_TIMEOUT': 300, # 5 minutes 'CAPTCHA_TIMEOUT': 300, # 5 minutes

View File

@@ -0,0 +1,32 @@
# Generated by Django 5.0 on 2025-07-20 08:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0026_organizationstats'),
]
operations = [
migrations.RemoveField(
model_name='organizationstats',
name='total_buyers',
),
migrations.AddField(
model_name='organizationstats',
name='active_quotas_weight',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='organizationstats',
name='closed_quotas_weight',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='organizationstats',
name='total_quotas_weight',
field=models.PositiveBigIntegerField(default=0),
),
]

View File

@@ -143,10 +143,12 @@ class OrganizationStats(BaseModel):
null=True null=True
) )
total_quota_received = models.PositiveBigIntegerField(default=0) total_quota_received = models.PositiveBigIntegerField(default=0)
active_quotas_weight = models.PositiveBigIntegerField(default=0)
closed_quotas_weight = models.PositiveBigIntegerField(default=0)
total_quotas_weight = models.PositiveBigIntegerField(default=0)
total_distributed = models.PositiveBigIntegerField(default=0) total_distributed = models.PositiveBigIntegerField(default=0)
total_inventory_in = models.PositiveBigIntegerField(default=0) total_inventory_in = models.PositiveBigIntegerField(default=0)
total_sold = models.PositiveBigIntegerField(default=0) total_sold = models.PositiveBigIntegerField(default=0)
total_buyers = models.PositiveBigIntegerField(default=0)
def __str__(self): def __str__(self):
return f'Organization: {self.organization.name}' return f'Organization: {self.organization.name}'

View File

@@ -1,11 +1,43 @@
from django.db.models import Sum from django.db.models import Sum
from apps.product.models import QuotaDistribution from apps.product.models import QuotaDistribution
from apps.warehouse.models import InventoryQuotaSaleTransaction from apps.warehouse.models import InventoryQuotaSaleTransaction
from apps.authentication.models import Organization, OrganizationStats
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
def update_organization_stats(instance: Organization):
""" update all stats of organization from quota """
if hasattr(instance, 'stats'):
stat = instance.stats
else:
stat = OrganizationStats.objects.create(organization=instance)
# set organization stats from quotas, distributions transactions & etc
stat.total_quota_received = instance.assigned_quotas.count()
stat.active_quotas_weight = instance.assigned_quotas.filter(is_closed=False).count()
stat.closed_quotas_weight = instance.assigned_quotas.filter(is_closed=True).count()
stat.total_distributed = instance.distributions.count()
stat.total_inventory_in = instance.inventory.count()
stat.total_sold = instance.distributions.all().aggregate(
total_sold=Sum('been_sold')
)['total_sold'] or 0
stat.save(update_fields=[
"total_quota_received",
"active_quotas_weight",
"closed_quotas_weight",
"total_distributed",
"total_inventory_in",
"total_sold",
])
@receiver([post_save, post_delete], sender=QuotaDistribution) @receiver([post_save, post_delete], sender=QuotaDistribution)
@receiver([post_save, post_delete], sender=InventoryQuotaSaleTransaction) @receiver([post_save, post_delete], sender=InventoryQuotaSaleTransaction)
def update_organization_stats(sender, instance, **kwargs): def organization_stats(sender, instance, **kwargs):
pass if sender == QuotaDistribution:
update_organization_stats(instance.assigned_organization)
elif sender == InventoryQuotaSaleTransaction:
update_organization_stats(instance.inventory_entry.organization)

View File

@@ -24,7 +24,42 @@ class MyConsumer(AsyncWebsocketConsumer):
unit = await get_unit(int(message)) unit = await get_unit(int(message))
print("Received:", message) print("Received:", message)
# پاسخ به کلاینت
await self.send(text_data=json.dumps({ await self.send(text_data=json.dumps({
"message": unit "message": unit.unit
})) }))
class SendFromServerConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.group_name = 'mojtaba_group' # noqa
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
await self.send(text_data=json.dumps({"message": "Connected!"}))
async def disconnect(self, code):
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)
async def receive(self, text_data=None, bytes_data=None):
data = json.loads(text_data)
msg = data.get("message", "")
await self.channel_layer.group_send(
self.group_name,
{
"type": "chat.message",
"message": msg
}
)
async def chat_message(self, event):
message = event['message']
await self.send(text_data=json.dumps({
"message": message
}))

View File

@@ -2,5 +2,5 @@ from django.urls import re_path
from apps.authentication.websocket import consumer from apps.authentication.websocket import consumer
websocket_urlpatterns = [ websocket_urlpatterns = [
re_path(r"ws/somepath/$", consumer.MyConsumer.as_asgi()), re_path(r"ws/somepath/$", consumer.SendFromServerConsumer.as_asgi()),
] ]

View File

@@ -0,0 +1,92 @@
# Generated by Django 5.0 on 2025-07-21 06:06
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pos_device', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='device',
name='acceptor',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='device',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='devices', to='pos_device.paymentcompany'),
),
migrations.AddField(
model_name='device',
name='latitude',
field=models.DecimalField(decimal_places=10, max_digits=20, null=True),
),
migrations.AddField(
model_name='device',
name='longitude',
field=models.DecimalField(decimal_places=10, max_digits=20, null=True),
),
migrations.AddField(
model_name='device',
name='multi_device',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='device',
name='password',
field=models.CharField(max_length=25, null=True),
),
migrations.AddField(
model_name='device',
name='serial',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='device',
name='server_in',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='device',
name='terminal',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='paymentcompany',
name='activation',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='paymentcompany',
name='name_en',
field=models.CharField(max_length=250, null=True),
),
migrations.AddField(
model_name='paymentcompany',
name='name_fa',
field=models.CharField(max_length=250, null=True),
),
migrations.CreateModel(
name='DeviceVersion',
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)),
('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

@@ -3,10 +3,41 @@ from apps.core.models import BaseModel
class PaymentCompany(BaseModel): class PaymentCompany(BaseModel):
pass name_fa = models.CharField(max_length=250, null=True)
name_en = models.CharField(max_length=250, null=True)
activation = models.BooleanField(default=False)
def __str__(self):
return f'Payment Company: {self.name_fa}-{self.id}'
def save(self, *args, **kwargs):
return super(PaymentCompany, self).save(*args, **kwargs)
class Device(BaseModel): class Device(BaseModel):
acceptor = models.IntegerField(default=0)
terminal = models.IntegerField(default=0)
serial = models.TextField(null=True)
password = models.CharField(max_length=25, null=True)
multi_device = models.BooleanField(default=False)
server_in = models.BooleanField(default=False)
latitude = models.DecimalField(max_digits=20, decimal_places=10, null=True)
longitude = models.DecimalField(max_digits=20, decimal_places=10, null=True)
company = models.ForeignKey(
PaymentCompany,
on_delete=models.CASCADE,
related_name='devices',
null=True
)
def __str__(self):
return f'Device: {self.serial} - {self.id}'
def save(self, *args, **kwargs):
return super(Device, self).save(*args, **kwargs)
class DeviceVersion(BaseModel):
pass pass

View File

@@ -0,0 +1,47 @@
# Generated by Django 5.0 on 2025-07-20 08:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0051_quotabrokervalue_value'),
]
operations = [
migrations.RemoveField(
model_name='productstats',
name='total_quota',
),
migrations.AddField(
model_name='productstats',
name='active_quotas_weight',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='productstats',
name='closed_quotas_weight',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='productstats',
name='quotas_number',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='productstats',
name='total_distributed_weight',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='productstats',
name='total_quota_weight',
field=models.PositiveBigIntegerField(default=0),
),
migrations.AddField(
model_name='productstats',
name='total_warehouse_entry',
field=models.PositiveBigIntegerField(default=0),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0 on 2025-07-21 06:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0052_remove_productstats_total_quota_and_more'),
]
operations = [
migrations.AddField(
model_name='attribute',
name='required',
field=models.BooleanField(default=False),
),
]

View File

@@ -141,8 +141,13 @@ class ProductStats(BaseModel):
related_name='stats', related_name='stats',
null=True null=True
) )
total_quota = models.PositiveBigIntegerField(default=0) quotas_number = models.PositiveBigIntegerField(default=0)
active_quotas_weight = models.PositiveBigIntegerField(default=0)
closed_quotas_weight = models.PositiveBigIntegerField(default=0)
total_quota_weight = models.PositiveBigIntegerField(default=0)
total_remaining = models.PositiveBigIntegerField(default=0) total_remaining = models.PositiveBigIntegerField(default=0)
total_distributed_weight = models.PositiveBigIntegerField(default=0)
total_warehouse_entry = models.PositiveBigIntegerField(default=0)
total_sold = models.PositiveBigIntegerField(default=0) total_sold = models.PositiveBigIntegerField(default=0)
total_transactions = models.PositiveBigIntegerField(default=0) total_transactions = models.PositiveBigIntegerField(default=0)
@@ -150,7 +155,7 @@ class ProductStats(BaseModel):
return f'Product: {self.product.name}-{self.product.id} stats' return f'Product: {self.product.name}-{self.product.id} stats'
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
return super(ProductStats).save(*args, **kwargs) return super(ProductStats, self).save(*args, **kwargs)
class Attribute(BaseModel): class Attribute(BaseModel):
@@ -170,7 +175,7 @@ class Attribute(BaseModel):
related_name="attributes", related_name="attributes",
null=True null=True
) )
required = models.BooleanField(default=False)
is_global = models.BooleanField(default=False) is_global = models.BooleanField(default=False)
def __str__(self): def __str__(self):

View File

@@ -1,8 +1,18 @@
from django.db.models import Sum from django.db.models import Sum
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from .models import QuotaDistribution, Quota, Product from .models import (
from apps.warehouse.models import InventoryQuotaSaleTransaction QuotaDistribution,
Quota,
Product,
ProductStats,
QuotaStats
)
from apps.warehouse.models import (
InventoryQuotaSaleTransaction,
InventoryEntry
)
from django.db import models
def recalculate_remaining_amount(quota): def recalculate_remaining_amount(quota):
@@ -22,15 +32,113 @@ def update_quota_remaining(sender, instance, **kwargs):
def update_product_stats(instance: Product): def update_product_stats(instance: Product):
pass """ update all stats of product """
if hasattr(instance, 'stats'):
stat = instance.stats
else:
stat = ProductStats.objects.create(product=instance)
# number of quotas
quotas_count = instance.quotas.filter(is_closed=False).count() # noqa
# total weight of product that assigned in quota
active_quotas_weight = instance.quotas.filter(is_closed=False).aggregate(
total=models.Sum('quota_weight')
)['total'] or 0
closed_quotas_weight = instance.quotas.filter(is_closed=True).aggregate( # noqa
total=models.Sum('quota_weight')
)['total'] or 0
total_quotas_weight = instance.quotas.all().aggregate( # noqa
total=models.Sum('quota_weight')
)['total'] or 0
# total remaining weight of product quotas
total_remaining_quotas_weight = instance.quotas.filter(is_closed=False).aggregate( # noqa
total=models.Sum('remaining_weight')
)['total'] or 0
# product total distributed weight from quota
total_distributed_weight = QuotaDistribution.objects.filter(
quota__product_id=instance.id,
quota__is_closed=False
).aggregate(total_weight=models.Sum('weight'))['total_weight'] or 0
# total sold of product from quota
total_sold = QuotaDistribution.objects.filter(
quota__product_id=instance.id,
quota__is_closed=False
).aggregate(total_sold=models.Sum('been_sold'))['total_sold'] or 0
# total entry from product to inventory
total_warehouse_entry = QuotaDistribution.objects.filter(
quota__product_id=instance.id,
quota__is_closed=False
).aggregate(total_entry=models.Sum('warehouse_entry'))['total_entry'] or 0
stat.quotas_number = quotas_count
stat.active_quotas_weight = active_quotas_weight
stat.closed_quotas_weight = closed_quotas_weight
stat.total_quota_weight = total_quotas_weight
stat.total_remaining = total_remaining_quotas_weight
stat.total_distributed_weight = total_distributed_weight
stat.total_warehouse_entry = total_warehouse_entry
stat.total_sold = total_sold
stat.save(update_fields=[
"quotas_number",
"active_quotas_weight",
"closed_quotas_weight",
"total_quota_weight",
"total_remaining",
"total_distributed_weight",
"total_warehouse_entry",
"total_sold",
])
def update_quota_stats(instance: Quota): def update_quota_stats(instance: Quota):
pass """ update all stats of quota """
if hasattr(instance, 'stats'):
stat = instance.stats
else:
stat = QuotaStats.objects.create(quota=instance)
total_distributed = instance.quota_distributed
total_remaining = instance.remaining_weight
# total entry to inventory from quota
total_inventory = InventoryEntry.objects.filter(
distribution__quota=instance
).aggregate(
total_inventory=models.Sum('weight')
)['total_inventory'] or 0
# total sale of distributions from quota
total_sale = instance.distributions_assigned.filter(
quota__is_closed=False
).aggregate(total_sale=models.Sum('been_sold'))['total_sale'] or 0
stat.total_distributed = total_distributed
stat.remaining = total_remaining
stat.total_inventory = total_inventory
stat.total_sale = total_sale
stat.save(update_fields=[
"total_distributed",
"remaining",
"total_inventory",
"total_sale",
])
@receiver([post_save, post_delete], sender=QuotaDistribution) @receiver([post_save, post_delete], sender=QuotaDistribution)
@receiver([post_save, post_delete], sender=InventoryQuotaSaleTransaction) @receiver([post_save, post_delete], sender=InventoryQuotaSaleTransaction)
def update_stats_on_change(sender, instance, **kwargs): def update_stats_on_change(sender, instance, **kwargs):
update_product_stats(instance) if sender == QuotaDistribution:
update_quota_stats(instance) update_product_stats(instance.quota.product)
update_quota_stats(instance.quota)
elif sender == InventoryQuotaSaleTransaction:
update_product_stats(instance.quota_distribution.quota.product)
update_quota_stats(instance.quota_distribution.quota)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0 on 2025-07-20 08:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('warehouse', '0011_inventoryquotasaletransaction'),
]
operations = [
migrations.AddField(
model_name='inventoryentry',
name='balance',
field=models.PositiveBigIntegerField(default=0),
),
]

View File

@@ -18,7 +18,7 @@ class InventoryEntry(BaseModel):
null=True null=True
) )
weight = models.PositiveBigIntegerField(default=0) weight = models.PositiveBigIntegerField(default=0)
balance = models balance = models.PositiveBigIntegerField(default=0)
lading_number = models.CharField(max_length=50, null=True) lading_number = models.CharField(max_length=50, null=True)
delivery_address = models.TextField(blank=True, null=True) delivery_address = models.TextField(blank=True, null=True)
document = models.CharField(max_length=250, null=True) document = models.CharField(max_length=250, null=True)