From 511e5501bba491cc6c2a9fec3b719c5262ddfd1c Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 9 Dec 2025 15:32:11 +0700 Subject: [PATCH 1/6] feat[BE]: create GetOverhead API, and fixing chickin use newest productwarehouse schema --- .../controllers/closing.controller.go | 22 +++ .../closings/dto/closingMarketing.dto.go | 1 - .../closings/dto/closingOverhead.dto.go | 175 ++++++++++++++++++ internal/modules/closings/module.go | 7 +- internal/modules/closings/route.go | 2 + .../closings/services/closing.service.go | 37 +++- .../expense_realization.repository.go | 26 ++- .../expenses/services/expense.service.go | 24 ++- .../modules/inventory/adjustments/route.go | 4 +- .../project_chickin.repository.go | 11 ++ .../chickins/services/chickin.service.go | 8 +- .../repositories/project_budget.repository.go | 13 ++ 12 files changed, 309 insertions(+), 21 deletions(-) create mode 100644 internal/modules/closings/dto/closingOverhead.dto.go diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go index a9282f21..4ad6e02e 100644 --- a/internal/modules/closings/controllers/closing.controller.go +++ b/internal/modules/closings/controllers/closing.controller.go @@ -123,3 +123,25 @@ func (u *ClosingController) GetPenjualan(c *fiber.Ctx) error { Data: dto.ToPenjualanRealisasiResponseDTO(projectFlock.Category, uint(projectFlockID), result), }) } + +func (u *ClosingController) GetOverhead(c *fiber.Ctx) error { + param := c.Params("project_flock_id") + + projectFlockID, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Project Flock Id") + } + + result, err := u.ClosingService.GetOverhead(c, uint(projectFlockID)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get overhead successfully", + Data: result, + }) +} diff --git a/internal/modules/closings/dto/closingMarketing.dto.go b/internal/modules/closings/dto/closingMarketing.dto.go index 4c47a7e0..e64a6735 100644 --- a/internal/modules/closings/dto/closingMarketing.dto.go +++ b/internal/modules/closings/dto/closingMarketing.dto.go @@ -11,7 +11,6 @@ import ( ) // === Response DTO === - type SalesDTO struct { Id uint `json:"id"` RealizationDate time.Time `json:"realization_date"` diff --git a/internal/modules/closings/dto/closingOverhead.dto.go b/internal/modules/closings/dto/closingOverhead.dto.go new file mode 100644 index 00000000..95f3e10b --- /dev/null +++ b/internal/modules/closings/dto/closingOverhead.dto.go @@ -0,0 +1,175 @@ +package dto + +import ( + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" +) + +// === DTO Structs === + +type OverheadDTO struct { + ItemName string `json:"item_name"` + UOMName string `json:"uom_name"` + BudgetQuantity float64 `json:"budget_quantity"` + BudgetUnitPrice float64 `json:"budget_unit_price"` + BudgetTotalAmount float64 `json:"budget_total_amount"` + ActualDate string `json:"actual_date"` + ActualQuantity float64 `json:"actual_quantity"` + ActualUnitPrice float64 `json:"actual_unit_price"` + ActualTotalAmount float64 `json:"actual_total_amount"` + CostPerBird float64 `json:"cost_per_bird"` +} + +type TotalDTO struct { + BudgetQuantity float64 `json:"budget_quantity"` + BudgetTotalAmount float64 `json:"budget_total_amount"` + ActualQuantity float64 `json:"actual_quantity"` + ActualTotalAmount float64 `json:"actual_total_amount"` + CostPerBird float64 `json:"cost_per_bird"` +} + +type OverheadListDTO struct { + Total TotalDTO `json:"total"` + Overheads []OverheadDTO `json:"overheads"` +} + +// === Mapper Functions === + +func ToOverheadDTO(budget *entity.ProjectBudget, realization *entity.ExpenseRealization) OverheadDTO { + if budget == nil && realization == nil { + return OverheadDTO{} + } + + var itemName, itemUOM string + if budget != nil { + itemName, itemUOM = getItemInfo(budget.Nonstock) + } + + if itemName == "" && realization != nil && realization.ExpenseNonstock != nil { + itemName, itemUOM = getItemInfo(realization.ExpenseNonstock.Nonstock) + } + + dto := OverheadDTO{ + ItemName: itemName, + UOMName: itemUOM, + } + + if budget != nil { + dto.BudgetQuantity = budget.Qty + dto.BudgetUnitPrice = budget.Price + dto.BudgetTotalAmount = calculateTotal(budget.Qty, budget.Price) + } + + if realization != nil { + dto.ActualQuantity = realization.Qty + dto.ActualUnitPrice = realization.Price + dto.ActualTotalAmount = calculateTotal(realization.Qty, realization.Price) + dto.ActualDate = formatRealizationDate(realization) + } + + return dto +} + +func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalChickinQty float64) OverheadListDTO { + overheadsByNonstockID := make(map[uint]*OverheadDTO) + latestDateByNonstockID := make(map[uint]string) + + for i := range budgets { + nonstockID := budgets[i].NonstockId + if overheadsByNonstockID[nonstockID] == nil { + overheadsByNonstockID[nonstockID] = &OverheadDTO{} + } + + itemName, itemUOM := getItemInfo(budgets[i].Nonstock) + overheadsByNonstockID[nonstockID].ItemName = itemName + overheadsByNonstockID[nonstockID].UOMName = itemUOM + overheadsByNonstockID[nonstockID].BudgetQuantity = budgets[i].Qty + overheadsByNonstockID[nonstockID].BudgetUnitPrice = budgets[i].Price + overheadsByNonstockID[nonstockID].BudgetTotalAmount = calculateTotal(budgets[i].Qty, budgets[i].Price) + } + + for i := range realizations { + if realizations[i].ExpenseNonstock == nil || realizations[i].ExpenseNonstock.NonstockId == nil { + continue + } + + nonstockID := uint(*realizations[i].ExpenseNonstock.NonstockId) + if overheadsByNonstockID[nonstockID] == nil { + overheadsByNonstockID[nonstockID] = &OverheadDTO{} + } + + overheadsByNonstockID[nonstockID].ActualQuantity += realizations[i].Qty + overheadsByNonstockID[nonstockID].ActualTotalAmount += calculateTotal(realizations[i].Qty, realizations[i].Price) + + if overheadsByNonstockID[nonstockID].ItemName == "" { + itemName, itemUOM := getItemInfo(realizations[i].ExpenseNonstock.Nonstock) + overheadsByNonstockID[nonstockID].ItemName = itemName + overheadsByNonstockID[nonstockID].UOMName = itemUOM + } + + realizationDateStr := formatRealizationDate(&realizations[i]) + if realizationDateStr != "" { + if latestDateByNonstockID[nonstockID] == "" || realizationDateStr > latestDateByNonstockID[nonstockID] { + latestDateByNonstockID[nonstockID] = realizationDateStr + } + } + } + + var totalBudgetQuantity, totalBudgetAmount, totalActualQuantity, totalActualAmount float64 + overheadItems := make([]OverheadDTO, 0, len(overheadsByNonstockID)) + + for nonstockID, overhead := range overheadsByNonstockID { + overhead.ActualDate = latestDateByNonstockID[nonstockID] + overhead.CostPerBird = calculateCostPerBird(overhead.ActualTotalAmount, totalChickinQty) + + if overhead.ActualQuantity > 0 { + overhead.ActualUnitPrice = overhead.ActualTotalAmount / overhead.ActualQuantity + } + + totalBudgetQuantity += overhead.BudgetQuantity + totalBudgetAmount += overhead.BudgetTotalAmount + totalActualQuantity += overhead.ActualQuantity + totalActualAmount += overhead.ActualTotalAmount + + overheadItems = append(overheadItems, *overhead) + } + + return OverheadListDTO{ + Total: TotalDTO{ + BudgetQuantity: totalBudgetQuantity, + BudgetTotalAmount: totalBudgetAmount, + ActualQuantity: totalActualQuantity, + ActualTotalAmount: totalActualAmount, + CostPerBird: calculateCostPerBird(totalActualAmount, totalChickinQty), + }, + Overheads: overheadItems, + } +} + +// === Helper Functions === + +func getItemInfo(nonstock *entity.Nonstock) (string, string) { + if nonstock != nil && nonstock.Id != 0 { + return nonstock.Name, nonstock.Uom.Name + } + return "", "" +} + +func calculateTotal(qty, price float64) float64 { + return qty * price +} + +func calculateCostPerBird(totalPrice, totalChickinQty float64) float64 { + if totalChickinQty > 0 { + return totalPrice / totalChickinQty + } + return 0 +} + +func formatRealizationDate(realization *entity.ExpenseRealization) string { + if realization != nil && realization.ExpenseNonstock != nil && realization.ExpenseNonstock.Expense != nil { + if !realization.ExpenseNonstock.Expense.RealizationDate.IsZero() { + return realization.ExpenseNonstock.Expense.RealizationDate.Format("2006-01-02T15:04:05Z07:00") + } + } + return "" +} diff --git a/internal/modules/closings/module.go b/internal/modules/closings/module.go index 77941256..af129eda 100644 --- a/internal/modules/closings/module.go +++ b/internal/modules/closings/module.go @@ -9,7 +9,9 @@ import ( commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services" + rExpenseRealization "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" + rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" @@ -22,12 +24,15 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * closingRepo := rClosing.NewClosingRepository(db) userRepo := rUser.NewUserRepository(db) projectFlockRepo := rProjectFlock.NewProjectflockRepository(db) + projectBudgetRepo := rProjectFlock.NewProjectBudgetRepository(db) marketingRepo := rMarketings.NewMarketingRepository(db) marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db) + expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db) + chickinRepo := rChickin.NewChickinRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db) approvalService := commonSvc.NewApprovalService(approvalRepo) - closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, validate) + closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, validate) userService := sUser.NewUserService(userRepo, validate) ClosingRoutes(router, userService, closingService) diff --git a/internal/modules/closings/route.go b/internal/modules/closings/route.go index ba18f3b9..2d2221a8 100644 --- a/internal/modules/closings/route.go +++ b/internal/modules/closings/route.go @@ -22,5 +22,7 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService route.Get("/", ctrl.GetAll) route.Get("/:project_flock_id/penjualan", ctrl.GetPenjualan) + route.Get("/:project_flock_id/overhead", ctrl.GetOverhead) route.Get("/:projectFlockId", ctrl.GetClosingSummary) + } diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 7fcd51ec..621fdb8f 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -9,8 +9,10 @@ import ( "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" + expenseRealizationRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" + chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" @@ -26,6 +28,7 @@ type ClosingService interface { GetProjectFlockByID(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error) GetPenjualan(ctx *fiber.Ctx, projectFlockID uint) ([]entity.MarketingDeliveryProduct, error) GetClosingSummary(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingSummaryDTO, error) + GetOverhead(ctx *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error) } type closingService struct { @@ -36,9 +39,12 @@ type closingService struct { MarketingRepo marketingRepository.MarketingRepository MarketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository ApprovalSvc commonSvc.ApprovalService + ExpenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository + ProjectBudgetRepo projectflockRepository.ProjectBudgetRepository + ChickinRepo chickinRepository.ProjectChickinRepository } -func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate) ClosingService { +func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, validate *validator.Validate) ClosingService { return &closingService{ Log: utils.Log, Validate: validate, @@ -47,6 +53,9 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje MarketingRepo: marketingRepo, MarketingDeliveryProductRepo: marketingDeliveryProductRepo, ApprovalSvc: approvalSvc, + ExpenseRealizationRepo: expenseRealizationRepo, + ProjectBudgetRepo: projectBudgetRepo, + ChickinRepo: chickinRepo, } } @@ -188,3 +197,29 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID return statusProject, statusClosing, nil } + +func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error) { + budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID) + if err != nil { + return nil, err + } + + realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID) + if err != nil { + return nil, err + } + + chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID) + if err != nil { + return nil, err + } + + var totalChickinQty float64 + for _, chickin := range chickins { + totalChickinQty += chickin.UsageQty + } + + result := dto.ToOverheadListDTOs(budgets, realizations, totalChickinQty) + + return &result, nil +} diff --git a/internal/modules/expenses/repositories/expense_realization.repository.go b/internal/modules/expenses/repositories/expense_realization.repository.go index 77f075f7..e60324ca 100644 --- a/internal/modules/expenses/repositories/expense_realization.repository.go +++ b/internal/modules/expenses/repositories/expense_realization.repository.go @@ -12,6 +12,7 @@ type ExpenseRealizationRepository interface { repository.BaseRepository[entity.ExpenseRealization] IdExists(ctx context.Context, id uint64) (bool, error) GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error) + GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error) } type ExpenseRealizationRepositoryImpl struct { @@ -30,11 +31,22 @@ func (r *ExpenseRealizationRepositoryImpl) IdExists(ctx context.Context, id uint func (r *ExpenseRealizationRepositoryImpl) GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error) { var realization entity.ExpenseRealization - err := r.DB().WithContext(ctx). - Where("expense_nonstock_id = ?", expenseNonstockID). - First(&realization).Error - if err != nil { - return nil, err - } - return &realization, nil + err := r.DB().WithContext(ctx).Where("expense_nonstock_id = ?", expenseNonstockID).First(&realization).Error + return &realization, err +} + +func (r *ExpenseRealizationRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error) { + var realizations []entity.ExpenseRealization + err := r.DB().WithContext(ctx). + Preload("ExpenseNonstock"). + Preload("ExpenseNonstock.Nonstock"). + Preload("ExpenseNonstock.Nonstock.Uom"). + Preload("ExpenseNonstock.Expense"). + Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id"). + Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = expense_nonstocks.project_flock_kandang_id"). + Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id"). + Where("project_flock_kandangs.project_flock_id = ?", projectFlockID). + Where("expenses.category = ?", "BOP"). + Find(&realizations).Error + return realizations, err } diff --git a/internal/modules/expenses/services/expense.service.go b/internal/modules/expenses/services/expense.service.go index 7de05689..0b768f0a 100644 --- a/internal/modules/expenses/services/expense.service.go +++ b/internal/modules/expenses/services/expense.service.go @@ -11,6 +11,7 @@ import ( 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" + middleware "gitlab.com/mbugroup/lti-api.git/internal/middleware" expenseDto "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/dto" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/validations" @@ -188,7 +189,11 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate reference number") } - createdBy := uint64(1) //todo get from auth + actorID, err := middleware.ActorIDFromContext(c) + if err != nil { + return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context") + } + createdBy := uint64(actorID) expense = &entity.Expense{ ReferenceNumber: referenceNumber, PoNumber: req.PoNumber, @@ -496,7 +501,10 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) } } - actorID := uint(1) // TODO: replace with authenticated user id + actorID, err := middleware.ActorIDFromContext(c) + if err != nil { + return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context") + } if *latestApproval.Action != entity.ApprovalActionUpdated { approvalAction := entity.ApprovalActionUpdated @@ -655,7 +663,10 @@ func (s *expenseService) CompleteExpense(c *fiber.Ctx, id uint, notes *string) ( return nil, err } - actorID := uint(1) // TODO: replace with authenticated user id + actorID, err := middleware.ActorIDFromContext(c) + if err != nil { + return nil, fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context") + } latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowExpense, id, nil) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { @@ -960,11 +971,14 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest, return nil, fiber.NewError(fiber.StatusBadRequest, "No expense IDs provided") } - actorID := uint(1) // TODO: replace with authenticated user id + actorID, err := middleware.ActorIDFromContext(c) + if err != nil { + return nil, fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context") + } var results []expenseDto.ExpenseDetailDTO - err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { + err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx)) expenseRepoTx := repository.NewExpenseRepository(tx) diff --git a/internal/modules/inventory/adjustments/route.go b/internal/modules/inventory/adjustments/route.go index 8f58bb4d..57200215 100644 --- a/internal/modules/inventory/adjustments/route.go +++ b/internal/modules/inventory/adjustments/route.go @@ -1,7 +1,7 @@ package adjustments import ( - // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/controllers" adjustment "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func AdjustmentRoutes(v1 fiber.Router, u user.UserService, s adjustment.Adjustme ctrl := controller.NewAdjustmentController(s) route := v1.Group("/adjustments") - + route.Use(m.Auth(u)) // Standard CRUD routes following master data pattern route.Get("/", ctrl.AdjustmentHistory) // Get all with pagination and filters route.Post("/", ctrl.Adjustment) // Create adjustment diff --git a/internal/modules/production/chickins/repositories/project_chickin.repository.go b/internal/modules/production/chickins/repositories/project_chickin.repository.go index a98dab67..bef062f5 100644 --- a/internal/modules/production/chickins/repositories/project_chickin.repository.go +++ b/internal/modules/production/chickins/repositories/project_chickin.repository.go @@ -11,6 +11,7 @@ import ( type ProjectChickinRepository interface { repository.BaseRepository[entity.ProjectChickin] GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error) + GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectChickin, error) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) GetPendingByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) @@ -40,6 +41,16 @@ func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, pr return &chickin, nil } +func (r *ChickinRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectChickin, error) { + var chickins []entity.ProjectChickin + err := r.db.WithContext(ctx). + Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = project_chickins.project_flock_kandang_id"). + Where("project_flock_kandangs.project_flock_id = ?", projectFlockID). + Order("project_chickins.created_at DESC"). + Find(&chickins).Error + return chickins, err +} + func (r *ChickinRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) { var chickins []entity.ProjectChickin err := r.db.WithContext(ctx). diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 660f1e7e..54fd2cb1 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -197,7 +197,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti if category == string(utils.ProjectFlockCategoryLaying) { for _, chickin := range newChikins { - updates := map[string]any{"quantity": gorm.Expr("quantity - ?", chickin.PendingUsageQty)} + updates := map[string]any{"qty": gorm.Expr("qty - ?", chickin.PendingUsageQty)} if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -498,7 +498,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit for _, chickin := range chickins { if categoryForRejection == string(utils.ProjectFlockCategoryGrowing) { - updates := map[string]any{"quantity": gorm.Expr("quantity + ?", chickin.PendingUsageQty)} + updates := map[string]any{"qty": gorm.Expr("qty + ?", chickin.PendingUsageQty)} if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -600,7 +600,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti if chickin.ProductWarehouseId != targetPW.Id { if err := productWarehouseTx.PatchOne(ctx.Context(), chickin.ProductWarehouseId, map[string]any{ - "quantity": gorm.Expr("quantity - ?", quantityToConvert), + "qty": gorm.Expr("qty - ?", quantityToConvert), }, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Source product warehouse %d not found", chickin.ProductWarehouseId)) @@ -610,7 +610,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti } if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{ - "quantity": gorm.Expr("quantity + ?", quantityToConvert), + "qty": gorm.Expr("qty + ?", quantityToConvert), }, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Target product warehouse %d not found", targetPW.Id)) diff --git a/internal/modules/production/project_flocks/repositories/project_budget.repository.go b/internal/modules/production/project_flocks/repositories/project_budget.repository.go index 943a22b3..720bfc40 100644 --- a/internal/modules/production/project_flocks/repositories/project_budget.repository.go +++ b/internal/modules/production/project_flocks/repositories/project_budget.repository.go @@ -1,6 +1,8 @@ package repository import ( + "context" + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gorm.io/gorm" @@ -8,6 +10,7 @@ import ( type ProjectBudgetRepository interface { repository.BaseRepository[entity.ProjectBudget] + GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectBudget, error) } type ProjectBudgetRepositoryImpl struct { @@ -21,3 +24,13 @@ func NewProjectBudgetRepository(db *gorm.DB) ProjectBudgetRepository { db: db, } } + +func (r *ProjectBudgetRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectBudget, error) { + var budgets []entity.ProjectBudget + err := r.db.WithContext(ctx). + Where("project_flock_id = ?", projectFlockID). + Preload("Nonstock"). + Preload("Nonstock.Uom"). + Find(&budgets).Error + return budgets, err +} From d7c543bc9d4df8822272ad2e72eff68606d56811 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 9 Dec 2025 19:24:17 +0700 Subject: [PATCH 2/6] Refactor[BE]: : delete sales orders and delivery order folder and refactor to just one root marketing folder --- .../closings/dto/closingMarketing.dto.go | 2 +- internal/modules/closings/module.go | 2 +- .../closings/services/closing.service.go | 4 +- .../deliveryorder.controller.go} | 12 +++--- .../salesorder.controller.go} | 6 +-- .../marketing/delivery-orderss/module.go | 38 ------------------ .../marketing/delivery-orderss/route.go | 31 --------------- .../deliveryorder.dto.go} | 23 +++++------ .../salesorder.dto.go} | 0 internal/modules/marketing/module.go | 37 +++++++++++++++++- .../salesorder.repository.go} | 0 ...salesorder_delivery_product.repository.go} | 0 .../salesorder_product.repository.go} | 0 internal/modules/marketing/route.go | 38 ++++++++++-------- .../modules/marketing/sales-orders/module.go | 39 ------------------- .../modules/marketing/sales-orders/route.go | 27 ------------- .../deliveryorder.service.go} | 18 ++++----- .../salesorder.service.go} | 4 +- .../deliveryorder.validation.go} | 8 ++-- .../salesorder.validation.go} | 0 20 files changed, 97 insertions(+), 192 deletions(-) rename internal/modules/marketing/{delivery-orderss/controllers/delivery-orders.controller.go => controllers/deliveryorder.controller.go} (92%) rename internal/modules/marketing/{sales-orders/controllers/sales-orders.controller.go => controllers/salesorder.controller.go} (95%) delete mode 100644 internal/modules/marketing/delivery-orderss/module.go delete mode 100644 internal/modules/marketing/delivery-orderss/route.go rename internal/modules/marketing/{delivery-orderss/dto/delivery-orders.dto.go => dto/deliveryorder.dto.go} (94%) rename internal/modules/marketing/{sales-orders/dto/sales-orders.dto.go => dto/salesorder.dto.go} (100%) rename internal/modules/marketing/{sales-orders/repositories/marketings.repository.go => repositories/salesorder.repository.go} (100%) rename internal/modules/marketing/{sales-orders/repositories/marketing-delivery-products.repository.go => repositories/salesorder_delivery_product.repository.go} (100%) rename internal/modules/marketing/{sales-orders/repositories/marketing-products.repository.go => repositories/salesorder_product.repository.go} (100%) delete mode 100644 internal/modules/marketing/sales-orders/module.go delete mode 100644 internal/modules/marketing/sales-orders/route.go rename internal/modules/marketing/{delivery-orderss/services/delivery-orders.service.go => services/deliveryorder.service.go} (96%) rename internal/modules/marketing/{sales-orders/services/sales-orders.service.go => services/salesorder.service.go} (99%) rename internal/modules/marketing/{delivery-orderss/validations/delivery-orders.validation.go => validations/deliveryorder.validation.go} (91%) rename internal/modules/marketing/{sales-orders/validations/sales-orders.validation.go => validations/salesorder.validation.go} (100%) diff --git a/internal/modules/closings/dto/closingMarketing.dto.go b/internal/modules/closings/dto/closingMarketing.dto.go index e64a6735..a442fc9d 100644 --- a/internal/modules/closings/dto/closingMarketing.dto.go +++ b/internal/modules/closings/dto/closingMarketing.dto.go @@ -4,7 +4,7 @@ import ( "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - deliveryOrdersDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto" + deliveryOrdersDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto" kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto" diff --git a/internal/modules/closings/module.go b/internal/modules/closings/module.go index af129eda..566f26b2 100644 --- a/internal/modules/closings/module.go +++ b/internal/modules/closings/module.go @@ -10,7 +10,7 @@ import ( rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services" rExpenseRealization "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" - rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" + rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 621fdb8f..dc2ae81e 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -10,8 +10,8 @@ import ( repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" expenseRealizationRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" - marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" - marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" + marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" + marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" diff --git a/internal/modules/marketing/delivery-orderss/controllers/delivery-orders.controller.go b/internal/modules/marketing/controllers/deliveryorder.controller.go similarity index 92% rename from internal/modules/marketing/delivery-orderss/controllers/delivery-orders.controller.go rename to internal/modules/marketing/controllers/deliveryorder.controller.go index 292381d0..73904cc3 100644 --- a/internal/modules/marketing/delivery-orderss/controllers/delivery-orders.controller.go +++ b/internal/modules/marketing/controllers/deliveryorder.controller.go @@ -4,9 +4,9 @@ import ( "math" "strconv" - "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto" - service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations" + "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations" "gitlab.com/mbugroup/lti-api.git/internal/response" "github.com/gofiber/fiber/v2" @@ -23,7 +23,7 @@ func NewDeliveryOrdersController(deliveryOrdersService service.DeliveryOrdersSer } func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error { - query := &validation.Query{ + query := &validation.DeliveryOrderQuery{ Page: c.QueryInt("page", 1), Limit: c.QueryInt("limit", 10), MarketingId: uint(c.QueryInt("marketing_id", 0)), @@ -76,7 +76,7 @@ func (u *DeliveryOrdersController) GetOne(c *fiber.Ctx) error { } func (u *DeliveryOrdersController) CreateOne(c *fiber.Ctx) error { - req := new(validation.Create) + req := new(validation.DeliveryOrderCreate) if err := c.BodyParser(req); err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") @@ -97,7 +97,7 @@ func (u *DeliveryOrdersController) CreateOne(c *fiber.Ctx) error { } func (u *DeliveryOrdersController) UpdateOne(c *fiber.Ctx) error { - req := new(validation.Update) + req := new(validation.DeliveryOrderUpdate) param := c.Params("id") id, err := strconv.Atoi(param) diff --git a/internal/modules/marketing/sales-orders/controllers/sales-orders.controller.go b/internal/modules/marketing/controllers/salesorder.controller.go similarity index 95% rename from internal/modules/marketing/sales-orders/controllers/sales-orders.controller.go rename to internal/modules/marketing/controllers/salesorder.controller.go index 16d3b5be..416af20f 100644 --- a/internal/modules/marketing/sales-orders/controllers/sales-orders.controller.go +++ b/internal/modules/marketing/controllers/salesorder.controller.go @@ -3,9 +3,9 @@ package controller import ( "strconv" - "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/dto" - service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations" + "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations" "gitlab.com/mbugroup/lti-api.git/internal/response" "github.com/gofiber/fiber/v2" diff --git a/internal/modules/marketing/delivery-orderss/module.go b/internal/modules/marketing/delivery-orderss/module.go deleted file mode 100644 index efe3737d..00000000 --- a/internal/modules/marketing/delivery-orderss/module.go +++ /dev/null @@ -1,38 +0,0 @@ -package delivery_orderss - -import ( - "fmt" - - "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" - "gorm.io/gorm" - - commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" - commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" - sDeliveryOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services" - rMarketing "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" - rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" - sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" - "gitlab.com/mbugroup/lti-api.git/internal/utils" -) - -type DeliveryOrdersModule struct{} - -func (DeliveryOrdersModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { - marketingRepo := rMarketing.NewMarketingRepository(db) - marketingProductRepo := rMarketing.NewMarketingProductRepository(db) - marketingDeliveryProductRepo := rMarketing.NewMarketingDeliveryProductRepository(db) - userRepo := rUser.NewUserRepository(db) - approvalRepo := commonRepo.NewApprovalRepository(db) - approvalSvc := commonSvc.NewApprovalService(approvalRepo) - - // Register workflow steps for MARKETINGS approval - if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil { - panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err)) - } - - deliveryOrdersService := sDeliveryOrders.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, validate) - userService := sUser.NewUserService(userRepo, validate) - - DeliveryOrdersRoutes(router, userService, deliveryOrdersService) -} diff --git a/internal/modules/marketing/delivery-orderss/route.go b/internal/modules/marketing/delivery-orderss/route.go deleted file mode 100644 index c83330da..00000000 --- a/internal/modules/marketing/delivery-orderss/route.go +++ /dev/null @@ -1,31 +0,0 @@ -package delivery_orderss - -import ( - m "gitlab.com/mbugroup/lti-api.git/internal/middleware" - controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/controllers" - deliveryOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services" - user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" - - "github.com/gofiber/fiber/v2" -) - -func DeliveryOrdersRoutes(v1 fiber.Router, u user.UserService, s deliveryOrders.DeliveryOrdersService) { - ctrl := controller.NewDeliveryOrdersController(s) - - v1.Get("/", ctrl.GetAll) - v1.Get("/:id", ctrl.GetOne) - - // Sisanya di group /delivery-orders - route := v1.Group("/delivery-orders") - route.Use(m.Auth(u)) - - // route.Get("/", m.Auth(u), ctrl.GetAll) - // route.Post("/", m.Auth(u), ctrl.CreateOne) - // route.Get("/:id", m.Auth(u), ctrl.GetOne) - // route.Patch("/:id", m.Auth(u), ctrl.UpdateOne) - // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) - - route.Post("/", ctrl.CreateOne) - route.Patch("/:id", ctrl.UpdateOne) - -} diff --git a/internal/modules/marketing/delivery-orderss/dto/delivery-orders.dto.go b/internal/modules/marketing/dto/deliveryorder.dto.go similarity index 94% rename from internal/modules/marketing/delivery-orderss/dto/delivery-orders.dto.go rename to internal/modules/marketing/dto/deliveryorder.dto.go index 69037499..b2bb70d7 100644 --- a/internal/modules/marketing/delivery-orderss/dto/delivery-orders.dto.go +++ b/internal/modules/marketing/dto/deliveryorder.dto.go @@ -24,7 +24,7 @@ type MarketingListDTO struct { Customer customerDTO.CustomerRelationDTO `json:"customer"` SalesPerson userDTO.UserRelationDTO `json:"sales_person"` SoDocs string `json:"so_docs"` - SalesOrder []MarketingProductDTO `json:"sales_order"` + SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"` CreatedUser userDTO.UserRelationDTO `json:"created_user"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -36,13 +36,14 @@ type MarketingDetailDTO struct { Customer customerDTO.CustomerRelationDTO `json:"customer"` SalesPerson userDTO.UserRelationDTO `json:"sales_person"` SoDocs string `json:"so_docs"` - SalesOrder []MarketingProductDTO `json:"sales_order"` + SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"` DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"` CreatedUser userDTO.UserRelationDTO `json:"created_user"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"` } + type MarketingDeliveryProductDTO struct { Id uint `json:"id"` MarketingProductId uint `json:"marketing_product_id"` @@ -73,7 +74,7 @@ type DeliveryGroupDTO struct { Deliveries []DeliveryItemDTO `json:"deliveries"` } -type MarketingProductDTO struct { +type DeliveryMarketingProductDTO struct { Id uint `json:"id"` MarketingId uint `json:"marketing_id"` ProductWarehouseId uint `json:"product_warehouse_id"` @@ -95,14 +96,14 @@ func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO { } } -func ToMarketingProductDTO(e entity.MarketingProduct) MarketingProductDTO { +func ToDeliveryMarketingProductDTO(e entity.MarketingProduct) DeliveryMarketingProductDTO { var productWarehouse *productwarehouseDTO.ProductWarehousNestedDTO if e.ProductWarehouse.Id != 0 { mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse) productWarehouse = &mapped } - return MarketingProductDTO{ + return DeliveryMarketingProductDTO{ Id: e.Id, MarketingId: e.MarketingId, ProductWarehouseId: e.ProductWarehouseId, @@ -155,11 +156,11 @@ func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.M latestApproval = mapped } - var salesOrderProducts []MarketingProductDTO + var salesOrderProducts []DeliveryMarketingProductDTO if len(marketing.Products) > 0 { - salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products)) + salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products)) for i, product := range marketing.Products { - salesOrderProducts[i] = ToMarketingProductDTO(product) + salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product) } } @@ -195,11 +196,11 @@ func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity salesPerson = mapped } - var salesOrderProducts []MarketingProductDTO + var salesOrderProducts []DeliveryMarketingProductDTO if len(marketing.Products) > 0 { - salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products)) + salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products)) for i, product := range marketing.Products { - salesOrderProducts[i] = ToMarketingProductDTO(product) + salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product) } } diff --git a/internal/modules/marketing/sales-orders/dto/sales-orders.dto.go b/internal/modules/marketing/dto/salesorder.dto.go similarity index 100% rename from internal/modules/marketing/sales-orders/dto/sales-orders.dto.go rename to internal/modules/marketing/dto/salesorder.dto.go diff --git a/internal/modules/marketing/module.go b/internal/modules/marketing/module.go index 9bf4f018..33048bdf 100644 --- a/internal/modules/marketing/module.go +++ b/internal/modules/marketing/module.go @@ -1,13 +1,48 @@ package marketing import ( + "fmt" + "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "gorm.io/gorm" + + commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" + rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services" + rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories" + rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" + sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" + "gitlab.com/mbugroup/lti-api.git/internal/utils" ) type MarketingModule struct{} func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { - RegisterRoutes(router, db, validate) + // Initialize repositories + marketingRepo := repository.NewMarketingRepository(db) + marketingProductRepo := repository.NewMarketingProductRepository(db) + marketingDeliveryProductRepo := repository.NewMarketingDeliveryProductRepository(db) + userRepo := rUser.NewUserRepository(db) + customerRepo := rCustomer.NewCustomerRepository(db) + productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) + + // Initialize approval service + approvalRepo := commonRepo.NewApprovalRepository(db) + approvalSvc := commonSvc.NewApprovalService(approvalRepo) + + // Register workflow steps for marketing approval + if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil { + panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err)) + } + + // Initialize services + salesOrdersService := service.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, validate) + deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, validate) + userService := sUser.NewUserService(userRepo, validate) + + // Register routes + RegisterRoutes(router, userService, salesOrdersService, deliveryOrdersService) } diff --git a/internal/modules/marketing/sales-orders/repositories/marketings.repository.go b/internal/modules/marketing/repositories/salesorder.repository.go similarity index 100% rename from internal/modules/marketing/sales-orders/repositories/marketings.repository.go rename to internal/modules/marketing/repositories/salesorder.repository.go diff --git a/internal/modules/marketing/sales-orders/repositories/marketing-delivery-products.repository.go b/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go similarity index 100% rename from internal/modules/marketing/sales-orders/repositories/marketing-delivery-products.repository.go rename to internal/modules/marketing/repositories/salesorder_delivery_product.repository.go diff --git a/internal/modules/marketing/sales-orders/repositories/marketing-products.repository.go b/internal/modules/marketing/repositories/salesorder_product.repository.go similarity index 100% rename from internal/modules/marketing/sales-orders/repositories/marketing-products.repository.go rename to internal/modules/marketing/repositories/salesorder_product.repository.go diff --git a/internal/modules/marketing/route.go b/internal/modules/marketing/route.go index 1ab03896..75ecc0f6 100644 --- a/internal/modules/marketing/route.go +++ b/internal/modules/marketing/route.go @@ -1,27 +1,31 @@ package marketing import ( - "gitlab.com/mbugroup/lti-api.git/internal/modules" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/controllers" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services" + user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" - "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" - "gorm.io/gorm" - - salesOrderss "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders" - deliveryOrderss "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss" - // MODULE IMPORTS ) -func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { - group := router.Group("/marketing") +func RegisterRoutes(router fiber.Router, userService user.UserService, salesOrdersService service.SalesOrdersService, deliveryOrdersService service.DeliveryOrdersService) { + salesOrdersCtrl := controller.NewSalesOrdersController(salesOrdersService) + deliveryOrdersCtrl := controller.NewDeliveryOrdersController(deliveryOrdersService) - allModules := []modules.Module{ - salesOrderss.SalesOrdersModule{}, - deliveryOrderss.DeliveryOrdersModule{}, - // MODULE REGISTRY - } + route := router.Group("/marketing") + route.Use(m.Auth(userService)) - for _, m := range allModules { - m.RegisterRoutes(group, db, validate) - } + route.Get("/", deliveryOrdersCtrl.GetAll) + route.Get("/:id", deliveryOrdersCtrl.GetOne) + route.Delete("/:id", salesOrdersCtrl.DeleteOne) + + route.Post("/sales-orders", salesOrdersCtrl.CreateOne) + route.Patch("/sales-orders/:id", salesOrdersCtrl.UpdateOne) + route.Post("/sales-orders/approvals", salesOrdersCtrl.Approval) + + route.Get("/delivery-orders", deliveryOrdersCtrl.GetAll) + route.Get("/delivery-orders/:id", deliveryOrdersCtrl.GetOne) + route.Post("/delivery-orders", deliveryOrdersCtrl.CreateOne) + route.Patch("/delivery-orders/:id", deliveryOrdersCtrl.UpdateOne) } diff --git a/internal/modules/marketing/sales-orders/module.go b/internal/modules/marketing/sales-orders/module.go deleted file mode 100644 index 0d9583d0..00000000 --- a/internal/modules/marketing/sales-orders/module.go +++ /dev/null @@ -1,39 +0,0 @@ -package sales_orders - -import ( - "fmt" - - "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" - "gorm.io/gorm" - - commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" - commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" - rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" - rSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" - sSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services" - rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories" - rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" - sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" - "gitlab.com/mbugroup/lti-api.git/internal/utils" -) - -type SalesOrdersModule struct{} - -func (SalesOrdersModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { - marketingRepo := rSalesOrders.NewMarketingRepository(db) - userRepo := rUser.NewUserRepository(db) - customerRepo := rCustomer.NewCustomerRepository(db) - productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) - - approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(db)) - - if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil { - panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err)) - } - - salesOrdersService := sSalesOrders.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, validate) - userService := sUser.NewUserService(userRepo, validate) - - SalesOrdersRoutes(router, userService, salesOrdersService) -} diff --git a/internal/modules/marketing/sales-orders/route.go b/internal/modules/marketing/sales-orders/route.go deleted file mode 100644 index f87cea66..00000000 --- a/internal/modules/marketing/sales-orders/route.go +++ /dev/null @@ -1,27 +0,0 @@ -package sales_orders - -import ( - m "gitlab.com/mbugroup/lti-api.git/internal/middleware" - controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/controllers" - salesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services" - user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" - - "github.com/gofiber/fiber/v2" -) - -func SalesOrdersRoutes(v1 fiber.Router, u user.UserService, s salesOrders.SalesOrdersService) { - ctrl := controller.NewSalesOrdersController(s) - - v1.Delete("/:id", ctrl.DeleteOne) - route := v1.Group("/sales-orders") - route.Use(m.Auth(u)) - - // route.Post("/", m.Auth(u), ctrl.CreateOne) - // route.Patch("/:id", m.Auth(u), ctrl.UpdateOne) - // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) - - route.Post("/", ctrl.CreateOne) - route.Patch("/:id", ctrl.UpdateOne) - - route.Post("/approvals", ctrl.Approval) -} diff --git a/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go b/internal/modules/marketing/services/deliveryorder.service.go similarity index 96% rename from internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go rename to internal/modules/marketing/services/deliveryorder.service.go index 52ced7d7..85b15dc5 100644 --- a/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -11,9 +11,9 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" m "gitlab.com/mbugroup/lti-api.git/internal/middleware" productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" - "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations" - marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" + "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" + marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" "github.com/go-playground/validator/v10" @@ -23,10 +23,10 @@ import ( ) type DeliveryOrdersService interface { - GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.MarketingListDTO, int64, error) + GetAll(ctx *fiber.Ctx, params *validation.DeliveryOrderQuery) ([]dto.MarketingListDTO, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*dto.MarketingDetailDTO, error) - CreateOne(ctx *fiber.Ctx, req *validation.Create) (*dto.MarketingDetailDTO, error) - UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*dto.MarketingDetailDTO, error) + CreateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderCreate) (*dto.MarketingDetailDTO, error) + UpdateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderUpdate, id uint) (*dto.MarketingDetailDTO, error) } type deliveryOrdersService struct { @@ -85,7 +85,7 @@ func (s deliveryOrdersService) getMarketingWithDeliveries(c *fiber.Ctx, marketin return &responseDTO, nil } -func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.MarketingListDTO, int64, error) { +func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryOrderQuery) ([]dto.MarketingListDTO, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err } @@ -164,7 +164,7 @@ func (s deliveryOrdersService) GetOne(c *fiber.Ctx, id uint) (*dto.MarketingDeta return &responseDTO, nil } -func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*dto.MarketingDetailDTO, error) { +func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.DeliveryOrderCreate) (*dto.MarketingDetailDTO, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } @@ -285,7 +285,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) return s.getMarketingWithDeliveries(c, req.MarketingId) } -func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*dto.MarketingDetailDTO, error) { +func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryOrderUpdate, id uint) (*dto.MarketingDetailDTO, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } diff --git a/internal/modules/marketing/sales-orders/services/sales-orders.service.go b/internal/modules/marketing/services/salesorder.service.go similarity index 99% rename from internal/modules/marketing/sales-orders/services/sales-orders.service.go rename to internal/modules/marketing/services/salesorder.service.go index 061ffaf7..7d60cd6c 100644 --- a/internal/modules/marketing/sales-orders/services/sales-orders.service.go +++ b/internal/modules/marketing/services/salesorder.service.go @@ -11,8 +11,8 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" m "gitlab.com/mbugroup/lti-api.git/internal/middleware" productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" - repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations" customerRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories" userRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" diff --git a/internal/modules/marketing/delivery-orderss/validations/delivery-orders.validation.go b/internal/modules/marketing/validations/deliveryorder.validation.go similarity index 91% rename from internal/modules/marketing/delivery-orderss/validations/delivery-orders.validation.go rename to internal/modules/marketing/validations/deliveryorder.validation.go index 3317e952..7db2cdd1 100644 --- a/internal/modules/marketing/delivery-orderss/validations/delivery-orders.validation.go +++ b/internal/modules/marketing/validations/deliveryorder.validation.go @@ -11,22 +11,22 @@ type DeliveryProduct struct { VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"` } -type Create struct { +type DeliveryOrderCreate struct { MarketingId uint `json:"marketing_id" validate:"required,gt=0"` DeliveryProducts []DeliveryProduct `json:"delivery_products" validate:"required,min=1,dive"` } -type Update struct { +type DeliveryOrderUpdate struct { DeliveryProducts []DeliveryProduct `json:"delivery_products" validate:"omitempty,min=1,dive"` } -type Query struct { +type DeliveryOrderQuery struct { Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"` } -type Approve struct { +type DeliveryOrderApprove struct { Action string `json:"action" validate:"required_strict"` ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"` Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` diff --git a/internal/modules/marketing/sales-orders/validations/sales-orders.validation.go b/internal/modules/marketing/validations/salesorder.validation.go similarity index 100% rename from internal/modules/marketing/sales-orders/validations/sales-orders.validation.go rename to internal/modules/marketing/validations/salesorder.validation.go From 576f8083a3600360271850e0cbf13a8e4bd437e7 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 10 Dec 2025 08:23:52 +0700 Subject: [PATCH 3/6] Feat[BE}: inisiate repport module --- .../controllers/repport.controller.go | 144 ++++++++++++++++++ internal/modules/repports/dto/repport.dto.go | 16 ++ internal/modules/repports/module.go | 17 +++ internal/modules/repports/route.go | 20 +++ .../repports/services/repport.service.go | 131 ++++++++++++++++ .../validations/repport.validation.go | 15 ++ internal/route/route.go | 2 + 7 files changed, 345 insertions(+) create mode 100644 internal/modules/repports/controllers/repport.controller.go create mode 100644 internal/modules/repports/dto/repport.dto.go create mode 100644 internal/modules/repports/module.go create mode 100644 internal/modules/repports/route.go create mode 100644 internal/modules/repports/services/repport.service.go create mode 100644 internal/modules/repports/validations/repport.validation.go diff --git a/internal/modules/repports/controllers/repport.controller.go b/internal/modules/repports/controllers/repport.controller.go new file mode 100644 index 00000000..abe1901f --- /dev/null +++ b/internal/modules/repports/controllers/repport.controller.go @@ -0,0 +1,144 @@ +package controller + +import ( + "math" + "strconv" + + "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" + "gitlab.com/mbugroup/lti-api.git/internal/response" + + "github.com/gofiber/fiber/v2" +) + +type RepportController struct { + RepportService service.RepportService +} + +func NewRepportController(repportService service.RepportService) *RepportController { + return &RepportController{ + RepportService: repportService, + } +} + +func (u *RepportController) GetAll(c *fiber.Ctx) error { + query := &validation.Query{ + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + Search: c.Query("search", ""), + } + + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + + result, totalResults, err := u.RepportService.GetAll(c, query) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.RepportListDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get all repports successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + }, + Data: result, + }) +} + +func (u *RepportController) GetOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + result, err := u.RepportService.GetOne(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get repport successfully", + Data: result, + }) +} + +func (u *RepportController) CreateOne(c *fiber.Ctx) error { + req := new(validation.Create) + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.RepportService.CreateOne(c, req) + if err != nil { + return err + } + + return c.Status(fiber.StatusCreated). + JSON(response.Success{ + Code: fiber.StatusCreated, + Status: "success", + Message: "Create repport successfully", + Data: result, + }) +} + +func (u *RepportController) UpdateOne(c *fiber.Ctx) error { + req := new(validation.Update) + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.RepportService.UpdateOne(c, req, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Update repport successfully", + Data: result, + }) +} + +func (u *RepportController) DeleteOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := u.RepportService.DeleteOne(c, uint(id)); err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Common{ + Code: fiber.StatusOK, + Status: "success", + Message: "Delete repport successfully", + }) +} diff --git a/internal/modules/repports/dto/repport.dto.go b/internal/modules/repports/dto/repport.dto.go new file mode 100644 index 00000000..154c6f47 --- /dev/null +++ b/internal/modules/repports/dto/repport.dto.go @@ -0,0 +1,16 @@ +package dto + +import "time" + +// === DTO Structs === + +type RepportListDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type RepportDetailDTO struct { + RepportListDTO +} diff --git a/internal/modules/repports/module.go b/internal/modules/repports/module.go new file mode 100644 index 00000000..83bf6ce6 --- /dev/null +++ b/internal/modules/repports/module.go @@ -0,0 +1,17 @@ +package repports + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + sRepport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" +) + +type RepportModule struct{} + +func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + repportService := sRepport.NewRepportService(validate) + + RepportRoutes(router, repportService) +} diff --git a/internal/modules/repports/route.go b/internal/modules/repports/route.go new file mode 100644 index 00000000..ccb551ed --- /dev/null +++ b/internal/modules/repports/route.go @@ -0,0 +1,20 @@ +package repports + +import ( + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/controllers" + repport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" + + "github.com/gofiber/fiber/v2" +) + +func RepportRoutes(v1 fiber.Router, s repport.RepportService) { + ctrl := controller.NewRepportController(s) + + route := v1.Group("/repports") + + route.Get("/", ctrl.GetAll) + route.Post("/", ctrl.CreateOne) + route.Get("/:id", ctrl.GetOne) + route.Patch("/:id", ctrl.UpdateOne) + route.Delete("/:id", ctrl.DeleteOne) +} diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go new file mode 100644 index 00000000..69765579 --- /dev/null +++ b/internal/modules/repports/services/repport.service.go @@ -0,0 +1,131 @@ +package service + +import ( + "strings" + "time" + + dto "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/sirupsen/logrus" +) + +type RepportService interface { + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.RepportListDTO, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*dto.RepportListDTO, error) + CreateOne(ctx *fiber.Ctx, req *validation.Create) (*dto.RepportListDTO, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*dto.RepportListDTO, error) + DeleteOne(ctx *fiber.Ctx, id uint) error +} + +type repportService struct { + Log *logrus.Logger + Validate *validator.Validate + dummyData map[uint]dto.RepportListDTO + nextID uint +} + +func NewRepportService(validate *validator.Validate) RepportService { + // Initialize with dummy data + now := time.Now().UTC() + dummyData := map[uint]dto.RepportListDTO{ + 1: {Id: 1, Name: "Sales Report", CreatedAt: now, UpdatedAt: now}, + 2: {Id: 2, Name: "Inventory Report", CreatedAt: now, UpdatedAt: now}, + 3: {Id: 3, Name: "Production Report", CreatedAt: now, UpdatedAt: now}, + } + + return &repportService{ + Log: utils.Log, + Validate: validate, + dummyData: dummyData, + nextID: 4, + } +} + +func (s *repportService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.RepportListDTO, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + // Convert map to slice + var results []dto.RepportListDTO + for _, v := range s.dummyData { + // Apply search filter if provided + if params.Search != "" && !strings.Contains(strings.ToLower(v.Name), strings.ToLower(params.Search)) { + continue + } + results = append(results, v) + } + + total := int64(len(results)) + + // Apply pagination + offset := (params.Page - 1) * params.Limit + if offset >= int(total) { + return []dto.RepportListDTO{}, total, nil + } + + end := offset + params.Limit + if end > int(total) { + end = int(total) + } + + return results[offset:end], total, nil +} + +func (s *repportService) GetOne(c *fiber.Ctx, id uint) (*dto.RepportListDTO, error) { + if data, ok := s.dummyData[id]; ok { + return &data, nil + } + return nil, fiber.NewError(fiber.StatusNotFound, "Report not found") +} + +func (s *repportService) CreateOne(c *fiber.Ctx, req *validation.Create) (*dto.RepportListDTO, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + now := time.Now().UTC() + newReport := dto.RepportListDTO{ + Id: s.nextID, + Name: req.Name, + CreatedAt: now, + UpdatedAt: now, + } + s.dummyData[s.nextID] = newReport + s.nextID++ + + return &newReport, nil +} + +func (s *repportService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*dto.RepportListDTO, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + data, ok := s.dummyData[id] + if !ok { + return nil, fiber.NewError(fiber.StatusNotFound, "Report not found") + } + + if req.Name != nil { + data.Name = *req.Name + } + + data.UpdatedAt = time.Now().UTC() + s.dummyData[id] = data + + return &data, nil +} + +func (s *repportService) DeleteOne(c *fiber.Ctx, id uint) error { + if _, ok := s.dummyData[id]; !ok { + return fiber.NewError(fiber.StatusNotFound, "Report not found") + } + + delete(s.dummyData, id) + return nil +} diff --git a/internal/modules/repports/validations/repport.validation.go b/internal/modules/repports/validations/repport.validation.go new file mode 100644 index 00000000..7d16d3ee --- /dev/null +++ b/internal/modules/repports/validations/repport.validation.go @@ -0,0 +1,15 @@ +package validation + +type Create struct { + Name string `json:"name" validate:"required_strict,min=3"` +} + +type Update struct { + Name *string `json:"name,omitempty" validate:"omitempty"` +} + +type Query struct { + Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` + Search string `query:"search" validate:"omitempty,max=50"` +} diff --git a/internal/route/route.go b/internal/route/route.go index 4d1c1bae..294fc900 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -19,6 +19,7 @@ import ( purchases "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases" ssoModule "gitlab.com/mbugroup/lti-api.git/internal/modules/sso" users "gitlab.com/mbugroup/lti-api.git/internal/modules/users" + repports "gitlab.com/mbugroup/lti-api.git/internal/modules/repports" // MODULE IMPORTS ) @@ -42,6 +43,7 @@ func Routes(app *fiber.App, db *gorm.DB) { expenses.ExpenseModule{}, ssoModule.Module{}, closings.ClosingModule{}, + repports.RepportModule{}, // MODULE REGISTRY } From e00f168a15a320f4ef5254461ccb869255f64af3 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 10 Dec 2025 11:31:49 +0700 Subject: [PATCH 4/6] Fix[BE} : Fixing duplocate SO number --- .../modules/marketing/repositories/salesorder.repository.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/modules/marketing/repositories/salesorder.repository.go b/internal/modules/marketing/repositories/salesorder.repository.go index df8a7c98..ed33d194 100644 --- a/internal/modules/marketing/repositories/salesorder.repository.go +++ b/internal/modules/marketing/repositories/salesorder.repository.go @@ -70,6 +70,7 @@ func (r *MarketingRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB, if err := db.WithContext(ctx). Model(&entity.Marketing{}). Where(fmt.Sprintf("%s = ?", column), value). + Where("deleted_at IS NULL"). Count(&count).Error; err != nil { return false, err } @@ -87,6 +88,7 @@ func (r *MarketingRepositoryImpl) generateSequentialNumber(ctx context.Context, err := db.WithContext(ctx). Model(&entity.Marketing{}). Where(fmt.Sprintf("%s LIKE ?", column), prefix+"%"). + Where("deleted_at IS NULL"). Select(column). Order(fmt.Sprintf("%s DESC", column)). Limit(20). From 16d1358b3a85615ffe65f7b1d1a8070d927c4eb6 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 10 Dec 2025 11:53:21 +0700 Subject: [PATCH 5/6] FIX[BE}: really fixed duplicate SO number --- ...0044651_create_so_number_sequence.down.sql | 3 + ...210044651_create_so_number_sequence.up.sql | 12 +++ .../repositories/salesorder.repository.go | 79 ++----------------- 3 files changed, 21 insertions(+), 73 deletions(-) create mode 100644 internal/database/migrations/20251210044651_create_so_number_sequence.down.sql create mode 100644 internal/database/migrations/20251210044651_create_so_number_sequence.up.sql diff --git a/internal/database/migrations/20251210044651_create_so_number_sequence.down.sql b/internal/database/migrations/20251210044651_create_so_number_sequence.down.sql new file mode 100644 index 00000000..4d80dd2c --- /dev/null +++ b/internal/database/migrations/20251210044651_create_so_number_sequence.down.sql @@ -0,0 +1,3 @@ +-- Drop function and sequence for sales order numbers +DROP FUNCTION IF EXISTS generate_so_number(); +DROP SEQUENCE IF EXISTS so_number_seq; diff --git a/internal/database/migrations/20251210044651_create_so_number_sequence.up.sql b/internal/database/migrations/20251210044651_create_so_number_sequence.up.sql new file mode 100644 index 00000000..833a8323 --- /dev/null +++ b/internal/database/migrations/20251210044651_create_so_number_sequence.up.sql @@ -0,0 +1,12 @@ +-- Create sequence for sales order numbers +CREATE SEQUENCE so_number_seq START WITH 1 INCREMENT BY 1; + +CREATE OR REPLACE FUNCTION generate_so_number() +RETURNS VARCHAR AS $$ +DECLARE + next_val INTEGER; +BEGIN + next_val := nextval('so_number_seq'); + RETURN 'SO-' || LPAD(next_val::TEXT, 5, '0'); +END; +$$ LANGUAGE plpgsql; diff --git a/internal/modules/marketing/repositories/salesorder.repository.go b/internal/modules/marketing/repositories/salesorder.repository.go index ed33d194..51351e55 100644 --- a/internal/modules/marketing/repositories/salesorder.repository.go +++ b/internal/modules/marketing/repositories/salesorder.repository.go @@ -3,14 +3,10 @@ package repository import ( "context" "fmt" - "strconv" - "strings" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" - "gorm.io/gorm/clause" ) type MarketingRepository interface { @@ -43,82 +39,19 @@ func (r *MarketingRepositoryImpl) GetNextSequence(ctx context.Context) (uint, er } func (r *MarketingRepositoryImpl) NextSoNumber(ctx context.Context, tx *gorm.DB) (string, error) { - return r.generateSequentialNumber(ctx, tx, "so_number", utils.MarketingSoNumberPrefix, utils.MarketingNumberPadding) -} - -func parseNumericSuffix(value, prefix string) (int, bool) { - if !strings.HasPrefix(value, prefix) { - return 0, false - } - suffix := strings.TrimPrefix(value, prefix) - if suffix == "" { - return 0, false - } - trimmed := strings.TrimLeft(suffix, "0") - if trimmed == "" { - trimmed = "0" - } - number, err := strconv.Atoi(trimmed) - if err != nil { - return 0, false - } - return number, true -} - -func (r *MarketingRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB, column, value string) (bool, error) { - var count int64 - if err := db.WithContext(ctx). - Model(&entity.Marketing{}). - Where(fmt.Sprintf("%s = ?", column), value). - Where("deleted_at IS NULL"). - Count(&count).Error; err != nil { - return false, err - } - return count > 0, nil -} - -func (r *MarketingRepositoryImpl) generateSequentialNumber(ctx context.Context, tx *gorm.DB, column, prefix string, padding int) (string, error) { - db := tx if db == nil { db = r.DB() } - var values []string + var soNumber string err := db.WithContext(ctx). - Model(&entity.Marketing{}). - Where(fmt.Sprintf("%s LIKE ?", column), prefix+"%"). - Where("deleted_at IS NULL"). - Select(column). - Order(fmt.Sprintf("%s DESC", column)). - Limit(20). - Clauses(clause.Locking{Strength: "UPDATE"}). - Pluck(column, &values).Error + Raw("SELECT generate_so_number()"). + Scan(&soNumber).Error + if err != nil { - return "", err + return "", fmt.Errorf("failed to generate SO number: %w", err) } - next := 1 - for _, value := range values { - if number, ok := parseNumericSuffix(value, prefix); ok { - next = number + 1 - break - } - } - - const maxAttempts = 20 - for attempt := 0; attempt < maxAttempts; attempt++ { - candidate := fmt.Sprintf("%s%0*d", prefix, padding, next) - exists, err := r.numberExists(ctx, db, column, candidate) - if err != nil { - return "", err - } - if !exists { - return candidate, nil - } - next++ - } - - return "", fmt.Errorf("unable to generate unique %s", column) - + return soNumber, nil } From 3f9865d26763cb832a7a87bf3f15a7be761462a2 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 10 Dec 2025 13:41:53 +0700 Subject: [PATCH 6/6] feat[BE]: menambahkan repo expense dan menhapus API API yang tidak akan digunakan di module repport --- .../controllers/repport.controller.go | 80 +++----------- internal/modules/repports/module.go | 8 +- internal/modules/repports/route.go | 6 +- .../repports/services/repport.service.go | 101 +++++++----------- .../validations/repport.validation.go | 8 -- 5 files changed, 65 insertions(+), 138 deletions(-) diff --git a/internal/modules/repports/controllers/repport.controller.go b/internal/modules/repports/controllers/repport.controller.go index abe1901f..e4b6088e 100644 --- a/internal/modules/repports/controllers/repport.controller.go +++ b/internal/modules/repports/controllers/repport.controller.go @@ -22,27 +22,27 @@ func NewRepportController(repportService service.RepportService) *RepportControl } } -func (u *RepportController) GetAll(c *fiber.Ctx) error { +func (c *RepportController) GetAll(ctx *fiber.Ctx) error { query := &validation.Query{ - Page: c.QueryInt("page", 1), - Limit: c.QueryInt("limit", 10), - Search: c.Query("search", ""), + Page: ctx.QueryInt("page", 1), + Limit: ctx.QueryInt("limit", 10), + Search: ctx.Query("search", ""), } if query.Page < 1 || query.Limit < 1 { return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") } - result, totalResults, err := u.RepportService.GetAll(c, query) + result, totalResults, err := c.RepportService.GetAll(ctx, query) if err != nil { return err } - return c.Status(fiber.StatusOK). + return ctx.Status(fiber.StatusOK). JSON(response.SuccessWithPaginate[dto.RepportListDTO]{ Code: fiber.StatusOK, Status: "success", - Message: "Get all repports successfully", + Message: "Get all reports successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, @@ -53,92 +53,46 @@ func (u *RepportController) GetAll(c *fiber.Ctx) error { }) } -func (u *RepportController) GetOne(c *fiber.Ctx) error { - param := c.Params("id") +func (c *RepportController) GetOne(ctx *fiber.Ctx) error { + param := ctx.Params("id") id, err := strconv.Atoi(param) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") } - result, err := u.RepportService.GetOne(c, uint(id)) + result, err := c.RepportService.GetOne(ctx, uint(id)) if err != nil { return err } - return c.Status(fiber.StatusOK). + return ctx.Status(fiber.StatusOK). JSON(response.Success{ Code: fiber.StatusOK, Status: "success", - Message: "Get repport successfully", + Message: "Get report successfully", Data: result, }) } -func (u *RepportController) CreateOne(c *fiber.Ctx) error { - req := new(validation.Create) - - if err := c.BodyParser(req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } - - result, err := u.RepportService.CreateOne(c, req) - if err != nil { - return err - } - - return c.Status(fiber.StatusCreated). - JSON(response.Success{ - Code: fiber.StatusCreated, - Status: "success", - Message: "Create repport successfully", - Data: result, - }) -} - -func (u *RepportController) UpdateOne(c *fiber.Ctx) error { - req := new(validation.Update) - param := c.Params("id") +func (c *RepportController) GetExpense(ctx *fiber.Ctx) error { + param := ctx.Params("id") id, err := strconv.Atoi(param) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") } - if err := c.BodyParser(req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } - - result, err := u.RepportService.UpdateOne(c, req, uint(id)) + result, err := c.RepportService.GetOne(ctx, uint(id)) if err != nil { return err } - return c.Status(fiber.StatusOK). + return ctx.Status(fiber.StatusOK). JSON(response.Success{ Code: fiber.StatusOK, Status: "success", - Message: "Update repport successfully", + Message: "Get report successfully", Data: result, }) } - -func (u *RepportController) DeleteOne(c *fiber.Ctx) error { - param := c.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - - if err := u.RepportService.DeleteOne(c, uint(id)); err != nil { - return err - } - - return c.Status(fiber.StatusOK). - JSON(response.Common{ - Code: fiber.StatusOK, - Status: "success", - Message: "Delete repport successfully", - }) -} diff --git a/internal/modules/repports/module.go b/internal/modules/repports/module.go index 83bf6ce6..be0ba7a3 100644 --- a/internal/modules/repports/module.go +++ b/internal/modules/repports/module.go @@ -6,12 +6,18 @@ import ( "gorm.io/gorm" sRepport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" + + expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" ) type RepportModule struct{} func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { - repportService := sRepport.NewRepportService(validate) + // Initialize expense realization repository + expRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db) + + // Initialize report service with expense realization repo + repportService := sRepport.NewRepportService(validate, expRealizationRepo) RepportRoutes(router, repportService) } diff --git a/internal/modules/repports/route.go b/internal/modules/repports/route.go index ccb551ed..d01fd4b2 100644 --- a/internal/modules/repports/route.go +++ b/internal/modules/repports/route.go @@ -1,6 +1,7 @@ package repports import ( + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/controllers" repport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" @@ -13,8 +14,7 @@ func RepportRoutes(v1 fiber.Router, s repport.RepportService) { route := v1.Group("/repports") route.Get("/", ctrl.GetAll) - route.Post("/", ctrl.CreateOne) route.Get("/:id", ctrl.GetOne) - route.Patch("/:id", ctrl.UpdateOne) - route.Delete("/:id", ctrl.DeleteOne) + + route.Get("expense", ctrl.GetExpense) } diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 69765579..82fd5470 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -4,10 +4,12 @@ import ( "strings" "time" - dto "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" + "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" + expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" + "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" @@ -16,32 +18,45 @@ import ( type RepportService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.RepportListDTO, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*dto.RepportListDTO, error) - CreateOne(ctx *fiber.Ctx, req *validation.Create) (*dto.RepportListDTO, error) - UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*dto.RepportListDTO, error) - DeleteOne(ctx *fiber.Ctx, id uint) error + GetExpense(ctx *fiber.Ctx, id uint) (*dto.RepportListDTO, error) } type repportService struct { - Log *logrus.Logger - Validate *validator.Validate - dummyData map[uint]dto.RepportListDTO - nextID uint + Log *logrus.Logger + Validate *validator.Validate + dummyData map[uint]dto.RepportListDTO + ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository } -func NewRepportService(validate *validator.Validate) RepportService { +func NewRepportService(validate *validator.Validate, expenseRealizationRepo expenseRepo.ExpenseRealizationRepository) RepportService { // Initialize with dummy data - now := time.Now().UTC() + now := time.Now() dummyData := map[uint]dto.RepportListDTO{ - 1: {Id: 1, Name: "Sales Report", CreatedAt: now, UpdatedAt: now}, - 2: {Id: 2, Name: "Inventory Report", CreatedAt: now, UpdatedAt: now}, - 3: {Id: 3, Name: "Production Report", CreatedAt: now, UpdatedAt: now}, + 1: { + Id: 1, + Name: "Sales Report", + CreatedAt: now, + UpdatedAt: now, + }, + 2: { + Id: 2, + Name: "Inventory Report", + CreatedAt: now, + UpdatedAt: now, + }, + 3: { + Id: 3, + Name: "Production Report", + CreatedAt: now, + UpdatedAt: now, + }, } return &repportService{ - Log: utils.Log, - Validate: validate, - dummyData: dummyData, - nextID: 4, + Log: utils.Log, + Validate: validate, + dummyData: dummyData, + ExpenseRealizationRepo: expenseRealizationRepo, } } @@ -60,10 +75,10 @@ func (s *repportService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.R results = append(results, v) } - total := int64(len(results)) - // Apply pagination + total := int64(len(results)) offset := (params.Page - 1) * params.Limit + if offset >= int(total) { return []dto.RepportListDTO{}, total, nil } @@ -83,49 +98,9 @@ func (s *repportService) GetOne(c *fiber.Ctx, id uint) (*dto.RepportListDTO, err return nil, fiber.NewError(fiber.StatusNotFound, "Report not found") } -func (s *repportService) CreateOne(c *fiber.Ctx, req *validation.Create) (*dto.RepportListDTO, error) { - if err := s.Validate.Struct(req); err != nil { - return nil, err +func (s *repportService) GetExpense(c *fiber.Ctx, id uint) (*dto.RepportListDTO, error) { + if data, ok := s.dummyData[id]; ok { + return &data, nil } - - now := time.Now().UTC() - newReport := dto.RepportListDTO{ - Id: s.nextID, - Name: req.Name, - CreatedAt: now, - UpdatedAt: now, - } - s.dummyData[s.nextID] = newReport - s.nextID++ - - return &newReport, nil -} - -func (s *repportService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*dto.RepportListDTO, error) { - if err := s.Validate.Struct(req); err != nil { - return nil, err - } - - data, ok := s.dummyData[id] - if !ok { - return nil, fiber.NewError(fiber.StatusNotFound, "Report not found") - } - - if req.Name != nil { - data.Name = *req.Name - } - - data.UpdatedAt = time.Now().UTC() - s.dummyData[id] = data - - return &data, nil -} - -func (s *repportService) DeleteOne(c *fiber.Ctx, id uint) error { - if _, ok := s.dummyData[id]; !ok { - return fiber.NewError(fiber.StatusNotFound, "Report not found") - } - - delete(s.dummyData, id) - return nil + return nil, fiber.NewError(fiber.StatusNotFound, "Report not found") } diff --git a/internal/modules/repports/validations/repport.validation.go b/internal/modules/repports/validations/repport.validation.go index 7d16d3ee..a7ec4a6d 100644 --- a/internal/modules/repports/validations/repport.validation.go +++ b/internal/modules/repports/validations/repport.validation.go @@ -1,13 +1,5 @@ package validation -type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` -} - -type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` -} - type Query struct { Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`