package service import ( "context" "errors" "fmt" "strings" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" m "gitlab.com/mbugroup/lti-api.git/internal/middleware" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/injections/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/injections/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" "gorm.io/gorm" ) type InjectionService interface { GetOne(ctx *fiber.Ctx, id uint) (*entity.Payment, error) CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Payment, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Payment, error) } type injectionService struct { Log *logrus.Logger Validate *validator.Validate Repository repository.InjectionRepository ApprovalSvc commonSvc.ApprovalService approvalWorkflow approvalutils.ApprovalWorkflowKey } func NewInjectionService( repo repository.InjectionRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate, ) InjectionService { return &injectionService{ Log: utils.Log, Validate: validate, Repository: repo, ApprovalSvc: approvalSvc, approvalWorkflow: utils.ApprovalWorkflowInjection, } } func (s injectionService) withRelations(db *gorm.DB) *gorm.DB { return db.Preload("CreatedUser"). Preload("BankWarehouse") } func (s injectionService) GetOne(c *fiber.Ctx, id uint) (*entity.Payment, error) { injection, err := s.Repository.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Injection not found") } if err != nil { s.Log.Errorf("Failed get injection by id: %+v", err) return nil, err } if !isInjectionTransaction(injection.TransactionType) { return nil, fiber.NewError(fiber.StatusNotFound, "Injection not found") } if s.ApprovalSvc != nil { approval, err := s.ApprovalSvc.LatestByTarget(c.Context(), s.approvalWorkflow, id, s.approvalQueryModifier()) if err != nil { s.Log.Warnf("Unable to load latest approval for injection %d: %+v", id, err) } else { injection.LatestApproval = approval } } return injection, nil } func (s *injectionService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Payment, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } if err := s.ensureBankExists(c.Context(), req.BankId); err != nil { return nil, err } adjustmentDate, err := utils.ParseDateString(req.AdjustmentDate) if err != nil { return nil, utils.BadRequest(err.Error()) } actorID, err := m.ActorIDFromContext(c) if err != nil { return nil, err } code, err := s.generateInjectionCode(c.Context()) if err != nil { return nil, err } createBody := &entity.Payment{ PaymentCode: code, TransactionType: string(utils.TransactionTypeInjection), PartyType: string(utils.PaymentPartyCustomer), PartyId: 0, PaymentDate: adjustmentDate, PaymentMethod: string(utils.PaymentMethodSaldo), BankId: req.BankId, Direction: "IN", Nominal: req.Nominal, Notes: req.Notes, CreatedBy: actorID, } err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { injectionRepoTx := repository.NewInjectionRepository(dbTransaction) if err := injectionRepoTx.CreateOne(c.Context(), createBody, nil); err != nil { return err } if s.ApprovalSvc != nil { action := entity.ApprovalActionCreated approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) _, err := approvalSvcTx.CreateApproval( c.Context(), s.approvalWorkflow, createBody.Id, utils.InjectionStepPengajuan, &action, actorID, nil, ) if err != nil { return err } } return nil }) if err != nil { s.Log.Errorf("Failed to create injection: %+v", err) return nil, err } return s.GetOne(c, createBody.Id) } func (s injectionService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Payment, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } updateBody := make(map[string]any) requiresVerification := req.BankId != nil || req.AdjustmentDate != nil || req.Nominal != nil || req.Notes != nil if requiresVerification { current, err := s.Repository.GetByID(c.Context(), id, nil) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Injection not found") } if err != nil { s.Log.Errorf("Failed get injection by id: %+v", err) return nil, err } if !isInjectionTransaction(current.TransactionType) { return nil, fiber.NewError(fiber.StatusNotFound, "Injection not found") } } if req.BankId != nil { if err := s.ensureBankExists(c.Context(), req.BankId); err != nil { return nil, err } updateBody["bank_id"] = *req.BankId } if req.AdjustmentDate != nil { parsedDate, err := utils.ParseDateString(*req.AdjustmentDate) if err != nil { return nil, utils.BadRequest(err.Error()) } updateBody["payment_date"] = parsedDate } if req.Nominal != nil { updateBody["nominal"] = *req.Nominal } if req.Notes != nil { updateBody["notes"] = *req.Notes } if len(updateBody) == 0 { return s.GetOne(c, id) } if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Injection not found") } s.Log.Errorf("Failed to update injection: %+v", err) return nil, err } return s.GetOne(c, id) } func isInjectionTransaction(transactionType string) bool { return strings.EqualFold(transactionType, string(utils.TransactionTypeInjection)) } func (s injectionService) generateInjectionCode(ctx context.Context) (string, error) { sequence, err := s.Repository.NextPaymentSequence(ctx) if err != nil { return "", err } return fmt.Sprintf("INJ-%05d", sequence), nil } func (s injectionService) approvalQueryModifier() func(*gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Preload("ActionUser") } } func (s injectionService) ensureBankExists(ctx context.Context, bankId *uint) error { return commonSvc.EnsureRelations(ctx, commonSvc.RelationCheck{Name: "Bank", ID: bankId, Exists: s.Repository.BankExists}, ) }