package repository import ( "context" "database/sql" "fmt" "time" "github.com/google/uuid" "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" "github.com/muyuqingfeng/iloom/shared/pkg/types" ) type PlanRepo struct { db *sql.DB } func NewPlanRepo(db *sql.DB) *PlanRepo { return &PlanRepo{db: db} } func (r *PlanRepo) List(ctx context.Context, companyID string, p types.Pagination, status string) ([]model.ProductionPlan, int, error) { countQuery := `SELECT COUNT(*) FROM ilm_production_plan WHERE purchaser_id = ?` args := []interface{}{companyID} if status != "" { countQuery += " AND status = ?" args = append(args, status) } var total int if err := r.db.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil { return nil, 0, fmt.Errorf("count plans: %w", err) } query := `SELECT id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, target_quantity, completed_quantity, production_price, yarn_usage_per_meter, status, remark, start_time, created_by, created_at, updated_at FROM ilm_production_plan WHERE purchaser_id = ?` queryArgs := []interface{}{companyID} if status != "" { query += " AND status = ?" queryArgs = append(queryArgs, status) } query += " ORDER BY created_at DESC" query += " LIMIT ? OFFSET ?" queryArgs = append(queryArgs, p.PageSize, p.Offset()) rows, err := r.db.QueryContext(ctx, query, queryArgs...) if err != nil { return nil, 0, fmt.Errorf("list plans: %w", err) } defer rows.Close() var plans []model.ProductionPlan for rows.Next() { var plan model.ProductionPlan if err := rows.Scan( &plan.ID, &plan.PlanCode, &plan.PurchaserID, &plan.ProductName, &plan.FabricCode, &plan.Color, &plan.ColorCode, &plan.TargetQuantity, &plan.CompletedQuantity, &plan.ProductionPrice, &plan.YarnUsagePerMeter, &plan.Status, &plan.Remark, &plan.StartTime, &plan.CreatedBy, &plan.CreatedAt, &plan.UpdatedAt, ); err != nil { return nil, 0, fmt.Errorf("scan plan: %w", err) } plans = append(plans, plan) } return plans, total, nil } func (r *PlanRepo) Create(ctx context.Context, tx *sql.Tx, plan *model.ProductionPlan) error { query := `INSERT INTO ilm_production_plan (id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, target_quantity, completed_quantity, production_price, yarn_usage_per_meter, status, remark, start_time, created_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` _, err := tx.ExecContext(ctx, query, plan.ID, plan.PlanCode, plan.PurchaserID, plan.ProductName, plan.FabricCode, plan.Color, plan.ColorCode, plan.TargetQuantity, plan.CompletedQuantity, plan.ProductionPrice, plan.YarnUsagePerMeter, plan.Status, plan.Remark, plan.StartTime, plan.CreatedBy, plan.CreatedAt, plan.UpdatedAt, ) if err != nil { return fmt.Errorf("insert plan: %w", err) } return nil } func (r *PlanRepo) CreateYarnRatios(ctx context.Context, tx *sql.Tx, ratios []model.YarnRatio) error { for _, ratio := range ratios { query := `INSERT INTO ilm_yarn_ratio (id, plan_id, yarn_name, yarn_type, ratio, amount_per_meter, total_amount, company_id, created_at) VALUES (?,?,?,?,?,?,?,?,?)` _, err := tx.ExecContext(ctx, query, ratio.ID, ratio.PlanID, ratio.YarnName, ratio.YarnType, ratio.Ratio, ratio.AmountPerMeter, ratio.TotalAmount, ratio.CompanyID, ratio.CreatedAt, ) if err != nil { return fmt.Errorf("insert yarn ratio: %w", err) } } return nil } func (r *PlanRepo) GetByID(ctx context.Context, id string) (*model.ProductionPlan, error) { query := `SELECT id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, target_quantity, completed_quantity, production_price, yarn_usage_per_meter, status, remark, start_time, created_by, created_at, updated_at FROM ilm_production_plan WHERE id = ?` plan := &model.ProductionPlan{} err := r.db.QueryRowContext(ctx, query, id).Scan( &plan.ID, &plan.PlanCode, &plan.PurchaserID, &plan.ProductName, &plan.FabricCode, &plan.Color, &plan.ColorCode, &plan.TargetQuantity, &plan.CompletedQuantity, &plan.ProductionPrice, &plan.YarnUsagePerMeter, &plan.Status, &plan.Remark, &plan.StartTime, &plan.CreatedBy, &plan.CreatedAt, &plan.UpdatedAt, ) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, fmt.Errorf("get plan by id: %w", err) } // Load yarn ratios yarnRows, err := r.db.QueryContext(ctx, `SELECT id, plan_id, yarn_name, yarn_type, ratio, amount_per_meter, total_amount, company_id, created_at FROM ilm_yarn_ratio WHERE plan_id = ? ORDER BY created_at`, id) if err != nil { return nil, fmt.Errorf("get yarn ratios: %w", err) } defer yarnRows.Close() for yarnRows.Next() { var yr model.YarnRatio if err := yarnRows.Scan( &yr.ID, &yr.PlanID, &yr.YarnName, &yr.YarnType, &yr.Ratio, &yr.AmountPerMeter, &yr.TotalAmount, &yr.CompanyID, &yr.CreatedAt, ); err != nil { return nil, fmt.Errorf("scan yarn ratio: %w", err) } plan.YarnRatios = append(plan.YarnRatios, yr) } // Load factories factoryRows, err := r.db.QueryContext(ctx, `SELECT pf.id, pf.plan_id, pf.factory_id, pf.factory_type, pf.created_at, COALESCE(c.name, '') as factory_name FROM ilm_plan_factory pf LEFT JOIN ilm_company c ON c.id = pf.factory_id WHERE pf.plan_id = ? ORDER BY pf.created_at`, id) if err != nil { return nil, fmt.Errorf("get plan factories: %w", err) } defer factoryRows.Close() for factoryRows.Next() { var pf model.PlanFactory if err := factoryRows.Scan( &pf.ID, &pf.PlanID, &pf.FactoryID, &pf.FactoryType, &pf.CreatedAt, &pf.FactoryName, ); err != nil { return nil, fmt.Errorf("scan plan factory: %w", err) } plan.Factories = append(plan.Factories, pf) } return plan, nil } func (r *PlanRepo) Update(ctx context.Context, plan *model.ProductionPlan) error { query := `UPDATE ilm_production_plan SET product_name = ?, fabric_code = ?, color = ?, color_code = ?, target_quantity = ?, production_price = ?, yarn_usage_per_meter = ?, status = ?, remark = ?, updated_at = ? WHERE id = ?` _, err := r.db.ExecContext(ctx, query, plan.ProductName, plan.FabricCode, plan.Color, plan.ColorCode, plan.TargetQuantity, plan.ProductionPrice, plan.YarnUsagePerMeter, plan.Status, plan.Remark, time.Now(), plan.ID, ) if err != nil { return fmt.Errorf("update plan: %w", err) } return nil } func (r *PlanRepo) Delete(ctx context.Context, id, companyID string) error { _, err := r.db.ExecContext(ctx, `DELETE FROM ilm_production_plan WHERE id = ? AND purchaser_id = ?`, id, companyID, ) if err != nil { return fmt.Errorf("delete plan: %w", err) } return nil } func (r *PlanRepo) AssignFactory(ctx context.Context, planID, factoryID, factoryType string) error { id := uuid.New().String() _, err := r.db.ExecContext(ctx, `INSERT INTO ilm_plan_factory (id, plan_id, factory_id, factory_type, created_at) VALUES (?, ?, ?, ?, ?)`, id, planID, factoryID, factoryType, time.Now(), ) if err != nil { return fmt.Errorf("assign factory: %w", err) } return nil } func (r *PlanRepo) GetSteps(ctx context.Context, planID string) ([]model.ProcessStep, error) { rows, err := r.db.QueryContext(ctx, `SELECT ps.id, ps.plan_id, ps.step_type, ps.status, ps.notes, ps.operator_id, ps.company_id, ps.timestamp, ps.created_at, COALESCE(su.real_name, '') as operator_name FROM ilm_process_step ps LEFT JOIN sys_user su ON su.user_id = ps.operator_id WHERE ps.plan_id = ? ORDER BY ps.created_at`, planID) if err != nil { return nil, fmt.Errorf("get steps: %w", err) } defer rows.Close() var steps []model.ProcessStep for rows.Next() { var s model.ProcessStep if err := rows.Scan( &s.ID, &s.PlanID, &s.StepType, &s.Status, &s.Notes, &s.OperatorID, &s.CompanyID, &s.Timestamp, &s.CreatedAt, &s.OperatorName, ); err != nil { return nil, fmt.Errorf("scan step: %w", err) } steps = append(steps, s) } return steps, nil } func (r *PlanRepo) GetInventory(ctx context.Context, planID string) ([]model.InventoryRecord, error) { rows, err := r.db.QueryContext(ctx, `SELECT ir.id, ir.plan_id, ir.warehouse_id, ir.quantity, ir.rolls, ir.price_per_meter, ir.price_note, ir.warehouse_location, ir.operator_id, ir.company_id, ir.created_at, COALESCE(su.real_name, '') as operator_name FROM ilm_inventory_record ir LEFT JOIN sys_user su ON su.user_id = ir.operator_id WHERE ir.plan_id = ? ORDER BY ir.created_at DESC`, planID) if err != nil { return nil, fmt.Errorf("get inventory: %w", err) } defer rows.Close() var records []model.InventoryRecord for rows.Next() { var rec model.InventoryRecord if err := rows.Scan( &rec.ID, &rec.PlanID, &rec.WarehouseID, &rec.Quantity, &rec.Rolls, &rec.PricePerMeter, &rec.PriceNote, &rec.WarehouseLocation, &rec.OperatorID, &rec.CompanyID, &rec.CreatedAt, &rec.OperatorName, ); err != nil { return nil, fmt.Errorf("scan inventory record: %w", err) } records = append(records, rec) } return records, nil } func (r *PlanRepo) CountByStatus(ctx context.Context, companyID string) (total, producing, completed int, err error) { query := `SELECT COUNT(*), COALESCE(SUM(CASE WHEN status = 'producing' THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END), 0) FROM ilm_production_plan WHERE purchaser_id = ?` err = r.db.QueryRowContext(ctx, query, companyID).Scan(&total, &producing, &completed) if err != nil { return 0, 0, 0, fmt.Errorf("count by status: %w", err) } return } func (r *PlanRepo) Recent(ctx context.Context, companyID string, limit int) ([]model.ProductionPlan, error) { query := `SELECT id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, target_quantity, completed_quantity, production_price, yarn_usage_per_meter, status, remark, start_time, created_by, created_at, updated_at FROM ilm_production_plan WHERE purchaser_id = ? ORDER BY created_at DESC LIMIT ?` rows, err := r.db.QueryContext(ctx, query, companyID, limit) if err != nil { return nil, fmt.Errorf("recent plans: %w", err) } defer rows.Close() var plans []model.ProductionPlan for rows.Next() { var plan model.ProductionPlan if err := rows.Scan( &plan.ID, &plan.PlanCode, &plan.PurchaserID, &plan.ProductName, &plan.FabricCode, &plan.Color, &plan.ColorCode, &plan.TargetQuantity, &plan.CompletedQuantity, &plan.ProductionPrice, &plan.YarnUsagePerMeter, &plan.Status, &plan.Remark, &plan.StartTime, &plan.CreatedBy, &plan.CreatedAt, &plan.UpdatedAt, ); err != nil { return nil, fmt.Errorf("scan recent plan: %w", err) } plans = append(plans, plan) } return plans, nil }