Files
platforms/backend/api/internal/seed/mock.go
2026-08-16 01:17:41 +08:00

1045 lines
40 KiB
Go
Raw 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.
// Package seed writes linked development data without replacing existing rows.
package seed
import (
"errors"
"fmt"
"reflect"
"time"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
const mockIdentityPrefix = "00000000-0000-7000-8000-"
// MockData idempotently writes one connected development scenario across all
// domain tables. Fixed identities and unique business numbers make reruns safe.
func MockData(database *gorm.DB) error {
passwordHash, err := bcrypt.GenerateFromPassword([]byte("Mock@123456"), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("hash mock password: %w", err)
}
now := time.Date(2026, time.July, 1, 9, 0, 0, 0, time.Local)
yesterday := now.Add(-24 * time.Hour)
nextYear := now.AddDate(1, 0, 0)
return database.Transaction(func(tx *gorm.DB) error {
gas := models.GasBasic{
Entity: entity(1, common.StatusEnable), Code: "MOCK-GAS-001", Name: "和气示例气站",
CreditCode: "91310000MOCKGAS001", Principal: "张站长",
Address: "上海市浦东新区示例路 1 号", Longitude: "121.5440", Latitude: "31.2210",
}
if err := put(tx, &gas); err != nil {
return err
}
gasAccount := models.GasAccount{
Entity: entity(2, common.StatusEnable), GasBasicID: gas.ID, Username: "mock_gas_admin",
DisplayName: "示例气站管理员", PasswordHash: string(passwordHash), RoleCode: "admin",
}
if err := put(tx, &gasAccount); err != nil {
return err
}
delivery := models.DeliveryBasic{
Entity: entity(3, common.StatusEnable), DeliveryCode: "MOCK-DELIVERY-001",
GasBasicID: gas.ID, Name: "和气示例配送点", Principal: "李主管",
Address: "上海市浦东新区示例路 18 号",
}
if err := put(tx, &delivery); err != nil {
return err
}
deliveryAccount := models.DeliveryAccount{
Entity: entity(4, common.StatusEnable), DeliveryBasicID: delivery.ID,
Username: "mock_delivery_admin", DisplayName: "示例配送点管理员",
PasswordHash: string(passwordHash), RoleCode: "admin",
}
if err := put(tx, &deliveryAccount); err != nil {
return err
}
staff := models.StaffAccount{
Entity: entity(5, common.StatusEnable), Username: "mock_driver", PasswordHash: string(passwordHash),
Name: "王师傅", Phone: "13900000001", RoleCode: "delivery",
GasBasicID: gas.ID, DeliveryBasicID: delivery.ID, WorkStatus: "on_duty",
}
if err := put(tx, &staff); err != nil {
return err
}
credential := models.StaffCredential{
Entity: entity(6, common.StatusEnable), StaffAccountID: staff.ID,
CredentialType: "delivery", CredentialNo: "MOCK-CERT-001", ExpiredAt: &nextYear,
}
if err := put(tx, &credential); err != nil {
return err
}
user := models.UserAccount{
Entity: entity(7, common.StatusEnable), Username: "mock_customer", PasswordHash: string(passwordHash),
Name: "陈女士", Phone: "13800000001", RealName: "陈示例",
}
if err := put(tx, &user); err != nil {
return err
}
address := models.UserAddress{
Entity: entity(8, common.StatusEnable), UserAccountID: user.ID,
Address: "上海市浦东新区客户路 88 号", Longitude: "121.5500",
Latitude: "31.2250", IsDefault: true,
}
if err := put(tx, &address); err != nil {
return err
}
serviceRelation := models.UserServiceRelation{
Entity: entity(9, common.StatusEnable), UserAccountID: user.ID, GasBasicID: gas.ID,
DeliveryBasicID: delivery.ID, StaffAccountID: staff.ID,
}
if err := put(tx, &serviceRelation); err != nil {
return err
}
productType := models.ProductType{
Entity: entity(10, common.StatusEnable), Code: "MOCK-LPG-15KG", Name: "15kg 液化气钢瓶",
}
if err := put(tx, &productType); err != nil {
return err
}
warehouse := models.ProductWarehouse{
Entity: entity(11, common.StatusEnable), Code: "MOCK-WH-001", Name: "示例中心库房",
Address: gas.Address, Manager: "赵库管", Phone: "13700000001",
}
if err := put(tx, &warehouse); err != nil {
return err
}
producer := models.ProducerAccount{
Entity: entity(49, common.StatusEnable), ProducerCode: "MOCK-PRODUCER-001",
Name: "和气示例生产企业", CreditCode: "91310000MOCKPROD01",
Principal: "周厂长", Phone: "13600000001", Address: "上海市示例工业路 6 号",
Username: "mock_producer_admin", DisplayName: "示例生产管理员",
PasswordHash: string(passwordHash), RoleCode: "admin", Remark: "仅用于开发联调",
}
if err := put(tx, &producer); err != nil {
return err
}
enabledAt := yesterday
productInfo := models.ProductInfo{
Entity: entity(12, common.StatusEnable), Code: "MOCK-CYLINDER-001", Name: "示例液化气钢瓶",
ProductStatus: common.StatusInStock,
ProductTypeID: productType.ID, Params: `{"weight":"15kg","medium":"LPG"}`,
WarehouseID: warehouse.ID, ProducedAt: now.AddDate(-1, 0, 0), EnabledAt: &enabledAt,
}
if err := linkMockProductProducer(&productInfo, producer); err != nil {
return err
}
if err := put(tx, &productInfo); err != nil {
return err
}
productOwner := models.ProductOwner{
Entity: entity(13, common.StatusEnable), ProductInfoID: productInfo.ID,
WarehouseID: warehouse.ID, Action: "stock_in",
OccurredAt: yesterday, Reason: "模拟数据初始化", OperatorName: "系统",
}
if err := put(tx, &productOwner); err != nil {
return err
}
completedAt := yesterday.Add(2 * time.Hour)
productRepair := models.ProductRepair{
Entity: entity(14, common.StatusEnable), ProductInfoID: productInfo.ID,
RepairNo: "MOCK-REPAIR-001", RepairType: "inspection", StartedAt: yesterday,
CompletedAt: &completedAt, Result: "passed", TargetProductStatus: common.StatusInStock,
Content: "外观、阀门与气密性检查", Operator: staff.Name,
}
if err := put(tx, &productRepair); err != nil {
return err
}
contract := models.GasorderContract{
Entity: entity(15, common.StatusEnable), ContractStatus: common.StatusActive, ContractNo: "MOCK-CONTRACT-001",
UserAccountID: user.ID, GasBasicID: gas.ID, DeliveryBasicID: delivery.ID,
Title: "居民瓶装气配送示例合同", Terms: "按需配送,安全使用。",
DefaultDeliveryFee: 500, SignedAt: yesterday, EffectiveAt: yesterday, ExpiredAt: &nextYear,
}
if err := put(tx, &contract); err != nil {
return err
}
contractRevision := models.GasorderContractRevision{
Entity: entity(16, common.StatusEnable), GasorderContractID: contract.ID, Action: "activate",
ContractStatus: common.StatusActive, EffectiveAt: contract.EffectiveAt, ExpiredAt: contract.ExpiredAt,
OperatorIdentity: gasAccount.Identity, OperatorName: gasAccount.DisplayName,
OccurredAt: yesterday, Reason: "模拟合同启用",
}
if err := put(tx, &contractRevision); err != nil {
return err
}
contractProduct := models.GasorderContractProduct{
Entity: entity(17, common.StatusEnable), GasorderContractID: contract.ID,
ProductInfoID: productInfo.ID, ProductCode: productInfo.Code,
ProductTypeName: productType.Name, ProductParams: productInfo.Params,
UnitPrice: 9800, BoundAt: yesterday,
}
if err := put(tx, &contractProduct); err != nil {
return err
}
gasOrder := models.GasorderBasic{
Entity: entity(18, common.StatusEnable), OrderStatus: common.StatusCompleted, OrderNo: "MOCK-GASORDER-001",
RequestNo: "MOCK-REQ-GASORDER-001", GasorderContractID: contract.ID,
UserAccountID: user.ID, CreatorType: "user", CreatorID: user.ID,
CreatorIdentity: user.Identity, GasBasicID: gas.ID, DeliveryBasicID: delivery.ID,
StaffAccountID: staff.ID, Address: address.Address, Longitude: address.Longitude,
Latitude: address.Latitude, ContactName: user.Name, ContactPhone: user.Phone,
ProductAmount: 9800, DeliveryFee: 500, PayableAmount: 10300,
OperatorIdentity: user.Identity, OperatorName: user.Name, Remark: "示例配送订单",
}
if err := put(tx, &gasOrder); err != nil {
return err
}
gasOrderItem := models.GasorderItem{
Entity: entity(19, common.StatusEnable), GasorderBasicID: gasOrder.ID,
GasorderContractProductID: contractProduct.ID, ProductInfoID: productInfo.ID,
ProductCode: productInfo.Code, ProductTypeName: productType.Name,
ProductParams: productInfo.Params, UnitPrice: contractProduct.UnitPrice,
}
if err := put(tx, &gasOrderItem); err != nil {
return err
}
assignment := models.GasorderAssign{
Entity: entity(20, common.StatusEnable), GasorderBasicID: gasOrder.ID, GasBasicID: gas.ID,
DeliveryBasicID: delivery.ID, StaffAccountID: staff.ID,
AssignerIdentity: deliveryAccount.Identity, AssignerName: deliveryAccount.DisplayName,
AssignedAt: now.Add(-2 * time.Hour), Reason: "系统示例派单",
}
if err := put(tx, &assignment); err != nil {
return err
}
orderStatus := models.GasorderStatus{
Entity: entity(21, common.StatusEnable), GasorderBasicID: gasOrder.ID,
FromStatus: common.StatusDelivering, ToStatus: common.StatusCompleted,
OperatorIdentity: staff.Identity, OperatorName: staff.Name,
OccurredAt: now, Reason: "用户已签收",
}
if err := put(tx, &orderStatus); err != nil {
return err
}
trackCompletedAt := now.Add(-10 * time.Minute)
track := models.GasorderTrack{
Entity: entity(22, common.StatusEnable), GasorderBasicID: gasOrder.ID,
StaffAccountID: staff.ID, AttemptNo: 1, StartedAt: now.Add(-90 * time.Minute),
CompletedAt: &trackCompletedAt,
}
if err := put(tx, &track); err != nil {
return err
}
trackPoint := models.GasorderTrackPoint{
Entity: entity(23, common.StatusEnable), GasorderTrackID: track.ID,
Longitude: address.Longitude, Latitude: address.Latitude,
RequestNo: mockTrackPointRequestNo, OccurredAt: trackCompletedAt,
ReceivedAt: trackCompletedAt.Add(mockTrackPointReceiveDelay),
Source: "gps", Accuracy: "10m", Speed: mockTrackPointSpeed,
Direction: mockTrackPointDirection,
}
if err := put(tx, &trackPoint); err != nil {
return err
}
confirmation := models.GasorderConfirm{
Entity: entity(24, common.StatusEnable), GasorderBasicID: gasOrder.ID,
ConfirmType: "signature", RecipientName: user.Name, RecipientPhone: user.Phone,
ProofURI: "/mock/proofs/gasorder-001.png", ConfirmedAt: now, Remark: "模拟签收",
}
if err := put(tx, &confirmation); err != nil {
return err
}
category := models.EcCategory{
Entity: entity(25, common.StatusEnable), Name: "瓶装燃气", SortNo: 10,
}
if err := put(tx, &category); err != nil {
return err
}
ecProduct := models.EcProduct{
Entity: entity(26, common.StatusEnable), EcCategoryID: category.ID,
ProductCode: "MOCK-EC-LPG-001", Name: "15kg 液化气配送服务",
PriceAmount: 10300, StockQuantity: 50,
}
if err := put(tx, &ecProduct); err != nil {
return err
}
attribute := models.EcProductAttribute{
Entity: entity(27, common.StatusEnable), EcProductID: ecProduct.ID,
Name: "规格", Value: "15kg/瓶", SortNo: 1,
}
if err := put(tx, &attribute); err != nil {
return err
}
image := models.EcProductImage{
Entity: entity(28, common.StatusEnable), EcProductID: ecProduct.ID,
ImageURI: "/mock/products/lpg-15kg.png", SortNo: 1, IsCover: true,
}
if err := put(tx, &image); err != nil {
return err
}
cart := models.EcCart{
Entity: entity(29, common.StatusEnable), UserAccountID: user.ID,
EcProductID: ecProduct.ID, Quantity: 1, Selected: true,
}
if err := put(tx, &cart); err != nil {
return err
}
ecOrder := models.EcOrder{
Entity: entity(30, common.StatusEnable), OrderStatus: mockEcOrderPaidStatus(), OrderNo: "MOCK-ECORDER-001",
RequestNo: "MOCK-REQ-ECORDER-001", UserAccountID: user.ID,
GasStationID: gas.ID, DeliveryPointID: delivery.ID, UserAddressID: address.ID,
Address: address.Address, Longitude: address.Longitude, Latitude: address.Latitude,
ContactName: user.Name, ContactPhone: user.Phone, Remark: "模拟已支付商城订单",
ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount,
TotalAmount: ecProduct.PriceAmount, PaidAt: &now,
}
desiredEcOrder := ecOrder
if err := put(tx, &ecOrder); err != nil {
return err
}
if err := repairMockEcOrder(tx, desiredEcOrder, mockEcOrderPaidStatus(), ecProduct.PriceAmount, &now); err != nil {
return err
}
ecOrderItem := models.EcOrderItem{
Entity: entity(31, common.StatusEnable), EcOrderID: ecOrder.ID, EcProductID: ecProduct.ID,
ProductSnapshot: `{"code":"MOCK-EC-LPG-001","name":"15kg 液化气配送服务"}`,
Quantity: 1, SaleAmount: ecProduct.PriceAmount,
}
if err := put(tx, &ecOrderItem); err != nil {
return err
}
review := models.EcReview{
Entity: entity(32, common.StatusEnable), EcOrderID: ecOrder.ID,
EcProductID: ecProduct.ID, UserAccountID: user.ID,
Score: 5, Content: "配送及时,服务规范。",
}
if err := put(tx, &review); err != nil {
return err
}
wallet := models.WalletBasic{
Entity: entity(33, common.StatusEnable), OwnerType: "user", OwnerID: user.ID,
OwnerIdentity: user.Identity, AlipayID: "mock@example.com", AlipayName: user.Name,
WxpayID: "mock_customer", WxpayName: user.Name,
PayPasswordHash: string(passwordHash), Balance: 50000, WithdrawalBalance: 30000,
}
if err := put(tx, &wallet); err != nil {
return err
}
bank := models.WalletBank{
Entity: entity(34, common.StatusEnable), WalletBasicID: wallet.ID,
CardNoCiphertext: "mock-ciphertext-card", CardFingerprint: "mock-card-fingerprint-001",
CardNoLast4: "8888", BankName: "示例银行", CardOwner: user.RealName,
IDCardCiphertext: "mock-ciphertext-id", PhoneCiphertext: "mock-ciphertext-phone",
BindID: "MOCK-BIND-001", BankType: "debit", Bank: "mock_bank",
}
if err := put(tx, &bank); err != nil {
return err
}
walletPayment := models.PaymentOrder{
Entity: entity(35, common.StatusEnable), PaymentStatus: 23,
PaymentNo: "MOCK-PAYMENT-001", RequestNo: "MOCK-REQ-PAYMENT-001", BusinessType: "gasorder", BusinessIdentity: gasOrder.Identity,
UserIdentity: user.Identity, MerchantIdentity: "platform", Channel: "wallet", PayType: "wallet", ChannelTradeNo: "MOCK-TRADE-001",
Amount: gasOrder.PayableAmount, Subject: "模拟供气订单", ClientArgs: `{}`, ExpiresAt: now.Add(30 * time.Minute), PaidAt: &now,
}
if err := put(tx, &walletPayment); err != nil {
return err
}
walletRecord := models.WalletRecord{
Entity: entity(36, common.StatusEnable), WalletBasicID: wallet.ID,
RecordNo: "MOCK-RECORD-001", RequestNo: "MOCK-REQ-RECORD-001",
Direction: "in", TradeType: "recharge", Amount: 50000,
BalanceAfter: 50000, WithdrawalBalanceAfter: 30000,
InTradeNo: walletPayment.PaymentNo, PayChannel: "manual",
OperatorIdentity: gasAccount.Identity, OperatorName: gasAccount.DisplayName,
Ymd: 20260701, Ym: 202607, Remark: "模拟钱包充值",
}
if err := put(tx, &walletRecord); err != nil {
return err
}
refundCompletedAt := now
refund := models.PaymentRefund{
Entity: entity(37, common.StatusEnable), RefundStatus: 20, WalletBasicID: wallet.ID,
PaymentOrderID: walletPayment.ID, RefundNo: "MOCK-REFUND-001", RequestNo: "MOCK-REQ-REFUND-001",
BusinessType: "gasorder", BusinessIdentity: gasOrder.Identity, UserIdentity: user.Identity,
Amount: 1000, Reason: "模拟部分退款", ReviewerIdentity: gasAccount.Identity, ReviewedAt: &refundCompletedAt, CompletedAt: &refundCompletedAt,
}
if err := put(tx, &refund); err != nil {
return err
}
applyCash := models.WalletApplyCash{
Entity: entity(38, common.StatusEnable), ApplyStatus: common.StatusApproved, WalletBasicID: wallet.ID, WalletBankID: bank.ID,
CashNo: "MOCK-CASH-001", RequestNo: "MOCK-REQ-CASH-001", Amount: 5000,
Channel: "bank", TradeNo: "MOCK-CASH-TRADE-001", Remark: "模拟提现",
ReviewerIdentity: gasAccount.Identity, ReviewerName: gasAccount.DisplayName,
ReviewedAt: &now, ReviewReason: "模拟审核通过", CompletedAt: &now, BalanceReserved: true,
}
if err := put(tx, &applyCash); err != nil {
return err
}
gasOrderPayment := models.GasorderPayment{
Entity: entity(39, common.StatusEnable), GasorderBasicID: gasOrder.ID,
PaymentOrderID: walletPayment.ID, AttemptNo: 1, Amount: gasOrder.PayableAmount,
}
if err := put(tx, &gasOrderPayment); err != nil {
return err
}
finPayment := models.FinPayment{
Entity: entity(40, common.StatusEnable), PaymentStatus: common.StatusPaid, EcOrderID: ecOrder.ID,
Channel: "wallet", Amount: ecOrder.TotalAmount, PaidAt: &now,
}
if err := put(tx, &finPayment); err != nil {
return err
}
settlement := models.FinSettlement{
Entity: entity(41, common.StatusEnable), SettlementNo: "MOCK-SETTLEMENT-001",
SubjectType: "gas", SubjectID: gas.ID,
PeriodStart: now.AddDate(0, 0, -30), PeriodEnd: now,
}
if err := put(tx, &settlement); err != nil {
return err
}
reconciliation := models.FinReconciliation{
Entity: entity(42, common.StatusEnable), ReconciliationStatus: common.StatusMatched, Channel: "wallet",
BillDate: now, DifferenceAmount: 0,
}
if err := put(tx, &reconciliation); err != nil {
return err
}
content := models.CmsContent{
Entity: entity(43, common.StatusEnable), ContentType: "notice",
Title: "模拟数据使用说明", Body: "本内容由 platform-cli mock-data 生成。",
VersionNo: 1, PublishStatus: "published",
}
if err := put(tx, &content); err != nil {
return err
}
ticket := models.CsTicket{
Entity: entity(44, common.StatusEnable), TicketStatus: common.StatusOpen, TicketNo: "MOCK-TICKET-001",
UserAccountID: user.ID, Category: "delivery", Priority: "normal",
}
if err := put(tx, &ticket); err != nil {
return err
}
attendance := models.StaffAttendance{
Entity: entity(48, common.StatusEnable), StaffAccountID: staff.ID,
RoleCode: staff.RoleCode, Action: "clock_in", OccurredAt: yesterday,
Longitude: gas.Longitude, Latitude: gas.Latitude,
DeviceIdentity: "mock-staff-device-001", RequestNo: "MOCK-ATTENDANCE-001",
}
if err := put(tx, &attendance); err != nil {
return err
}
recharge := models.WalletRechargeOrder{
Entity: entity(50, common.StatusEnable), RechargeStatus: common.StatusCompleted,
WalletBasicID: wallet.ID, RechargeNo: "MOCK-RECHARGE-001",
RequestNo: "MOCK-REQ-RECHARGE-001", Amount: 50000, Channel: "mock",
OwnerType: wallet.OwnerType, OwnerIdentity: wallet.OwnerIdentity, CompletedAt: &now,
}
if err := put(tx, &recharge); err != nil {
return err
}
refundItem := models.PaymentRefundItem{
Identity: entity(51, common.StatusEnable).Identity, PaymentRefundID: refund.ID,
OrderItemIdentity: gasOrderItem.Identity, Quantity: 1, Amount: refund.Amount,
}
if err := put(tx, &refundItem); err != nil {
return err
}
contentRead := models.CmsContentRead{
Entity: entity(52, common.StatusEnable), UserAccountID: user.ID, CmsContentID: content.ID,
VersionNo: content.VersionNo, ShownAt: yesterday, ConfirmedAt: &now,
ClientVersion: "mock-1.0.0", DeviceIdentity: "mock-user-device-001",
RequestNo: "MOCK-CONTENT-READ-001",
}
if err := put(tx, &contentRead); err != nil {
return err
}
evidence := models.CsTicketEvidence{
Entity: entity(53, common.StatusEnable), CsTicketID: ticket.ID,
EvidenceType: "created", MediaType: "image", FileURI: "/mock/tickets/ticket-001.png",
CapturedAt: yesterday, ReceivedAt: now, Longitude: address.Longitude, Latitude: address.Latitude,
Source: "mock", IntegrityStatus: "verified", OperatorIdentity: staff.Identity,
RequestNo: "MOCK-TICKET-EVIDENCE-001",
}
if err := put(tx, &evidence); err != nil {
return err
}
command := models.IotCommand{
Entity: entity(54, common.StatusEnable), DeviceIdentity: productInfo.Identity,
DeviceID: "0000000000000001", IdempotencyKey: "MOCK-IOT-COMMAND-001",
Action: "query_status", RequestPayload: `{"source":"mock-data"}`,
CommandStatus: "pending", ExpiresAt: now.Add(10 * time.Minute),
}
if err := put(tx, &command); err != nil {
return err
}
deviceMessage := models.IotDeviceMessage{
Identity: entity(55, common.StatusEnable).Identity, DeviceID: command.DeviceID,
Topic: "mock/device/0000000000000001/up", MessageType: "telemetry",
PayloadHex: "0000", DecodedFrame: `{"source":"mock-data"}`,
DeviceOccurredAt: &yesterday, ReceivedAt: now,
}
if err := put(tx, &deviceMessage); err != nil {
return err
}
outbox := models.IotOutbox{
Identity: entity(56, common.StatusEnable).Identity, CommandIdentity: command.Identity,
EventType: "iot.command.requested", Payload: `{"source":"mock-data"}`,
OutboxStatus: "pending", AvailableAt: now,
}
if err := put(tx, &outbox); err != nil {
return err
}
if err := ensureMockRoot(tx, string(passwordHash)); err != nil {
return err
}
var rootRole models.PlatformRole
if err := tx.Where("role_code = ?", "root").First(&rootRole).Error; err != nil {
return fmt.Errorf("find root role for mock menu: %w", err)
}
roleMenu := models.PlatformRoleMenu{PlatformRoleID: rootRole.ID, MenuIdentity: "dashboard_overview"}
if err := tx.Where("platform_role_id = ? AND menu_identity = ?", rootRole.ID, roleMenu.MenuIdentity).FirstOrCreate(&roleMenu).Error; err != nil {
return fmt.Errorf("seed root role menu: %w", err)
}
return seedAdditionalCoreScenarios(tx, string(passwordHash), now)
})
}
// seedAdditionalCoreScenarios 补齐十组可独立检索的核心主数据。
// 每组记录都使用本组父表的数据库 ID 建立关联,避免仅有展示字段而无真实关系。
func seedAdditionalCoreScenarios(database *gorm.DB, passwordHash string, now time.Time) error {
// 联调合同统一设置一年有效期,保证合同详情具有明确到期时间。
nextYear := now.AddDate(1, 0, 0)
for scenario := 2; scenario <= 10; scenario++ {
sequence := 1000 + scenario*100
suffix := fmt.Sprintf("%03d", scenario)
phoneSuffix := fmt.Sprintf("%08d", scenario)
gas := models.GasBasic{
Entity: entity(sequence+1, common.StatusEnable), Code: "MOCK-GAS-" + suffix,
Name: fmt.Sprintf("和气示例气站 %d", scenario), CreditCode: "91310000MOCKGAS" + suffix,
Principal: fmt.Sprintf("示例站长%d", scenario), Address: fmt.Sprintf("上海市示例路 %d 号", scenario),
Longitude: fmt.Sprintf("121.5%03d", scenario), Latitude: fmt.Sprintf("31.2%03d", scenario),
}
if err := put(database, &gas); err != nil {
return err
}
delivery := models.DeliveryBasic{
Entity: entity(sequence+2, common.StatusEnable), DeliveryCode: "MOCK-DELIVERY-" + suffix,
GasBasicID: gas.ID, Name: fmt.Sprintf("和气示例配送点 %d", scenario),
Principal: fmt.Sprintf("示例主管%d", scenario), Address: fmt.Sprintf("上海市配送路 %d 号", scenario),
}
if err := put(database, &delivery); err != nil {
return err
}
staff := models.StaffAccount{
Entity: entity(sequence+3, common.StatusEnable), Username: "mock_driver_" + suffix,
PasswordHash: passwordHash, Name: fmt.Sprintf("示例配送员%d", scenario), Phone: "139" + phoneSuffix,
RoleCode: "delivery", GasBasicID: gas.ID, DeliveryBasicID: delivery.ID, WorkStatus: "on_duty",
}
if err := put(database, &staff); err != nil {
return err
}
user := models.UserAccount{
Entity: entity(sequence+4, common.StatusEnable), Username: "mock_customer_" + suffix,
PasswordHash: passwordHash, Name: fmt.Sprintf("示例客户%d", scenario), Phone: "138" + phoneSuffix,
RealName: fmt.Sprintf("模拟用户%d", scenario),
}
if err := put(database, &user); err != nil {
return err
}
address := models.UserAddress{
Entity: entity(sequence+5, common.StatusEnable), UserAccountID: user.ID,
Address: fmt.Sprintf("上海市客户路 %d 号", scenario), Longitude: gas.Longitude,
Latitude: gas.Latitude, IsDefault: true,
}
if err := put(database, &address); err != nil {
return err
}
relation := models.UserServiceRelation{
Entity: entity(sequence+6, common.StatusEnable), UserAccountID: user.ID,
GasBasicID: gas.ID, DeliveryBasicID: delivery.ID, StaffAccountID: staff.ID,
}
if err := put(database, &relation); err != nil {
return err
}
productType := models.ProductType{
Entity: entity(sequence+7, common.StatusEnable), Code: "MOCK-TYPE-" + suffix,
Name: fmt.Sprintf("示例智能瓶阀类型 %d", scenario),
}
if err := put(database, &productType); err != nil {
return err
}
warehouse := models.ProductWarehouse{
Entity: entity(sequence+8, common.StatusEnable), Code: "MOCK-WH-" + suffix,
Name: fmt.Sprintf("示例库房 %d", scenario), Address: gas.Address,
Manager: fmt.Sprintf("示例库管%d", scenario), Phone: "137" + phoneSuffix,
}
if err := put(database, &warehouse); err != nil {
return err
}
producer := models.ProducerAccount{
Entity: entity(sequence+20, common.StatusEnable), ProducerCode: "MOCK-PRODUCER-" + suffix,
Name: fmt.Sprintf("示例生产企业 %d", scenario), CreditCode: "91310000MOCKPROD" + suffix,
Principal: fmt.Sprintf("示例厂长%d", scenario), Phone: "136" + phoneSuffix,
Address: fmt.Sprintf("上海市工业路 %d 号", scenario), Username: "mock_producer_" + suffix,
DisplayName: fmt.Sprintf("示例生产管理员%d", scenario), PasswordHash: passwordHash,
RoleCode: "admin", Remark: "仅用于开发联调",
}
if err := put(database, &producer); err != nil {
return err
}
product := models.ProductInfo{
Entity: entity(sequence+9, common.StatusEnable), Code: "MOCK-CYLINDER-" + suffix,
Name: fmt.Sprintf("示例智能瓶阀 %d", scenario), ProductStatus: common.StatusInStock,
ProductTypeID: productType.ID, Params: `{"source":"mock-data"}`,
WarehouseID: warehouse.ID, ProducedAt: now.AddDate(-1, 0, scenario),
}
if err := linkMockProductProducer(&product, producer); err != nil {
return err
}
if err := put(database, &product); err != nil {
return err
}
contract := models.GasorderContract{
Entity: entity(sequence+10, common.StatusEnable), ContractStatus: common.StatusActive,
ContractNo: "MOCK-CONTRACT-" + suffix, UserAccountID: user.ID, GasBasicID: gas.ID,
DeliveryBasicID: delivery.ID, Title: fmt.Sprintf("示例供气合同 %d", scenario),
Terms: "仅用于开发联调。", DefaultDeliveryFee: 500,
SignedAt: now, EffectiveAt: now, ExpiredAt: &nextYear,
}
if err := put(database, &contract); err != nil {
return err
}
// 仅回填本函数生成的联调合同,修复旧种子执行后已存在的空到期时间。
if err := database.Model(&models.GasorderContract{}).
Where("identity = ? AND contract_no = ? AND expired_at IS NULL", contract.Identity, contract.ContractNo).
Update("expired_at", nextYear).Error; err != nil {
return fmt.Errorf("backfill mock contract expiry: %w", err)
}
contractProduct := models.GasorderContractProduct{
Entity: entity(sequence+11, common.StatusEnable), GasorderContractID: contract.ID,
ProductInfoID: product.ID, ProductCode: product.Code, ProductTypeName: productType.Name,
ProductParams: product.Params, UnitPrice: int64(9000 + scenario*100), BoundAt: now,
}
if err := put(database, &contractProduct); err != nil {
return err
}
gasOrder := models.GasorderBasic{
Entity: entity(sequence+12, common.StatusEnable), OrderStatus: mockGasorderInitialStatus(),
OrderNo: "MOCK-GASORDER-" + suffix, RequestNo: "MOCK-REQ-GASORDER-" + suffix,
GasorderContractID: contract.ID, UserAccountID: user.ID, CreatorType: "user", CreatorID: user.ID,
CreatorIdentity: user.Identity, GasBasicID: gas.ID, DeliveryBasicID: delivery.ID,
StaffAccountID: staff.ID, Address: address.Address, Longitude: address.Longitude, Latitude: address.Latitude,
ContactName: user.Name, ContactPhone: user.Phone, ProductAmount: contractProduct.UnitPrice,
DeliveryFee: 500, PayableAmount: contractProduct.UnitPrice + 500,
OperatorIdentity: user.Identity, OperatorName: user.Name, Remark: "模拟待处理订单",
}
if err := put(database, &gasOrder); err != nil {
return err
}
if err := repairMockGasorderInitialStatus(database, gasOrder); err != nil {
return err
}
gasOrderItem := models.GasorderItem{
Entity: entity(sequence+13, common.StatusEnable), GasorderBasicID: gasOrder.ID,
GasorderContractProductID: contractProduct.ID, ProductInfoID: product.ID,
ProductCode: product.Code, ProductTypeName: productType.Name,
ProductParams: product.Params, UnitPrice: contractProduct.UnitPrice,
}
if err := put(database, &gasOrderItem); err != nil {
return err
}
category := models.EcCategory{
Entity: entity(sequence+14, common.StatusEnable), Name: fmt.Sprintf("示例商品分类 %d", scenario), SortNo: scenario,
}
if err := put(database, &category); err != nil {
return err
}
ecProduct := models.EcProduct{
Entity: entity(sequence+15, common.StatusEnable), EcCategoryID: category.ID,
ProductCode: "MOCK-EC-PRODUCT-" + suffix, Name: fmt.Sprintf("示例配送商品 %d", scenario),
PriceAmount: gasOrder.PayableAmount, StockQuantity: 20 + scenario,
}
if err := put(database, &ecProduct); err != nil {
return err
}
ecOrder := models.EcOrder{
Entity: entity(sequence+16, common.StatusEnable), OrderStatus: mockEcOrderPendingStatus(),
OrderNo: "MOCK-ECORDER-" + suffix, RequestNo: "MOCK-REQ-ECORDER-" + suffix,
UserAccountID: user.ID, GasStationID: gas.ID, DeliveryPointID: delivery.ID,
UserAddressID: address.ID, Address: address.Address,
Longitude: address.Longitude, Latitude: address.Latitude,
ContactName: user.Name, ContactPhone: user.Phone, Remark: "模拟待支付商城订单",
ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount,
TotalAmount: ecProduct.PriceAmount,
}
desiredEcOrder := ecOrder
if err := put(database, &ecOrder); err != nil {
return err
}
if err := repairMockEcOrder(database, desiredEcOrder, mockEcOrderPendingStatus(), ecProduct.PriceAmount, nil); err != nil {
return err
}
wallet := models.WalletBasic{
Entity: entity(sequence+17, common.StatusEnable), OwnerType: "user", OwnerID: user.ID,
OwnerIdentity: user.Identity, AlipayID: "mock-" + suffix + "@example.invalid", AlipayName: user.Name,
WxpayID: "mock_customer_" + suffix, WxpayName: user.Name, PayPasswordHash: passwordHash,
Balance: int64(scenario * 10000), WithdrawalBalance: int64(scenario * 5000),
}
if err := put(database, &wallet); err != nil {
return err
}
content := models.CmsContent{
Entity: entity(sequence+18, common.StatusEnable), ContentType: "notice",
Title: fmt.Sprintf("模拟公告 %d", scenario), Body: "本内容由 mock-data 生成。",
VersionNo: 1, PublishStatus: "published",
}
if err := put(database, &content); err != nil {
return err
}
ticket := models.CsTicket{
Entity: entity(sequence+19, common.StatusEnable), TicketStatus: common.StatusOpen,
TicketNo: "MOCK-TICKET-" + suffix, UserAccountID: user.ID, Category: "delivery", Priority: "normal",
}
if err := put(database, &ticket); err != nil {
return err
}
}
return nil
}
// mockGasorderInitialStatus 统一 Mock 配送订单初始状态,必须与正式订单状态机保持一致。
func mockGasorderInitialStatus() int {
return common.StatusCreated
}
// Mock 商城订单使用商城交易状态,不复用其他领域含义相近的通用状态。
func mockEcOrderPendingStatus() int { return 16 }
func mockEcOrderPaidStatus() int { return 18 }
// repairMockEcOrder 幂等修复固定 Mock 商城订单的状态、金额与支付时间。
func repairMockEcOrder(database *gorm.DB, order models.EcOrder, status int, amount int64, paidAt *time.Time) error {
result := database.Model(&models.EcOrder{}).
Where("identity = ? AND order_no LIKE ?", order.Identity, "MOCK-ECORDER-%").
Updates(map[string]any{
"order_status": status, "product_amount": amount, "payable_amount": amount,
"total_amount": amount, "paid_at": paidAt, "request_no": order.RequestNo,
"user_address_id": order.UserAddressID, "address": order.Address,
"longitude": order.Longitude, "latitude": order.Latitude,
"contact_name": order.ContactName, "contact_phone": order.ContactPhone,
"remark": order.Remark,
})
if result.Error != nil {
return fmt.Errorf("repair mock ec order: %w", result.Error)
}
return nil
}
type mockEcOrderRepairSpec struct {
identity string
orderNo string
requestNo string
userIdentity string
addressIdentity string
contactName string
contactPhone string
address string
longitude string
latitude string
status int
amount int64
paidAt *time.Time
remark string
}
// mockEcOrderRepairSpecs 返回固定 Mock 商城订单的文档化交易状态与金额。
func mockEcOrderRepairSpecs() []mockEcOrderRepairSpec {
paidAt := time.Date(2026, time.July, 1, 9, 0, 0, 0, time.Local)
specs := []mockEcOrderRepairSpec{{
identity: entity(30, common.StatusEnable).Identity,
orderNo: "MOCK-ECORDER-001",
requestNo: "MOCK-REQ-ECORDER-001",
userIdentity: entity(7, common.StatusEnable).Identity,
addressIdentity: entity(8, common.StatusEnable).Identity,
contactName: "陈女士",
contactPhone: "13800000001",
address: "上海市浦东新区客户路 88 号",
longitude: "121.5500",
latitude: "31.2250",
status: mockEcOrderPaidStatus(),
amount: 10300,
paidAt: &paidAt,
remark: "模拟已支付商城订单",
}}
for scenario := 2; scenario <= 10; scenario++ {
sequence := 1000 + scenario*100
specs = append(specs, mockEcOrderRepairSpec{
identity: entity(sequence+16, common.StatusEnable).Identity,
orderNo: fmt.Sprintf("MOCK-ECORDER-%03d", scenario),
requestNo: fmt.Sprintf("MOCK-REQ-ECORDER-%03d", scenario),
userIdentity: entity(sequence+4, common.StatusEnable).Identity,
addressIdentity: entity(sequence+5, common.StatusEnable).Identity,
contactName: fmt.Sprintf("示例客户%d", scenario),
contactPhone: "138" + fmt.Sprintf("%08d", scenario),
address: fmt.Sprintf("上海市客户路 %d 号", scenario),
longitude: fmt.Sprintf("121.5%03d", scenario),
latitude: fmt.Sprintf("31.2%03d", scenario),
status: mockEcOrderPendingStatus(),
amount: int64(9500 + scenario*100),
remark: "模拟待支付商城订单",
})
}
return specs
}
// RepairMockEcOrders 仅修复固定 Mock 商城订单,不触发其他模拟资源写入。
func RepairMockEcOrders(database *gorm.DB) (int64, error) {
var repaired int64
err := database.Transaction(func(tx *gorm.DB) error {
for _, spec := range mockEcOrderRepairSpecs() {
var user models.UserAccount
if err := tx.Where("identity = ?", spec.userIdentity).First(&user).Error; err != nil {
return fmt.Errorf("load mock ec order user %s: %w", spec.orderNo, err)
}
if err := tx.Model(&user).Updates(map[string]any{
"name": spec.contactName, "phone": spec.contactPhone,
}).Error; err != nil {
return fmt.Errorf("repair mock ec order user %s: %w", spec.orderNo, err)
}
var address models.UserAddress
if err := tx.Where("identity = ?", spec.addressIdentity).First(&address).Error; err != nil {
return fmt.Errorf("load mock ec order address %s: %w", spec.orderNo, err)
}
if err := tx.Model(&address).Updates(map[string]any{
"address": spec.address, "longitude": spec.longitude, "latitude": spec.latitude,
}).Error; err != nil {
return fmt.Errorf("repair mock ec order address %s: %w", spec.orderNo, err)
}
result := tx.Model(&models.EcOrder{}).
Where("identity = ? AND order_no = ?", spec.identity, spec.orderNo).
Updates(map[string]any{
"order_status": spec.status, "product_amount": spec.amount,
"payable_amount": spec.amount, "total_amount": spec.amount,
"paid_at": spec.paidAt, "request_no": spec.requestNo,
"user_address_id": address.ID, "address": spec.address,
"longitude": spec.longitude, "latitude": spec.latitude,
"contact_name": spec.contactName, "contact_phone": spec.contactPhone,
"remark": spec.remark,
})
if result.Error != nil {
return fmt.Errorf("repair mock ec order %s: %w", spec.orderNo, result.Error)
}
repaired += result.RowsAffected
}
return nil
})
return repaired, err
}
// repairMockGasorderInitialStatus 幂等修复历史种子数据中误写为“待处理”的 Mock 配送订单。
func repairMockGasorderInitialStatus(database *gorm.DB, order models.GasorderBasic) error {
result := database.Model(&models.GasorderBasic{}).
Where("identity = ? AND order_no LIKE ? AND order_status = ?", order.Identity, "MOCK-GASORDER-%", common.StatusPending).
Update("order_status", mockGasorderInitialStatus())
if result.Error != nil {
return fmt.Errorf("repair mock gasorder initial status: %w", result.Error)
}
return nil
}
// RepairMockGasorderInitialStatuses 批量修复固定 Mock 标识范围内误写为“待处理”的配送订单,返回修复数量。
func RepairMockGasorderInitialStatuses(database *gorm.DB) (int64, error) {
result := database.Model(&models.GasorderBasic{}).
Where("identity LIKE ? AND order_no LIKE ? AND order_status = ?", mockIdentityPrefix+"%", "MOCK-GASORDER-%", common.StatusPending).
Update("order_status", mockGasorderInitialStatus())
if result.Error != nil {
return 0, fmt.Errorf("repair mock gasorder initial statuses: %w", result.Error)
}
return result.RowsAffected, nil
}
const (
mockTrackPointRequestNo = "MOCK-TRACK-POINT-001"
mockTrackPointSpeed = "12 km/h"
mockTrackPointDirection = "东北45°"
mockTrackPointReceiveDelay = 2 * time.Second
)
// RepairMockTrackPointSample 仅补全固定演示轨迹点的缺失上报字段,不覆盖已有有效数据。
func RepairMockTrackPointSample(database *gorm.DB) (int64, error) {
var point models.GasorderTrackPoint
identity := fmt.Sprintf("%s%012d", mockIdentityPrefix, 23)
if err := database.Where("identity = ?", identity).First(&point).Error; err != nil {
return 0, fmt.Errorf("find mock track point sample: %w", err)
}
updates := map[string]any{}
if point.RequestNo == "" {
updates["request_no"] = mockTrackPointRequestNo
}
if point.ReceivedAt.IsZero() {
updates["received_at"] = point.OccurredAt.Add(mockTrackPointReceiveDelay)
}
if point.Speed == "" {
updates["speed"] = mockTrackPointSpeed
}
if point.Direction == "" {
updates["direction"] = mockTrackPointDirection
}
if len(updates) == 0 {
return 0, nil
}
result := database.Model(&point).Updates(updates)
if result.Error != nil {
return 0, fmt.Errorf("repair mock track point sample: %w", result.Error)
}
return result.RowsAffected, nil
}
// linkMockProductProducer 为未来生成的 Mock 智能气阀建立真实生产商关联。
func linkMockProductProducer(product *models.ProductInfo, producer models.ProducerAccount) error {
if product == nil || producer.ID == 0 {
return errors.New("mock product producer is missing")
}
product.ProducerAccountID = producer.ID
return nil
}
// ensureMockRoot 复用已有 root 账号,仅在账号不存在时写入默认 root 用户。
func ensureMockRoot(database *gorm.DB, passwordHash string) error {
role := models.PlatformRole{
Entity: entity(45, common.StatusEnable), RoleCode: "root",
Name: "系统管理员", LocationScope: "precise", IsSystem: true,
}
if err := database.Where("role_code = ?", role.RoleCode).FirstOrCreate(&role).Error; err != nil {
return fmt.Errorf("seed root platform role: %w", err)
}
var account models.PlatformAccount
err := database.Where("username = ?", "root").First(&account).Error
if err == nil {
return nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("find root platform account: %w", err)
}
account = models.PlatformAccount{
Entity: entity(46, common.StatusEnable), Username: "root",
DisplayName: "平台根管理员", PasswordHash: passwordHash,
PlatformRoleCode: role.RoleCode,
}
if err := database.Create(&account).Error; err != nil {
return fmt.Errorf("seed root platform account: %w", err)
}
return nil
}
func entity(sequence int, status int) models.Entity {
return models.Entity{
Identity: fmt.Sprintf("%s%012d", mockIdentityPrefix, sequence),
Status: status,
}
}
func put[T any](database *gorm.DB, value *T) error {
identity := identityOf(value)
if identity == "" {
return fmt.Errorf("seed %T: missing identity", value)
}
if err := database.Where("identity = ?", identity).FirstOrCreate(value).Error; err != nil {
return fmt.Errorf("seed %T: %w", value, err)
}
return nil
}
func identityOf(value any) string {
record := reflect.Indirect(reflect.ValueOf(value))
if !record.IsValid() {
return ""
}
directIdentity := record.FieldByName("Identity")
if directIdentity.IsValid() && directIdentity.Kind() == reflect.String {
return directIdentity.String()
}
entityField := record.FieldByName("Entity")
if !entityField.IsValid() {
return ""
}
identityField := entityField.FieldByName("Identity")
if !identityField.IsValid() || identityField.Kind() != reflect.String {
return ""
}
return identityField.String()
}