- JWT claims extended with tenantId; login enforces strict tenant verification - AuthorityMiddleware: tenant scope check + Casbin path permission + anti-spoofing - CRM relation API (upstream/downstream one-hop, create/update/history, full graph) - CrmRepo backed by PostgreSQL with $N placeholders - gRPC tenant propagation via UnaryClientInterceptor (x-tenant-id metadata) - All legacy tables (12) gain tenant_id column with indexes - All model queries inject WHERE tenant_id filter - Casbin gorm-adapter downgraded to v3.28.0 for v2 compatibility - GraphSyncWorker (Kafka -> Neo4j) with idempotent MERGE - Full graph API restricted to admin role only - Database migrations for MySQL (CRM tables + tenant columns) and PostgreSQL (CRM init) - Docker Compose: added postgres service to main stack, graph stack with Kafka/Debezium/Neo4j Made-with: Cursor
117 lines
4.3 KiB
Go
Executable File
117 lines
4.3 KiB
Go
Executable File
package model
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
|
)
|
|
|
|
var _ InvProductModel = (*customInvProductModel)(nil)
|
|
|
|
type StockGroupResult struct {
|
|
Name string `db:"name"`
|
|
Count int64 `db:"count"`
|
|
Quantity float64 `db:"quantity"`
|
|
}
|
|
|
|
type (
|
|
InvProductModel interface {
|
|
invProductModel
|
|
FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, location string, status int64) ([]*InvProduct, int64, error)
|
|
FindStockSummary(ctx context.Context, tenantId string) (productCount, totalPieces, totalRolls int64, totalCostValue, totalSalesValue float64, err error)
|
|
FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error)
|
|
FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error)
|
|
}
|
|
|
|
customInvProductModel struct {
|
|
*defaultInvProductModel
|
|
}
|
|
)
|
|
|
|
func NewInvProductModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) InvProductModel {
|
|
return &customInvProductModel{
|
|
defaultInvProductModel: newInvProductModel(conn, c, opts...),
|
|
}
|
|
}
|
|
|
|
func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, location 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 location != "" {
|
|
where += " AND location = ?"
|
|
args = append(args, location)
|
|
}
|
|
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) FindStockSummary(ctx context.Context, tenantId string) (productCount, totalPieces, totalRolls int64, totalCostValue, totalSalesValue float64, err error) {
|
|
var summary struct {
|
|
ProductCount int64 `db:"product_count"`
|
|
TotalPieces int64 `db:"total_pieces"`
|
|
TotalRolls int64 `db:"total_rolls"`
|
|
TotalCostValue float64 `db:"total_cost_value"`
|
|
TotalSalesValue float64 `db:"total_sales_value"`
|
|
}
|
|
query := fmt.Sprintf("SELECT COUNT(*) AS product_count, COALESCE(SUM(unit_pieces), 0) AS total_pieces, COALESCE(SUM(unit_rolls), 0) AS total_rolls, COALESCE(SUM(stock_quantity * cost_price), 0) AS total_cost_value, COALESCE(SUM(stock_quantity * sales_price), 0) AS total_sales_value FROM %s WHERE deleted_at IS NULL AND tenant_id = ?", m.table)
|
|
err = m.QueryRowNoCacheCtx(ctx, &summary, query, tenantId)
|
|
if err != nil {
|
|
return 0, 0, 0, 0, 0, err
|
|
}
|
|
return summary.ProductCount, summary.TotalPieces, summary.TotalRolls, summary.TotalCostValue, summary.TotalSalesValue, nil
|
|
}
|
|
|
|
func (m *customInvProductModel) FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error) {
|
|
var list []StockGroupResult
|
|
query := fmt.Sprintf("SELECT color AS name, COUNT(*) AS count, COALESCE(SUM(stock_quantity), 0) AS quantity FROM %s WHERE deleted_at IS NULL AND tenant_id = ? GROUP BY color ORDER BY count DESC", m.table)
|
|
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 := fmt.Sprintf("SELECT location AS name, COUNT(*) AS count, COALESCE(SUM(stock_quantity), 0) AS quantity FROM %s WHERE deleted_at IS NULL AND tenant_id = ? GROUP BY location ORDER BY count DESC", m.table)
|
|
err := m.QueryRowsNoCacheCtx(ctx, &list, query, tenantId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return list, nil
|
|
}
|