From 1e9a6372029cf61ed99651442b1cf889240a137f Mon Sep 17 00:00:00 2001 From: giovanni-ce Date: Thu, 4 Dec 2025 20:00:46 +0700 Subject: [PATCH 1/2] adjust for changes erd product_warehouse --- .../repositories/product_warehouse.repository.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 { From 4c63bd14c3958da1fdc8ebe6641b000a2dd60677 Mon Sep 17 00:00:00 2001 From: giovanni-ce Date: Fri, 5 Dec 2025 17:15:05 +0700 Subject: [PATCH 2/2] feat[BE-298]: add api get one closing general information --- .../controllers/closing.controller.go | 14 +-- internal/modules/closings/dto/closing.dto.go | 62 ++++++++++++ internal/modules/closings/module.go | 9 +- internal/modules/closings/route.go | 4 +- .../closings/services/closing.service.go | 98 ++++++++++++++++--- 5 files changed, 161 insertions(+), 26 deletions(-) diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go index 4918c28f..705a7b20 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,7 +70,7 @@ 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 d831195c..248c7945 100644 --- a/internal/modules/closings/module.go +++ b/internal/modules/closings/module.go @@ -5,9 +5,10 @@ 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" - rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" ) @@ -18,9 +19,11 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * closingRepo := rClosing.NewClosingRepository(db) userRepo := rUser.NewUserRepository(db) - closingService := sClosing.NewClosingService(closingRepo, validate) + approvalRepo := commonRepo.NewApprovalRepository(db) + approvalService := commonSvc.NewApprovalService(approvalRepo) + + closingService := sClosing.NewClosingService(closingRepo, 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 6570a17d..acc6f8b2 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) @@ -21,5 +21,5 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) route.Get("/", ctrl.GetAll) - route.Get("/:id", ctrl.GetOne) + route.Get("/:projectFlockId", ctrl.GetClosingSummary) } diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index fd1b42eb..d024789d 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -1,12 +1,16 @@ 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" "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" @@ -16,20 +20,22 @@ import ( type ClosingService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) - GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error) + GetClosingSummary(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingSummaryDTO, error) } type closingService struct { - Log *logrus.Logger - Validate *validator.Validate - Repository repository.ClosingRepository + Log *logrus.Logger + Validate *validator.Validate + Repository repository.ClosingRepository + ApprovalSvc commonSvc.ApprovalService } -func NewClosingService(repo repository.ClosingRepository, validate *validator.Validate) ClosingService { +func NewClosingService(repo repository.ClosingRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate) ClosingService { return &closingService{ - Log: utils.Log, - Validate: validate, - Repository: repo, + Log: utils.Log, + Validate: validate, + Repository: repo, + ApprovalSvc: approvalSvc, } } @@ -37,6 +43,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 @@ -59,14 +71,72 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity return closings, total, nil } -func (s closingService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) { - closing, err := s.Repository.GetByID(c.Context(), id, s.withRelations) +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, "Closing not found") + return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found") } if err != nil { - s.Log.Errorf("Failed get closing by id: %+v", err) - return nil, err + 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") } - return closing, nil + + 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 }