package repository import ( "database/sql" "fmt" "strings" "github.com/wmms/wmms/internal/model" "github.com/wmms/wmms/pkg/database" ) // BeginTx starts a new database transaction func BeginTx() (*sql.Tx, error) { return database.DB.Begin() } type MaterialRepo struct{} func NewMaterialRepo() *MaterialRepo { return &MaterialRepo{} } func (r *MaterialRepo) Create(m *model.Material) error { query := `INSERT INTO materials (material_code, name, material_image, spec, category, unit, warning_stock) VALUES (?, ?, ?, ?, ?, ?, ?)` result, err := database.DB.Exec(query, m.MaterialCode, m.Name, m.MaterialImage, m.Spec, m.Category, m.Unit, m.WarningStock) if err != nil { if strings.Contains(err.Error(), "Duplicate entry") { return fmt.Errorf("material code already exists") } return err } id, err := result.LastInsertId() if err != nil { return err } m.ID = id return r.GetByID(m.ID, m) } func (r *MaterialRepo) GetByID(id int64, m *model.Material) error { query := `SELECT id, material_code, name, material_image, spec, category, unit, warning_stock, current_stock, created_at, updated_at FROM materials WHERE id = ?` var materialImage sql.NullString err := database.DB.QueryRow(query, id).Scan(&m.ID, &m.MaterialCode, &m.Name, &materialImage, &m.Spec, &m.Category, &m.Unit, &m.WarningStock, &m.CurrentStock, &m.CreatedAt, &m.UpdatedAt) if err != nil { return err } if materialImage.Valid { m.MaterialImage = &materialImage.String } return nil } func (r *MaterialRepo) GetByMaterialCode(code string) (*model.Material, error) { m := &model.Material{} query := `SELECT id, material_code, name, material_image, spec, category, unit, warning_stock, current_stock, created_at, updated_at FROM materials WHERE material_code = ?` var materialImage sql.NullString err := database.DB.QueryRow(query, code).Scan(&m.ID, &m.MaterialCode, &m.Name, &materialImage, &m.Spec, &m.Category, &m.Unit, &m.WarningStock, &m.CurrentStock, &m.CreatedAt, &m.UpdatedAt) if err != nil { return nil, err } if materialImage.Valid { m.MaterialImage = &materialImage.String } return m, nil } func (r *MaterialRepo) List(search, category string, limit, offset int) ([]model.Material, int64, error) { countQuery := `SELECT COUNT(*) FROM materials WHERE 1=1` var countArgs []interface{} if search != "" { countQuery += ` AND (material_code LIKE ? OR name LIKE ?)` searchPattern := "%" + search + "%" countArgs = append(countArgs, searchPattern, searchPattern) } if category != "" { countQuery += ` AND category = ?` countArgs = append(countArgs, category) } var total int64 err := database.DB.QueryRow(countQuery, countArgs...).Scan(&total) if err != nil { return nil, 0, err } query := `SELECT id, material_code, name, material_image, spec, category, unit, warning_stock, current_stock, created_at, updated_at FROM materials WHERE 1=1` var args []interface{} if search != "" { query += ` AND (material_code LIKE ? OR name LIKE ?)` searchPattern := "%" + search + "%" args = append(args, searchPattern, searchPattern) } if category != "" { query += ` AND category = ?` args = append(args, category) } query += ` ORDER BY id DESC LIMIT ? OFFSET ?` args = append(args, limit, offset) rows, err := database.DB.Query(query, args...) if err != nil { return nil, 0, err } defer rows.Close() var materials []model.Material for rows.Next() { var m model.Material var materialImage sql.NullString err := rows.Scan(&m.ID, &m.MaterialCode, &m.Name, &materialImage, &m.Spec, &m.Category, &m.Unit, &m.WarningStock, &m.CurrentStock, &m.CreatedAt, &m.UpdatedAt) if err != nil { return nil, 0, err } if materialImage.Valid { m.MaterialImage = &materialImage.String } materials = append(materials, m) } return materials, total, nil } func (r *MaterialRepo) Update(id int64, req *model.UpdateMaterialRequest) error { var sets []string var args []interface{} if req.MaterialCode != "" { sets = append(sets, "material_code = ?") args = append(args, req.MaterialCode) } if req.Name != "" { sets = append(sets, "name = ?") args = append(args, req.Name) } if req.MaterialImage != "" { sets = append(sets, "material_image = ?") args = append(args, req.MaterialImage) } if req.Spec != "" { sets = append(sets, "spec = ?") args = append(args, req.Spec) } if req.Category != "" { sets = append(sets, "category = ?") args = append(args, req.Category) } if req.Unit != "" { sets = append(sets, "unit = ?") args = append(args, req.Unit) } if req.WarningStock > 0 { sets = append(sets, "warning_stock = ?") args = append(args, req.WarningStock) } if len(sets) == 0 { return nil } query := fmt.Sprintf("UPDATE materials SET %s WHERE id = ?", strings.Join(sets, ", ")) args = append(args, id) _, err := database.DB.Exec(query, args...) return err } func (r *MaterialRepo) Delete(id int64) error { // Check if material has related records var count int // Check inbound_items database.DB.QueryRow(`SELECT COUNT(*) FROM inbound_items WHERE material_id = ?`, id).Scan(&count) if count > 0 { return fmt.Errorf("该物料有入库记录,无法删除") } // Check outbound_items database.DB.QueryRow(`SELECT COUNT(*) FROM outbound_items WHERE material_id = ?`, id).Scan(&count) if count > 0 { return fmt.Errorf("该物料有出库记录,无法删除") } // Check stock_logs database.DB.QueryRow(`SELECT COUNT(*) FROM stock_logs WHERE material_id = ?`, id).Scan(&count) if count > 0 { return fmt.Errorf("该物料有库存流水记录,无法删除") } _, err := database.DB.Exec(`DELETE FROM materials WHERE id = ?`, id) return err } func (r *MaterialRepo) UpdateStock(id int64, delta int) error { query := `UPDATE materials SET current_stock = current_stock + ? WHERE id = ?` result, err := database.DB.Exec(query, delta, id) if err != nil { return err } affected, err := result.RowsAffected() if err != nil { return err } if affected == 0 { return sql.ErrNoRows } return nil } func (r *MaterialRepo) GetStock(id int64) (int, error) { var stock int query := `SELECT current_stock FROM materials WHERE id = ?` err := database.DB.QueryRow(query, id).Scan(&stock) return stock, err } // GetStockForUpdateTx gets stock with row lock within a transaction (prevents race condition) func (r *MaterialRepo) GetStockForUpdateTx(tx *sql.Tx, id int64) (int, error) { query := `SELECT current_stock FROM materials WHERE id = ? FOR UPDATE` var stock int err := tx.QueryRow(query, id).Scan(&stock) return stock, err } // UpdateStockTx updates stock within a transaction func (r *MaterialRepo) UpdateStockTx(tx *sql.Tx, id int64, delta int) error { query := `UPDATE materials SET current_stock = current_stock + ? WHERE id = ?` _, err := tx.Exec(query, delta, id) return err }