package repository import ( "database/sql" "errors" "strings" "github.com/wmms/wmms/internal/model" "github.com/wmms/wmms/pkg/database" ) // ErrDuplicateCodeRule 表示该分类下已经有一条编码规则。code_rules 上有 // uk_code_rules_org_category(organization_id, category_id),新建选到已占用的 // 分类、或者改分类改到已占用的分类,都会撞上它。 var ErrDuplicateCodeRule = errors.New("该分类已有编码规则") // translateDuplicateCodeRule 把唯一键冲突翻译成可直接展示的中文错误, // 其余错误原样返回。前端只有拿到中文消息才会照原样弹出,否则显示"请求失败"。 func translateDuplicateCodeRule(err error) error { if err != nil && strings.Contains(err.Error(), "Duplicate entry") { return ErrDuplicateCodeRule } return err } type MasterDataRepository struct{} func NewMasterDataRepository() *MasterDataRepository { return &MasterDataRepository{} } func (r *MasterDataRepository) ListCategories(organizationID int64) ([]model.MaterialCategory, error) { rows, err := database.DB.Query(`SELECT id, organization_id, code, name, description, parent_id, status FROM material_categories WHERE organization_id = ? ORDER BY code`, organizationID) if err != nil { return nil, err } defer rows.Close() items := make([]model.MaterialCategory, 0) for rows.Next() { var item model.MaterialCategory if err := rows.Scan(&item.ID, &item.OrganizationID, &item.Code, &item.Name, &item.Description, &item.ParentID, &item.Status); err != nil { return nil, err } items = append(items, item) } return items, rows.Err() } func (r *MasterDataRepository) CreateCategory(item *model.MaterialCategory) error { result, err := database.DB.Exec(`INSERT INTO material_categories (organization_id, code, name, description, parent_id, status) VALUES (?, ?, ?, ?, ?, ?)`, item.OrganizationID, item.Code, item.Name, item.Description, item.ParentID, item.Status) if err != nil { return err } item.ID, err = result.LastInsertId() return err } func (r *MasterDataRepository) UpdateCategory(item *model.MaterialCategory) error { return execExpectingRow(`UPDATE material_categories SET name = ?, description = ?, parent_id = ?, status = ? WHERE id = ? AND organization_id = ?`, item.Name, item.Description, item.ParentID, item.Status, item.ID, item.OrganizationID) } func (r *MasterDataRepository) ListUnits(organizationID int64) ([]model.Unit, error) { rows, err := database.DB.Query(`SELECT id, organization_id, code, name, precision_scale, status FROM units WHERE organization_id = ? ORDER BY code`, organizationID) if err != nil { return nil, err } defer rows.Close() items := make([]model.Unit, 0) for rows.Next() { var item model.Unit if err := rows.Scan(&item.ID, &item.OrganizationID, &item.Code, &item.Name, &item.PrecisionScale, &item.Status); err != nil { return nil, err } items = append(items, item) } return items, rows.Err() } func (r *MasterDataRepository) CreateUnit(item *model.Unit) error { result, err := database.DB.Exec(`INSERT INTO units (organization_id, code, name, precision_scale, status) VALUES (?, ?, ?, ?, ?)`, item.OrganizationID, item.Code, item.Name, item.PrecisionScale, item.Status) if err != nil { return err } item.ID, err = result.LastInsertId() return err } func (r *MasterDataRepository) UpdateUnit(item *model.Unit) error { return execExpectingRow(`UPDATE units SET name = ?, precision_scale = ?, status = ? WHERE id = ? AND organization_id = ?`, item.Name, item.PrecisionScale, item.Status, item.ID, item.OrganizationID) } func (r *MasterDataRepository) ListCodeRules(organizationID int64) ([]model.CodeRule, error) { rows, err := database.DB.Query(`SELECT cr.id, cr.organization_id, cr.category_id, c.code, c.name, cr.prefix, cr.sequence_length, cr.next_sequence, cr.status FROM code_rules cr JOIN material_categories c ON c.id = cr.category_id WHERE cr.organization_id = ? ORDER BY c.code`, organizationID) if err != nil { return nil, err } defer rows.Close() items := make([]model.CodeRule, 0) for rows.Next() { var item model.CodeRule if err := rows.Scan(&item.ID, &item.OrganizationID, &item.CategoryID, &item.CategoryCode, &item.CategoryName, &item.Prefix, &item.SequenceLength, &item.NextSequence, &item.Status); err != nil { return nil, err } items = append(items, item) } return items, rows.Err() } func (r *MasterDataRepository) CreateCodeRule(item *model.CodeRule) error { result, err := database.DB.Exec(`INSERT INTO code_rules (organization_id, category_id, prefix, sequence_length, next_sequence, status) VALUES (?, ?, ?, ?, ?, ?)`, item.OrganizationID, item.CategoryID, item.Prefix, item.SequenceLength, item.NextSequence, item.Status) if err != nil { return translateDuplicateCodeRule(err) } item.ID, err = result.LastInsertId() return err } // UpdateCodeRule 更新编码规则。category_id 必须写进 SET:前端编辑弹窗里的 // 分类下拉没有禁用(与同页的编码输入框不同),而规则又只能改不能删, // 漏掉这一列会让"改分类"提示成功却完全不落库。 func (r *MasterDataRepository) UpdateCodeRule(item *model.CodeRule) error { return translateDuplicateCodeRule(execExpectingRow(`UPDATE code_rules SET category_id = ?, prefix = ?, sequence_length = ?, next_sequence = ?, status = ? WHERE id = ? AND organization_id = ?`, item.CategoryID, item.Prefix, item.SequenceLength, item.NextSequence, item.Status, item.ID, item.OrganizationID)) } // FindActiveCodeRule 取小类当前启用的编码规则,没有规则时返回 (nil, nil)。 // 只读,不加锁:占号发生在创建物料的事务里。 func (r *MasterDataRepository) FindActiveCodeRule(organizationID, categoryID int64) (*model.CodeRule, error) { query := `SELECT id, organization_id, category_id, prefix, sequence_length, next_sequence, status FROM code_rules WHERE organization_id = ? AND category_id = ? AND status = 'active'` rule := &model.CodeRule{} err := database.DB.QueryRow(query, organizationID, categoryID).Scan(&rule.ID, &rule.OrganizationID, &rule.CategoryID, &rule.Prefix, &rule.SequenceLength, &rule.NextSequence, &rule.Status) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, err } return rule, nil }