mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
feat[BE-378]:Create API Get All HPP Harian Kandang
This commit is contained in:
@@ -164,3 +164,26 @@ func (c *RepportController) GetPurchaseSupplier(ctx *fiber.Ctx) error {
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RepportController) GetHppPerKandang(ctx *fiber.Ctx) error {
|
||||
data, meta, err := c.RepportService.GetHppPerKandang(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
Code int `json:"code"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Meta dto.HppPerKandangMetaDTO `json:"meta"`
|
||||
Data dto.HppPerKandangResponseData `json:"data"`
|
||||
}{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get HPP harian kandang layer successfully",
|
||||
Meta: *meta,
|
||||
Data: *data,
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(resp)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package dto
|
||||
|
||||
type HppPerKandangFiltersDTO struct {
|
||||
AreaID string `json:"area_id"`
|
||||
LocationID string `json:"location_id"`
|
||||
KandangID string `json:"kandang_id"`
|
||||
WeightMin string `json:"weight_min"`
|
||||
WeightMax string `json:"weight_max"`
|
||||
Period string `json:"period"`
|
||||
ShowUnrecorded string `json:"show_unrecorded"`
|
||||
}
|
||||
|
||||
type HppPerKandangMetaDTO struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int64 `json:"total_pages"`
|
||||
TotalResults int64 `json:"total_results"`
|
||||
Filters HppPerKandangFiltersDTO `json:"filters"`
|
||||
}
|
||||
|
||||
type HppPerKandangResponseData struct {
|
||||
Period string `json:"period"`
|
||||
Rows []HppPerKandangRowDTO `json:"rows"`
|
||||
Summary HppPerKandangSummaryDTO `json:"summary"`
|
||||
}
|
||||
|
||||
type HppPerKandangRowDTO struct {
|
||||
ID int `json:"id"`
|
||||
Kandang HppPerKandangRowKandangDTO `json:"kandang"`
|
||||
WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"`
|
||||
RemainingChickenBirds int64 `json:"remaining_chicken_birds"`
|
||||
RemainingChickenWeightKg float64 `json:"remaining_chicken_weight_kg"`
|
||||
AvgWeightKg float64 `json:"avg_weight_kg"`
|
||||
EggProductionPieces int64 `json:"egg_production_pieces"`
|
||||
EggProductionKg float64 `json:"egg_production_kg"`
|
||||
// FeedCostRp float64 `json:"feed_cost_rp"`
|
||||
// OvkCostRp float64 `json:"ovk_cost_rp"`
|
||||
EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"`
|
||||
EggValueRp int64 `json:"egg_value_rp"`
|
||||
FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"`
|
||||
DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"`
|
||||
AverageDocPriceRp int64 `json:"average_doc_price_rp"`
|
||||
HppRp float64 `json:"hpp_rp"`
|
||||
RemainingValueRp int64 `json:"remaining_value_rp"`
|
||||
}
|
||||
|
||||
type HppPerKandangRowKandangDTO struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Location HppPerKandangLocationDTO `json:"location"`
|
||||
Pic HppPerKandangPICDTO `json:"pic"`
|
||||
}
|
||||
|
||||
type HppPerKandangLocationDTO struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type HppPerKandangPICDTO struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type HppPerKandangWeightRangeDTO struct {
|
||||
WeightMin float64 `json:"weight_min"`
|
||||
WeightMax float64 `json:"weight_max"`
|
||||
}
|
||||
|
||||
type HppPerKandangSupplierDTO struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
type HppPerKandangSummaryDTO struct {
|
||||
PerWeightRange []HppPerKandangSummaryWeightRangeDTO `json:"per_weight_range"`
|
||||
Total HppPerKandangSummaryTotalDTO `json:"total"`
|
||||
}
|
||||
|
||||
type HppPerKandangSummaryWeightRangeDTO struct {
|
||||
ID int `json:"id"`
|
||||
WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"`
|
||||
Label string `json:"label"`
|
||||
RemainingChickenBirds int64 `json:"remaining_chicken_birds"`
|
||||
RemainingChickenWeightKg float64 `json:"remaining_chicken_weight_kg"`
|
||||
AvgWeightKg float64 `json:"avg_weight_kg"`
|
||||
EggProductionPieces int64 `json:"egg_production_pieces"`
|
||||
EggProductionKg float64 `json:"egg_production_kg"`
|
||||
EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"`
|
||||
EggValueRp int64 `json:"egg_value_rp"`
|
||||
FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"`
|
||||
DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"`
|
||||
AverageDocPriceRp float64 `json:"average_doc_price_rp"`
|
||||
HppRp float64 `json:"hpp_rp"`
|
||||
RemainingValueRp int64 `json:"remaining_value_rp"`
|
||||
}
|
||||
|
||||
type HppPerKandangSummaryTotalDTO struct {
|
||||
TotalRemainingChickenBirds int64 `json:"total_remaining_chicken_birds"`
|
||||
TotalRemainingChickenWeightKg float64 `json:"total_remaining_chicken_weight_kg"`
|
||||
AverageWeightKg float64 `json:"average_weight_kg"`
|
||||
TotalRemainingValueRp int64 `json:"total_remaining_value_rp"`
|
||||
TotalEggProductionPieces int64 `json:"total_egg_production_pieces"`
|
||||
TotalEggProductionKg float64 `json:"total_egg_production_kg"`
|
||||
AverageEggHppRpPerKg float64 `json:"average_egg_hpp_rp_per_kg"`
|
||||
TotalEggValueRp int64 `json:"total_egg_value_rp"`
|
||||
TotalHppRp float64 `json:"total_hpp_rp"`
|
||||
TotalAverageDocPriceRp float64 `json:"total_average_doc_price_rp"`
|
||||
}
|
||||
|
||||
func NewHppPerKandangFiltersDTO(area, location, kandang, weightMin, weightMax, period, showUnrecorded string) HppPerKandangFiltersDTO {
|
||||
return HppPerKandangFiltersDTO{
|
||||
AreaID: area,
|
||||
LocationID: location,
|
||||
KandangID: kandang,
|
||||
WeightMin: weightMin,
|
||||
WeightMax: weightMax,
|
||||
Period: period,
|
||||
ShowUnrecorded: showUnrecorded,
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,11 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
recordingRepository := recordingRepo.NewRecordingRepository(db)
|
||||
approvalRepository := commonRepo.NewApprovalRepository(db)
|
||||
purchaseSupplierRepository := repportRepo.NewPurchaseSupplierRepository(db)
|
||||
hppPerKandangRepository := repportRepo.NewHppPerKandangRepository(db)
|
||||
userRepository := rUser.NewUserRepository(db)
|
||||
|
||||
approvalSvc := approvalService.NewApprovalService(approvalRepository)
|
||||
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, purchaseSupplierRepository)
|
||||
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, purchaseSupplierRepository, hppPerKandangRepository)
|
||||
userService := sUser.NewUserService(userRepository, validate)
|
||||
|
||||
RepportRoutes(router, userService, repportService)
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type HppPerKandangRow struct {
|
||||
KandangID uint
|
||||
KandangName string
|
||||
KandangStatus string
|
||||
LocationID uint
|
||||
LocationName string
|
||||
PicID uint
|
||||
PicName string
|
||||
RemainingChickenBirds float64
|
||||
RemainingChickenWeight float64
|
||||
EggProductionWeightKg float64
|
||||
EggProductionPieces float64
|
||||
}
|
||||
|
||||
type HppPerKandangCostRow struct {
|
||||
KandangID uint
|
||||
FeedCost float64
|
||||
OvkCost float64
|
||||
DocCost float64
|
||||
DocQty float64
|
||||
BudgetCost float64
|
||||
ExpenseCost float64
|
||||
}
|
||||
|
||||
type HppPerKandangSupplierRow struct {
|
||||
KandangID uint
|
||||
SupplierID uint
|
||||
SupplierName string
|
||||
SupplierAlias string
|
||||
Category string
|
||||
}
|
||||
|
||||
type HppPerKandangRepository interface {
|
||||
GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error)
|
||||
GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error)
|
||||
}
|
||||
|
||||
type hppPerKandangRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewHppPerKandangRepository(db *gorm.DB) HppPerKandangRepository {
|
||||
return &hppPerKandangRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error) {
|
||||
var rows []HppPerKandangRow
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("recordings AS r").
|
||||
Select(`
|
||||
k.id AS kandang_id,
|
||||
k.name AS kandang_name,
|
||||
k.status AS kandang_status,
|
||||
loc.id AS location_id,
|
||||
loc.name AS location_name,
|
||||
pic.id AS pic_id,
|
||||
pic.name AS pic_name,
|
||||
COALESCE(MAX(r.total_chick_qty), 0) AS remaining_chicken_birds,
|
||||
COALESCE(SUM(rbw.total_weight), 0) AS remaining_chicken_weight,
|
||||
COALESCE(SUM(re.weight), 0) AS egg_production_weight_kg,
|
||||
COALESCE(SUM(re.qty), 0) AS egg_production_pieces`).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||
Joins("JOIN users AS pic ON pic.id = k.pic_id").
|
||||
Joins("LEFT JOIN recording_bws AS rbw ON rbw.recording_id = r.id").
|
||||
Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id").
|
||||
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
||||
Where("r.deleted_at IS NULL")
|
||||
|
||||
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
|
||||
|
||||
query = query.Group("k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name").
|
||||
Order("k.id ASC")
|
||||
|
||||
if err := query.Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) {
|
||||
var rows []HppPerKandangCostRow
|
||||
|
||||
recordingPfk := r.db.WithContext(ctx).
|
||||
Table("recordings AS r").
|
||||
Select("DISTINCT pfk.id").
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
||||
Where("r.deleted_at IS NULL")
|
||||
recordingPfk = applyLocationFilters(recordingPfk, areaIDs, locationIDs, kandangIDs)
|
||||
|
||||
purchaseStockableKey := fifo.StockableKey("PURCHASE_ITEMS").String()
|
||||
transferStockableKey := fifo.StockableKey("STOCK_TRANSFER_DETAILS").String()
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("recordings AS r").
|
||||
Select(`
|
||||
k.id AS kandang_id,
|
||||
COALESCE(SUM(CASE
|
||||
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.quantity, 0) * COALESCE(tpi.price, 0)
|
||||
ELSE 0
|
||||
END), 0) AS feed_cost,
|
||||
COALESCE(SUM(CASE
|
||||
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.quantity, 0) * COALESCE(tpi.price, 0)
|
||||
ELSE 0
|
||||
END), 0) AS ovk_cost`,
|
||||
utils.FlagPakan, transferStockableKey, utils.FlagPakan,
|
||||
utils.FlagOVK, transferStockableKey, utils.FlagOVK).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||
Joins("LEFT JOIN recording_stocks AS rs ON rs.recording_id = r.id").
|
||||
Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?", fifo.UsableKeyRecordingStock.String(), entity.StockAllocationStatusActive).
|
||||
Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey).
|
||||
Joins("LEFT JOIN stock_transfer_details AS std ON std.id = sa.stockable_id AND sa.stockable_type = ?", transferStockableKey).
|
||||
Joins("LEFT JOIN stock_transfers AS st ON st.id = std.stock_transfer_id").
|
||||
Joins("LEFT JOIN purchase_items AS tpi ON tpi.product_id = std.product_id AND tpi.warehouse_id = st.from_warehouse_id").
|
||||
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||
Joins("LEFT JOIN flags AS tf ON tf.flagable_id = std.product_id AND tf.flagable_type = ?", entity.FlagableTypeProduct).
|
||||
Where("r.project_flock_kandangs_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
|
||||
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
||||
Where("r.deleted_at IS NULL")
|
||||
|
||||
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
|
||||
|
||||
query = query.Group("k.id").Order("k.id ASC")
|
||||
|
||||
if err := query.Scan(&rows).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
docRows := make([]struct {
|
||||
KandangID uint
|
||||
DocCost float64
|
||||
DocQty float64
|
||||
SupplierID *uint
|
||||
SupplierName *string
|
||||
SupplierAlias *string
|
||||
}, 0)
|
||||
|
||||
docQuery := r.db.WithContext(ctx).
|
||||
Table("project_chickins AS pc").
|
||||
Select(`
|
||||
pfk.kandang_id AS kandang_id,
|
||||
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS doc_cost,
|
||||
COALESCE(SUM(pc.usage_qty), 0) AS doc_qty,
|
||||
s.id AS supplier_id,
|
||||
s.name AS supplier_name,
|
||||
s.alias AS supplier_alias`).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||
Joins("LEFT JOIN purchase_items AS pi ON pi.product_warehouse_id = pc.product_warehouse_id").
|
||||
Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id").
|
||||
Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
|
||||
Where("pc.project_flock_kandang_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
|
||||
Group("pfk.kandang_id, s.id, s.name, s.alias")
|
||||
docQuery = applyLocationFilters(docQuery, areaIDs, locationIDs, kandangIDs)
|
||||
|
||||
if err := docQuery.Scan(&docRows).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
costMap := make(map[uint]*HppPerKandangCostRow, len(rows))
|
||||
for i := range rows {
|
||||
row := rows[i]
|
||||
costMap[row.KandangID] = &rows[i]
|
||||
}
|
||||
|
||||
docSuppliers := make([]HppPerKandangSupplierRow, 0)
|
||||
docSeen := make(map[uint]map[uint]bool)
|
||||
for _, doc := range docRows {
|
||||
entry, ok := costMap[doc.KandangID]
|
||||
if !ok {
|
||||
rows = append(rows, HppPerKandangCostRow{
|
||||
KandangID: doc.KandangID,
|
||||
})
|
||||
entry = &rows[len(rows)-1]
|
||||
costMap[doc.KandangID] = entry
|
||||
}
|
||||
entry.DocCost += doc.DocCost
|
||||
entry.DocQty += doc.DocQty
|
||||
if doc.SupplierID != nil {
|
||||
if docSeen[doc.KandangID] == nil {
|
||||
docSeen[doc.KandangID] = make(map[uint]bool)
|
||||
}
|
||||
if !docSeen[doc.KandangID][*doc.SupplierID] {
|
||||
docSeen[doc.KandangID][*doc.SupplierID] = true
|
||||
supplierName := ""
|
||||
if doc.SupplierName != nil {
|
||||
supplierName = *doc.SupplierName
|
||||
}
|
||||
supplierAlias := ""
|
||||
if doc.SupplierAlias != nil {
|
||||
supplierAlias = *doc.SupplierAlias
|
||||
}
|
||||
docSuppliers = append(docSuppliers, HppPerKandangSupplierRow{
|
||||
KandangID: doc.KandangID,
|
||||
SupplierID: *doc.SupplierID,
|
||||
SupplierName: supplierName,
|
||||
SupplierAlias: supplierAlias,
|
||||
Category: "DOC",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
budgetRows := make([]struct {
|
||||
KandangID uint
|
||||
BudgetCost float64
|
||||
}, 0)
|
||||
|
||||
pfkUsageSub := r.db.
|
||||
Table("project_chickins AS pc").
|
||||
Select(`
|
||||
pc.project_flock_kandang_id,
|
||||
SUM(pc.usage_qty) AS kandang_usage_qty`).
|
||||
Group("pc.project_flock_kandang_id")
|
||||
|
||||
projectUsageSub := r.db.
|
||||
Table("project_chickins AS pc").
|
||||
Select(`
|
||||
pfk.project_flock_id,
|
||||
SUM(pc.usage_qty) AS project_usage_qty`).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id").
|
||||
Group("pfk.project_flock_id")
|
||||
|
||||
budgetQuery := r.db.WithContext(ctx).
|
||||
Table("project_flock_kandangs AS pfk").
|
||||
Select(`
|
||||
k.id AS kandang_id,
|
||||
COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0) AS budget_cost`).
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||
Joins("JOIN project_budgets AS pb ON pb.project_flock_id = pfk.project_flock_id").
|
||||
Joins("LEFT JOIN (?) AS k_usage ON k_usage.project_flock_kandang_id = pfk.id", pfkUsageSub).
|
||||
Joins("LEFT JOIN (?) AS p_usage ON p_usage.project_flock_id = pfk.project_flock_id", projectUsageSub).
|
||||
Where("pfk.id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
|
||||
Group("k.id")
|
||||
budgetQuery = applyLocationFilters(budgetQuery, areaIDs, locationIDs, kandangIDs)
|
||||
|
||||
if err := budgetQuery.Scan(&budgetRows).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, budget := range budgetRows {
|
||||
entry, ok := costMap[budget.KandangID]
|
||||
if !ok {
|
||||
rows = append(rows, HppPerKandangCostRow{
|
||||
KandangID: budget.KandangID,
|
||||
})
|
||||
entry = &rows[len(rows)-1]
|
||||
costMap[budget.KandangID] = entry
|
||||
}
|
||||
entry.BudgetCost += budget.BudgetCost
|
||||
}
|
||||
|
||||
expenseRows := make([]struct {
|
||||
KandangID uint
|
||||
ExpenseCost float64
|
||||
}, 0)
|
||||
|
||||
expenseQuery := r.db.WithContext(ctx).
|
||||
Table("project_flock_kandangs AS pfk").
|
||||
Select(`
|
||||
k.id AS kandang_id,
|
||||
COALESCE(SUM(er.qty * er.price), 0) AS expense_cost`).
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||
Joins("JOIN expense_nonstocks AS en ON en.project_flock_kandang_id = pfk.id").
|
||||
Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id").
|
||||
Where("pfk.id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
|
||||
Group("k.id")
|
||||
expenseQuery = applyLocationFilters(expenseQuery, areaIDs, locationIDs, kandangIDs)
|
||||
|
||||
if err := expenseQuery.Scan(&expenseRows).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, exp := range expenseRows {
|
||||
entry, ok := costMap[exp.KandangID]
|
||||
if !ok {
|
||||
rows = append(rows, HppPerKandangCostRow{
|
||||
KandangID: exp.KandangID,
|
||||
})
|
||||
entry = &rows[len(rows)-1]
|
||||
costMap[exp.KandangID] = entry
|
||||
}
|
||||
entry.ExpenseCost += exp.ExpenseCost
|
||||
}
|
||||
|
||||
feedSuppliers := make([]HppPerKandangSupplierRow, 0)
|
||||
|
||||
feedQuery := r.db.WithContext(ctx).
|
||||
Table("recordings AS r").
|
||||
Select("DISTINCT k.id AS kandang_id, s.id AS supplier_id, s.name AS supplier_name, s.alias AS supplier_alias").
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||
Joins("LEFT JOIN recording_stocks AS rs ON rs.recording_id = r.id").
|
||||
Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?", fifo.UsableKeyRecordingStock.String(), entity.StockAllocationStatusActive).
|
||||
Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey).
|
||||
Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id").
|
||||
Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
|
||||
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||
Where("f.name IN ?", []utils.FlagType{utils.FlagPakan, utils.FlagOVK}).
|
||||
Where("r.project_flock_kandangs_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
|
||||
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
||||
Where("r.deleted_at IS NULL")
|
||||
feedQuery = applyLocationFilters(feedQuery, areaIDs, locationIDs, kandangIDs)
|
||||
|
||||
if err := feedQuery.Scan(&feedSuppliers).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for i := range feedSuppliers {
|
||||
if _, exists := costMap[feedSuppliers[i].KandangID]; !exists {
|
||||
rows = append(rows, HppPerKandangCostRow{
|
||||
KandangID: feedSuppliers[i].KandangID,
|
||||
})
|
||||
costMap[feedSuppliers[i].KandangID] = &rows[len(rows)-1]
|
||||
}
|
||||
feedSuppliers[i].Category = "FEED"
|
||||
}
|
||||
|
||||
supplierRows := append(docSuppliers, feedSuppliers...)
|
||||
|
||||
return rows, supplierRows, nil
|
||||
}
|
||||
|
||||
func applyLocationFilters(query *gorm.DB, areaIDs, locationIDs, kandangIDs []int64) *gorm.DB {
|
||||
if len(areaIDs) > 0 {
|
||||
query = query.Where("loc.area_id IN ?", areaIDs)
|
||||
}
|
||||
if len(locationIDs) > 0 {
|
||||
query = query.Where("k.location_id IN ?", locationIDs)
|
||||
}
|
||||
if len(kandangIDs) > 0 {
|
||||
query = query.Where("k.id IN ?", kandangIDs)
|
||||
}
|
||||
return query
|
||||
}
|
||||
@@ -18,4 +18,6 @@ func RepportRoutes(v1 fiber.Router, u user.UserService, s repport.RepportService
|
||||
route.Get("/expense", m.RequirePermissions(m.P_ReportExpenseGetAll), ctrl.GetExpense)
|
||||
route.Get("/marketing", m.RequirePermissions(m.P_ReportDeliveryGetAll), ctrl.GetMarketing)
|
||||
route.Get("/purchase-supplier", m.RequirePermissions(m.P_ReportPurchaseSupplierGetAll), ctrl.GetPurchaseSupplier)
|
||||
route.Get("/hpp-per-kandang", ctrl.GetHppPerKandang)
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||
repportRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/repositories"
|
||||
@@ -28,6 +34,7 @@ type RepportService interface {
|
||||
GetExpense(ctx *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error)
|
||||
GetMarketing(ctx *fiber.Ctx, params *validation.MarketingQuery) ([]dto.RepportMarketingItemDTO, int64, error)
|
||||
GetPurchaseSupplier(ctx *fiber.Ctx, params *validation.PurchaseSupplierQuery) ([]dto.PurchaseSupplierDTO, int64, error)
|
||||
GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error)
|
||||
}
|
||||
|
||||
type repportService struct {
|
||||
@@ -40,6 +47,16 @@ type repportService struct {
|
||||
RecordingRepo recordingRepo.RecordingRepository
|
||||
ApprovalSvc approvalService.ApprovalService
|
||||
PurchaseSupplierRepo repportRepo.PurchaseSupplierRepository
|
||||
HppPerKandangRepo repportRepo.HppPerKandangRepository
|
||||
}
|
||||
|
||||
type HppCostAggregate struct {
|
||||
FeedCost float64
|
||||
OvkCost float64
|
||||
DocCost float64
|
||||
DocQty float64
|
||||
BudgetCost float64
|
||||
ExpenseCost float64
|
||||
}
|
||||
|
||||
func NewRepportService(
|
||||
@@ -51,6 +68,7 @@ func NewRepportService(
|
||||
recordingRepo recordingRepo.RecordingRepository,
|
||||
approvalSvc approvalService.ApprovalService,
|
||||
purchaseSupplierRepo repportRepo.PurchaseSupplierRepository,
|
||||
hppPerKandangRepo repportRepo.HppPerKandangRepository,
|
||||
) RepportService {
|
||||
return &repportService{
|
||||
Log: utils.Log,
|
||||
@@ -62,6 +80,7 @@ func NewRepportService(
|
||||
RecordingRepo: recordingRepo,
|
||||
ApprovalSvc: approvalSvc,
|
||||
PurchaseSupplierRepo: purchaseSupplierRepo,
|
||||
HppPerKandangRepo: hppPerKandangRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,3 +284,438 @@ func (s *repportService) GetPurchaseSupplier(c *fiber.Ctx, params *validation.Pu
|
||||
|
||||
return result, totalSuppliers, nil
|
||||
}
|
||||
|
||||
func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error) {
|
||||
params, filters, err := s.parseHppPerKandangQuery(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
location, err := time.LoadLocation("Asia/Jakarta")
|
||||
if err != nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "failed to load timezone configuration")
|
||||
}
|
||||
|
||||
periodDate, err := time.ParseInLocation("2006-01-02", params.Period, location)
|
||||
if err != nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusBadRequest, "period must follow format YYYY-MM-DD")
|
||||
}
|
||||
|
||||
startOfDay := time.Date(periodDate.Year(), periodDate.Month(), periodDate.Day(), 0, 0, 0, 0, location)
|
||||
endOfDay := startOfDay.Add(24 * time.Hour)
|
||||
|
||||
repoRows, err := s.HppPerKandangRepo.GetRowsByPeriod(ctx.Context(), startOfDay, endOfDay, params.AreaIDs, params.LocationIDs, params.KandangIDs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
costRows, supplierRows, err := s.HppPerKandangRepo.GetFeedOvkDocCostByPeriod(ctx.Context(), startOfDay, endOfDay, params.AreaIDs, params.LocationIDs, params.KandangIDs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
costMap := make(map[uint]HppCostAggregate, len(costRows))
|
||||
for _, row := range costRows {
|
||||
costMap[row.KandangID] = HppCostAggregate{
|
||||
FeedCost: row.FeedCost,
|
||||
OvkCost: row.OvkCost,
|
||||
DocCost: row.DocCost,
|
||||
DocQty: row.DocQty,
|
||||
BudgetCost: row.BudgetCost,
|
||||
ExpenseCost: row.ExpenseCost,
|
||||
}
|
||||
}
|
||||
|
||||
docSupplierMap := make(map[uint][]dto.HppPerKandangSupplierDTO)
|
||||
feedSupplierMap := make(map[uint][]dto.HppPerKandangSupplierDTO)
|
||||
docSeen := make(map[uint]map[uint]bool)
|
||||
feedSeen := make(map[uint]map[uint]bool)
|
||||
|
||||
for _, sup := range supplierRows {
|
||||
if sup.SupplierID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
targetMap := feedSupplierMap
|
||||
seen := feedSeen
|
||||
category := "FEED"
|
||||
if strings.EqualFold(sup.Category, "DOC") {
|
||||
targetMap = docSupplierMap
|
||||
seen = docSeen
|
||||
category = "DOC"
|
||||
}
|
||||
|
||||
if seen[sup.KandangID] == nil {
|
||||
seen[sup.KandangID] = make(map[uint]bool)
|
||||
}
|
||||
if seen[sup.KandangID][sup.SupplierID] {
|
||||
continue
|
||||
}
|
||||
seen[sup.KandangID][sup.SupplierID] = true
|
||||
|
||||
targetMap[sup.KandangID] = append(targetMap[sup.KandangID], dto.HppPerKandangSupplierDTO{
|
||||
ID: int64(sup.SupplierID),
|
||||
Name: sup.SupplierName,
|
||||
Alias: sup.SupplierAlias,
|
||||
Category: category,
|
||||
})
|
||||
}
|
||||
|
||||
type weightRangeKey struct {
|
||||
Min float64
|
||||
Max float64
|
||||
}
|
||||
type weightRangeAggregate struct {
|
||||
Summary *dto.HppPerKandangSummaryWeightRangeDTO
|
||||
EggHppSum float64
|
||||
EggHppCount int
|
||||
}
|
||||
|
||||
dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows))
|
||||
perRangeMap := make(map[weightRangeKey]*weightRangeAggregate)
|
||||
var totalBirds int64
|
||||
var totalWeight float64
|
||||
var totalEggPieces int64
|
||||
var totalEggKg float64
|
||||
var totalRemainingValueRp int64
|
||||
var totalEggValueRp int64
|
||||
var totalHppSum float64
|
||||
var totalHppCount int
|
||||
var totalDocPriceSum float64
|
||||
var totalDocPriceCount int
|
||||
var totalEggHppSum float64
|
||||
var totalEggHppCount int
|
||||
|
||||
for _, row := range repoRows {
|
||||
birdsFloat := row.RemainingChickenBirds
|
||||
if math.IsNaN(birdsFloat) || math.IsInf(birdsFloat, 0) {
|
||||
birdsFloat = 0
|
||||
}
|
||||
weightFloat := row.RemainingChickenWeight
|
||||
if math.IsNaN(weightFloat) || math.IsInf(weightFloat, 0) {
|
||||
weightFloat = 0
|
||||
}
|
||||
eggPiecesFloat := row.EggProductionPieces
|
||||
if math.IsNaN(eggPiecesFloat) || math.IsInf(eggPiecesFloat, 0) {
|
||||
eggPiecesFloat = 0
|
||||
}
|
||||
eggWeightFloat := row.EggProductionWeightKg
|
||||
if math.IsNaN(eggWeightFloat) || math.IsInf(eggWeightFloat, 0) {
|
||||
eggWeightFloat = 0
|
||||
}
|
||||
|
||||
avgWeight := 0.0
|
||||
if birdsFloat > 0 {
|
||||
avgWeight = weightFloat / birdsFloat
|
||||
}
|
||||
weightMin := math.Floor(avgWeight*10) / 10
|
||||
if weightMin < 0 {
|
||||
weightMin = 0
|
||||
}
|
||||
weightMax := weightMin + 0.09
|
||||
rangeKey := weightRangeKey{Min: weightMin, Max: weightMax}
|
||||
|
||||
rowBirds := int64(math.Round(birdsFloat))
|
||||
costEntry := costMap[row.KandangID]
|
||||
totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost
|
||||
hppRp := 0.0
|
||||
if weightFloat > 0 {
|
||||
hppRp = totalCost / weightFloat
|
||||
}
|
||||
eggHpp := 0.0
|
||||
if eggWeightFloat > 0 {
|
||||
eggHpp = totalCost / eggWeightFloat
|
||||
}
|
||||
|
||||
rowEggPieces := int64(math.Round(eggPiecesFloat))
|
||||
rowEggValue := int64(eggHpp * eggWeightFloat)
|
||||
rowRemainingValue := int64(hppRp * weightFloat)
|
||||
avgDocPrice := int64(0)
|
||||
if costEntry.DocQty > 0 {
|
||||
avgDocPrice = int64(math.Round(costEntry.DocCost / costEntry.DocQty))
|
||||
}
|
||||
|
||||
dataRows = append(dataRows, dto.HppPerKandangRowDTO{
|
||||
ID: int(row.KandangID),
|
||||
Kandang: dto.HppPerKandangRowKandangDTO{
|
||||
ID: int64(row.KandangID),
|
||||
Name: row.KandangName,
|
||||
Status: row.KandangStatus,
|
||||
Location: dto.HppPerKandangLocationDTO{
|
||||
ID: int64(row.LocationID),
|
||||
Name: row.LocationName,
|
||||
},
|
||||
Pic: dto.HppPerKandangPICDTO{
|
||||
ID: int64(row.PicID),
|
||||
Name: row.PicName,
|
||||
},
|
||||
},
|
||||
WeightRange: dto.HppPerKandangWeightRangeDTO{
|
||||
WeightMin: weightMin,
|
||||
WeightMax: weightMax,
|
||||
},
|
||||
RemainingChickenBirds: rowBirds,
|
||||
RemainingChickenWeightKg: weightFloat,
|
||||
AvgWeightKg: avgWeight,
|
||||
// FeedCostRp: costEntry.FeedCost,
|
||||
// OvkCostRp: costEntry.OvkCost,
|
||||
DocSuppliers: docSupplierMap[row.KandangID],
|
||||
FeedSuppliers: feedSupplierMap[row.KandangID],
|
||||
EggProductionPieces: rowEggPieces,
|
||||
EggProductionKg: eggWeightFloat,
|
||||
AverageDocPriceRp: avgDocPrice,
|
||||
HppRp: hppRp,
|
||||
EggHppRpPerKg: eggHpp,
|
||||
RemainingValueRp: rowRemainingValue,
|
||||
EggValueRp: rowEggValue,
|
||||
})
|
||||
|
||||
totalBirds += rowBirds
|
||||
totalWeight += weightFloat
|
||||
totalEggPieces += rowEggPieces
|
||||
totalEggKg += eggWeightFloat
|
||||
totalRemainingValueRp += rowRemainingValue
|
||||
totalEggValueRp += rowEggValue
|
||||
if weightFloat > 0 {
|
||||
totalHppSum += hppRp
|
||||
totalHppCount++
|
||||
}
|
||||
if avgDocPrice > 0 {
|
||||
totalDocPriceSum += float64(avgDocPrice)
|
||||
totalDocPriceCount++
|
||||
}
|
||||
if eggWeightFloat > 0 {
|
||||
totalEggHppSum += eggHpp
|
||||
totalEggHppCount++
|
||||
}
|
||||
|
||||
rangeAgg, exists := perRangeMap[rangeKey]
|
||||
if !exists {
|
||||
rangeAgg = &weightRangeAggregate{
|
||||
Summary: &dto.HppPerKandangSummaryWeightRangeDTO{
|
||||
WeightRange: dto.HppPerKandangWeightRangeDTO{
|
||||
WeightMin: weightMin,
|
||||
WeightMax: weightMax,
|
||||
},
|
||||
Label: fmt.Sprintf("%.2f - %.2f", weightMin, weightMax),
|
||||
},
|
||||
}
|
||||
perRangeMap[rangeKey] = rangeAgg
|
||||
}
|
||||
|
||||
rangeSummary := rangeAgg.Summary
|
||||
rangeSummary.RemainingChickenBirds += rowBirds
|
||||
rangeSummary.RemainingChickenWeightKg += row.RemainingChickenWeight
|
||||
rangeSummary.EggProductionPieces += rowEggPieces
|
||||
rangeSummary.EggProductionKg += eggWeightFloat
|
||||
rangeSummary.RemainingValueRp += rowRemainingValue
|
||||
rangeSummary.EggValueRp += rowEggValue
|
||||
if eggWeightFloat > 0 {
|
||||
rangeAgg.EggHppSum += eggHpp
|
||||
rangeAgg.EggHppCount++
|
||||
}
|
||||
}
|
||||
|
||||
rangeKeys := make([]weightRangeKey, 0, len(perRangeMap))
|
||||
for key := range perRangeMap {
|
||||
rangeKeys = append(rangeKeys, key)
|
||||
}
|
||||
sort.Slice(rangeKeys, func(i, j int) bool {
|
||||
if rangeKeys[i].Min == rangeKeys[j].Min {
|
||||
return rangeKeys[i].Max < rangeKeys[j].Max
|
||||
}
|
||||
return rangeKeys[i].Min < rangeKeys[j].Min
|
||||
})
|
||||
|
||||
perRangeSummary := make([]dto.HppPerKandangSummaryWeightRangeDTO, 0, len(rangeKeys))
|
||||
for idx, key := range rangeKeys {
|
||||
agg := perRangeMap[key]
|
||||
entry := agg.Summary
|
||||
entry.ID = idx + 1
|
||||
if entry.RemainingChickenBirds > 0 {
|
||||
entry.AvgWeightKg = entry.RemainingChickenWeightKg / float64(entry.RemainingChickenBirds)
|
||||
}
|
||||
if agg.EggHppCount > 0 {
|
||||
entry.EggHppRpPerKg = agg.EggHppSum / float64(agg.EggHppCount)
|
||||
}
|
||||
perRangeSummary = append(perRangeSummary, *entry)
|
||||
}
|
||||
|
||||
totalSummary := dto.HppPerKandangSummaryTotalDTO{
|
||||
TotalRemainingChickenBirds: totalBirds,
|
||||
TotalRemainingChickenWeightKg: totalWeight,
|
||||
TotalEggProductionPieces: totalEggPieces,
|
||||
TotalEggProductionKg: totalEggKg,
|
||||
TotalRemainingValueRp: totalRemainingValueRp,
|
||||
TotalEggValueRp: totalEggValueRp,
|
||||
}
|
||||
if totalBirds > 0 {
|
||||
totalSummary.AverageWeightKg = totalWeight / float64(totalBirds)
|
||||
}
|
||||
if totalEggHppCount > 0 {
|
||||
totalSummary.AverageEggHppRpPerKg = totalEggHppSum / float64(totalEggHppCount)
|
||||
}
|
||||
if totalHppCount > 0 {
|
||||
totalSummary.TotalHppRp = totalHppSum / float64(totalHppCount)
|
||||
}
|
||||
if totalDocPriceCount > 0 {
|
||||
totalSummary.TotalAverageDocPriceRp = totalDocPriceSum / float64(totalDocPriceCount)
|
||||
}
|
||||
|
||||
limit := params.Limit
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
totalCount := len(dataRows)
|
||||
offset := (params.Page - 1) * limit
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if offset > totalCount {
|
||||
offset = totalCount
|
||||
}
|
||||
end := offset + limit
|
||||
if end > totalCount {
|
||||
end = totalCount
|
||||
}
|
||||
pagedRows := dataRows[offset:end]
|
||||
|
||||
data := dto.HppPerKandangResponseData{
|
||||
Period: params.Period,
|
||||
Rows: pagedRows,
|
||||
Summary: dto.HppPerKandangSummaryDTO{
|
||||
PerWeightRange: perRangeSummary,
|
||||
Total: totalSummary,
|
||||
},
|
||||
}
|
||||
|
||||
totalResults := int64(totalCount)
|
||||
|
||||
totalPages := int64(0)
|
||||
if totalResults > 0 {
|
||||
totalPages = int64(math.Ceil(float64(totalResults) / float64(limit)))
|
||||
}
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
meta := &dto.HppPerKandangMetaDTO{
|
||||
Page: params.Page,
|
||||
Limit: limit,
|
||||
TotalPages: totalPages,
|
||||
TotalResults: totalResults,
|
||||
Filters: filters,
|
||||
}
|
||||
|
||||
return &data, meta, nil
|
||||
}
|
||||
|
||||
func (s *repportService) parseHppPerKandangQuery(ctx *fiber.Ctx) (*validation.HppPerKandangQuery, dto.HppPerKandangFiltersDTO, error) {
|
||||
page := ctx.QueryInt("page", 1)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
limit := ctx.QueryInt("limit", 10)
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
rawArea := ctx.Query("area_id", "")
|
||||
rawLocation := ctx.Query("location_id", "")
|
||||
rawKandang := ctx.Query("kandang_id", "")
|
||||
rawWeightMin := ctx.Query("weight_min", "")
|
||||
rawWeightMax := ctx.Query("weight_max", "")
|
||||
period := ctx.Query("period", "")
|
||||
showUnrecorded := ctx.QueryBool("show_unrecorded", false)
|
||||
|
||||
areaIDs, err := parseCommaSeparatedInt64s(rawArea)
|
||||
if err != nil {
|
||||
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
locationIDs, err := parseCommaSeparatedInt64s(rawLocation)
|
||||
if err != nil {
|
||||
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
kandangIDs, err := parseCommaSeparatedInt64s(rawKandang)
|
||||
if err != nil {
|
||||
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
weightMin, err := parseOptionalFloat64(rawWeightMin)
|
||||
if err != nil {
|
||||
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
weightMax, err := parseOptionalFloat64(rawWeightMax)
|
||||
if err != nil {
|
||||
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
params := &validation.HppPerKandangQuery{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Period: period,
|
||||
ShowUnrecorded: showUnrecorded,
|
||||
AreaIDs: areaIDs,
|
||||
LocationIDs: locationIDs,
|
||||
KandangIDs: kandangIDs,
|
||||
WeightMin: weightMin,
|
||||
WeightMax: weightMax,
|
||||
}
|
||||
|
||||
showUnrecordedFilter := ""
|
||||
if showUnrecorded {
|
||||
showUnrecordedFilter = "true"
|
||||
}
|
||||
|
||||
filters := dto.NewHppPerKandangFiltersDTO(
|
||||
rawArea,
|
||||
rawLocation,
|
||||
rawKandang,
|
||||
rawWeightMin,
|
||||
rawWeightMax,
|
||||
period,
|
||||
showUnrecordedFilter,
|
||||
)
|
||||
|
||||
return params, filters, nil
|
||||
}
|
||||
|
||||
func parseCommaSeparatedInt64s(raw string) ([]int64, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(raw, ",")
|
||||
result := make([]int64, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(part, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid integer value '%s'", part)
|
||||
}
|
||||
result = append(result, id)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseOptionalFloat64(raw string) (*float64, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
value, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid float value '%s'", raw)
|
||||
}
|
||||
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
@@ -42,3 +42,15 @@ type PurchaseSupplierQuery struct {
|
||||
SortBy string `query:"sort_by" validate:"omitempty"`
|
||||
FilterBy string `query:"filter_by" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type HppPerKandangQuery struct {
|
||||
Page int `query:"page" validate:"omitempty,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,min=1,max=100,gt=0"`
|
||||
Period string `query:"period" validate:"required"`
|
||||
ShowUnrecorded bool `query:"show_unrecorded"`
|
||||
AreaIDs []int64 `query:"-"`
|
||||
LocationIDs []int64 `query:"-"`
|
||||
KandangIDs []int64 `query:"-"`
|
||||
WeightMin *float64 `query:"-"`
|
||||
WeightMax *float64 `query:"-"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user