mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-06-09 15:07:49 +00:00
ini api per farm
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
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"
|
||||
)
|
||||
|
||||
// HppPerFarmFlockMetaRow describes a LAYING project flock and the farm
|
||||
// (location) it belongs to. Farm identity is project_flocks.location_id.
|
||||
type HppPerFarmFlockMetaRow struct {
|
||||
ProjectFlockID uint
|
||||
FlockName string
|
||||
LocationID uint
|
||||
LocationName string
|
||||
AreaID uint
|
||||
}
|
||||
|
||||
// HppPerFarmDocRow holds the DOC/pullet acquisition cost trace per flock.
|
||||
// Used only as an informational field (average_doc_price_rp); it is NOT part
|
||||
// of total_cost because the pullet cost is expensed through depreciation.
|
||||
type HppPerFarmDocRow struct {
|
||||
ProjectFlockID uint
|
||||
DocCost float64
|
||||
DocQty float64
|
||||
}
|
||||
|
||||
type HppPerFarmRepository interface {
|
||||
GetCandidateFlocks(ctx context.Context, start time.Time, areaIDs, locationIDs []int64) ([]HppPerFarmFlockMetaRow, error)
|
||||
SumRecordingEggWeightByFlock(ctx context.Context, start, endExclusive time.Time, projectFlockIDs []uint) (map[uint]float64, error)
|
||||
SumMarketingDoTelurWeightByFlock(ctx context.Context, start, endExclusive time.Time, projectFlockIDs []uint) (map[uint]float64, error)
|
||||
GetDocCostByFlock(ctx context.Context, projectFlockIDs []uint) (map[uint]HppPerFarmDocRow, error)
|
||||
DB() *gorm.DB
|
||||
}
|
||||
|
||||
type hppPerFarmRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewHppPerFarmRepository(db *gorm.DB) HppPerFarmRepository {
|
||||
return &hppPerFarmRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *hppPerFarmRepository) DB() *gorm.DB {
|
||||
return r.db
|
||||
}
|
||||
|
||||
// GetCandidateFlocks returns the LAYING project flocks (with their farm/location
|
||||
// metadata) that are still active on or after the range start, scoped by area
|
||||
// and location. Mirrors ExpenseDepreciationRepository.GetCandidateFarms but adds
|
||||
// location info so flocks can be grouped per farm.
|
||||
func (r *hppPerFarmRepository) GetCandidateFlocks(ctx context.Context, start time.Time, areaIDs, locationIDs []int64) ([]HppPerFarmFlockMetaRow, error) {
|
||||
rows := make([]HppPerFarmFlockMetaRow, 0)
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("project_flocks AS pf").
|
||||
Select(`
|
||||
DISTINCT pf.id AS project_flock_id,
|
||||
pf.flock_name AS flock_name,
|
||||
pf.location_id AS location_id,
|
||||
loc.name AS location_name,
|
||||
pf.area_id AS area_id`).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
|
||||
Joins("JOIN locations AS loc ON loc.id = pf.location_id").
|
||||
Where("pf.deleted_at IS NULL").
|
||||
Where("pf.category = ?", utils.ProjectFlockCategoryLaying).
|
||||
Where("(pfk.closed_at IS NULL OR DATE(pfk.closed_at) >= DATE(?))", start)
|
||||
|
||||
if len(areaIDs) > 0 {
|
||||
query = query.Where("pf.area_id IN ?", areaIDs)
|
||||
}
|
||||
if len(locationIDs) > 0 {
|
||||
query = query.Where("pf.location_id IN ?", locationIDs)
|
||||
}
|
||||
|
||||
if err := query.Order("pf.location_id ASC, pf.id ASC").Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// SumRecordingEggWeightByFlock sums recording_eggs.weight (kg) per project flock
|
||||
// for non-rejected recordings whose record_datetime falls inside [start, endExclusive).
|
||||
func (r *hppPerFarmRepository) SumRecordingEggWeightByFlock(ctx context.Context, start, endExclusive time.Time, projectFlockIDs []uint) (map[uint]float64, error) {
|
||||
result := make(map[uint]float64)
|
||||
if len(projectFlockIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
latestApproval := r.db.WithContext(ctx).
|
||||
Table("approvals AS a").
|
||||
Select("a.approvable_id, a.action").
|
||||
Joins(`
|
||||
JOIN (
|
||||
SELECT approvable_id, MAX(action_at) AS latest_action_at
|
||||
FROM approvals
|
||||
WHERE approvable_type = ?
|
||||
GROUP BY approvable_id
|
||||
) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
|
||||
string(utils.ApprovalWorkflowRecording),
|
||||
)
|
||||
|
||||
type eggRow struct {
|
||||
ProjectFlockID uint
|
||||
Weight float64
|
||||
}
|
||||
rows := make([]eggRow, 0)
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("recordings AS r").
|
||||
Select(`
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
COALESCE(SUM(re.weight), 0) AS weight`).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||
Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
|
||||
Joins("JOIN recording_eggs AS re ON re.recording_id = r.id").
|
||||
Where("pfk.project_flock_id IN ?", projectFlockIDs).
|
||||
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, endExclusive).
|
||||
Where("r.deleted_at IS NULL").
|
||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||
Group("pfk.project_flock_id")
|
||||
|
||||
if err := query.Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result[row.ProjectFlockID] = row.Weight
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SumMarketingDoTelurWeightByFlock sums delivered TELUR weight (marketing_delivery_products.total_weight)
|
||||
// per project flock, for delivery_date inside [start, endExclusive). A delivery product that is
|
||||
// attributed to multiple flocks is prorated by each flock's allocated qty share, so that
|
||||
// the farm total equals the sum of its flocks.
|
||||
func (r *hppPerFarmRepository) SumMarketingDoTelurWeightByFlock(ctx context.Context, start, endExclusive time.Time, projectFlockIDs []uint) (map[uint]float64, error) {
|
||||
result := make(map[uint]float64)
|
||||
if len(projectFlockIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
telurFlags := []string{
|
||||
string(utils.FlagTelur),
|
||||
string(utils.FlagTelurUtuh),
|
||||
string(utils.FlagTelurPecah),
|
||||
string(utils.FlagTelurPutih),
|
||||
string(utils.FlagTelurRetak),
|
||||
}
|
||||
|
||||
// allocated qty per (marketing_delivery_product, project_flock)
|
||||
attrByFlock := r.db.WithContext(ctx).
|
||||
Table("(?) AS mda", commonRepo.MarketingDeliveryAttributionRowsQuery(r.db.WithContext(ctx))).
|
||||
Select(`
|
||||
mda.marketing_delivery_product_id AS mdp_id,
|
||||
mda.project_flock_id AS project_flock_id,
|
||||
SUM(mda.allocated_qty) AS flock_qty`).
|
||||
Group("mda.marketing_delivery_product_id, mda.project_flock_id")
|
||||
|
||||
// prorate each delivery product's total_weight across its attributed flocks.
|
||||
// Use EXISTS for the TELUR flag filter (not a JOIN) so a product carrying
|
||||
// multiple egg flags does not fan out and double-count the weight share.
|
||||
shareQuery := r.db.WithContext(ctx).
|
||||
Table("(?) AS a", attrByFlock).
|
||||
Select(`
|
||||
a.project_flock_id AS project_flock_id,
|
||||
mdp.total_weight * a.flock_qty / NULLIF(SUM(a.flock_qty) OVER (PARTITION BY a.mdp_id), 0) AS weight_share`).
|
||||
Joins("JOIN marketing_delivery_products AS mdp ON mdp.id = a.mdp_id").
|
||||
Joins("JOIN marketing_products AS mp ON mp.id = mdp.marketing_product_id").
|
||||
Joins("JOIN product_warehouses AS pw ON pw.id = mp.product_warehouse_id").
|
||||
Where("EXISTS (SELECT 1 FROM flags f WHERE f.flagable_id = pw.product_id AND f.flagable_type = ? AND f.name IN ?)", entity.FlagableTypeProduct, telurFlags).
|
||||
Where("mdp.delivery_date >= ? AND mdp.delivery_date < ?", start, endExclusive)
|
||||
|
||||
type doRow struct {
|
||||
ProjectFlockID uint
|
||||
Weight float64
|
||||
}
|
||||
rows := make([]doRow, 0)
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("(?) AS s", shareQuery).
|
||||
Select(`
|
||||
s.project_flock_id AS project_flock_id,
|
||||
COALESCE(SUM(s.weight_share), 0) AS weight`).
|
||||
Where("s.project_flock_id IN ?", projectFlockIDs).
|
||||
Group("s.project_flock_id")
|
||||
|
||||
if err := query.Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result[row.ProjectFlockID] = row.Weight
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetDocCostByFlock returns the DOC acquisition cost (qty * purchase price) and qty
|
||||
// traced to chick-in per project flock. Informational only.
|
||||
func (r *hppPerFarmRepository) GetDocCostByFlock(ctx context.Context, projectFlockIDs []uint) (map[uint]HppPerFarmDocRow, error) {
|
||||
result := make(map[uint]HppPerFarmDocRow)
|
||||
if len(projectFlockIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
rows := make([]HppPerFarmDocRow, 0)
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("project_chickins AS pc").
|
||||
Select(`
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
COALESCE(SUM(sa.qty * COALESCE(pi.price, 0)), 0) AS doc_cost,
|
||||
COALESCE(SUM(sa.qty), 0) AS doc_qty`).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id").
|
||||
Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.stockable_type = ? AND sa.status = ? AND sa.allocation_purpose = ?", fifo.UsableKeyProjectChickin.String(), fifo.StockableKeyPurchaseItems.String(), entity.StockAllocationStatusActive, entity.StockAllocationPurposeTraceChickin).
|
||||
Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id").
|
||||
Where("pfk.project_flock_id IN ?", projectFlockIDs).
|
||||
Group("pfk.project_flock_id")
|
||||
|
||||
if err := query.Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result[row.ProjectFlockID] = row
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user