fix(backend): 修复日期字段解析失败导致创建单据报错
新增 model.Date 类型,支持前端传入的纯日期格式 YYYY-MM-DD: - UnmarshalJSON 优先解析 YYYY-MM-DD,回退支持 RFC3339 - MarshalJSON 输出 YYYY-MM-DD - Value/Scan 实现 GORM MySQL DATE 列读写 - StockInOrder/StockOutOrder/InventoryCheck 的 OrderDate/CheckDate 改用 Date 类型 feat(client): SelectionArea 全局开启文字可选中复制 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -203,7 +203,7 @@ func main() {
|
||||
}
|
||||
|
||||
for _, o := range inOrders {
|
||||
order := upsertStockInOrder(db, shop.ID, wh1.ID, admin.ID, o.partnerID, o.orderNo, today.AddDate(0, 0, -3))
|
||||
order := upsertStockInOrder(db, shop.ID, wh1.ID, admin.ID, o.partnerID, o.orderNo, model.Date{Time: today.AddDate(0, 0, -3)})
|
||||
total := 0.0
|
||||
for _, it := range o.items {
|
||||
item := model.StockInItem{
|
||||
@@ -231,7 +231,7 @@ func main() {
|
||||
// ═══════════════════════════════════════════════════
|
||||
// 出库单(已审核)→ 减库存
|
||||
// ═══════════════════════════════════════════════════
|
||||
outOrder := upsertStockOutOrder(db, shop.ID, wh1.ID, admin.ID, cus1.ID, "CK20260404001", today.AddDate(0, 0, -1))
|
||||
outOrder := upsertStockOutOrder(db, shop.ID, wh1.ID, admin.ID, cus1.ID, "CK20260404001", model.Date{Time: today.AddDate(0, 0, -1)})
|
||||
outItems := []struct {
|
||||
prodIdx int
|
||||
qty float64
|
||||
@@ -371,7 +371,7 @@ func upsertNumberRule(db *gorm.DB, shopID uint64, ruleType, prefix string, curre
|
||||
}
|
||||
}
|
||||
|
||||
func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date time.Time) model.StockInOrder {
|
||||
func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date model.Date) model.StockInOrder {
|
||||
var o model.StockInOrder
|
||||
if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil {
|
||||
o = model.StockInOrder{
|
||||
@@ -390,7 +390,7 @@ func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, order
|
||||
return o
|
||||
}
|
||||
|
||||
func upsertStockOutOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date time.Time) model.StockOutOrder {
|
||||
func upsertStockOutOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date model.Date) model.StockOutOrder {
|
||||
var o model.StockOutOrder
|
||||
if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil {
|
||||
o = model.StockOutOrder{
|
||||
|
||||
@@ -35,6 +35,76 @@ func (j *JSON) Scan(value interface{}) error {
|
||||
return json.Unmarshal(bytes, j)
|
||||
}
|
||||
|
||||
// Date 仅含日期(YYYY-MM-DD)的类型,兼容前端只传日期字符串的场景
|
||||
type Date struct{ time.Time }
|
||||
|
||||
const dateFmt = "2006-01-02"
|
||||
|
||||
func (d *Date) UnmarshalJSON(b []byte) error {
|
||||
s := string(b)
|
||||
// 去掉引号
|
||||
if len(s) >= 2 && s[0] == '"' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
if s == "" || s == "null" {
|
||||
d.Time = time.Time{}
|
||||
return nil
|
||||
}
|
||||
// 尝试纯日期格式 YYYY-MM-DD
|
||||
if t, err := time.ParseInLocation(dateFmt, s, time.Local); err == nil {
|
||||
d.Time = t
|
||||
return nil
|
||||
}
|
||||
// 回退:标准 RFC3339
|
||||
t, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse date %q", s)
|
||||
}
|
||||
d.Time = t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Date) MarshalJSON() ([]byte, error) {
|
||||
if d.Time.IsZero() {
|
||||
return []byte(`""`), nil
|
||||
}
|
||||
return []byte(`"` + d.Time.Format(dateFmt) + `"`), nil
|
||||
}
|
||||
|
||||
// Value / Scan 让 GORM 正确读写 MySQL DATE 列
|
||||
func (d Date) Value() (driver.Value, error) {
|
||||
if d.Time.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return d.Time.Format(dateFmt), nil
|
||||
}
|
||||
|
||||
func (d *Date) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
d.Time = time.Time{}
|
||||
return nil
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
d.Time = v
|
||||
case []byte:
|
||||
t, err := time.ParseInLocation(dateFmt, string(v), time.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Time = t
|
||||
case string:
|
||||
t, err := time.ParseInLocation(dateFmt, v, time.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Time = t
|
||||
default:
|
||||
return fmt.Errorf("cannot scan type %T into Date", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Base 公共字段
|
||||
type Base struct {
|
||||
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
|
||||
@@ -13,7 +13,7 @@ type StockInOrder struct {
|
||||
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
||||
ReviewerID *uint64 `json:"reviewer_id"`
|
||||
Status string `gorm:"type:enum('draft','pending','approved','rejected');default:'draft'" json:"status"`
|
||||
OrderDate time.Time `gorm:"type:date" json:"order_date"`
|
||||
OrderDate Date `gorm:"type:date" json:"order_date"`
|
||||
TotalAmount float64 `gorm:"type:decimal(16,2);default:0" json:"total_amount"`
|
||||
ReviewedAt *time.Time `json:"reviewed_at"`
|
||||
CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"`
|
||||
@@ -51,7 +51,7 @@ type StockOutOrder struct {
|
||||
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
||||
ReviewerID *uint64 `json:"reviewer_id"`
|
||||
Status string `gorm:"type:enum('draft','pending','approved','rejected');default:'draft'" json:"status"`
|
||||
OrderDate time.Time `gorm:"type:date" json:"order_date"`
|
||||
OrderDate Date `gorm:"type:date" json:"order_date"`
|
||||
TotalAmount float64 `gorm:"type:decimal(16,2);default:0" json:"total_amount"`
|
||||
ReviewedAt *time.Time `json:"reviewed_at"`
|
||||
CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"`
|
||||
@@ -116,7 +116,7 @@ type InventoryCheck struct {
|
||||
WarehouseID uint64 `gorm:"not null" json:"warehouse_id"`
|
||||
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
||||
Status string `gorm:"type:enum('draft','completed');default:'draft'" json:"status"`
|
||||
CheckDate time.Time `gorm:"type:date" json:"check_date"`
|
||||
CheckDate Date `gorm:"type:date" json:"check_date"`
|
||||
Remark string `gorm:"size:500" json:"remark"`
|
||||
|
||||
Items []InventoryCheckItem `gorm:"foreignKey:CheckID" json:"items,omitempty"`
|
||||
|
||||
@@ -80,6 +80,7 @@ class _RouterApp extends ConsumerWidget {
|
||||
theme: AppTheme.light(),
|
||||
routerConfig: router,
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: (context, child) => SelectionArea(child: child!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user