Files
jiu/backend/internal/model/base.go
T
wangjia 754275cbd6 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>
2026-04-07 22:29:15 +08:00

121 lines
2.4 KiB
Go

package model
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
// JSON 类型,用于 custom_fields
type JSON map[string]interface{}
func (j JSON) Value() (driver.Value, error) {
if j == nil {
return nil, nil
}
b, err := json.Marshal(j)
return string(b), err
}
func (j *JSON) Scan(value interface{}) error {
if value == nil {
*j = nil
return nil
}
var bytes []byte
switch v := value.(type) {
case string:
bytes = []byte(v)
case []byte:
bytes = v
default:
return fmt.Errorf("cannot scan type %T into JSON", value)
}
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"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `gorm:"index" json:"-"`
}
// TenantBase 含租户隔离的公共字段
type TenantBase struct {
Base
ShopID uint64 `gorm:"not null;index" json:"shop_id"`
}