package model import ( "context" "fmt" "strings" "github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/sqlx" ) var _ InvProductModel = (*customInvProductModel)(nil) // StockGroupResult is kept for GroupBy queries (color / location). type StockGroupResult struct { Name string `db:"name"` Count int64 `db:"count"` Quantity float64 `db:"quantity"` } type ProductSummary struct { ProductName string `db:"product_name"` Spec string `db:"spec"` ColorCount int64 `db:"color_count"` TotalPanCount int64 `db:"total_pan_count"` TotalBoltCount int64 `db:"total_bolt_count"` TotalLengthM float64 `db:"total_length_m"` Colors string `db:"colors"` Locations string `db:"locations"` } type ColorDetail struct { ProductId string `db:"product_id"` ProductName string `db:"product_name"` Color string `db:"color"` Spec string `db:"spec"` ImageUrl string `db:"image_url"` SalesPrice float64 `db:"sales_price"` PanCount int64 `db:"pan_count"` BoltCount int64 `db:"bolt_count"` TotalLengthM float64 `db:"total_length_m"` Locations string `db:"locations"` } type ( InvProductModel interface { invProductModel FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error) FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error) FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error) BulkInsert(ctx context.Context, products []*InvProduct) (int64, error) } customInvProductModel struct { *defaultInvProductModel } ) func NewInvProductModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) InvProductModel { return &customInvProductModel{ defaultInvProductModel: newInvProductModel(conn, c, opts...), } } const bulkBatchSize = 200 func (m *customInvProductModel) BulkInsert(ctx context.Context, products []*InvProduct) (int64, error) { if len(products) == 0 { return 0, nil } var totalAffected int64 for i := 0; i < len(products); i += bulkBatchSize { end := i + bulkBatchSize if end > len(products) { end = len(products) } batch := products[i:end] placeholder := "(" + strings.Repeat("?,", 9) + "?)" placeholders := make([]string, len(batch)) args := make([]interface{}, 0, len(batch)*10) for j, p := range batch { placeholders[j] = placeholder args = append(args, p.ProductId, p.ProductName, p.ImageUrl, p.Spec, p.Color, p.SalesPrice, p.Remark, p.Status, p.TenantId, p.DeletedAt, ) } query := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", m.table, invProductRowsExpectAutoSet, strings.Join(placeholders, ",")) result, err := m.ExecNoCacheCtx(ctx, query, args...) if err != nil { return totalAffected, fmt.Errorf("batch %d-%d: %w", i, end-1, err) } n, _ := result.RowsAffected() totalAffected += n } return totalAffected, nil } func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error) { where := "WHERE deleted_at IS NULL AND tenant_id = ?" args := []interface{}{tenantId} if productName != "" { where += " AND product_name LIKE ?" args = append(args, "%"+productName+"%") } if spec != "" { where += " AND spec LIKE ?" args = append(args, "%"+spec+"%") } if color != "" { where += " AND color = ?" args = append(args, color) } if status >= 0 { where += " AND status = ?" args = append(args, status) } var total int64 countQuery := fmt.Sprintf("SELECT COUNT(*) FROM %s %s", m.table, where) err := m.QueryRowNoCacheCtx(ctx, &total, countQuery, args...) if err != nil { return nil, 0, err } var list []*InvProduct query := fmt.Sprintf("SELECT %s FROM %s %s ORDER BY created_at DESC LIMIT ? OFFSET ?", invProductRows, m.table, where) args = append(args, pageSize, (page-1)*pageSize) err = m.QueryRowsNoCacheCtx(ctx, &list, query, args...) if err != nil { return nil, 0, err } return list, total, nil } func (m *customInvProductModel) FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error) { var list []*ProductSummary query := `SELECT p.product_name, p.spec, COUNT(DISTINCT p.product_id) AS color_count, COUNT(DISTINCT pan.pan_id) AS total_pan_count, COUNT(bolt.bolt_id) AS total_bolt_count, COALESCE(SUM(bolt.length_m), 0) AS total_length_m, COALESCE(GROUP_CONCAT(DISTINCT p.color), '') AS colors, COALESCE(GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position), '') AS locations FROM ` + m.table + ` p LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id WHERE p.tenant_id = ? AND p.deleted_at IS NULL GROUP BY p.product_name, p.spec ORDER BY p.product_name` err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId) if err != nil { return nil, err } return list, nil } func (m *customInvProductModel) FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error) { var list []StockGroupResult query := `SELECT p.color AS name, COUNT(DISTINCT p.product_id) AS count, COALESCE(SUM(bolt.length_m), 0) AS quantity FROM ` + m.table + ` p LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id WHERE p.tenant_id = ? AND p.deleted_at IS NULL GROUP BY p.color ORDER BY count DESC` err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId) if err != nil { return nil, err } return list, nil } func (m *customInvProductModel) FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error) { var list []StockGroupResult query := `SELECT pan.position AS name, COUNT(DISTINCT p.product_id) AS count, COALESCE(SUM(bolt.length_m), 0) AS quantity FROM ` + m.table + ` p JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id WHERE p.tenant_id = ? AND p.deleted_at IS NULL AND pan.position != '' GROUP BY pan.position ORDER BY count DESC` err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId) if err != nil { return nil, err } return list, nil } func (m *customInvProductModel) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) { var list []*ColorDetail query := `SELECT p.product_id, p.product_name, p.color, p.spec, p.image_url, p.sales_price, COUNT(DISTINCT pan.pan_id) AS pan_count, COUNT(bolt.bolt_id) AS bolt_count, COALESCE(SUM(bolt.length_m), 0) AS total_length_m, COALESCE(GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position), '') AS locations FROM ` + m.table + ` p LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id WHERE p.tenant_id = ? AND p.deleted_at IS NULL AND p.product_name = ? GROUP BY p.product_id ORDER BY p.color` err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId, productName) if err != nil { return nil, err } return list, nil }