diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go index d15c8ffb..30f69bf8 100644 --- a/internal/modules/closings/controllers/closing.controller.go +++ b/internal/modules/closings/controllers/closing.controller.go @@ -53,15 +53,15 @@ func (u *ClosingController) GetAll(c *fiber.Ctx) error { }) } -func (u *ClosingController) GetOne(c *fiber.Ctx) error { - param := c.Params("id") +func (u *ClosingController) GetClosingSummary(c *fiber.Ctx) error { + param := c.Params("projectFlockId") id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + if err != nil || id <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid projectFlockId") } - result, err := u.ClosingService.GetOne(c, uint(id)) + result, err := u.ClosingService.GetClosingSummary(c, uint(id)) if err != nil { return err } @@ -70,8 +70,8 @@ func (u *ClosingController) GetOne(c *fiber.Ctx) error { JSON(response.Success{ Code: fiber.StatusOK, Status: "success", - Message: "Get closing successfully", - Data: dto.ToClosingListDTO(*result), + Message: "Retrieved project information successfully", + Data: result, }) } diff --git a/internal/modules/closings/dto/closing.dto.go b/internal/modules/closings/dto/closing.dto.go index ccb014e6..6a280312 100644 --- a/internal/modules/closings/dto/closing.dto.go +++ b/internal/modules/closings/dto/closing.dto.go @@ -1,6 +1,7 @@ package dto import ( + "fmt" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -26,6 +27,67 @@ type ClosingDetailDTO struct { ClosingListDTO } +type ClosingSummaryDTO struct { + LocationID uint `json:"location_id"` + Periode int `json:"periode"` + JenisProduk string `json:"jenis_produk"` + LabelPopulasi string `json:"label_populasi"` + JumlahPopulasi int `json:"jumlah_populasi"` + JumlahPopulasiFormatted string `json:"jumlah_populasi_formatted"` + JenisProject string `json:"jenis_project"` + KandangAktif int `json:"kandang_aktif"` + KandangAktifFormatted string `json:"kandang_aktif_formatted"` + StatusPembayaranPenjualan string `json:"status_pembayaran_penjualan"` + StatusPembayaranMitra string `json:"status_pembayaran_mitra"` + StatusProject string `json:"status_project"` + StatusClosing string `json:"status_closing"` +} + +func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosing string) ClosingSummaryDTO { + history := project.KandangHistory + + period := maxPeriod(history) + kandangCount := len(history) + population := sumPopulation(history) + populationInt := int(population) + + return ClosingSummaryDTO{ + LocationID: project.LocationId, + Periode: period, + JenisProduk: project.Category, + LabelPopulasi: "", + JumlahPopulasi: populationInt, + JumlahPopulasiFormatted: fmt.Sprintf("%d Ekor", populationInt), + JenisProject: "", + KandangAktif: kandangCount, + KandangAktifFormatted: fmt.Sprintf("%d Kandang", kandangCount), + StatusPembayaranPenjualan: "Tempo", + StatusPembayaranMitra: "", + StatusProject: statusProject, + StatusClosing: statusClosing, + } +} + +func maxPeriod(history []entity.ProjectFlockKandang) int { + max := 0 + for _, h := range history { + if h.Period > max { + max = h.Period + } + } + return max +} + +func sumPopulation(history []entity.ProjectFlockKandang) float64 { + var total float64 + for _, h := range history { + for _, chickin := range h.Chickins { + total += chickin.UsageQty + chickin.PendingUsageQty + } + } + return total +} + // === Mapper Functions === func ToClosingRelationDTO(e entity.ProjectFlock) ClosingRelationDTO { diff --git a/internal/modules/closings/module.go b/internal/modules/closings/module.go index 51a3bd9b..f2cf76b3 100644 --- a/internal/modules/closings/module.go +++ b/internal/modules/closings/module.go @@ -5,10 +5,11 @@ import ( "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" rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services" rMarketings "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" ) @@ -21,7 +22,10 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * marketingRepo := rMarketings.NewMarketingRepository(db) marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db) - closingService := sClosing.NewClosingService(closingRepo, marketingRepo, marketingDeliveryProductRepo, validate) + approvalRepo := commonRepo.NewApprovalRepository(db) + approvalService := commonSvc.NewApprovalService(approvalRepo) + + closingService := sClosing.NewClosingService(closingRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, 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 acdc92d7..10ed6038 100644 --- a/internal/modules/closings/route.go +++ b/internal/modules/closings/route.go @@ -12,7 +12,7 @@ import ( func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService) { ctrl := controller.NewClosingController(s) - route := v1.Group("/closings") + route := v1.Group("/closing") // route.Get("/", m.Auth(u), ctrl.GetAll) // route.Post("/", m.Auth(u), ctrl.CreateOne) @@ -22,6 +22,6 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService route.Get("/", ctrl.GetAll) route.Get("/:id", ctrl.GetOne) - + route.Get("/:projectFlockId", ctrl.GetClosingSummary) route.Get("/:project_flock_id/penjualan", ctrl.GetPenjualan) } diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index b0c29a7c..578672cc 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -1,14 +1,18 @@ package service import ( + "context" "errors" + commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "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" 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" "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" @@ -20,6 +24,7 @@ type ClosingService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) GetOne(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) } type closingService struct { @@ -28,15 +33,17 @@ type closingService struct { Repository repository.ClosingRepository MarketingRepo marketingRepository.MarketingRepository MarketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository + ApprovalSvc commonSvc.ApprovalService } -func NewClosingService(repo repository.ClosingRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, validate *validator.Validate) ClosingService { +func NewClosingService(repo repository.ClosingRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate) ClosingService { return &closingService{ Log: utils.Log, Validate: validate, Repository: repo, MarketingRepo: marketingRepo, MarketingDeliveryProductRepo: marketingDeliveryProductRepo, + ApprovalSvc: approvalSvc, } } @@ -44,6 +51,12 @@ func (s closingService) withRelations(db *gorm.DB) *gorm.DB { return db.Preload("CreatedUser") } +func (s closingService) withClosingRelations(db *gorm.DB) *gorm.DB { + return s.withRelations(db). + Preload("KandangHistory"). + Preload("KandangHistory.Chickins") +} + func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err @@ -103,3 +116,73 @@ func (s closingService) GetPenjualan(c *fiber.Ctx, projectFlockID uint) ([]entit } return realisasi, nil } + +func (s closingService) GetClosingSummary(c *fiber.Ctx, projectFlockID uint) (*dto.ClosingSummaryDTO, error) { + if projectFlockID == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + } + + project, err := s.Repository.GetByID(c.Context(), projectFlockID, s.withClosingRelations) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found") + } + if err != nil { + s.Log.Errorf("Failed get project flock %d for closing summary: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock") + } + + statusProject, statusClosing, err := s.getApprovalStatuses(c.Context(), projectFlockID) + if err != nil { + s.Log.Errorf("Failed to retrieve approval statuses for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch approval status") + } + + summary := dto.ToClosingSummaryDTO(*project, statusProject, statusClosing) + + return &summary, nil +} + +func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID uint) (string, string, error) { + if s.ApprovalSvc == nil { + return "", "Belum Selesai", nil + } + + records, _, err := s.ApprovalSvc.List(ctx, utils.ApprovalWorkflowProjectFlock.String(), &projectFlockID, 1, 1000, "") + if err != nil { + return "", "", err + } + + var ( + minStep uint16 + statusProject string + completed int + ) + + for _, rec := range records { + if minStep == 0 || rec.StepNumber < minStep { + minStep = rec.StepNumber + statusProject = rec.StepName + } + if rec.StepNumber == uint16(utils.ProjectFlockStepSelesai) { + completed++ + } + } + + if statusProject == "" && minStep > 0 { + if label, ok := approvalutils.ApprovalStepName(utils.ApprovalWorkflowProjectFlock, approvalutils.ApprovalStep(minStep)); ok { + statusProject = label + } + } + + statusClosing := "Belum Selesai" + switch { + case len(records) == 0 || completed == 0: + statusClosing = "Belum Selesai" + case completed < len(records): + statusClosing = "Sebagian" + default: + statusClosing = "Selesai" + } + + return statusProject, statusClosing, nil +} diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index 94652000..641ce531 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -151,7 +151,7 @@ func (r *ProductWarehouseRepositoryImpl) AdjustQuantities(ctx context.Context, d } if err := base.Model(&entity.ProductWarehouse{}). Where("id = ?", id). - Update("quantity", gorm.Expr("COALESCE(quantity,0) + ?", delta)).Error; err != nil { + Update("qty", gorm.Expr("COALESCE(qty,0) + ?", delta)).Error; err != nil { return err } } @@ -171,7 +171,7 @@ func (r *ProductWarehouseRepositoryImpl) CleanupEmpty(ctx context.Context, affec var emptyIDs []uint if err := r.DB().WithContext(ctx). Model(&entity.ProductWarehouse{}). - Where("id IN ? AND COALESCE(quantity,0) <= 0", ids). + Where("id IN ? AND COALESCE(qty,0) <= 0", ids). Pluck("id", &emptyIDs).Error; err != nil { return err } @@ -257,7 +257,7 @@ func (r *ProductWarehouseRepositoryImpl) GetByFlagAndWarehouseID(ctx context.Con Joins("JOIN products ON products.id = product_warehouses.product_id"). Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'"). Where("flags.name = ? AND product_warehouses.warehouse_id = ?", flagName, warehouseId). - Order("product_warehouses.created_at DESC"). + Order("product_warehouses.id DESC"). Preload("Product").Preload("Warehouse"). Find(&productWarehouses).Error if err != nil {