package repository import ( "database/sql" "github.com/wmms/wmms/internal/model" "github.com/wmms/wmms/pkg/database" ) type ApplicationRepository struct{} func NewApplicationRepository() *ApplicationRepository { return &ApplicationRepository{} } func (r *ApplicationRepository) Create(app *model.MaterialApplication) error { tx, err := database.DB.Begin() if err != nil { return err } defer tx.Rollback() query := `INSERT INTO material_applications (user_id, purpose, status) VALUES (?, ?, ?)` result, err := tx.Exec(query, app.UserID, app.Purpose, app.Status) if err != nil { return err } id, err := result.LastInsertId() if err != nil { return err } app.ID = id // Insert application items for _, item := range app.Items { itemQuery := `INSERT INTO application_items (application_id, material_id, quantity) VALUES (?, ?, ?)` itemResult, err := tx.Exec(itemQuery, app.ID, item.MaterialID, item.Quantity) if err != nil { return err } itemID, _ := itemResult.LastInsertId() item.ID = itemID } return tx.Commit() } func (r *ApplicationRepository) GetByID(id int64) (*model.MaterialApplication, error) { query := `SELECT a.id, a.user_id, a.purpose, a.status, a.rejection_reason, a.approved_by, a.created_at, a.updated_at, u.username FROM material_applications a JOIN users u ON a.user_id = u.id WHERE a.id = ?` var app model.MaterialApplication err := database.DB.QueryRow(query, id).Scan( &app.ID, &app.UserID, &app.Purpose, &app.Status, &app.RejectionReason, &app.ApprovedBy, &app.CreatedAt, &app.UpdatedAt, &app.Username, ) if err != nil { return nil, err } // Fetch items app.Items, err = r.GetApplicationItems(id) if err != nil { return nil, err } app.ApplicationNo = model.OrderNo("AP", app.ID) return &app, nil } func (r *ApplicationRepository) GetApplicationItems(applicationID int64) ([]model.ApplicationItem, error) { query := `SELECT ai.id, ai.application_id, ai.material_id, ai.quantity, m.material_code, m.name FROM application_items ai JOIN materials m ON ai.material_id = m.id WHERE ai.application_id = ?` rows, err := database.DB.Query(query, applicationID) if err != nil { return nil, err } defer rows.Close() var items []model.ApplicationItem for rows.Next() { var item model.ApplicationItem err := rows.Scan(&item.ID, &item.ApplicationID, &item.MaterialID, &item.Quantity, &item.MaterialCode, &item.MaterialName) if err != nil { return nil, err } items = append(items, item) } return items, nil } func (r *ApplicationRepository) ListAll(status string, userID int64, limit, offset int) ([]model.MaterialApplication, int, error) { // Count query countQuery := `SELECT COUNT(*) FROM material_applications a WHERE 1=1` var countArgs []interface{} if status != "" { countQuery += " AND a.status = ?" countArgs = append(countArgs, status) } if userID > 0 { countQuery += " AND a.user_id = ?" countArgs = append(countArgs, userID) } var total int err := database.DB.QueryRow(countQuery, countArgs...).Scan(&total) if err != nil { return nil, 0, err } // Data query query := `SELECT a.id, a.user_id, a.purpose, a.status, a.rejection_reason, a.approved_by, a.created_at, a.updated_at, u.username FROM material_applications a JOIN users u ON a.user_id = u.id WHERE 1=1` var args []interface{} if status != "" { query += " AND a.status = ?" args = append(args, status) } if userID > 0 { query += " AND a.user_id = ?" args = append(args, userID) } query += " ORDER BY a.created_at 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 apps []model.MaterialApplication for rows.Next() { var app model.MaterialApplication err := rows.Scan( &app.ID, &app.UserID, &app.Purpose, &app.Status, &app.RejectionReason, &app.ApprovedBy, &app.CreatedAt, &app.UpdatedAt, &app.Username, ) if err != nil { return nil, 0, err } // Fetch items for each application app.Items, err = r.GetApplicationItems(app.ID) if err != nil { return nil, 0, err } app.ApplicationNo = model.OrderNo("AP", app.ID) apps = append(apps, app) } return apps, total, nil } func (r *ApplicationRepository) ListByUserID(userID int64, status string, limit, offset int) ([]model.MaterialApplication, int, error) { return r.ListAll(status, userID, limit, offset) } func (r *ApplicationRepository) ListAllApplications(status string, limit, offset int) ([]model.MaterialApplication, int, error) { return r.ListAll(status, 0, limit, offset) } func (r *ApplicationRepository) UpdateStatus(id int64, status string) error { query := `UPDATE material_applications SET status = ?, updated_at = NOW() WHERE id = ?` _, err := database.DB.Exec(query, status, id) return err } func (r *ApplicationRepository) Approve(id int64, approverID int64) error { query := `UPDATE material_applications SET status = ?, approved_by = ?, updated_at = NOW() WHERE id = ?` _, err := database.DB.Exec(query, model.ApplicationStatusApproved, approverID, id) return err } func (r *ApplicationRepository) Reject(id int64, reason string) error { query := `UPDATE material_applications SET status = ?, rejection_reason = ?, updated_at = NOW() WHERE id = ?` _, err := database.DB.Exec(query, model.ApplicationStatusRejected, reason, id) return err } func (r *ApplicationRepository) Cancel(id int64) error { return r.UpdateStatus(id, model.ApplicationStatusCancelled) } func (r *ApplicationRepository) GetByIDWithDetails(id int64) (*model.MaterialApplication, error) { return r.GetByID(id) } // outbound_orders 没有 status 列:出库单一经创建即为完成态, // 这里与手工出库路径(OutboundRepo.CreateTx)写入同样的列。 func (r *ApplicationRepository) CreateOutboundOrder(order *model.OutboundOrder) error { query := `INSERT INTO outbound_orders (outbound_date, operator, handler, receiver) VALUES (NOW(), ?, ?, ?)` result, err := database.DB.Exec(query, order.Operator, order.Handler, order.Receiver) if err != nil { return err } id, err := result.LastInsertId() if err != nil { return err } order.ID = id order.OutboundNo = model.OrderNo("OUT", id) if _, err := database.DB.Exec(`UPDATE outbound_orders SET outbound_no = ? WHERE id = ?`, order.OutboundNo, id); err != nil { return err } return nil } // outbound_orders 与 inbound_orders 一样没有 status 列,单据状态由查询层统一写死。 // 这里写入的列必须和手工出库(OutboundRepo.CreateTx)一致,多写一列会让 INSERT 直接失败。 func (r *ApplicationRepository) CreateOutboundOrderTx(tx *sql.Tx, order *model.OutboundOrder) error { query := `INSERT INTO outbound_orders (outbound_date, operator, handler, receiver) VALUES (NOW(), ?, ?, ?)` result, err := tx.Exec(query, order.Operator, order.Handler, order.Receiver) if err != nil { return err } id, err := result.LastInsertId() if err != nil { return err } order.ID = id order.OutboundNo = model.OrderNo("OUT", id) if _, err := tx.Exec(`UPDATE outbound_orders SET outbound_no = ? WHERE id = ?`, order.OutboundNo, id); err != nil { return err } return nil } func (r *ApplicationRepository) CreateOutboundItem(item *model.OutboundItem) error { query := `INSERT INTO outbound_items (outbound_order_id, material_id, quantity) VALUES (?, ?, ?)` result, err := database.DB.Exec(query, item.OutboundOrderID, item.MaterialID, item.Quantity) if err != nil { return err } id, err := result.LastInsertId() if err != nil { return err } item.ID = id return nil } func (r *ApplicationRepository) CreateOutboundItemTx(tx *sql.Tx, item *model.OutboundItem) error { query := `INSERT INTO outbound_items (outbound_order_id, material_id, quantity) VALUES (?, ?, ?)` result, err := tx.Exec(query, item.OutboundOrderID, item.MaterialID, item.Quantity) if err != nil { return err } id, err := result.LastInsertId() if err != nil { return err } item.ID = id return nil } func (r *ApplicationRepository) CreateStockLog(log *model.StockLog) error { query := `INSERT INTO stock_logs (material_id, type, quantity, related_order_type, related_order_id) VALUES (?, ?, ?, ?, ?)` result, err := database.DB.Exec(query, log.MaterialID, log.Type, log.Quantity, log.RelatedOrderType, log.RelatedOrderID) if err != nil { return err } _, err = result.LastInsertId() return err } func (r *ApplicationRepository) CreateStockLogTx(tx *sql.Tx, log *model.StockLog) error { query := `INSERT INTO stock_logs (material_id, type, quantity, related_order_type, related_order_id) VALUES (?, ?, ?, ?, ?)` result, err := tx.Exec(query, log.MaterialID, log.Type, log.Quantity, log.RelatedOrderType, log.RelatedOrderID) if err != nil { return err } _, err = result.LastInsertId() return err } func (r *ApplicationRepository) ApproveTx(tx *sql.Tx, id int64, approverID int64) error { query := `UPDATE material_applications SET status = ?, approved_by = ?, updated_at = NOW() WHERE id = ?` _, err := tx.Exec(query, model.ApplicationStatusApproved, approverID, id) return err } func (r *ApplicationRepository) GetApproverName(userID int64) (string, error) { query := `SELECT username FROM users WHERE id = ?` var username string err := database.DB.QueryRow(query, userID).Scan(&username) return username, err } func (r *ApplicationRepository) InitApplicationTable() error { query := ` CREATE TABLE IF NOT EXISTS material_applications ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, purpose VARCHAR(500) DEFAULT '', status ENUM('pending','approved','rejected','cancelled') NOT NULL DEFAULT 'pending', rejection_reason VARCHAR(500) DEFAULT '', approved_by BIGINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )` _, err := database.DB.Exec(query) if err != nil { return err } // Create application_items table itemsQuery := ` CREATE TABLE IF NOT EXISTS application_items ( id BIGINT AUTO_INCREMENT PRIMARY KEY, application_id BIGINT NOT NULL, material_id BIGINT NOT NULL, quantity INT NOT NULL, FOREIGN KEY (application_id) REFERENCES material_applications(id) ON DELETE CASCADE, FOREIGN KEY (material_id) REFERENCES materials(id) )` _, err = database.DB.Exec(itemsQuery) return err }