2026-02-28 15:29:16 +08:00
|
|
|
package model
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2026-06-14 15:24:02 +08:00
|
|
|
"strings"
|
2026-02-28 15:29:16 +08:00
|
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var _ InvProductModel = (*customInvProductModel)(nil)
|
|
|
|
|
|
2026-06-14 15:24:02 +08:00
|
|
|
// StockGroupResult is kept for GroupBy queries (color / location).
|
2026-02-28 15:29:16 +08:00
|
|
|
type StockGroupResult struct {
|
|
|
|
|
Name string `db:"name"`
|
|
|
|
|
Count int64 `db:"count"`
|
|
|
|
|
Quantity float64 `db:"quantity"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 15:24:02 +08:00
|
|
|
type ProductSummary struct {
|
2026-07-05 08:06:28 +09:00
|
|
|
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"`
|
2026-06-14 15:24:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ColorDetail struct {
|
|
|
|
|
ProductId string `db:"product_id"`
|
|
|
|
|
ProductName string `db:"product_name"`
|
|
|
|
|
Color string `db:"color"`
|
2026-07-05 08:06:28 +09:00
|
|
|
ColorNo string `db:"color_no"`
|
|
|
|
|
ProductCode string `db:"product_code"`
|
2026-06-14 15:24:02 +08:00
|
|
|
Spec string `db:"spec"`
|
|
|
|
|
ImageUrl string `db:"image_url"`
|
|
|
|
|
SalesPrice float64 `db:"sales_price"`
|
|
|
|
|
PanCount int64 `db:"pan_count"`
|
|
|
|
|
BoltCount int64 `db:"bolt_count"`
|
2026-07-05 08:06:28 +09:00
|
|
|
BatchCount int64 `db:"batch_count"`
|
2026-06-14 15:24:02 +08:00
|
|
|
TotalLengthM float64 `db:"total_length_m"`
|
|
|
|
|
Locations string `db:"locations"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 15:29:16 +08:00
|
|
|
type (
|
|
|
|
|
InvProductModel interface {
|
|
|
|
|
invProductModel
|
2026-07-05 08:06:28 +09:00
|
|
|
FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, colorNo, productCode string, status int64) ([]*InvProduct, int64, error)
|
2026-06-14 15:24:02 +08:00
|
|
|
FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error)
|
|
|
|
|
FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error)
|
2026-07-05 08:06:28 +09:00
|
|
|
FindOneByTenantProductCode(ctx context.Context, tenantId, productCode string) (*InvProduct, error)
|
2026-03-30 02:53:55 +00:00
|
|
|
FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error)
|
|
|
|
|
FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error)
|
2026-07-05 08:06:28 +09:00
|
|
|
NextColorNo(ctx context.Context, tenantId, productName string) (string, error)
|
2026-06-14 15:24:02 +08:00
|
|
|
BulkInsert(ctx context.Context, products []*InvProduct) (int64, error)
|
2026-02-28 15:29:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
customInvProductModel struct {
|
|
|
|
|
*defaultInvProductModel
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func NewInvProductModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) InvProductModel {
|
|
|
|
|
return &customInvProductModel{
|
|
|
|
|
defaultInvProductModel: newInvProductModel(conn, c, opts...),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 15:24:02 +08:00
|
|
|
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]
|
|
|
|
|
|
2026-07-05 08:06:28 +09:00
|
|
|
placeholder := "(" + strings.Repeat("?,", 11) + "?)"
|
2026-06-14 15:24:02 +08:00
|
|
|
placeholders := make([]string, len(batch))
|
2026-07-05 08:06:28 +09:00
|
|
|
args := make([]interface{}, 0, len(batch)*12)
|
2026-06-14 15:24:02 +08:00
|
|
|
|
|
|
|
|
for j, p := range batch {
|
|
|
|
|
placeholders[j] = placeholder
|
|
|
|
|
args = append(args,
|
|
|
|
|
p.ProductId, p.ProductName, p.ImageUrl, p.Spec, p.Color,
|
2026-07-05 08:06:28 +09:00
|
|
|
p.ColorNo, p.ProductCode, p.SalesPrice, p.Remark, p.Status,
|
|
|
|
|
p.TenantId, p.DeletedAt,
|
2026-06-14 15:24:02 +08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-05 08:06:28 +09:00
|
|
|
func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, colorNo, productCode string, status int64) ([]*InvProduct, int64, error) {
|
2026-03-30 02:53:55 +00:00
|
|
|
where := "WHERE deleted_at IS NULL AND tenant_id = ?"
|
|
|
|
|
args := []interface{}{tenantId}
|
2026-02-28 15:29:16 +08:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
2026-07-05 08:06:28 +09:00
|
|
|
if colorNo != "" {
|
|
|
|
|
where += " AND color_no LIKE ?"
|
|
|
|
|
args = append(args, "%"+colorNo+"%")
|
|
|
|
|
}
|
|
|
|
|
if productCode != "" {
|
|
|
|
|
where += " AND product_code LIKE ?"
|
|
|
|
|
args = append(args, "%"+productCode+"%")
|
|
|
|
|
}
|
2026-02-28 15:29:16 +08:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-05 08:06:28 +09:00
|
|
|
func (m *customInvProductModel) FindOneByTenantProductCode(ctx context.Context, tenantId, productCode string) (*InvProduct, error) {
|
|
|
|
|
var resp InvProduct
|
|
|
|
|
query := fmt.Sprintf("SELECT %s FROM %s WHERE tenant_id = ? AND product_code = ? AND deleted_at IS NULL LIMIT 1", invProductRows, m.table)
|
|
|
|
|
err := m.QueryRowNoCacheCtx(ctx, &resp, query, tenantId, productCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *customInvProductModel) NextColorNo(ctx context.Context, tenantId, productName string) (string, error) {
|
|
|
|
|
var next int64
|
|
|
|
|
query := fmt.Sprintf(`SELECT COALESCE(MAX(CAST(color_no AS UNSIGNED)), 0) + 1
|
|
|
|
|
FROM %s
|
|
|
|
|
WHERE tenant_id = ? AND product_name = ? AND deleted_at IS NULL AND color_no REGEXP '^[0-9]+$'`, m.table)
|
|
|
|
|
if err := m.QueryRowNoCacheCtx(ctx, &next, query, tenantId, productName); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return fmt.Sprintf("%02d", next), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 15:24:02 +08:00
|
|
|
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,
|
2026-06-16 03:30:39 +09:00
|
|
|
COALESCE(GROUP_CONCAT(DISTINCT p.color), '') AS colors,
|
|
|
|
|
COALESCE(GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position), '') AS locations
|
2026-06-14 15:24:02 +08:00
|
|
|
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)
|
2026-02-28 15:29:16 +08:00
|
|
|
if err != nil {
|
2026-06-14 15:24:02 +08:00
|
|
|
return nil, err
|
2026-02-28 15:29:16 +08:00
|
|
|
}
|
2026-06-14 15:24:02 +08:00
|
|
|
return list, nil
|
2026-02-28 15:29:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 02:53:55 +00:00
|
|
|
func (m *customInvProductModel) FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error) {
|
2026-02-28 15:29:16 +08:00
|
|
|
var list []StockGroupResult
|
2026-06-14 15:24:02 +08:00
|
|
|
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`
|
2026-03-30 02:53:55 +00:00
|
|
|
err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId)
|
2026-02-28 15:29:16 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return list, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 02:53:55 +00:00
|
|
|
func (m *customInvProductModel) FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error) {
|
2026-02-28 15:29:16 +08:00
|
|
|
var list []StockGroupResult
|
2026-06-14 15:24:02 +08:00
|
|
|
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`
|
2026-03-30 02:53:55 +00:00
|
|
|
err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId)
|
2026-02-28 15:29:16 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return list, nil
|
|
|
|
|
}
|
2026-06-14 15:24:02 +08:00
|
|
|
|
|
|
|
|
func (m *customInvProductModel) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) {
|
|
|
|
|
var list []*ColorDetail
|
2026-07-05 08:06:28 +09:00
|
|
|
query := `SELECT p.product_id, p.product_name, p.color, p.color_no, p.product_code, p.spec, p.image_url, p.sales_price,
|
2026-06-14 15:24:02 +08:00
|
|
|
COUNT(DISTINCT pan.pan_id) AS pan_count,
|
|
|
|
|
COUNT(bolt.bolt_id) AS bolt_count,
|
2026-07-05 08:06:28 +09:00
|
|
|
(
|
|
|
|
|
SELECT COUNT(DISTINCT batch.batch_id)
|
|
|
|
|
FROM ` + "`inv_product_batch`" + ` batch
|
|
|
|
|
WHERE batch.product_id = p.product_id
|
|
|
|
|
AND batch.tenant_id = p.tenant_id
|
|
|
|
|
AND batch.deleted_at IS NULL
|
|
|
|
|
) AS batch_count,
|
2026-06-14 15:24:02 +08:00
|
|
|
COALESCE(SUM(bolt.length_m), 0) AS total_length_m,
|
2026-06-16 03:30:39 +09:00
|
|
|
COALESCE(GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position), '') AS locations
|
2026-06-14 15:24:02 +08:00
|
|
|
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
|
|
|
|
|
}
|