"""客户与供应商(partner)模型。 设计: - 客户 (Customer) 与供应商 (Supplier) 共享 PriceLevel 价格等级字典 - 联系人 (Contact) 可挂在任一客户/供应商上 - 客户/供应商都有期初应收/应付余额、累计金额、信用额度等 """ from django.db import models from apps.core.base_models import TenantScopedModel class PriceLevel(TenantScopedModel): """价格等级(VIP1、VIP2、批发、零售 等)。""" code = models.CharField(max_length=32) name = models.CharField(max_length=64) discount_rate = models.DecimalField( max_digits=6, decimal_places=4, default=0, help_text="默认折扣率" ) sort_order = models.IntegerField(default=0) class Meta: db_table = "partner_price_level" unique_together = [("tenant", "code")] ordering = ["sort_order", "code"] verbose_name_plural = "价格等级" def __str__(self): return f"{self.code} {self.name}" class Contact(TenantScopedModel): """联系人(可挂在 customer 或 supplier 上)。""" name = models.CharField(max_length=64) phone = models.CharField(max_length=32, blank=True, default="") email = models.EmailField(blank=True, default="") title = models.CharField(max_length=64, blank=True, default="", help_text="职位/称呼") customer = models.ForeignKey( "Customer", null=True, blank=True, on_delete=models.CASCADE, related_name="contacts", ) supplier = models.ForeignKey( "Supplier", null=True, blank=True, on_delete=models.CASCADE, related_name="contacts", ) is_primary = models.BooleanField(default=False, help_text="是否主联系人") class Meta: db_table = "partner_contact" verbose_name_plural = "联系人" def __str__(self): return f"{self.name} ({self.phone})" class Customer(TenantScopedModel): """客户。""" KIND_CHOICES = [ ("individual", "个人"), ("company", "企业"), ] code = models.CharField(max_length=64, help_text="客户编码(租户内唯一)") name = models.CharField(max_length=128) kind = models.CharField(max_length=16, choices=KIND_CHOICES, default="company") contact_name = models.CharField(max_length=64, blank=True, default="") phone = models.CharField(max_length=32, blank=True, default="") email = models.EmailField(blank=True, default="") address = models.CharField(max_length=255, blank=True, default="") tax_number = models.CharField(max_length=64, blank=True, default="", help_text="税号") bank_account = models.CharField(max_length=64, blank=True, default="") price_level = models.ForeignKey( PriceLevel, null=True, blank=True, on_delete=models.SET_NULL, related_name="customers", ) credit_limit = models.DecimalField( max_digits=18, decimal_places=4, default=0, help_text="信用额度" ) initial_receivable = models.DecimalField( max_digits=18, decimal_places=4, default=0, help_text="期初应收" ) is_active = models.BooleanField(default=True) remark = models.TextField(blank=True, default="") class Meta: db_table = "partner_customer" unique_together = [("tenant", "code")] verbose_name_plural = "客户" indexes = [ models.Index(fields=["tenant", "code"]), models.Index(fields=["tenant", "name"]), models.Index(fields=["tenant", "phone"]), ] ordering = ["code"] def __str__(self): return f"{self.code} {self.name}" class Supplier(TenantScopedModel): """供应商。""" code = models.CharField(max_length=64) name = models.CharField(max_length=128) contact_name = models.CharField(max_length=64, blank=True, default="") phone = models.CharField(max_length=32, blank=True, default="") email = models.EmailField(blank=True, default="") address = models.CharField(max_length=255, blank=True, default="") tax_number = models.CharField(max_length=64, blank=True, default="") bank_account = models.CharField(max_length=64, blank=True, default="") initial_payable = models.DecimalField( max_digits=18, decimal_places=4, default=0, help_text="期初应付" ) is_active = models.BooleanField(default=True) remark = models.TextField(blank=True, default="") class Meta: db_table = "partner_supplier" unique_together = [("tenant", "code")] verbose_name_plural = "供应商" indexes = [ models.Index(fields=["tenant", "code"]), models.Index(fields=["tenant", "name"]), ] ordering = ["code"] def __str__(self): return f"{self.code} {self.name}" class CustomerProductPrice(TenantScopedModel): """客户专属价格本(客户 × 商品 → 成交价,取价优先级最高)。""" customer = models.ForeignKey( Customer, on_delete=models.CASCADE, related_name="product_prices" ) product = models.ForeignKey( "catalog.Product", on_delete=models.CASCADE, related_name="customer_prices" ) price = models.DecimalField(max_digits=18, decimal_places=4) class Meta: db_table = "partner_customer_product_price" unique_together = [("tenant", "customer", "product")] verbose_name_plural = "客户专属价" ordering = ["customer", "product"] def __str__(self): return f"{self.customer.code}/{self.product.code} = {self.price}"