166 lines
5.5 KiB
Python
166 lines
5.5 KiB
Python
"""客户与供应商(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}"
|