From abb01c5eab4b6d6b0ec0240bcdf85efd7755a78b Mon Sep 17 00:00:00 2001 From: Habib Fatkhul Rohman Date: Mon, 1 Dec 2025 15:17:44 +0700 Subject: [PATCH] feat: enhance UOM service with audit trail and logging capabilities --- modules/uom/dto/uom_dto.go | 17 +++++++ modules/uom/service/uom_service.go | 78 +++++++++++++++++++----------- pkg/constants/common.go | 4 ++ pkg/utils/utils.go | 56 ++++++++++++++++++++- providers/core.go | 3 +- 5 files changed, 129 insertions(+), 29 deletions(-) diff --git a/modules/uom/dto/uom_dto.go b/modules/uom/dto/uom_dto.go index d7d357d..22d7388 100644 --- a/modules/uom/dto/uom_dto.go +++ b/modules/uom/dto/uom_dto.go @@ -3,6 +3,7 @@ package dto import ( "errors" + "github.com/Caknoooo/go-gin-clean-starter/database/entities" pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto" ) @@ -55,3 +56,19 @@ type UomResponse struct { IsActive bool `json:"is_active"` Client pkgdto.ClientResponse `json:"client"` } + +func ToUomResponse(e entities.MUomEntity) UomResponse { + return UomResponse{ + ID: e.ID.String(), + Name: e.Name, + Description: e.Description, + Symbol: e.Symbol, + Code: e.Code, + StdPrecision: e.StdPrecision, + IsActive: e.IsActive, + Client: pkgdto.ClientResponse{ + ID: e.Client.ID.String(), + Name: e.Client.Name, + }, + } +} diff --git a/modules/uom/service/uom_service.go b/modules/uom/service/uom_service.go index edce14e..dd317fc 100644 --- a/modules/uom/service/uom_service.go +++ b/modules/uom/service/uom_service.go @@ -8,8 +8,11 @@ import ( dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/uom/dto" "github.com/Caknoooo/go-gin-clean-starter/modules/uom/query" "github.com/Caknoooo/go-gin-clean-starter/modules/uom/repository" + "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" "github.com/google/uuid" + "github.com/sirupsen/logrus" "gorm.io/gorm" ) @@ -25,13 +28,15 @@ type uomService struct { db *gorm.DB uomRepo repository.UomRepository sequenceService sequenceservice.SequenceService + log *logrus.Logger } -func NewUomService(uomRepo repository.UomRepository, sequenceService sequenceservice.SequenceService, db *gorm.DB) UomService { +func NewUomService(uomRepo repository.UomRepository, sequenceService sequenceservice.SequenceService, db *gorm.DB, log *logrus.Logger) UomService { return &uomService{ uomRepo: uomRepo, sequenceService: sequenceService, db: db, + log: log, } } @@ -56,12 +61,13 @@ func (s *uomService) Create(ctx context.Context, req dtodomain.UomCreateRequest) } uom := entities.MUomEntity{ - Name: req.Name, - Description: req.Description, - Symbol: req.Symbol, - Code: code, - StdPrecision: req.StdPrecision, - IsActive: req.IsActive, + Name: req.Name, + Description: req.Description, + Symbol: req.Symbol, + Code: code, + StdPrecision: req.StdPrecision, + IsActive: req.IsActive, + FullAuditTrail: utils.FillAuditTrail(ctx, constants.CREATE), } clientUUID, err := uuid.Parse(req.ClientID) @@ -81,7 +87,7 @@ func (s *uomService) Create(ctx context.Context, req dtodomain.UomCreateRequest) if err != nil { return dtodomain.UomResponse{}, err } - return toUomResponse(result), nil + return dtodomain.ToUomResponse(result), nil } func (s *uomService) GetById(ctx context.Context, uomId string) (dtodomain.UomResponse, error) { @@ -89,7 +95,7 @@ func (s *uomService) GetById(ctx context.Context, uomId string) (dtodomain.UomRe if err != nil { return dtodomain.UomResponse{}, err } - return toUomResponse(uom), nil + return dtodomain.ToUomResponse(uom), nil } func (s *uomService) GetAll(ctx context.Context, filter query.UomFilter) ([]dtodomain.UomResponse, int64, error) { @@ -99,7 +105,7 @@ func (s *uomService) GetAll(ctx context.Context, filter query.UomFilter) ([]dtod } var responses []dtodomain.UomResponse for _, u := range uoms { - responses = append(responses, toUomResponse(u)) + responses = append(responses, dtodomain.ToUomResponse(u)) } if responses == nil { responses = make([]dtodomain.UomResponse, 0) @@ -119,6 +125,9 @@ func (s *uomService) Update(ctx context.Context, req dtodomain.UomUpdateRequest, tx.Rollback() return dtodomain.UomResponse{}, err } + + before := uom + uom.Name = req.Name uom.Description = req.Description uom.Symbol = req.Symbol @@ -136,6 +145,7 @@ func (s *uomService) Update(ctx context.Context, req dtodomain.UomUpdateRequest, } else { uom.Client = entities.M_Client{} } + uom.FullAuditTrail = utils.FillAuditTrail(ctx, constants.UPDATE) updated, err := s.uomRepo.Update(ctx, tx, uom) if err != nil { tx.Rollback() @@ -144,11 +154,19 @@ func (s *uomService) Update(ctx context.Context, req dtodomain.UomUpdateRequest, tx.Commit() result, err := s.uomRepo.GetById(ctx, nil, updated.ID.String()) + changes := utils.GetChangedFields(before, result) + s.log.WithFields(logrus.Fields{ + "user_id": utils.GetUserID(ctx), + "action": "update", + "entity": "uom", + "entity_id": uomId, + "changes": changes, // berisi field yang berubah saja + }).Info("UOM updated") if err != nil { return dtodomain.UomResponse{}, err } - return toUomResponse(result), nil + return dtodomain.ToUomResponse(result), nil } func (s *uomService) Delete(ctx context.Context, uomId string) error { @@ -158,26 +176,32 @@ func (s *uomService) Delete(ctx context.Context, uomId string) error { tx.Rollback() } }() + uom, err := s.uomRepo.GetById(ctx, tx, uomId) + if err != nil { + tx.Rollback() + return err + } + + // Isi audit trail DeletedBy + uom.FullAuditTrail = utils.FillAuditTrail(ctx, constants.DELETE) + + // Update audit trail sebelum delete + if _, err := s.uomRepo.Update(ctx, tx, uom); err != nil { + tx.Rollback() + return err + } + if err := s.uomRepo.Delete(ctx, tx, uomId); err != nil { tx.Rollback() return err } tx.Commit() + + s.log.WithFields(logrus.Fields{ + "user_id": utils.GetUserID(ctx), + "action": "delete", + "entity": "uom", + "entity_id": uomId, + }).Info("UOM deleted") return nil } - -func toUomResponse(e entities.MUomEntity) dtodomain.UomResponse { - return dtodomain.UomResponse{ - ID: e.ID.String(), - Name: e.Name, - Description: e.Description, - Symbol: e.Symbol, - Code: e.Code, - StdPrecision: e.StdPrecision, - IsActive: e.IsActive, - Client: pkgdto.ClientResponse{ - ID: e.Client.ID.String(), - Name: e.Client.Name, - }, - } -} diff --git a/pkg/constants/common.go b/pkg/constants/common.go index 43f8286..9bc0950 100644 --- a/pkg/constants/common.go +++ b/pkg/constants/common.go @@ -15,4 +15,8 @@ const ( LOGGER = "logger" SUPERADMIN = "superadmin" COMPLETED = "completed" + USERID = "user_id" + CREATE = "create" + UPDATE = "update" + DELETE = "delete" ) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index df76987..5e48cf1 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,6 +1,14 @@ package utils -import "strings" +import ( + "context" + "reflect" + "strings" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" + "github.com/google/uuid" +) func GetInitials(name string) string { name = strings.TrimSpace(name) @@ -25,3 +33,49 @@ func GetInitials(name string) string { } return strings.ToUpper(initials) } + +func GetUserID(ctx context.Context) string { + val := ctx.Value(constants.USERID) + if id, ok := val.(string); ok { + return id + } + return "" +} + +func FillAuditTrail(ctx context.Context, action string) entities.FullAuditTrail { + userID := GetUserID(ctx) + uid, _ := uuid.Parse(userID) + audit := entities.Audit{} + switch action { + case "create": + audit.CreatedBy = uid + case "update": + audit.UpdatedBy = uid + case "delete": + audit.DeletedBy = uid + } + return entities.FullAuditTrail{Audit: audit} +} + +func GetChangedFields(before, after interface{}) map[string]map[string]interface{} { + changes := make(map[string]map[string]interface{}) + vBefore := reflect.ValueOf(before) + vAfter := reflect.ValueOf(after) + t := vBefore.Type() + + for i := 0; i < vBefore.NumField(); i++ { + field := t.Field(i).Name + if field == "FullAuditTrail" { + continue + } + valBefore := vBefore.Field(i).Interface() + valAfter := vAfter.Field(i).Interface() + if !reflect.DeepEqual(valBefore, valAfter) { + changes[field] = map[string]interface{}{ + "old": valBefore, + "new": valAfter, + } + } + } + return changes +} diff --git a/providers/core.go b/providers/core.go index dda6f92..5bbb4ce 100644 --- a/providers/core.go +++ b/providers/core.go @@ -135,6 +135,7 @@ func RegisterDependencies(injector *do.Injector) { // Initialize db := do.MustInvokeNamed[*gorm.DB](injector, constants.DB) jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService) + log := do.MustInvokeNamed[*logrus.Logger](injector, constants.LOGGER) // Repository userRepository := repository.NewUserRepository(db) @@ -181,7 +182,7 @@ func RegisterDependencies(injector *do.Injector) { clientServ := clientService.NewClientService(clientRepository, db) permissionsServ := permissionsService.NewPermissionsService(permissionsRepository, db) categoryServ := categoryService.NewCategoryService(categoryRepository, db) - uomServ := uomService.NewUomService(uomRepository, sequenceServ, db) + uomServ := uomService.NewUomService(uomRepository, sequenceServ, db, log) mvendorServ := mvendorService.NewVendorService(mvendorRepository, sequenceServ, db) warehouseServ := warehouseService.NewWarehouseService(warehouseRepository, db) zonaServ := zonaService.NewZonaService(zonaRepository, db)