feat: add linked mock data seeding CLI
This commit is contained in:
@@ -4,8 +4,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"git.apinb.com/bsm-sdk/core/database"
|
||||||
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/config"
|
||||||
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/initdb"
|
||||||
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/seed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const serviceKey = "heqi"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
printUsage()
|
printUsage()
|
||||||
@@ -19,6 +26,12 @@ func main() {
|
|||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
case "mock-data":
|
||||||
|
if err := writeMockData(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("mock data written successfully")
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
||||||
printUsage()
|
printUsage()
|
||||||
@@ -27,5 +40,27 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printUsage() {
|
func printUsage() {
|
||||||
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract>")
|
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract|mock-data>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMockData() error {
|
||||||
|
config.New(serviceKey)
|
||||||
|
if config.Spec.Databases == nil {
|
||||||
|
return fmt.Errorf("database configuration is required")
|
||||||
|
}
|
||||||
|
databaseService, err := database.NewDatabase(
|
||||||
|
config.Spec.Databases.Driver,
|
||||||
|
config.Spec.Databases.Source,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect database: %w", err)
|
||||||
|
}
|
||||||
|
if err := initdb.New(databaseService); err != nil {
|
||||||
|
return fmt.Errorf("initialize platform data: %w", err)
|
||||||
|
}
|
||||||
|
if err := seed.MockData(databaseService); err != nil {
|
||||||
|
return fmt.Errorf("write mock data: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
506
backend/api/internal/seed/mock.go
Normal file
506
backend/api/internal/seed/mock.go
Normal file
@@ -0,0 +1,506 @@
|
|||||||
|
// Package seed writes linked development data without replacing existing rows.
|
||||||
|
package seed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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, "enabled"), 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, "enabled"), 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, "enabled"), 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, "enabled"), 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, "enabled"), Username: "mock_driver", PasswordHash: string(passwordHash),
|
||||||
|
Name: "王师傅", Phone: "13900000001", RoleCode: "driver",
|
||||||
|
GasBasicID: gas.ID, DeliveryBasicID: delivery.ID, WorkStatus: "on_duty",
|
||||||
|
}
|
||||||
|
if err := put(tx, &staff); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
credential := models.StaffCredential{
|
||||||
|
Entity: entity(6, "enabled"), 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, "enabled"), 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, "enabled"), 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, "enabled"), 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, "enabled"), Code: "MOCK-LPG-15KG", Name: "15kg 液化气钢瓶",
|
||||||
|
}
|
||||||
|
if err := put(tx, &productType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
warehouse := models.ProductWarehouse{
|
||||||
|
Entity: entity(11, "enabled"), Code: "MOCK-WH-001", Name: "示例中心库房",
|
||||||
|
Address: gas.Address, Manager: "赵库管", Phone: "13700000001", IsEnabled: true,
|
||||||
|
}
|
||||||
|
if err := put(tx, &warehouse); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
enabledAt := yesterday
|
||||||
|
productInfo := models.ProductInfo{
|
||||||
|
Entity: entity(12, "enabled"), Code: "MOCK-CYLINDER-001", Name: "示例液化气钢瓶",
|
||||||
|
ProductTypeID: productType.ID, Params: `{"weight":"15kg","medium":"LPG"}`,
|
||||||
|
WarehouseID: warehouse.ID, GasBasicID: gas.ID, ProducedAt: now.AddDate(-1, 0, 0),
|
||||||
|
IsEnabled: true, EnabledAt: &enabledAt,
|
||||||
|
}
|
||||||
|
if err := put(tx, &productInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
productOwner := models.ProductOwner{
|
||||||
|
Entity: entity(13, "enabled"), ProductInfoID: productInfo.ID,
|
||||||
|
WarehouseID: warehouse.ID, GasBasicID: gas.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, "completed"), ProductInfoID: productInfo.ID,
|
||||||
|
RepairNo: "MOCK-REPAIR-001", RepairType: "inspection", StartedAt: yesterday,
|
||||||
|
CompletedAt: &completedAt, Result: "passed", TargetStatus: "enabled",
|
||||||
|
Content: "外观、阀门与气密性检查", Operator: staff.Name,
|
||||||
|
}
|
||||||
|
if err := put(tx, &productRepair); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
contract := models.GasorderContract{
|
||||||
|
Entity: entity(15, "active"), 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, "active"), GasorderContractID: contract.ID, Action: "activate",
|
||||||
|
ContractStatus: "active", 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, "active"), 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, "completed"), 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, "completed"), 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, "completed"), 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, "completed"), GasorderBasicID: gasOrder.ID,
|
||||||
|
FromStatus: "delivering", ToStatus: "completed",
|
||||||
|
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, "completed"), 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, "completed"), 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, "completed"), 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, "enabled"), Name: "瓶装燃气", SortNo: 10,
|
||||||
|
}
|
||||||
|
if err := put(tx, &category); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ecProduct := models.EcProduct{
|
||||||
|
Entity: entity(26, "enabled"), 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, "enabled"), EcProductID: ecProduct.ID,
|
||||||
|
Name: "规格", Value: "15kg/瓶", SortNo: 1,
|
||||||
|
}
|
||||||
|
if err := put(tx, &attribute); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
image := models.EcProductImage{
|
||||||
|
Entity: entity(28, "enabled"), 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, "enabled"), 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, "paid"), 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, "paid"), 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, "published"), 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, "enabled"), 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, "enabled"), 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.WalletPayment{
|
||||||
|
Entity: entity(35, "success"), WalletBasicID: wallet.ID,
|
||||||
|
PaymentNo: "MOCK-PAYMENT-001", OrderNo: gasOrder.OrderNo,
|
||||||
|
TradeNo: "MOCK-TRADE-001", PaymentType: "gasorder",
|
||||||
|
PayChannel: "balance", PayType: "wallet", Amount: gasOrder.PayableAmount,
|
||||||
|
Args: `{}`, CallbackMsg: `{"status":"success"}`,
|
||||||
|
}
|
||||||
|
if err := put(tx, &walletPayment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
walletRecord := models.WalletRecord{
|
||||||
|
Entity: entity(36, "completed"), 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.WalletRefund{
|
||||||
|
Entity: entity(37, "completed"), WalletBasicID: wallet.ID,
|
||||||
|
WalletPaymentID: walletPayment.ID, RefundNo: "MOCK-REFUND-001",
|
||||||
|
OrderIdentity: gasOrder.Identity, Amount: 1000, Reason: "模拟部分退款",
|
||||||
|
OrderInfo: `{"order_no":"MOCK-GASORDER-001"}`, Result: `{"status":"success"}`,
|
||||||
|
TradeNo: "MOCK-REFUND-TRADE-001", CompletedAt: &refundCompletedAt,
|
||||||
|
}
|
||||||
|
if err := put(tx, &refund); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
applyCash := models.WalletApplyCash{
|
||||||
|
Entity: entity(38, "approved"), 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,
|
||||||
|
}
|
||||||
|
if err := put(tx, &applyCash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gasOrderPayment := models.GasorderPayment{
|
||||||
|
Entity: entity(39, "success"), GasorderBasicID: gasOrder.ID,
|
||||||
|
WalletPaymentID: walletPayment.ID, AttemptNo: 1, Amount: gasOrder.PayableAmount,
|
||||||
|
}
|
||||||
|
if err := put(tx, &gasOrderPayment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
finPayment := models.FinPayment{
|
||||||
|
Entity: entity(40, "paid"), 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, "completed"), 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, "matched"), Channel: "wallet",
|
||||||
|
BillDate: now, DifferenceAmount: 0,
|
||||||
|
}
|
||||||
|
if err := put(tx, &reconciliation); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
content := models.CmsContent{
|
||||||
|
Entity: entity(43, "enabled"), 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, "open"), TicketNo: "MOCK-TICKET-001",
|
||||||
|
UserAccountID: user.ID, Category: "delivery", Priority: "normal",
|
||||||
|
}
|
||||||
|
if err := put(tx, &ticket); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
role := models.PlatformRole{
|
||||||
|
Entity: entity(45, "enabled"), RoleCode: "mock_operator",
|
||||||
|
Name: "模拟运营人员", DataScope: "global", IsSystem: false,
|
||||||
|
}
|
||||||
|
if err := put(tx, &role); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
platformAccount := models.PlatformAccount{
|
||||||
|
Entity: entity(46, "enabled"), Username: "mock_operator",
|
||||||
|
DisplayName: "模拟运营人员", PasswordHash: string(passwordHash),
|
||||||
|
PlatformRoleCode: role.RoleCode, Phone: "13600000001",
|
||||||
|
}
|
||||||
|
if err := put(tx, &platformAccount); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu models.PlatformMenu
|
||||||
|
if err := tx.Where("menu_code = ?", "dashboard").First(&menu).Error; err != nil {
|
||||||
|
return fmt.Errorf("find dashboard menu: %w", err)
|
||||||
|
}
|
||||||
|
roleMenu := models.PlatformRoleMenu{
|
||||||
|
PlatformRoleID: role.ID, PlatformMenuID: menu.ID,
|
||||||
|
}
|
||||||
|
if err := tx.Where(
|
||||||
|
"platform_role_id = ? AND platform_menu_id = ?",
|
||||||
|
role.ID, menu.ID,
|
||||||
|
).FirstOrCreate(&roleMenu).Error; err != nil {
|
||||||
|
return fmt.Errorf("seed platform_role_menu: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func entity(sequence int, status string) models.Entity {
|
||||||
|
return models.Entity{
|
||||||
|
Identity: fmt.Sprintf("%s%012d", mockIdentityPrefix, sequence),
|
||||||
|
Status: status,
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
entityField := record.FieldByName("Entity")
|
||||||
|
if !entityField.IsValid() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
identityField := entityField.FieldByName("Identity")
|
||||||
|
if !identityField.IsValid() || identityField.Kind() != reflect.String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return identityField.String()
|
||||||
|
}
|
||||||
41
backend/api/internal/seed/mock_test.go
Normal file
41
backend/api/internal/seed/mock_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package seed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMockEntityIdentitiesAreStableAndUnique(t *testing.T) {
|
||||||
|
identityPattern := regexp.MustCompile(
|
||||||
|
`^00000000-0000-7000-8000-[0-9]{12}$`,
|
||||||
|
)
|
||||||
|
seen := make(map[string]struct{}, 46)
|
||||||
|
for sequence := 1; sequence <= 46; sequence++ {
|
||||||
|
record := entity(sequence, "enabled")
|
||||||
|
if !identityPattern.MatchString(record.Identity) {
|
||||||
|
t.Fatalf("entity(%d) identity = %q", sequence, record.Identity)
|
||||||
|
}
|
||||||
|
if _, exists := seen[record.Identity]; exists {
|
||||||
|
t.Fatalf("duplicate identity %q", record.Identity)
|
||||||
|
}
|
||||||
|
seen[record.Identity] = struct{}{}
|
||||||
|
if record.Status != "enabled" || record.Version != 1 {
|
||||||
|
t.Fatalf("entity(%d) has unexpected defaults: %#v", sequence, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentityOfEmbeddedEntity(t *testing.T) {
|
||||||
|
record := struct {
|
||||||
|
Entity struct {
|
||||||
|
Identity string
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
record.Entity.Identity = "mock-identity"
|
||||||
|
if got := identityOf(&record); got != record.Entity.Identity {
|
||||||
|
t.Fatalf("identityOf() = %q, want %q", got, record.Entity.Identity)
|
||||||
|
}
|
||||||
|
if got := identityOf(nil); got != "" {
|
||||||
|
t.Fatalf("identityOf(nil) = %q, want empty", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user