material_manage_system/internal/service/material_log.go
2026-10-09 10:38:02 +08:00

166 lines
5.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"database/sql"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/wmms/wmms/internal/model"
"github.com/wmms/wmms/internal/repository"
)
type MaterialLogService struct {
logRepo *repository.MaterialLogRepo
}
func NewMaterialLogService() *MaterialLogService {
return &MaterialLogService{
logRepo: repository.NewMaterialLogRepo(),
}
}
func (s *MaterialLogService) LogCreate(material *model.Material, operator string) error {
newValue, _ := json.Marshal(material)
log := &model.MaterialLog{
OperationType: "CREATE",
MaterialID: material.ID,
NewValue: string(newValue),
Operator: operator,
Description: fmt.Sprintf("新增物料:%s", material.Name),
}
return s.logRepo.Create(log)
}
func (s *MaterialLogService) LogUpdate(material *model.Material, oldMaterial *model.Material, operator string) error {
// 上层取「修改后」的物料失败时仍会走到这里,material 为 nil,取字段会 panic
if material == nil {
return nil
}
return s.logRepo.Create(buildUpdateLog(material, oldMaterial, operator))
}
// LogUpdateTx 在调用方事务内写修改日志,供作废、恢复等要求主记录与日志原子提交的
// 场景使用;material 为 nil 时同样跳过,避免解引用 panic。
func (s *MaterialLogService) LogUpdateTx(tx *sql.Tx, material *model.Material, oldMaterial *model.Material, operator string) error {
if material == nil {
return nil
}
return s.logRepo.CreateTx(tx, buildUpdateLog(material, oldMaterial, operator))
}
func buildUpdateLog(material, oldMaterial *model.Material, operator string) *model.MaterialLog {
oldValue, _ := json.Marshal(oldMaterial)
newValue, _ := json.Marshal(material)
return &model.MaterialLog{
OperationType: "UPDATE",
MaterialID: material.ID,
OldValue: string(oldValue),
NewValue: string(newValue),
Operator: operator,
Description: updateDescription(material, oldMaterial),
}
}
// updateDescription 让日志详情直接说明改了什么,用户不必去比对 old_value/new_value 里的整段 JSON。
// 比对不出任何变化时退回只带物料名的简版。
func updateDescription(material, oldMaterial *model.Material) string {
description := fmt.Sprintf("修改物料:%s", material.Name)
if oldMaterial == nil {
return description
}
changes := materialChanges(oldMaterial, material)
if len(changes) == 0 {
return description
}
return fmt.Sprintf("%s(%s)", description, strings.Join(changes, ";"))
}
// materialChanges 逐字段比对,返回「字段名:旧值 → 新值」形式的变更列表,顺序即展示顺序。
// 分类和基本单位在服务层拿不到名称,如实显示 ID;物料图片只报「已更新」,
// 图片 URL 往往很长,整条塞进描述会把别的变更挤没。
func materialChanges(oldMaterial, newMaterial *model.Material) []string {
fields := []struct {
label string
oldValue string
newValue string
}{
{"物料编码", oldMaterial.MaterialCode, newMaterial.MaterialCode},
{"名称", oldMaterial.Name, newMaterial.Name},
{"规格", oldMaterial.Spec, newMaterial.Spec},
{"厂商型号", derefString(oldMaterial.ManufacturerModel), derefString(newMaterial.ManufacturerModel)},
{"分类", derefID(oldMaterial.CategoryID), derefID(newMaterial.CategoryID)},
{"基本单位", derefID(oldMaterial.BaseUnitID), derefID(newMaterial.BaseUnitID)},
{"单位", oldMaterial.Unit, newMaterial.Unit},
// 作废和恢复也是走这里的:状态一变,日志就写明「状态:启用 → 作废」
{"状态", materialStatusLabel(oldMaterial.Status), materialStatusLabel(newMaterial.Status)},
}
var changes []string
for _, field := range fields {
if field.oldValue != field.newValue {
changes = append(changes, fmt.Sprintf("%s:%s → %s",
field.label, orEmptyValue(field.oldValue), orEmptyValue(field.newValue)))
}
}
if derefString(oldMaterial.MaterialImage) != derefString(newMaterial.MaterialImage) {
changes = append(changes, "物料图片:已更新")
}
return changes
}
func derefString(value *string) string {
if value == nil {
return ""
}
return *value
}
func derefID(value *int64) string {
if value == nil {
return ""
}
return strconv.FormatInt(*value, 10)
}
// orEmptyValue 把空值显示成「空」,否则会写出「规格: → 6205」这种读不出是新增还是清空的描述。
func orEmptyValue(value string) string {
if value == "" {
return "空"
}
return value
}
// materialStatusLabel 把状态值转成界面用语,日志里直接写 active/obsolete 对用户没有意义。
func materialStatusLabel(status string) string {
switch status {
case "active":
return "启用"
case "obsolete":
return "作废"
default:
return status
}
}
func (s *MaterialLogService) LogDelete(material *model.Material, operator string) error {
oldValue, _ := json.Marshal(material)
log := &model.MaterialLog{
OperationType: "DELETE",
MaterialID: material.ID,
OldValue: string(oldValue),
Operator: operator,
Description: fmt.Sprintf("删除物料:%s", material.Name),
}
return s.logRepo.Create(log)
}
func (s *MaterialLogService) List(organizationID int64, operationType, materialId, startDate, endDate string, limit, offset int) ([]model.MaterialLog, int64, error) {
return s.logRepo.List(organizationID, operationType, materialId, startDate, endDate, limit, offset)
}
func (s *MaterialLogService) GetByMaterialID(organizationID, materialID int64) ([]model.MaterialLog, error) {
return s.logRepo.GetByMaterialID(organizationID, materialID)
}