diff --git a/deploy/migration_add_order_type.sql b/deploy/migration_add_order_type.sql new file mode 100644 index 0000000..3410e2d --- /dev/null +++ b/deploy/migration_add_order_type.sql @@ -0,0 +1,18 @@ +-- 为 bundle_order_records 表添加 order_type 字段 +-- 订单类型: 1=套餐订单, 2=增值服务订单 + +ALTER TABLE bundle_order_records +ADD COLUMN order_type INT DEFAULT 1 COMMENT '订单类型 1:套餐订单 2:增值服务订单'; + +-- 为现有订单设置默认值(兼容性:现有订单都是套餐订单) +UPDATE bundle_order_records +SET order_type = 1 +WHERE order_type IS NULL; + +-- 添加索引以提高查询性能 +CREATE INDEX idx_order_type ON bundle_order_records(order_type); + +-- 说明: +-- 1. 默认值 1 确保向后兼容(现有订单都被视为套餐订单) +-- 2. 新的增值服务订单将使用 order_type = 2 +-- 3. 索引加速按订单类型筛选的查询 diff --git a/internal/dao/orderRecordsDao.go b/internal/dao/orderRecordsDao.go index dcdb93e..4671aca 100644 --- a/internal/dao/orderRecordsDao.go +++ b/internal/dao/orderRecordsDao.go @@ -459,7 +459,11 @@ func CreateOrderAddRecord(req *bundle.OrderAddRecord) (res *bundle.CommonRespons tx.Rollback() } }() + + // 生成订单号和UUID orderNo := utils.GetOrderNo() + mainOrderUUID := app.ModuleClients.SfNode.Generate().Base64() + // 增值订单默认先用后付(规则 2:增值服务订单只有先用后付类型) addOrderMode := req.OrderMode if addOrderMode == 0 { @@ -473,11 +477,47 @@ func CreateOrderAddRecord(req *bundle.OrderAddRecord) (res *bundle.CommonRespons if addOrderMode == model.OrderModePayLater && addContractTplType == 0 { addContractTplType = model.ContractTplValueAddPayLater } + + // 计算总金额 + var totalAmount float32 + for _, i := range req.AddPriceOptionsList { + totalAmount += i.Amount + } + + // 创建增值服务主订单记录 + mainOrder := &model.BundleOrderRecords{ + UUID: mainOrderUUID, + OrderNo: orderNo, + BundleUUID: req.BundleUuid, // 保留原套餐UUID用于追溯 + CustomerID: req.CustomerID, + CustomerNum: req.CustomerNum, + CustomerName: req.CustomerName, + TotalAmount: totalAmount, + Amount: 0, // 增值服务订单不涉及套餐金额 + SignContract: req.SignContract, + Signature: req.Signature, + SignedTime: req.SignedTime, + Status: model.BundleStatusSignedUnpaid, // 已签未支付 + OrderMode: addOrderMode, + DueTime: req.DueTime, + PayLaterStatus: addPayLaterStatus, + ContractTplType: addContractTplType, + OrderType: model.OrderTypeValueAdd, // 标记为增值服务订单 + ExpirationTime: req.ExpirationDate, + } + + // 创建主订单 + if err = tx.Create(mainOrder).Error; err != nil { + tx.Rollback() + return nil, commonErr.ReturnError(err, msg.ErrorCreateOrderInfo, "创建增值服务主订单失败") + } + + // 创建子订单记录 var childOrders []*model.BundleOrderValueAdd for _, i := range req.AddPriceOptionsList { childOrder := &model.BundleOrderValueAdd{ UUID: app.ModuleClients.SfNode.Generate().Base64(), - OrderUUID: req.OrderUUID, // 修正: 这里应使用主订单UUID + OrderUUID: mainOrderUUID, // 关联到新创建的增值服务主订单 CustomerID: req.CustomerID, CustomerNum: req.CustomerNum, CustomerName: req.CustomerName, @@ -501,16 +541,17 @@ func CreateOrderAddRecord(req *bundle.OrderAddRecord) (res *bundle.CommonRespons } childOrders = append(childOrders, childOrder) - // 如果是类型5服务,更新主订单的过期时间 - if i.ServiceType == 5 && req.ExpirationDate != "" { + // 如果是类型5服务(可用时长),更新原套餐订单的过期时间 + if i.ServiceType == 5 && req.ExpirationDate != "" && req.OrderUUID != "" { if err := tx.Model(&model.BundleOrderRecords{}). - Where("uuid = ?", req.BundleUuid). + Where("uuid = ?", req.OrderUUID). Update("expiration_time", req.ExpirationDate).Error; err != nil { tx.Rollback() - return nil, commonErr.ReturnError(err, msg.ErrorCreateOrderInfo, "更新主订单过期时间失败: ") + return nil, commonErr.ReturnError(err, msg.ErrorCreateOrderInfo, "更新原套餐订单过期时间失败: ") } } } + // 批量创建子订单(提高性能) if err = tx.Model(&model.BundleOrderValueAdd{}).Create(childOrders).Error; err != nil { tx.Rollback() @@ -524,7 +565,7 @@ func CreateOrderAddRecord(req *bundle.OrderAddRecord) (res *bundle.CommonRespons } return &bundle.CommonResponse{ - Uuid: req.BundleUuid, + Uuid: mainOrderUUID, // 返回新创建的增值服务主订单UUID OrderNo: orderNo, Msg: msg.SuccessCreateOrderInfo, }, nil @@ -573,6 +614,9 @@ func OrderRecordsListV2(req *bundle.OrderRecordsRequestV2) (res *bundle.OrderRec if req.PayLaterStatus != 0 { modelObj = modelObj.Where("bundle_order_records.pay_later_status = ?", req.PayLaterStatus) } + if req.OrderType != 0 { + modelObj = modelObj.Where("bundle_order_records.order_type = ?", req.OrderType) + } err = modelObj.Count(&count).Error if req.PageSize != 0 && req.Page != 0 { modelObj = modelObj.Limit(int(req.PageSize)).Offset(int(req.Page-1) * int(req.PageSize)) @@ -606,6 +650,7 @@ func OrderRecordsListV2(req *bundle.OrderRecordsRequestV2) (res *bundle.OrderRec DueTime: record.DueTime, PayLaterStatus: record.PayLaterStatus, ContractTplType: record.ContractTplType, + OrderType: record.OrderType, } // 聚合子订单 diff --git a/internal/logic/orderRecordsLogic.go b/internal/logic/orderRecordsLogic.go index 4941ab2..65ec4c7 100644 --- a/internal/logic/orderRecordsLogic.go +++ b/internal/logic/orderRecordsLogic.go @@ -117,6 +117,7 @@ func CreateOrderRecord(req *bundle.OrderCreateRecord) (res *bundle.CommonRespons DueTime: req.DueTime, PayLaterStatus: payLaterStatus, ContractTplType: req.ContractTplType, + OrderType: model.OrderTypeBundle, // 套餐订单 } res, err = dao.CreateOrderRecord(orderRecord) return @@ -190,6 +191,17 @@ func PackagePriceAndTime(req *bundle.OrderRecord) (res *bundle.PackagePriceAndTi } func CreateOrderAddRecord(req *bundle.OrderAddRecord) (res *bundle.CommonResponse, err error) { res = new(bundle.CommonResponse) + // 增值订单下单前置校验(规则5:增值订单不受套餐过期限制,可无限续杯) + if eligibility, e := CheckOrderEligibility(&bundle.CheckOrderEligibilityRequest{ + CustomerID: req.CustomerID, + OrderKind: model.OrderKindValueAdd, + OrderMode: req.OrderMode, + }); e != nil { + return nil, e + } else if !eligibility.Allow { + res.Msg = eligibility.Msg + return res, errors.New(eligibility.Reason) + } res, err = dao.CreateOrderAddRecord(req) return } diff --git a/internal/model/bundle_order_records.go b/internal/model/bundle_order_records.go index f2b1ee6..b748508 100644 --- a/internal/model/bundle_order_records.go +++ b/internal/model/bundle_order_records.go @@ -52,6 +52,7 @@ type BundleOrderRecords struct { DueTime string `gorm:"column:due_time;type:varchar(64);comment:先用后付到期应付时间" json:"dueTime"` PayLaterStatus int32 `gorm:"column:pay_later_status;type:int;default:0;comment:先用后付状态 0:无 1:待付款 2:已付款 3:逾期未付;index:idx_paylater_status" json:"payLaterStatus"` ContractTplType int32 `gorm:"column:contract_tpl_type;type:int;default:0;comment:合同模板类型 1:套餐普通 2:套餐先用后付 3:增值先用后付" json:"contractTplType"` + OrderType int32 `gorm:"column:order_type;type:int;default:1;comment:订单类型 1:套餐订单 2:增值服务订单" json:"orderType"` } type BundleOrderValueAdd struct { gorm.Model @@ -141,8 +142,8 @@ const ( // 合同模板类型 const ( - ContractTplBundleNormal int32 = 1 // 套餐-普通 - ContractTplBundlePayLater int32 = 2 // 套餐-先用后付 + ContractTplBundleNormal int32 = 1 // 套餐-普通 + ContractTplBundlePayLater int32 = 2 // 套餐-先用后付 ContractTplValueAddPayLater int32 = 3 // 增值-先用后付 ) @@ -159,3 +160,9 @@ const ( OrderGateReasonBundleActiveExists = "BUNDLE_ACTIVE_EXISTS" OrderGateReasonValueAddLocked = "VALUE_ADD_OVERDUE_LOCKED" ) + +// 订单类型 +const ( + OrderTypeBundle int32 = 1 // 套餐订单 + OrderTypeValueAdd int32 = 2 // 增值服务订单 +) diff --git a/pb/bundle.proto b/pb/bundle.proto index 85abb46..cfb1e28 100644 --- a/pb/bundle.proto +++ b/pb/bundle.proto @@ -273,6 +273,7 @@ message OrderCreateRecord{ string dueTime = 27 [json_name = "dueTime"]; // 先用后付到期应付时间(北京时间) int32 payLaterStatus = 28 [json_name = "payLaterStatus"]; // 先用后付状态 0:无 1:待付款 2:已付款 3:逾期未付 int32 contractTplType = 29 [json_name = "contractTplType"]; // 合同模板类型 1:套餐普通 2:套餐先用后付 3:增值先用后付 + int32 orderType = 30 [json_name = "orderType"]; // 订单类型 1:套餐订单 2:增值服务订单 } message OrderCreateAddRecord{ int32 serviceType = 1 [json_name = "serviceType"]; @@ -313,6 +314,7 @@ message OrderRecordsRequestV2{ uint64 purchaseType = 17; int32 orderMode = 18 [json_name = "orderMode"]; // 订单模式筛选 1:普通 2:先用后付 int32 payLaterStatus = 19 [json_name = "payLaterStatus"]; // 先用后付状态筛选 0:无 1:待付款 2:已付款 3:逾期未付 + int32 orderType = 20 [json_name = "orderType"]; // 订单类型筛选 1:套餐订单 2:增值服务订单 } message OrderRecordsResponseV2{ repeated OrderBundleRecordInfo bundleInfo = 1; @@ -341,6 +343,7 @@ message OrderBundleRecordInfo{ string dueTime = 18 [json_name = "dueTime"]; // 先用后付到期应付时间 int32 payLaterStatus = 19 [json_name = "payLaterStatus"]; // 先用后付状态 0:无 1:待付款 2:已付款 3:逾期未付 int32 contractTplType = 20 [json_name = "contractTplType"]; // 合同模板类型 1:套餐普通 2:套餐先用后付 + int32 orderType = 21 [json_name = "orderType"]; // 订单类型 1:套餐订单 2:增值服务订单 } message OrderAddBundleRecordInfo{ string orderAddNo = 1; @@ -537,6 +540,7 @@ message OrderRecord { string dueTime = 44 [json_name = "dueTime"]; // 先用后付到期时间 int32 payLaterStatus = 45 [json_name = "payLaterStatus"]; // 先用后付状态 0:无 1:待付款 2:已付款 3:逾期未付 int32 contractTplType = 46 [json_name = "contractTplType"]; // 合同模板类型 + int32 orderType = 47 [json_name = "orderType"]; // 订单类型 1:套餐订单 2:增值服务订单 } // 下单前置校验 diff --git a/pb/bundle/bundle.pb.go b/pb/bundle/bundle.pb.go index 7fb4bfe..e55d8c6 100644 --- a/pb/bundle/bundle.pb.go +++ b/pb/bundle/bundle.pb.go @@ -946,6 +946,7 @@ type OrderCreateRecord struct { DueTime string `protobuf:"bytes,27,opt,name=dueTime,proto3" json:"dueTime"` // 先用后付到期应付时间(北京时间) PayLaterStatus int32 `protobuf:"varint,28,opt,name=payLaterStatus,proto3" json:"payLaterStatus"` // 先用后付状态 0:无 1:待付款 2:已付款 3:逾期未付 ContractTplType int32 `protobuf:"varint,29,opt,name=contractTplType,proto3" json:"contractTplType"` // 合同模板类型 1:套餐普通 2:套餐先用后付 3:增值先用后付 + OrderType int32 `protobuf:"varint,30,opt,name=orderType,proto3" json:"orderType"` // 订单类型 1:套餐订单 2:增值服务订单 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1183,6 +1184,13 @@ func (x *OrderCreateRecord) GetContractTplType() int32 { return 0 } +func (x *OrderCreateRecord) GetOrderType() int32 { + if x != nil { + return x.OrderType + } + return 0 +} + type OrderCreateAddRecord struct { state protoimpl.MessageState `protogen:"open.v1"` ServiceType int32 `protobuf:"varint,1,opt,name=serviceType,proto3" json:"serviceType"` @@ -1376,6 +1384,7 @@ type OrderRecordsRequestV2 struct { PurchaseType uint64 `protobuf:"varint,17,opt,name=purchaseType,proto3" json:"purchaseType"` OrderMode int32 `protobuf:"varint,18,opt,name=orderMode,proto3" json:"orderMode"` // 订单模式筛选 1:普通 2:先用后付 PayLaterStatus int32 `protobuf:"varint,19,opt,name=payLaterStatus,proto3" json:"payLaterStatus"` // 先用后付状态筛选 0:无 1:待付款 2:已付款 3:逾期未付 + OrderType int32 `protobuf:"varint,20,opt,name=orderType,proto3" json:"orderType"` // 订单类型筛选 1:套餐订单 2:增值服务订单 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1543,6 +1552,13 @@ func (x *OrderRecordsRequestV2) GetPayLaterStatus() int32 { return 0 } +func (x *OrderRecordsRequestV2) GetOrderType() int32 { + if x != nil { + return x.OrderType + } + return 0 +} + type OrderRecordsResponseV2 struct { state protoimpl.MessageState `protogen:"open.v1"` BundleInfo []*OrderBundleRecordInfo `protobuf:"bytes,1,rep,name=bundleInfo,proto3" json:"bundleInfo"` @@ -1633,6 +1649,7 @@ type OrderBundleRecordInfo struct { DueTime string `protobuf:"bytes,18,opt,name=dueTime,proto3" json:"dueTime"` // 先用后付到期应付时间 PayLaterStatus int32 `protobuf:"varint,19,opt,name=payLaterStatus,proto3" json:"payLaterStatus"` // 先用后付状态 0:无 1:待付款 2:已付款 3:逾期未付 ContractTplType int32 `protobuf:"varint,20,opt,name=contractTplType,proto3" json:"contractTplType"` // 合同模板类型 1:套餐普通 2:套餐先用后付 + OrderType int32 `protobuf:"varint,21,opt,name=orderType,proto3" json:"orderType"` // 订单类型 1:套餐订单 2:增值服务订单 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1807,6 +1824,13 @@ func (x *OrderBundleRecordInfo) GetContractTplType() int32 { return 0 } +func (x *OrderBundleRecordInfo) GetOrderType() int32 { + if x != nil { + return x.OrderType + } + return 0 +} + type OrderAddBundleRecordInfo struct { state protoimpl.MessageState `protogen:"open.v1"` OrderAddNo string `protobuf:"bytes,1,opt,name=orderAddNo,proto3" json:"orderAddNo"` @@ -3257,6 +3281,7 @@ type OrderRecord struct { DueTime string `protobuf:"bytes,44,opt,name=dueTime,proto3" json:"dueTime"` // 先用后付到期时间 PayLaterStatus int32 `protobuf:"varint,45,opt,name=payLaterStatus,proto3" json:"payLaterStatus"` // 先用后付状态 0:无 1:待付款 2:已付款 3:逾期未付 ContractTplType int32 `protobuf:"varint,46,opt,name=contractTplType,proto3" json:"contractTplType"` // 合同模板类型 + OrderType int32 `protobuf:"varint,47,opt,name=orderType,proto3" json:"orderType"` // 订单类型 1:套餐订单 2:增值服务订单 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3613,6 +3638,13 @@ func (x *OrderRecord) GetContractTplType() int32 { return 0 } +func (x *OrderRecord) GetOrderType() int32 { + if x != nil { + return x.OrderType + } + return 0 +} + // 下单前置校验 type CheckOrderEligibilityRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -21081,7 +21113,7 @@ const file_pb_bundle_proto_rawDesc = "" + "\buserName\x18\n" + " \x01(\tR\buserName\x124\n" + "\x15competitiveAdditional\x18\v \x01(\x05R\x15competitiveAdditional\x12\"\n" + - "\fpurchaseType\x18\f \x01(\x05R\fpurchaseType\"\xdd\a\n" + + "\fpurchaseType\x18\f \x01(\x05R\fpurchaseType\"\xfb\a\n" + "\x11OrderCreateRecord\x12\x1e\n" + "\n" + "bundleUuid\x18\x01 \x01(\tR\n" + @@ -21126,7 +21158,8 @@ const file_pb_bundle_proto_rawDesc = "" + "\torderMode\x18\x1a \x01(\x05R\torderMode\x12\x18\n" + "\adueTime\x18\x1b \x01(\tR\adueTime\x12&\n" + "\x0epayLaterStatus\x18\x1c \x01(\x05R\x0epayLaterStatus\x12(\n" + - "\x0fcontractTplType\x18\x1d \x01(\x05R\x0fcontractTplType\"\x9c\x04\n" + + "\x0fcontractTplType\x18\x1d \x01(\x05R\x0fcontractTplType\x12\x1c\n" + + "\torderType\x18\x1e \x01(\x05R\torderType\"\x9c\x04\n" + "\x14OrderCreateAddRecord\x12 \n" + "\vserviceType\x18\x01 \x01(\x05R\vserviceType\x12\x1a\n" + "\bvalueUid\x18\x02 \x01(\tR\bvalueUid\x12\"\n" + @@ -21149,7 +21182,7 @@ const file_pb_bundle_proto_rawDesc = "" + "\torderMode\x18\x0e \x01(\x05R\torderMode\x12\x18\n" + "\adueTime\x18\x0f \x01(\tR\adueTime\x12&\n" + "\x0epayLaterStatus\x18\x10 \x01(\x05R\x0epayLaterStatus\x12(\n" + - "\x0fcontractTplType\x18\x11 \x01(\x05R\x0fcontractTplType\"\xb7\x05\n" + + "\x0fcontractTplType\x18\x11 \x01(\x05R\x0fcontractTplType\"\xd5\x05\n" + "\x15OrderRecordsRequestV2\x12\"\n" + "\fcustomerName\x18\x01 \x01(\tR\fcustomerName\x12\x16\n" + "\x06telNum\x18\x02 \x01(\tR\x06telNum\x12\x1e\n" + @@ -21174,14 +21207,15 @@ const file_pb_bundle_proto_rawDesc = "" + "\fbundlePayEnd\x18\x10 \x01(\tR\fbundlePayEnd\x12\"\n" + "\fpurchaseType\x18\x11 \x01(\x04R\fpurchaseType\x12\x1c\n" + "\torderMode\x18\x12 \x01(\x05R\torderMode\x12&\n" + - "\x0epayLaterStatus\x18\x13 \x01(\x05R\x0epayLaterStatus\"\x9d\x01\n" + + "\x0epayLaterStatus\x18\x13 \x01(\x05R\x0epayLaterStatus\x12\x1c\n" + + "\torderType\x18\x14 \x01(\x05R\torderType\"\x9d\x01\n" + "\x16OrderRecordsResponseV2\x12=\n" + "\n" + "bundleInfo\x18\x01 \x03(\v2\x1d.bundle.OrderBundleRecordInfoR\n" + "bundleInfo\x12\x12\n" + "\x04page\x18\x02 \x01(\x05R\x04page\x12\x1a\n" + "\bpageSize\x18\x03 \x01(\x05R\bpageSize\x12\x14\n" + - "\x05total\x18\x04 \x01(\x05R\x05total\"\xc1\x05\n" + + "\x05total\x18\x04 \x01(\x05R\x05total\"\xdf\x05\n" + "\x15OrderBundleRecordInfo\x12\x18\n" + "\aorderNo\x18\x01 \x01(\tR\aorderNo\x12\x1e\n" + "\n" + @@ -21207,7 +21241,8 @@ const file_pb_bundle_proto_rawDesc = "" + "\torderMode\x18\x11 \x01(\x05R\torderMode\x12\x18\n" + "\adueTime\x18\x12 \x01(\tR\adueTime\x12&\n" + "\x0epayLaterStatus\x18\x13 \x01(\x05R\x0epayLaterStatus\x12(\n" + - "\x0fcontractTplType\x18\x14 \x01(\x05R\x0fcontractTplType\"\xe4\x04\n" + + "\x0fcontractTplType\x18\x14 \x01(\x05R\x0fcontractTplType\x12\x1c\n" + + "\torderType\x18\x15 \x01(\x05R\torderType\"\xe4\x04\n" + "\x18OrderAddBundleRecordInfo\x12\x1e\n" + "\n" + "orderAddNo\x18\x01 \x01(\tR\n" + @@ -21343,7 +21378,7 @@ const file_pb_bundle_proto_rawDesc = "" + "\x03msg\x18\x02 \x01(\tR\x03msg\"Y\n" + "\x16BundleDetailResponseV2\x12-\n" + "\x06bundle\x18\x01 \x01(\v2\x15.bundle.BundleProfileR\x06bundle\x12\x10\n" + - "\x03msg\x18\x02 \x01(\tR\x03msg\"\xa2\r\n" + + "\x03msg\x18\x02 \x01(\tR\x03msg\"\xc0\r\n" + "\vOrderRecord\x12\x12\n" + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1e\n" + "\n" + @@ -21405,7 +21440,8 @@ const file_pb_bundle_proto_rawDesc = "" + "\torderMode\x18+ \x01(\x05R\torderMode\x12\x18\n" + "\adueTime\x18, \x01(\tR\adueTime\x12&\n" + "\x0epayLaterStatus\x18- \x01(\x05R\x0epayLaterStatus\x12(\n" + - "\x0fcontractTplType\x18. \x01(\x05R\x0fcontractTplType\"z\n" + + "\x0fcontractTplType\x18. \x01(\x05R\x0fcontractTplType\x12\x1c\n" + + "\torderType\x18/ \x01(\x05R\torderType\"z\n" + "\x1cCheckOrderEligibilityRequest\x12\x1e\n" + "\n" + "customerID\x18\x01 \x01(\tR\n" +