Files
Django/gvsdsdk/order/models.py

449 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:订单 SAAS 数据模型
SAAS 级订单模板与订单实例——支持等级定价、可选绑定支付网关。
设计原则:
1. 模板驱动:通过 OrderTemplate 定义订单结构,子服务基于模板快速创建订单
2. 等级定价OrderLevel 定义不同等级的价格/权益,订单可关联等级
3. 支付可选:订单可选绑定 PaymentGatewayConfig不绑定则不涉及支付
4. 不可变快照:订单创建后,模板变更不影响已有订单
"""
import datetime
from django.db import models
from gvsdsdk.model_base import QModel
from gvsdsdk.models import UUIDField
# === 订单等级表 ===
class OrderLevel(QModel):
"""订单等级表——定义订单模板的定价阶梯
例如VIP 月卡、VIP 季卡、SVIP 年卡等。
每个等级属于一个订单模板,有独立的价格和有效期。
"""
LevelUUID = UUIDField(primary_key=True, verbose_name='等级UUID')
Template = models.ForeignKey(
'OrderTemplate', on_delete=models.CASCADE,
related_name='Levels',
verbose_name='订单模板',
)
LevelCode = models.CharField(max_length=64, verbose_name='等级编码')
LevelName = models.CharField(max_length=128, verbose_name='等级名称')
LevelDescription = models.CharField(max_length=512, default='', verbose_name='等级描述')
Price = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='价格')
OriginalPrice = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='原价')
Currency = models.CharField(max_length=10, default='CNY', verbose_name='币种')
DurationDays = models.IntegerField(default=0, verbose_name='有效天数')
DurationMonths = models.IntegerField(default=0, verbose_name='有效月数')
LevelSort = models.SmallIntegerField(default=0, verbose_name='排序')
LevelStatus = models.SmallIntegerField(default=1, verbose_name='等级状态')
LevelMetadata = models.JSONField(default=dict, verbose_name='等级元数据')
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
UpdateTime = models.DateTimeField(null=True, blank=True, verbose_name='更新时间')
class Meta:
db_table = 'OrderLevel'
app_label = 'gvsdsdk'
verbose_name = '订单等级'
verbose_name_plural = '订单等级'
managed = False
unique_together = [('Template', 'LevelCode')]
ordering = ['LevelSort']
indexes = [
models.Index(fields=['Template', 'LevelStatus'], name='idx_ordlvl_template_status'),
]
def __str__(self):
return f'{self.LevelName}({self.LevelCode})'
@property
def IsActive(self):
return self.LevelStatus == 1
def GetDuration(self):
"""获取有效时长(天)"""
return self.DurationDays + self.DurationMonths * 30
# === 订单模板表 ===
class OrderTemplate(QModel):
"""订单模板表——定义订单的结构与配置
每个模板属于一个租户TenantUUID可选关联一个公司CompanyUUID
模板定义了订单的基本信息、支付配置、等级设置等。
子服务通过模板快速创建标准化的订单。
"""
TPL_SERVICE = 'service'
TPL_GOODS = 'goods'
TPL_MEMBERSHIP = 'membership'
TPL_RECHARGE = 'recharge'
TPL_CUSTOM = 'custom'
TPL_TYPE_CHOICES = [
(TPL_SERVICE, '服务'),
(TPL_GOODS, '商品'),
(TPL_MEMBERSHIP, '会员'),
(TPL_RECHARGE, '充值'),
(TPL_CUSTOM, '自定义'),
]
TPL_DRAFT = 0
TPL_ACTIVE = 1
TPL_ARCHIVED = 2
TPL_STATUS_CHOICES = [
(TPL_DRAFT, '草稿'),
(TPL_ACTIVE, '启用'),
(TPL_ARCHIVED, '归档'),
]
TemplateUUID = UUIDField(primary_key=True, verbose_name='模板UUID')
TenantUUID = UUIDField(verbose_name='租户UUID')
CompanyUUID = UUIDField(null=True, blank=True, verbose_name='公司UUID')
TemplateCode = models.CharField(max_length=64, verbose_name='模板编码')
TemplateName = models.CharField(max_length=256, verbose_name='模板名称')
TemplateDescription = models.CharField(max_length=512, default='', verbose_name='模板描述')
TemplateType = models.CharField(max_length=32, choices=TPL_TYPE_CHOICES, verbose_name='模板类型')
TemplateStatus = models.SmallIntegerField(
default=TPL_DRAFT, choices=TPL_STATUS_CHOICES,
verbose_name='模板状态',
)
# 可选绑定支付网关
PaymentConfig = models.ForeignKey(
'payment.PaymentGatewayConfig', on_delete=models.SET_NULL,
null=True, blank=True, related_name='order_templates',
verbose_name='支付网关配置',
)
# 订单级配置
RequirePayment = models.BooleanField(default=True, verbose_name='需要支付')
AllowRefund = models.BooleanField(default=False, verbose_name='允许退款')
RefundPolicy = models.JSONField(default=dict, verbose_name='退款策略')
AutoConfirm = models.BooleanField(default=False, verbose_name='自动确认')
ConfirmTimeoutSeconds = models.IntegerField(default=0, verbose_name='确认超时秒数')
# 模板元数据(扩展字段)
TemplateMetadata = models.JSONField(default=dict, verbose_name='模板元数据')
TemplateSort = models.SmallIntegerField(default=0, verbose_name='排序')
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
UpdateTime = models.DateTimeField(null=True, blank=True, verbose_name='更新时间')
class Meta:
db_table = 'OrderTemplate'
app_label = 'gvsdsdk'
verbose_name = '订单模板'
verbose_name_plural = '订单模板'
managed = False
unique_together = [('TenantUUID', 'TemplateCode')]
ordering = ['TemplateSort', '-CreateTime']
indexes = [
models.Index(fields=['TenantUUID', 'TemplateStatus'], name='idx_ordtpl_tenant_status'),
models.Index(fields=['TemplateType'], name='idx_ordtpl_type'),
]
def __str__(self):
return f'{self.TemplateName}({self.TemplateCode})'
@property
def IsActive(self):
return self.TemplateStatus == self.TPL_ACTIVE
def GetLevels(self):
"""获取模板的所有等级"""
return self.Levels.filter(LevelStatus=1).order_by('LevelSort')
def GetItems(self):
"""获取模板的所有行项目定义"""
return self.Items.filter(ItemStatus=1).order_by('ItemSort')
# === 订单模板项表 ===
class OrderTemplateItem(QModel):
"""订单模板项表——定义模板中的行项目
每个行项目代表订单中的一个条目,如商品、服务项等。
创建订单时,模板项会被复制到订单项中。
"""
ITEM_MANDATORY = 0
ITEM_OPTIONAL = 1
ITEM_OPTIONALITY_CHOICES = [
(ITEM_MANDATORY, '必选'),
(ITEM_OPTIONAL, '可选'),
]
ItemUUID = UUIDField(primary_key=True, verbose_name='模板项UUID')
Template = models.ForeignKey(
OrderTemplate, on_delete=models.CASCADE,
related_name='Items',
verbose_name='订单模板',
)
ItemCode = models.CharField(max_length=64, verbose_name='项目编码')
ItemName = models.CharField(max_length=256, verbose_name='项目名称')
ItemDescription = models.CharField(max_length=512, default='', verbose_name='项目描述')
ItemType = models.CharField(max_length=32, default='default', verbose_name='项目类型')
UnitPrice = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='单价')
Currency = models.CharField(max_length=10, default='CNY', verbose_name='币种')
DefaultQuantity = models.IntegerField(default=1, verbose_name='默认数量')
MinQuantity = models.IntegerField(default=1, verbose_name='最小数量')
MaxQuantity = models.IntegerField(default=999, verbose_name='最大数量')
Optionality = models.SmallIntegerField(
default=ITEM_MANDATORY, choices=ITEM_OPTIONALITY_CHOICES,
verbose_name='是否可选',
)
ItemSort = models.SmallIntegerField(default=0, verbose_name='排序')
ItemStatus = models.SmallIntegerField(default=1, verbose_name='项目状态')
ItemMetadata = models.JSONField(default=dict, verbose_name='项目元数据')
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
class Meta:
db_table = 'OrderTemplateItem'
app_label = 'gvsdsdk'
verbose_name = '订单模板项'
verbose_name_plural = '订单模板项'
managed = False
unique_together = [('Template', 'ItemCode')]
ordering = ['ItemSort']
indexes = [
models.Index(fields=['Template', 'ItemStatus'], name='idx_ordtplitem_template_status'),
]
def __str__(self):
return f'{self.ItemName}({self.ItemCode})'
# === 订单表 ===
class Order(QModel):
"""订单表——SAAS 级统一订单记录
每个订单基于一个模板创建,包含订单基本信息、等级、支付状态等。
子服务通过此表管理业务订单生命周期。
"""
ORD_PENDING = 0
ORD_CONFIRMED = 1
ORD_PAID = 2
ORD_PROCESSING = 3
ORD_COMPLETED = 4
ORD_CANCELLED = 5
ORD_REFUNDING = 6
ORD_REFUNDED = 7
ORD_CLOSED = 8
ORD_STATUS_CHOICES = [
(ORD_PENDING, '待确认'),
(ORD_CONFIRMED, '已确认'),
(ORD_PAID, '已支付'),
(ORD_PROCESSING, '处理中'),
(ORD_COMPLETED, '已完成'),
(ORD_CANCELLED, '已取消'),
(ORD_REFUNDING, '退款中'),
(ORD_REFUNDED, '已退款'),
(ORD_CLOSED, '已关闭'),
]
OrderUUID = UUIDField(primary_key=True, verbose_name='订单UUID')
TenantUUID = UUIDField(verbose_name='租户UUID')
CompanyUUID = UUIDField(null=True, blank=True, verbose_name='公司UUID')
OrderNO = models.CharField(max_length=64, unique=True, verbose_name='订单编号')
# 来源模板
Template = models.ForeignKey(
OrderTemplate, on_delete=models.SET_NULL,
null=True, related_name='orders',
verbose_name='订单模板',
)
# 等级
Level = models.ForeignKey(
OrderLevel, on_delete=models.SET_NULL,
null=True, blank=True, related_name='orders',
verbose_name='订单等级',
)
# 订单主体
OrderTitle = models.CharField(max_length=256, verbose_name='订单标题')
OrderDescription = models.CharField(max_length=1024, default='', verbose_name='订单描述')
OrderType = models.CharField(max_length=32, verbose_name='订单类型')
# 用户
BuyerUserUUID = UUIDField(verbose_name='买家用户UUID')
SellerUserUUID = UUIDField(null=True, blank=True, verbose_name='卖家用户UUID')
# 金额
TotalAmount = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='总金额')
DiscountAmount = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='优惠金额')
PayAmount = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='实付金额')
Currency = models.CharField(max_length=10, default='CNY', verbose_name='币种')
# 状态
OrderStatus = models.SmallIntegerField(
default=ORD_PENDING, choices=ORD_STATUS_CHOICES,
verbose_name='订单状态',
)
# 支付关联(可选)
PaymentTransaction = models.ForeignKey(
'payment.PaymentTransaction', on_delete=models.SET_NULL,
null=True, blank=True, related_name='orders',
verbose_name='支付交易',
)
PaymentConfig = models.ForeignKey(
'payment.PaymentGatewayConfig', on_delete=models.SET_NULL,
null=True, blank=True, related_name='paid_orders',
verbose_name='支付网关配置',
)
# 时间
ConfirmTime = models.DateTimeField(null=True, blank=True, verbose_name='确认时间')
PayTime = models.DateTimeField(null=True, blank=True, verbose_name='支付时间')
CompleteTime = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
CancelTime = models.DateTimeField(null=True, blank=True, verbose_name='取消时间')
ExpireTime = models.DateTimeField(null=True, blank=True, verbose_name='过期时间')
# 快照(创建时冻结模板信息)
TemplateSnapshot = models.JSONField(default=dict, verbose_name='模板快照')
LevelSnapshot = models.JSONField(default=dict, verbose_name='等级快照')
# 扩展
OrderMetadata = models.JSONField(default=dict, verbose_name='订单元数据')
Remark = models.CharField(max_length=512, default='', verbose_name='备注')
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
UpdateTime = models.DateTimeField(null=True, blank=True, verbose_name='更新时间')
class Meta:
db_table = 'Order'
app_label = 'gvsdsdk'
verbose_name = '订单'
verbose_name_plural = '订单'
managed = False
ordering = ['-CreateTime']
indexes = [
models.Index(fields=['TenantUUID', 'OrderStatus', 'CreateTime'], name='idx_order_tenant_status_time'),
models.Index(fields=['BuyerUserUUID'], name='idx_order_buyer'),
models.Index(fields=['OrderNO'], name='idx_order_no'),
models.Index(fields=['OrderType'], name='idx_order_type'),
]
def __str__(self):
return self.OrderNO
@property
def IsPaid(self):
return self.OrderStatus >= self.ORD_PAID
@property
def IsCompleted(self):
return self.OrderStatus == self.ORD_COMPLETED
@property
def IsCancelled(self):
return self.OrderStatus == self.ORD_CANCELLED
@property
def IsTerminal(self):
return self.OrderStatus in (
self.ORD_COMPLETED, self.ORD_CANCELLED,
self.ORD_REFUNDED, self.ORD_CLOSED,
)
def Confirm(self):
"""确认订单"""
self.OrderStatus = self.ORD_CONFIRMED
self.ConfirmTime = datetime.datetime.utcnow()
self.save(update_fields=['OrderStatus', 'ConfirmTime', 'UpdateTime'])
def MarkPaid(self, payment_transaction=None):
"""标记为已支付"""
self.OrderStatus = self.ORD_PAID
self.PayTime = datetime.datetime.utcnow()
if payment_transaction:
self.PaymentTransaction = payment_transaction
self.save(update_fields=['OrderStatus', 'PayTime', 'PaymentTransaction', 'UpdateTime'])
def StartProcessing(self):
"""开始处理"""
self.OrderStatus = self.ORD_PROCESSING
self.save(update_fields=['OrderStatus', 'UpdateTime'])
def Complete(self):
"""完成订单"""
self.OrderStatus = self.ORD_COMPLETED
self.CompleteTime = datetime.datetime.utcnow()
self.save(update_fields=['OrderStatus', 'CompleteTime', 'UpdateTime'])
def Cancel(self, reason=''):
"""取消订单"""
self.OrderStatus = self.ORD_CANCELLED
self.CancelTime = datetime.datetime.utcnow()
if reason:
self.Remark = reason
self.save(update_fields=['OrderStatus', 'CancelTime', 'Remark', 'UpdateTime'])
def StartRefund(self):
"""开始退款"""
self.OrderStatus = self.ORD_REFUNDING
self.save(update_fields=['OrderStatus', 'UpdateTime'])
def RefundDone(self):
"""退款完成"""
self.OrderStatus = self.ORD_REFUNDED
self.save(update_fields=['OrderStatus', 'UpdateTime'])
def Close(self):
"""关闭订单"""
self.OrderStatus = self.ORD_CLOSED
self.save(update_fields=['OrderStatus', 'UpdateTime'])
def GetItems(self):
"""获取订单所有行项目"""
return self.Items.all().order_by('ItemSort')
# === 订单项表 ===
class OrderItem(QModel):
"""订单项表——订单中的行项目
创建订单时从模板项复制生成,记录实际购买的商品/服务。
"""
ItemUUID = UUIDField(primary_key=True, verbose_name='订单项UUID')
Order = models.ForeignKey(
Order, on_delete=models.CASCADE,
related_name='Items',
verbose_name='订单',
)
# 来源模板项(可选,手动创建的订单可能没有模板项)
TemplateItem = models.ForeignKey(
OrderTemplateItem, on_delete=models.SET_NULL,
null=True, blank=True, related_name='order_items',
verbose_name='模板项',
)
ItemCode = models.CharField(max_length=64, verbose_name='项目编码')
ItemName = models.CharField(max_length=256, verbose_name='项目名称')
ItemDescription = models.CharField(max_length=512, default='', verbose_name='项目描述')
ItemType = models.CharField(max_length=32, default='default', verbose_name='项目类型')
UnitPrice = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='单价')
Quantity = models.IntegerField(default=1, verbose_name='数量')
Subtotal = models.DecimalField(max_digits=18, decimal_places=4, default=0, verbose_name='小计')
Currency = models.CharField(max_length=10, default='CNY', verbose_name='币种')
ItemSort = models.SmallIntegerField(default=0, verbose_name='排序')
ItemStatus = models.SmallIntegerField(default=1, verbose_name='项目状态')
ItemMetadata = models.JSONField(default=dict, verbose_name='项目元数据')
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
class Meta:
db_table = 'OrderItem'
app_label = 'gvsdsdk'
verbose_name = '订单项'
verbose_name_plural = '订单项'
managed = False
ordering = ['ItemSort']
indexes = [
models.Index(fields=['Order'], name='idx_orditem_order'),
]
def __str__(self):
return f'{self.ItemName}x{self.Quantity}'
def CalcSubtotal(self):
"""计算小计"""
self.Subtotal = self.UnitPrice * self.Quantity
return self.Subtotal