1
0
forked from erp-dev/erp

feat: sales return order and purchase return order

This commit is contained in:
2025-12-01 16:52:59 +08:00
parent a3b4af2602
commit fae33809ac
17 changed files with 1745 additions and 6 deletions

View File

@@ -318,6 +318,224 @@ class SalesOrderItem(ModelBase):
return round(self.price * self.real_quantity(), 2)
class PurchaseReturnStatusEnum(models.IntegerChoices):
PENDING = 1, '审批中'
APPROVED = 2, '审批通过'
CANCELLED = 3, '作废'
class PurchaseReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
"""采购退货单(库存出库 + 供应商应付减少)"""
id = models.BigAutoField(primary_key=True)
merchant = models.ForeignKey(
basic_info_models.Merchant,
on_delete=models.PROTECT,
related_name='purchase_return_orders',
verbose_name='所属商户',
)
supplier = models.ForeignKey(
basic_info_models.Supplier,
on_delete=models.PROTECT,
related_name='purchase_return_orders',
verbose_name='供应商',
)
purchase_order = models.ForeignKey(
'PurchaseOrder',
on_delete=models.SET_NULL,
related_name='return_orders',
null=True,
blank=True,
verbose_name='原采购单',
)
return_date = models.DateField(verbose_name='退货日期')
operator = models.ForeignKey(
basic_info_models.Employee,
on_delete=models.PROTECT,
related_name='purchase_return_orders',
verbose_name='经办人',
)
warehouse = models.ForeignKey(
basic_info_models.WareHouse,
on_delete=models.PROTECT,
related_name='purchase_return_orders',
verbose_name='仓库',
)
status = models.IntegerField(
choices=PurchaseReturnStatusEnum.choices,
default=PurchaseReturnStatusEnum.PENDING,
verbose_name='状态',
)
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
class Meta:
verbose_name = '采购退货单'
verbose_name_plural = '采购退货单'
def __str__(self):
return f'采购退货 {self.id} - {self.supplier.name}'
def get_direction(self) -> int:
return -1
def get_counterparty_field_name(self) -> str:
return 'supplier'
class PurchaseReturnOrderItem(ModelBase):
id = models.BigAutoField(primary_key=True)
purchase_return_order = models.ForeignKey(
PurchaseReturnOrder,
on_delete=models.CASCADE,
related_name='items',
verbose_name='采购退货单',
)
product = models.ForeignKey(
basic_info_models.Product,
on_delete=models.PROTECT,
related_name='purchase_return_order_items',
verbose_name='产品',
)
price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价')
color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量')
unit = models.CharField(max_length=50, verbose_name='单位')
spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
empty_diff_percent = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='空差百分比')
quantity_of_rolls = models.CharField(max_length=255, null=True, blank=True, verbose_name='各条数数量')
num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
consume_detail_ids = models.CharField(
max_length=255,
null=True,
blank=True,
verbose_name='关联入库明细ID',
)
batch_number = models.CharField(null=True, blank=True, max_length=100, verbose_name='批次号')
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
class Meta:
verbose_name = '采购退货明细'
verbose_name_plural = '采购退货明细'
def real_quantity(self):
return round(self.quantity * (1 - self.empty_diff_percent / 100), 2)
def diff_quantity(self):
return round(self.quantity * (self.empty_diff_percent / 100), 2)
def total_amount(self):
return round(self.price * self.real_quantity(), 2)
class SalesReturnStatusEnum(models.IntegerChoices):
PENDING = 1, '审批中'
APPROVED = 2, '审批通过'
CANCELLED = 3, '作废'
class SalesReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
"""销售退货单(库存入库 + 客户应收减少)"""
id = models.BigAutoField(primary_key=True)
merchant = models.ForeignKey(
basic_info_models.Merchant,
on_delete=models.PROTECT,
related_name='sales_return_orders',
verbose_name='所属商户',
)
customer = models.ForeignKey(
basic_info_models.Customer,
on_delete=models.PROTECT,
related_name='sales_return_orders',
verbose_name='客户',
)
sales_order = models.ForeignKey(
'SalesOrder',
on_delete=models.SET_NULL,
related_name='return_orders',
null=True,
blank=True,
verbose_name='原销售单',
)
return_date = models.DateField(verbose_name='退货日期')
operator = models.ForeignKey(
basic_info_models.Employee,
on_delete=models.PROTECT,
related_name='sales_return_orders',
verbose_name='经办人',
)
warehouse = models.ForeignKey(
basic_info_models.WareHouse,
on_delete=models.PROTECT,
related_name='sales_return_orders',
verbose_name='仓库',
)
status = models.IntegerField(
choices=SalesReturnStatusEnum.choices,
default=SalesReturnStatusEnum.PENDING,
verbose_name='状态',
)
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
class Meta:
verbose_name = '销售退货单'
verbose_name_plural = '销售退货单'
def __str__(self):
return f'销售退货 {self.id} - {self.customer.name}'
def get_direction(self) -> int:
return 1
def get_counterparty_field_name(self) -> str:
return 'customer'
class SalesReturnOrderItem(ModelBase):
id = models.BigAutoField(primary_key=True)
sales_return_order = models.ForeignKey(
SalesReturnOrder,
on_delete=models.CASCADE,
related_name='items',
verbose_name='销售退货单',
)
product = models.ForeignKey(
basic_info_models.Product,
on_delete=models.PROTECT,
related_name='sales_return_order_items',
verbose_name='产品',
)
price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价')
color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量')
unit = models.CharField(max_length=50, verbose_name='单位')
spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
empty_diff_percent = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='空差百分比')
quantity_of_rolls = models.CharField(max_length=255, null=True, blank=True, verbose_name='各条数数量')
num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
consume_detail_ids = models.CharField(
max_length=255,
null=True,
blank=True,
verbose_name='关联入库明细ID',
)
batch_number = models.CharField(null=True, blank=True, max_length=100, verbose_name='批次号')
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
class Meta:
verbose_name = '销售退货明细'
verbose_name_plural = '销售退货明细'
def real_quantity(self):
return round(self.quantity * (1 - self.empty_diff_percent / 100), 2)
def diff_quantity(self):
return round(self.quantity * (self.empty_diff_percent / 100), 2)
def total_amount(self):
return round(self.price * self.real_quantity(), 2)
class PaymentOrderStatusEnum(models.IntegerChoices):
PENDING = 1, '审批中'
APPROVED = 2, '审批通过'
@@ -474,6 +692,8 @@ class BalanceChangeSourceEnum(models.IntegerChoices):
SALES_ORDER = 2, '销售单'
PAYMENT_ORDER = 3, '付款单'
RECEIPT_ORDER = 4, '收款单'
PURCHASE_RETURN_ORDER = 5, '采购退货单'
SALES_RETURN_ORDER = 6, '销售退货单'
class BalanceChangeDirectionEnum(models.IntegerChoices):