diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go index d02de808..aa191500 100644 --- a/internal/modules/closings/controllers/closing.controller.go +++ b/internal/modules/closings/controllers/closing.controller.go @@ -246,6 +246,68 @@ func (u *ClosingController) GetSapronakByKandang(c *fiber.Ctx) error { }) } +func (u *ClosingController) GetExpeditionHPP(c *fiber.Ctx) error { + param := c.Params("project_flock_id") + + projectFlockID, err := strconv.Atoi(param) + if err != nil || projectFlockID <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id") + } + + var projectFlockKandangID *uint + if raw := c.Query("project_flock_kandang_id"); raw != "" { + idInt, convErr := strconv.Atoi(raw) + if convErr != nil || idInt <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_kandang_id") + } + idUint := uint(idInt) + projectFlockKandangID = &idUint + } + + result, err := u.ClosingService.GetExpeditionHPP(c, uint(projectFlockID), projectFlockKandangID) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get expedition HPP successfully", + Data: result, + }) +} + +func (u *ClosingController) GetExpeditionHPPByKandang(c *fiber.Ctx) error { + projectParam := c.Params("project_flock_id") + kandangParam := c.Params("project_flock_kandang_id") + + projectFlockID, err := strconv.Atoi(projectParam) + if err != nil || projectFlockID <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id") + } + + pfkID, err := strconv.Atoi(kandangParam) + if err != nil || pfkID <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_kandang_id") + } + + kandangID := uint(pfkID) + + result, err := u.ClosingService.GetExpeditionHPP(c, uint(projectFlockID), &kandangID) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get expedition HPP successfully", + Data: result, + }) +} + func (u *ClosingController) GetClosingDataProduksi(c *fiber.Ctx) error { param := c.Params("projectFlockId") diff --git a/internal/modules/closings/dto/closingExpedition.dto.go b/internal/modules/closings/dto/closingExpedition.dto.go new file mode 100644 index 00000000..5f8a09d4 --- /dev/null +++ b/internal/modules/closings/dto/closingExpedition.dto.go @@ -0,0 +1,14 @@ +package dto + +// ExpeditionCostItemDTO merepresentasikan biaya ekspedisi per vendor. +type ExpeditionCostItemDTO struct { + Id uint64 `json:"id"` + ExpeditionVendorName string `json:"expedition_vendor_name"` + HPPAmount float64 `json:"hpp_amount"` +} + +// ExpeditionHPPDTO adalah struktur response utama untuk HPP Ekspedisi. +type ExpeditionHPPDTO struct { + ExpeditionCosts []ExpeditionCostItemDTO `json:"expedition_costs"` + TotalHPPAmount float64 `json:"total_hpp_amount"` +} diff --git a/internal/modules/closings/dto/sapronak.dto.go b/internal/modules/closings/dto/closingSapronak.dto.go similarity index 100% rename from internal/modules/closings/dto/sapronak.dto.go rename to internal/modules/closings/dto/closingSapronak.dto.go diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go index c1f90b5a..6a59c5f9 100644 --- a/internal/modules/closings/repositories/closing.repository.go +++ b/internal/modules/closings/repositories/closing.repository.go @@ -22,6 +22,7 @@ type ClosingRepository interface { SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) + GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) FetchSapronakIncoming(ctx context.Context, kandangID uint) ([]SapronakIncomingRow, error) FetchSapronakIncomingDetails(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error) FetchSapronakUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error) @@ -59,6 +60,11 @@ type SapronakRow struct { Notes string `gorm:"column:notes"` } +type ExpeditionHPPRow struct { + SupplierName string `gorm:"column:supplier_name"` + TotalAmount float64 `gorm:"column:total_amount"` +} + type SapronakQueryParams struct { Type string WarehouseIDs []uint @@ -274,6 +280,45 @@ func (r *ClosingRepositoryImpl) GetFcrStandardsByFcrID(ctx context.Context, fcrI return standards, nil } +func (r *ClosingRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) { + db := r.DB().WithContext(ctx) + + if projectFlockID == 0 { + return nil, fmt.Errorf("invalid project flock id") + } + + query := db. + Table("expense_realizations AS er"). + Joins("JOIN expense_nonstocks ens ON ens.id = er.expense_nonstock_id"). + Joins("JOIN expenses e ON e.id = ens.expense_id"). + Joins("JOIN project_flock_kandangs pfk ON pfk.id = ens.project_flock_kandang_id"). + Joins("JOIN nonstocks n ON n.id = ens.nonstock_id"). + Joins("JOIN flags f ON f.flagable_id = n.id AND f.flagable_type = ?", entity.FlagableTypeNonstock). + Joins("JOIN suppliers s ON s.id = e.supplier_id"). + Where("pfk.project_flock_id = ?", projectFlockID). + Where("e.category = ?", "BOP"). + Where("UPPER(f.name) = ?", strings.ToUpper(string(utils.FlagEkspedisi))) + + if projectFlockKandangID != nil && *projectFlockKandangID != 0 { + query = query.Where("pfk.id = ?", *projectFlockKandangID) + } + + var rows []ExpeditionHPPRow + err := query. + Select( + "e.supplier_id AS supplier_id, " + + "s.name AS supplier_name, " + + "SUM(er.qty * er.price) AS total_amount", + ). + Group("e.supplier_id, s.name"). + Scan(&rows).Error + if err != nil { + return nil, err + } + + return rows, nil +} + const ( sapronakIncomingPurchasesSQL = ` SELECT diff --git a/internal/modules/closings/route.go b/internal/modules/closings/route.go index 4aa86803..08b6e725 100644 --- a/internal/modules/closings/route.go +++ b/internal/modules/closings/route.go @@ -27,5 +27,7 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService route.Get("/:project_flock_id/perhitungan_sapronak", ctrl.GetSapronakByProject) route.Get("/:projectFlockId", ctrl.GetClosingSummary) route.Get("/:projectFlockId/sapronak", ctrl.GetClosingSapronak) + route.Get("/:project_flock_id/expedition-hpp", ctrl.GetExpeditionHPP) + route.Get("/:project_flock_id/:project_flock_kandang_id/expedition-hpp", ctrl.GetExpeditionHPPByKandang) route.Get("/:projectFlockId/data-produksi", ctrl.GetClosingDataProduksi) } diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index f8e24137..895f05f7 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -34,6 +34,7 @@ type ClosingService interface { GetOverhead(ctx *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error) GetClosingDataProduksi(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingProductionReportDTO, error) GetClosingSapronak(ctx *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error) + GetExpeditionHPP(ctx *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error) } type closingService struct { @@ -383,6 +384,40 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.Ove return &result, nil } +// GetExpeditionHPP menghitung HPP ekspedisi per vendor untuk sebuah project flock. +// Jika projectFlockKandangID tidak nil, maka hanya data untuk kandang tersebut yang dihitung. +func (s closingService) GetExpeditionHPP(c *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error) { + if projectFlockID == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + } + + rows, err := s.Repository.GetExpeditionHPP(c.Context(), projectFlockID, projectFlockKandangID) + if err != nil { + s.Log.Errorf("Failed to get expedition HPP for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch expedition HPP") + } + + expeditionCosts := make([]dto.ExpeditionCostItemDTO, 0, len(rows)) + var totalHPP float64 + + for idx, row := range rows { + expeditionCosts = append(expeditionCosts, dto.ExpeditionCostItemDTO{ + Id: uint64(idx + 1), + ExpeditionVendorName: row.SupplierName, + HPPAmount: row.TotalAmount, + }) + + totalHPP += row.TotalAmount + } + + result := &dto.ExpeditionHPPDTO{ + ExpeditionCosts: expeditionCosts, + TotalHPPAmount: totalHPP, + } + + return result, nil +} + func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint) (*dto.ClosingProductionReportDTO, error) { if projectFlockID == 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")