mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'feat/BE/Sprint-7' of https://gitlab.com/mbugroup/lti-api into feat/BE/US-304/permission-middleware-adjustment
This commit is contained in:
@@ -245,3 +245,87 @@ func (u *ClosingController) GetSapronakByKandang(c *fiber.Ctx) error {
|
||||
Data: payload,
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil || id <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid projectFlockId")
|
||||
}
|
||||
|
||||
result, err := u.ClosingService.GetClosingDataProduksi(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Retrieved production data successfully",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -58,6 +58,52 @@ type ClosingSummaryDTO struct {
|
||||
StatusClosing string `json:"closing_status"`
|
||||
}
|
||||
|
||||
type ClosingPurchaseDTO struct {
|
||||
InitialPopulation int `json:"initial_population"`
|
||||
ClaimCulling int `json:"claim_culling"`
|
||||
FinalPopulation int `json:"final_population"`
|
||||
FeedIn float64 `json:"feed_in"`
|
||||
FeedUsed float64 `json:"feed_used"`
|
||||
FeedUsedPerHead float64 `json:"feed_used_per_head"`
|
||||
}
|
||||
|
||||
type ClosingSalesDTO struct {
|
||||
SalesPopulation int `json:"sales_population"`
|
||||
SalesWeight float64 `json:"sales_weight"`
|
||||
AverageWeight float64 `json:"average_weight"`
|
||||
AverageSellingPrice float64 `json:"chicken_average_selling_price"`
|
||||
}
|
||||
|
||||
type ClosingEggSalesDTO struct {
|
||||
EggPieces int `json:"egg_pieces"`
|
||||
EggMassKg float64 `json:"egg_mass_kg"`
|
||||
AverageEggWeightKg float64 `json:"average_egg_weight_kg"`
|
||||
AverageSellingPrice float64 `json:"egg_average_selling_price"`
|
||||
}
|
||||
|
||||
type ClosingPerformanceDTO struct {
|
||||
Depletion float64 `json:"depletion"`
|
||||
Age float64 `json:"age_day"`
|
||||
MortalityStd float64 `json:"mortality_std"`
|
||||
MortalityAct float64 `json:"mortality_act"`
|
||||
DeffMortality float64 `json:"deff_mortality"`
|
||||
FcrStd float64 `json:"fcr_std"`
|
||||
FcrAct float64 `json:"fcr_act"`
|
||||
DeffFcr float64 `json:"deff_fcr"`
|
||||
Awg float64 `json:"awg"`
|
||||
}
|
||||
|
||||
type ClosingSalesGroupDTO struct {
|
||||
Chicken ClosingSalesDTO `json:"chicken"`
|
||||
Egg *ClosingEggSalesDTO `json:"egg,omitempty"`
|
||||
}
|
||||
|
||||
type ClosingProductionReportDTO struct {
|
||||
Purchase ClosingPurchaseDTO `json:"purchase"`
|
||||
Sales ClosingSalesGroupDTO `json:"sales"`
|
||||
Performance ClosingPerformanceDTO `json:"performance"`
|
||||
}
|
||||
|
||||
func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosing string) ClosingSummaryDTO {
|
||||
history := project.KandangHistory
|
||||
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -16,6 +16,13 @@ import (
|
||||
type ClosingRepository interface {
|
||||
repository.BaseRepository[entity.ProjectFlock]
|
||||
GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error)
|
||||
SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error)
|
||||
SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error)
|
||||
SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error)
|
||||
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)
|
||||
@@ -53,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
|
||||
@@ -111,6 +123,202 @@ func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params Sapronak
|
||||
return rows, totalResults, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error) {
|
||||
if len(projectFlockKandangIDs) == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
var purchaseAgg struct {
|
||||
TotalIn float64 `gorm:"column:total_in"`
|
||||
}
|
||||
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("purchase_items pi").
|
||||
Joins("JOIN flags f ON f.flagable_id = pi.product_id AND f.flagable_type = 'products'").
|
||||
Where("f.name = ?", "PAKAN").
|
||||
Where("pi.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||
Select("COALESCE(SUM(pi.total_qty), 0) AS total_in").
|
||||
Scan(&purchaseAgg).Error
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
var usageAgg struct {
|
||||
TotalUsed float64 `gorm:"column:total_used"`
|
||||
}
|
||||
|
||||
err = r.DB().WithContext(ctx).
|
||||
Table("recording_stocks rs").
|
||||
Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id").
|
||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||
Where("f.name = ?", "PAKAN").
|
||||
Select("COALESCE(SUM(COALESCE(rs.usage_qty, 0) + COALESCE(rs.pending_qty, 0)), 0) AS total_used").
|
||||
Scan(&usageAgg).Error
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return purchaseAgg.TotalIn, usageAgg.TotalUsed, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
||||
if len(projectFlockKandangIDs) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var agg struct {
|
||||
Total float64 `gorm:"column:total_culling"`
|
||||
}
|
||||
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("recording_depletions rd").
|
||||
Joins("JOIN product_warehouses pw ON pw.id = rd.product_warehouse_id").
|
||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||
Where("f.name = ?", utils.FlagAyamCulling).
|
||||
Select("COALESCE(SUM(rd.qty), 0) AS total_culling").
|
||||
Scan(&agg).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return agg.Total, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error) {
|
||||
if len(projectFlockKandangIDs) == 0 {
|
||||
return 0, 0, 0, nil
|
||||
}
|
||||
|
||||
var agg struct {
|
||||
TotalWeight float64 `gorm:"column:total_weight"`
|
||||
TotalQty float64 `gorm:"column:total_qty"`
|
||||
TotalPrice float64 `gorm:"column:total_price"`
|
||||
}
|
||||
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("marketing_products mp").
|
||||
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||
Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price").
|
||||
Scan(&agg).Error
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) {
|
||||
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
|
||||
return 0, 0, 0, nil
|
||||
}
|
||||
|
||||
var agg struct {
|
||||
TotalWeight float64 `gorm:"column:total_weight"`
|
||||
TotalQty float64 `gorm:"column:total_qty"`
|
||||
TotalPrice float64 `gorm:"column:total_price"`
|
||||
}
|
||||
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("marketing_products mp").
|
||||
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||
Where("f.name IN ?", flagNames).
|
||||
Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price").
|
||||
Scan(&agg).Error
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) {
|
||||
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var agg struct {
|
||||
TotalQty float64 `gorm:"column:total_qty"`
|
||||
}
|
||||
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("recording_eggs re").
|
||||
Joins("JOIN product_warehouses pw ON pw.id = re.product_warehouse_id").
|
||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||
Where("f.name IN ?", flagNames).
|
||||
Select("COALESCE(SUM(re.qty), 0) AS total_qty").
|
||||
Scan(&agg).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return agg.TotalQty, nil
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) {
|
||||
if fcrID == 0 {
|
||||
return []entity.FcrStandard{}, nil
|
||||
}
|
||||
|
||||
var standards []entity.FcrStandard
|
||||
if err := r.DB().WithContext(ctx).
|
||||
Where("fcr_id = ?", fcrID).
|
||||
Order("weight ASC").
|
||||
Find(&standards).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
@@ -260,7 +468,6 @@ type SapronakDetailRow struct {
|
||||
Price float64
|
||||
}
|
||||
|
||||
|
||||
func (r *ClosingRepositoryImpl) withCtx(ctx context.Context) *gorm.DB { return r.DB().WithContext(ctx) }
|
||||
|
||||
func applyJoins(db *gorm.DB, joins ...string) *gorm.DB {
|
||||
@@ -323,7 +530,7 @@ func (r *ClosingRepositoryImpl) usageQuery(
|
||||
`)
|
||||
db = applyJoins(db, joins...)
|
||||
return db.
|
||||
Joins("JOIN product_warehouses pw ON " + pwJoinCond).
|
||||
Joins("JOIN product_warehouses pw ON "+pwJoinCond).
|
||||
Joins("JOIN products p ON p.id = pw.product_id").
|
||||
Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||
Where(where, args...)
|
||||
@@ -356,7 +563,7 @@ func (r *ClosingRepositoryImpl) detailQuery(
|
||||
) *gorm.DB {
|
||||
db := r.withCtx(ctx).
|
||||
Table(table).
|
||||
Joins("JOIN product_warehouses pw ON " + pwJoinCond).
|
||||
Joins("JOIN product_warehouses pw ON "+pwJoinCond).
|
||||
Joins("JOIN products p ON p.id = pw.product_id").
|
||||
Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct)
|
||||
|
||||
@@ -450,7 +657,6 @@ func (r *ClosingRepositoryImpl) FetchSapronakChickinUsageDetails(ctx context.Con
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func (r *ClosingRepositoryImpl) incomingPurchaseBase(ctx context.Context, kandangID uint) *gorm.DB {
|
||||
return r.withCtx(ctx).
|
||||
Table("purchase_items AS pi").
|
||||
@@ -527,7 +733,7 @@ func (r *ClosingRepositoryImpl) fetchStockLogs(ctx context.Context, kandangID ui
|
||||
COALESCE(sl.increase,0) AS increase,
|
||||
COALESCE(sl.decrease,0) AS decrease,
|
||||
COALESCE(p.product_price,0) AS price,
|
||||
` + movementSelect + `
|
||||
`+movementSelect+`
|
||||
`).
|
||||
Joins("JOIN product_warehouses pw ON pw.id = sl.product_warehouse_id").
|
||||
Joins("JOIN products p ON p.id = pw.product_id").
|
||||
@@ -597,4 +803,4 @@ func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kand
|
||||
return fmt.Sprintf("TRF-%d", row.ID)
|
||||
})
|
||||
return in, out, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,7 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService
|
||||
route.Get("/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak",m.RequirePermissions(m.P_ClosingCountSapronakKandang) ,ctrl.GetSapronakByKandang)
|
||||
route.Get("/:project_flock_id/perhitungan_sapronak",m.RequirePermissions(m.P_ClosingCountSapronak) ,ctrl.GetSapronakByProject)
|
||||
route.Get("/:projectFlockId/sapronak",m.RequirePermissions(m.P_ClosingSapronak), 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)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
@@ -30,7 +32,9 @@ type ClosingService interface {
|
||||
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)
|
||||
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 {
|
||||
@@ -379,3 +383,270 @@ 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")
|
||||
}
|
||||
|
||||
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 data produksi: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||
}
|
||||
|
||||
var population float64
|
||||
for _, history := range project.KandangHistory {
|
||||
for _, chickin := range history.Chickins {
|
||||
population += chickin.UsageQty + chickin.PendingUsageQty
|
||||
}
|
||||
}
|
||||
|
||||
isGrowing := strings.EqualFold(project.Category, string(utils.ProjectFlockCategoryGrowing))
|
||||
|
||||
projectFlockKandangIDs, err := s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch project flock kandangs for %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandangs")
|
||||
}
|
||||
|
||||
feedIn, feedUsed, err := s.Repository.SumFeedPurchaseAndUsedByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to sum feed purchase/used qty for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch feed purchase data")
|
||||
}
|
||||
|
||||
claimCulling, err := s.Repository.SumClaimCullingByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to sum claim culling for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch claim culling data")
|
||||
}
|
||||
|
||||
finalPopulation := population - claimCulling
|
||||
|
||||
var standards []entity.FcrStandard
|
||||
if project.FcrId > 0 {
|
||||
standards, err = s.Repository.GetFcrStandardsByFcrID(c.Context(), project.FcrId)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch FCR standards for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch FCR standard data")
|
||||
}
|
||||
}
|
||||
// masih dummy, karena tab penjualan agenya masih dummy
|
||||
age := 1.0
|
||||
|
||||
feedUsedPerHead := 0.0
|
||||
if population > 0 {
|
||||
feedUsedPerHead = feedUsed / population
|
||||
}
|
||||
|
||||
purchase := dto.ClosingPurchaseDTO{
|
||||
InitialPopulation: int(population),
|
||||
ClaimCulling: int(claimCulling),
|
||||
FinalPopulation: int(finalPopulation),
|
||||
FeedIn: feedIn,
|
||||
FeedUsed: feedUsed,
|
||||
FeedUsedPerHead: feedUsedPerHead,
|
||||
}
|
||||
|
||||
chickenFlagNames := []string{string(utils.FlagPullet)}
|
||||
chickenSalesWeight, chickenSalesQty, chickenSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, chickenFlagNames)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch chicken sales data for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chicken sales data")
|
||||
}
|
||||
|
||||
var chickenAverageWeight float64
|
||||
if chickenSalesQty > 0 {
|
||||
chickenAverageWeight = chickenSalesWeight / chickenSalesQty
|
||||
}
|
||||
|
||||
var chickenAverageSellingPrice float64
|
||||
if chickenSalesWeight > 0 {
|
||||
chickenAverageSellingPrice = chickenSalesPrice / chickenSalesWeight
|
||||
}
|
||||
|
||||
chickenSales := dto.ClosingSalesDTO{
|
||||
SalesPopulation: int(chickenSalesQty),
|
||||
SalesWeight: chickenSalesWeight,
|
||||
AverageWeight: chickenAverageWeight,
|
||||
AverageSellingPrice: chickenAverageSellingPrice,
|
||||
}
|
||||
|
||||
chickenDepletion := population - chickenSalesQty
|
||||
if chickenDepletion < 0 {
|
||||
chickenDepletion = 0
|
||||
}
|
||||
|
||||
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age, standards)
|
||||
|
||||
var eggSales *dto.ClosingEggSalesDTO
|
||||
var eggPerformance *dto.ClosingPerformanceDTO
|
||||
if !isGrowing {
|
||||
eggFlagNames := []string{
|
||||
string(utils.FlagTelur),
|
||||
string(utils.FlagTelurUtuh),
|
||||
string(utils.FlagTelurPecah),
|
||||
string(utils.FlagTelurPutih),
|
||||
string(utils.FlagTelurRetak),
|
||||
}
|
||||
|
||||
eggSalesWeight, eggSalesQty, eggSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, eggFlagNames)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch egg sales data for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch egg sales data")
|
||||
}
|
||||
|
||||
var averageEggWeight float64
|
||||
if eggSalesQty > 0 {
|
||||
averageEggWeight = eggSalesWeight / eggSalesQty
|
||||
}
|
||||
|
||||
var averageEggSellingPrice float64
|
||||
if eggSalesWeight > 0 {
|
||||
averageEggSellingPrice = eggSalesPrice / eggSalesWeight
|
||||
}
|
||||
|
||||
eggSales = &dto.ClosingEggSalesDTO{
|
||||
EggPieces: int(eggSalesQty),
|
||||
EggMassKg: eggSalesWeight,
|
||||
AverageEggWeightKg: averageEggWeight,
|
||||
AverageSellingPrice: averageEggSellingPrice,
|
||||
}
|
||||
|
||||
harvestEggQty, err := s.Repository.SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, eggFlagNames)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch recording egg qty for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch egg harvest data")
|
||||
}
|
||||
|
||||
eggDepletion := harvestEggQty - eggSalesQty
|
||||
if eggDepletion < 0 {
|
||||
eggDepletion = 0
|
||||
}
|
||||
|
||||
eggPerf := calculatePerformanceMetrics(averageEggWeight, eggSalesWeight, feedUsed, harvestEggQty, eggDepletion, age, standards)
|
||||
eggPerformance = &eggPerf
|
||||
}
|
||||
|
||||
sales := dto.ClosingSalesGroupDTO{
|
||||
Chicken: chickenSales,
|
||||
Egg: eggSales,
|
||||
}
|
||||
|
||||
performance := dto.ClosingPerformanceDTO{
|
||||
Depletion: chickenPerformance.Depletion,
|
||||
Age: age,
|
||||
MortalityStd: chickenPerformance.MortalityStd,
|
||||
MortalityAct: chickenPerformance.MortalityAct,
|
||||
DeffMortality: chickenPerformance.DeffMortality,
|
||||
}
|
||||
if eggPerformance != nil {
|
||||
performance.FcrStd = eggPerformance.FcrStd
|
||||
performance.FcrAct = eggPerformance.FcrAct
|
||||
performance.DeffFcr = eggPerformance.DeffFcr
|
||||
performance.Awg = eggPerformance.Awg
|
||||
} else {
|
||||
performance.FcrStd = chickenPerformance.FcrStd
|
||||
performance.FcrAct = chickenPerformance.FcrAct
|
||||
performance.DeffFcr = chickenPerformance.DeffFcr
|
||||
performance.Awg = chickenPerformance.Awg
|
||||
}
|
||||
|
||||
result := dto.ClosingProductionReportDTO{
|
||||
Purchase: purchase,
|
||||
Sales: sales,
|
||||
Performance: performance,
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopulation, depletion, age float64, standards []entity.FcrStandard) dto.ClosingPerformanceDTO {
|
||||
mortalityStd, fcrStd := closestFcrValues(standards, averageWeight)
|
||||
|
||||
fcrAct := 0.0
|
||||
if totalWeight > 0 {
|
||||
fcrAct = feedUsed / totalWeight
|
||||
}
|
||||
|
||||
mortalityAct := 0.0
|
||||
if basePopulation > 0 {
|
||||
mortalityAct = (depletion / basePopulation) * 100
|
||||
}
|
||||
|
||||
deffMortality := mortalityStd - mortalityAct
|
||||
deffFcr := fcrStd - fcrAct
|
||||
|
||||
awg := 0.0
|
||||
if age > 0 {
|
||||
awg = averageWeight / age
|
||||
}
|
||||
|
||||
return dto.ClosingPerformanceDTO{
|
||||
Depletion: depletion,
|
||||
Age: age,
|
||||
MortalityStd: mortalityStd,
|
||||
MortalityAct: mortalityAct,
|
||||
DeffMortality: deffMortality,
|
||||
FcrStd: fcrStd,
|
||||
FcrAct: fcrAct,
|
||||
DeffFcr: deffFcr,
|
||||
Awg: awg,
|
||||
}
|
||||
}
|
||||
|
||||
func closestFcrValues(standards []entity.FcrStandard, averageWeight float64) (float64, float64) {
|
||||
if len(standards) == 0 || averageWeight <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
closest := standards[0]
|
||||
minDiff := math.Abs(closest.Weight - averageWeight)
|
||||
for _, std := range standards[1:] {
|
||||
diff := math.Abs(std.Weight - averageWeight)
|
||||
if diff < minDiff {
|
||||
minDiff = diff
|
||||
closest = std
|
||||
}
|
||||
}
|
||||
|
||||
return closest.Mortality, closest.FcrNumber
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user