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 {
|
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
|
total := 0.0
|
||||||
for _, it := range o.items {
|
for _, it := range o.items {
|
||||||
item := model.StockInItem{
|
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 {
|
outItems := []struct {
|
||||||
prodIdx int
|
prodIdx int
|
||||||
qty float64
|
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
|
var o model.StockInOrder
|
||||||
if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil {
|
if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil {
|
||||||
o = model.StockInOrder{
|
o = model.StockInOrder{
|
||||||
@@ -390,7 +390,7 @@ func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, order
|
|||||||
return o
|
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
|
var o model.StockOutOrder
|
||||||
if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil {
|
if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil {
|
||||||
o = model.StockOutOrder{
|
o = model.StockOutOrder{
|
||||||
|
|||||||
@@ -35,6 +35,76 @@ func (j *JSON) Scan(value interface{}) error {
|
|||||||
return json.Unmarshal(bytes, j)
|
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 公共字段
|
// Base 公共字段
|
||||||
type Base struct {
|
type Base struct {
|
||||||
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type StockInOrder struct {
|
|||||||
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
||||||
ReviewerID *uint64 `json:"reviewer_id"`
|
ReviewerID *uint64 `json:"reviewer_id"`
|
||||||
Status string `gorm:"type:enum('draft','pending','approved','rejected');default:'draft'" json:"status"`
|
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"`
|
TotalAmount float64 `gorm:"type:decimal(16,2);default:0" json:"total_amount"`
|
||||||
ReviewedAt *time.Time `json:"reviewed_at"`
|
ReviewedAt *time.Time `json:"reviewed_at"`
|
||||||
CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"`
|
CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"`
|
||||||
@@ -51,7 +51,7 @@ type StockOutOrder struct {
|
|||||||
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
||||||
ReviewerID *uint64 `json:"reviewer_id"`
|
ReviewerID *uint64 `json:"reviewer_id"`
|
||||||
Status string `gorm:"type:enum('draft','pending','approved','rejected');default:'draft'" json:"status"`
|
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"`
|
TotalAmount float64 `gorm:"type:decimal(16,2);default:0" json:"total_amount"`
|
||||||
ReviewedAt *time.Time `json:"reviewed_at"`
|
ReviewedAt *time.Time `json:"reviewed_at"`
|
||||||
CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"`
|
CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"`
|
||||||
@@ -116,7 +116,7 @@ type InventoryCheck struct {
|
|||||||
WarehouseID uint64 `gorm:"not null" json:"warehouse_id"`
|
WarehouseID uint64 `gorm:"not null" json:"warehouse_id"`
|
||||||
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
OperatorID uint64 `gorm:"not null" json:"operator_id"`
|
||||||
Status string `gorm:"type:enum('draft','completed');default:'draft'" json:"status"`
|
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"`
|
Remark string `gorm:"size:500" json:"remark"`
|
||||||
|
|
||||||
Items []InventoryCheckItem `gorm:"foreignKey:CheckID" json:"items,omitempty"`
|
Items []InventoryCheckItem `gorm:"foreignKey:CheckID" json:"items,omitempty"`
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class _RouterApp extends ConsumerWidget {
|
|||||||
theme: AppTheme.light(),
|
theme: AppTheme.light(),
|
||||||
routerConfig: router,
|
routerConfig: router,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
builder: (context, child) => SelectionArea(child: child!),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user