811 lines
31 KiB
Go
811 lines
31 KiB
Go
// 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
|
|
}
|
|
|
|
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 := 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,
|
|
OccurredAt: trackCompletedAt, Source: "gps", Accuracy: "10m",
|
|
}
|
|
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: common.StatusPaid, OrderNo: "MOCK-ECORDER-001",
|
|
UserAccountID: user.ID, GasStationID: gas.ID, DeliveryPointID: delivery.ID,
|
|
TotalAmount: ecProduct.PriceAmount,
|
|
}
|
|
if err := put(tx, &ecOrder); 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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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 {
|
|
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
|
|
}
|
|
|
|
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 := 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,
|
|
}
|
|
if err := put(database, &contract); err != nil {
|
|
return 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: common.StatusPending,
|
|
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
|
|
}
|
|
|
|
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: common.StatusPending,
|
|
OrderNo: "MOCK-ECORDER-" + suffix, UserAccountID: user.ID,
|
|
GasStationID: gas.ID, DeliveryPointID: delivery.ID, TotalAmount: ecProduct.PriceAmount,
|
|
}
|
|
if err := put(database, &ecOrder); 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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
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()
|
|
}
|