mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
366 lines
14 KiB
Go
366 lines
14 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ClosingKeuanganRepository handles database operations for closing keuangan
|
|
type ClosingKeuanganRepository interface {
|
|
repository.BaseRepository[interface{}]
|
|
|
|
// All Product Usage
|
|
GetAllProductUsageByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, flagFilters []string) ([]ProductUsageRow, error)
|
|
|
|
// Depletion per kandang
|
|
GetTotalDepletionByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
|
|
|
// Weight produced from uniformity per kandang
|
|
GetTotalWeightProducedFromUniformityByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
|
|
|
// DB returns the underlying GORM DB instance
|
|
DB() *gorm.DB
|
|
}
|
|
|
|
type ClosingKeuanganRepositoryImpl struct {
|
|
*repository.BaseRepositoryImpl[interface{}]
|
|
}
|
|
|
|
func NewClosingKeuanganRepository(db *gorm.DB) ClosingKeuanganRepository {
|
|
return &ClosingKeuanganRepositoryImpl{
|
|
BaseRepositoryImpl: repository.NewBaseRepository[interface{}](db),
|
|
}
|
|
}
|
|
|
|
// Result Rows
|
|
|
|
type ProductUsageRow struct {
|
|
ProductID uint `gorm:"column:product_id"`
|
|
ProductName string `gorm:"column:product_name"`
|
|
FlagNames string `gorm:"column:flag_names"`
|
|
TotalQty float64 `gorm:"column:total_qty"`
|
|
Price float64 `gorm:"column:price"`
|
|
TotalPengeluaran float64 `gorm:"column:total_pengeluaran"`
|
|
}
|
|
|
|
// GetAllProductUsageByProjectFlockKandangID gets all product usage for a project flock kandang
|
|
// Combines data from all usable types: recordings, chickins, marketing, transfers, adjustments
|
|
// flagFilters: optional filter to get only specific flags (e.g., ["PAKAN", "OVK"]), empty means get all
|
|
func (r *ClosingKeuanganRepositoryImpl) GetAllProductUsageByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, flagFilters []string) ([]ProductUsageRow, error) {
|
|
if projectFlockKandangID == 0 {
|
|
return []ProductUsageRow{}, nil
|
|
}
|
|
|
|
type SubQueryResult struct {
|
|
ProductID uint `gorm:"column:product_id"`
|
|
ProductName string `gorm:"column:product_name"`
|
|
TotalQty float64 `gorm:"column:total_qty"`
|
|
Price float64 `gorm:"column:price"`
|
|
}
|
|
|
|
type AggregatedResult struct {
|
|
ProductID uint `gorm:"column:product_id"`
|
|
ProductName string `gorm:"column:product_name"`
|
|
TotalQty float64 `gorm:"column:total_qty"`
|
|
Price float64 `gorm:"column:price"`
|
|
PriceCount int `gorm:"-"` // For calculating average price
|
|
}
|
|
|
|
type FlagResult struct {
|
|
ProductID uint `gorm:"column:product_id"`
|
|
FlagNames string `gorm:"column:flag_names"`
|
|
}
|
|
|
|
var allResults []SubQueryResult
|
|
|
|
// Subquery 1: Recordings
|
|
var recordingsResults []SubQueryResult
|
|
err := r.DB().WithContext(ctx).
|
|
Table("recordings r").
|
|
Select("pw.product_id, p.name as product_name, "+
|
|
"COALESCE(SUM(CASE "+
|
|
"WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN COALESCE(sa.qty, 0) "+
|
|
"WHEN sa.stockable_type = 'STOCK_TRANSFER_IN' THEN COALESCE(std.usage_qty, 0) "+
|
|
"WHEN sa.stockable_type = 'TRANSFERTOLAYING_IN' THEN COALESCE(ltt.total_used, 0) "+
|
|
"WHEN sa.stockable_type = 'ADJUSTMENT_IN' THEN COALESCE(adjs.total_used, 0) "+
|
|
"WHEN sa.stockable_type = 'PROJECT_FLOCK_POPULATION' THEN COALESCE(pfp.total_used_qty, 0) "+
|
|
"ELSE 0 END), 0) as total_qty, "+
|
|
"COALESCE(AVG(CASE WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN COALESCE(pi.price, 0) END), 0) as price").
|
|
Joins("JOIN recording_stocks rs ON rs.recording_id = r.id").
|
|
Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id").
|
|
Joins("JOIN products p ON p.id = pw.product_id").
|
|
Joins("LEFT JOIN stock_allocations sa ON sa.usable_type = 'RECORDING_STOCK' AND sa.usable_id = rs.id AND sa.status = 'ACTIVE'").
|
|
Joins("LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = 'PURCHASE_ITEMS'").
|
|
Joins("LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = 'STOCK_TRANSFER_IN'").
|
|
Joins("LEFT JOIN laying_transfer_targets ltt ON ltt.id = sa.stockable_id AND sa.stockable_type = 'TRANSFERTOLAYING_IN'").
|
|
Joins("LEFT JOIN adjustment_stocks adjs ON adjs.id = sa.stockable_id AND sa.stockable_type = 'ADJUSTMENT_IN'").
|
|
Joins("LEFT JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = 'PROJECT_FLOCK_POPULATION'").
|
|
Where("r.project_flock_kandangs_id = ?", projectFlockKandangID).
|
|
Where("r.deleted_at IS NULL").
|
|
Group("pw.product_id, p.name").
|
|
Scan(&recordingsResults).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get recordings product usage: %w", err)
|
|
}
|
|
fmt.Printf("[REPO] Recordings query: %d results for projectFlockKandangID=%d\n", len(recordingsResults), projectFlockKandangID)
|
|
allResults = append(allResults, recordingsResults...)
|
|
|
|
// Subquery 2: Chickins
|
|
var chickinsResults []SubQueryResult
|
|
err = r.DB().WithContext(ctx).
|
|
Table("project_chickins pc").
|
|
Select("pw.product_id, p.name as product_name, "+
|
|
"COALESCE(SUM(pc.usage_qty), 0) as total_qty, "+
|
|
"COALESCE(AVG(COALESCE(pi.price, 0)), 0) as price").
|
|
Joins("JOIN product_warehouses pw ON pw.id = pc.product_warehouse_id").
|
|
Joins("JOIN products p ON p.id = pw.product_id").
|
|
Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
|
|
Where("pc.project_flock_kandang_id = ?", projectFlockKandangID).
|
|
Where("pc.usage_qty > 0").
|
|
Group("pw.product_id, p.name").
|
|
Scan(&chickinsResults).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get chickins product usage: %w", err)
|
|
}
|
|
fmt.Printf("[REPO] Chickins query: %d results for projectFlockKandangID=%d\n", len(chickinsResults), projectFlockKandangID)
|
|
allResults = append(allResults, chickinsResults...)
|
|
|
|
// Subquery 3: Marketing Delivery
|
|
var marketingResults []SubQueryResult
|
|
err = r.DB().WithContext(ctx).
|
|
Table("marketing_delivery_products mdp").
|
|
Select("pw.product_id, p.name as product_name, "+
|
|
"COALESCE(SUM(mdp.usage_qty), 0) as total_qty, "+
|
|
"COALESCE(AVG(COALESCE(pi.price, 0)), 0) as price").
|
|
Joins("JOIN marketing_products mp ON mp.id = mdp.marketing_product_id").
|
|
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
|
Joins("JOIN products p ON p.id = pw.product_id").
|
|
Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
|
|
Where("pw.project_flock_kandang_id = ?", projectFlockKandangID).
|
|
Group("pw.product_id, p.name").
|
|
Scan(&marketingResults).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get marketing product usage: %w", err)
|
|
}
|
|
fmt.Printf("[REPO] Marketing query: %d results for projectFlockKandangID=%d\n", len(marketingResults), projectFlockKandangID)
|
|
allResults = append(allResults, marketingResults...)
|
|
|
|
// Subquery 4: Laying Transfer Sources
|
|
var layingTransferResults []SubQueryResult
|
|
err = r.DB().WithContext(ctx).
|
|
Table("laying_transfer_sources lts").
|
|
Select("pw.product_id, p.name as product_name, "+
|
|
"COALESCE(SUM(lts.usage_qty), 0) as total_qty, "+
|
|
"COALESCE(AVG(COALESCE(pi.price, 0)), 0) as price").
|
|
Joins("JOIN laying_transfers lt ON lt.id = lts.laying_transfer_id").
|
|
Joins("JOIN product_warehouses pw ON pw.id = lts.product_warehouse_id").
|
|
Joins("JOIN products p ON p.id = pw.product_id").
|
|
Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
|
|
Where("pw.project_flock_kandang_id = ?", projectFlockKandangID).
|
|
Group("pw.product_id, p.name").
|
|
Scan(&layingTransferResults).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get laying transfer product usage: %w", err)
|
|
}
|
|
fmt.Printf("[REPO] Laying Transfer query: %d results for projectFlockKandangID=%d\n", len(layingTransferResults), projectFlockKandangID)
|
|
allResults = append(allResults, layingTransferResults...)
|
|
|
|
// Subquery 5: Stock Transfer Details
|
|
var stockTransferResults []SubQueryResult
|
|
err = r.DB().WithContext(ctx).
|
|
Table("stock_transfer_details std").
|
|
Select("pw.product_id, p.name as product_name, "+
|
|
"COALESCE(SUM(std.usage_qty), 0) as total_qty, "+
|
|
"COALESCE(AVG(COALESCE(pi.price, 0)), 0) as price").
|
|
Joins("JOIN product_warehouses pw ON pw.id = std.source_product_warehouse_id").
|
|
Joins("JOIN products p ON p.id = std.product_id").
|
|
Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
|
|
Where("pw.project_flock_kandang_id = ?", projectFlockKandangID).
|
|
Group("pw.product_id, p.name").
|
|
Scan(&stockTransferResults).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get stock transfer product usage: %w", err)
|
|
}
|
|
fmt.Printf("[REPO] Stock Transfer query: %d results for projectFlockKandangID=%d\n", len(stockTransferResults), projectFlockKandangID)
|
|
allResults = append(allResults, stockTransferResults...)
|
|
|
|
// Subquery 6: Adjustment Stocks
|
|
var adjustmentResults []SubQueryResult
|
|
err = r.DB().WithContext(ctx).
|
|
Table("adjustment_stocks ads").
|
|
Select("pw.product_id, p.name as product_name, "+
|
|
"COALESCE(SUM(ads.usage_qty), 0) as total_qty, "+
|
|
"COALESCE(AVG(COALESCE(pi.price, 0)), 0) as price").
|
|
Joins("JOIN product_warehouses pw ON pw.id = ads.product_warehouse_id").
|
|
Joins("JOIN products p ON p.id = pw.product_id").
|
|
Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
|
|
Where("pw.project_flock_kandang_id = ?", projectFlockKandangID).
|
|
Where("ads.usage_qty > 0").
|
|
Group("pw.product_id, p.name").
|
|
Scan(&adjustmentResults).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get adjustment product usage: %w", err)
|
|
}
|
|
fmt.Printf("[REPO] Adjustment query: %d results for projectFlockKandangID=%d\n", len(adjustmentResults), projectFlockKandangID)
|
|
allResults = append(allResults, adjustmentResults...)
|
|
|
|
fmt.Printf("[REPO] Total raw results before aggregation: %d items\n", len(allResults))
|
|
|
|
// Aggregate results by product_id
|
|
aggregatedMap := make(map[uint]*AggregatedResult)
|
|
for _, result := range allResults {
|
|
key := result.ProductID
|
|
if existing, exists := aggregatedMap[key]; exists {
|
|
existing.TotalQty += result.TotalQty
|
|
existing.Price += result.Price
|
|
existing.PriceCount++
|
|
} else {
|
|
aggregatedMap[key] = &AggregatedResult{
|
|
ProductID: result.ProductID,
|
|
ProductName: result.ProductName,
|
|
TotalQty: result.TotalQty,
|
|
Price: result.Price,
|
|
PriceCount: 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Printf("[REPO] Aggregated to %d unique products\n", len(aggregatedMap))
|
|
|
|
// Get flags for all products
|
|
productIDs := make([]uint, 0, len(aggregatedMap))
|
|
for id := range aggregatedMap {
|
|
productIDs = append(productIDs, id)
|
|
}
|
|
|
|
var flagResults []FlagResult
|
|
if len(productIDs) > 0 {
|
|
err = r.DB().WithContext(ctx).
|
|
Table("products p").
|
|
Select("p.id as product_id, STRING_AGG(DISTINCT f.name, ', ') as flag_names").
|
|
Joins("LEFT JOIN flags f ON f.flagable_type = 'products' AND f.flagable_id = p.id").
|
|
Where("p.id IN ?", productIDs).
|
|
Group("p.id").
|
|
Scan(&flagResults).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get product flags: %w", err)
|
|
}
|
|
}
|
|
fmt.Printf("[REPO] Fetched flags for %d products\n", len(flagResults))
|
|
|
|
// Build flag map
|
|
flagMap := make(map[uint]string)
|
|
for _, flag := range flagResults {
|
|
flagMap[flag.ProductID] = flag.FlagNames
|
|
}
|
|
|
|
// Combine results and calculate average price
|
|
results := make([]ProductUsageRow, 0, len(aggregatedMap))
|
|
for _, agg := range aggregatedMap {
|
|
avgPrice := float64(0)
|
|
if agg.PriceCount > 0 {
|
|
avgPrice = agg.Price / float64(agg.PriceCount)
|
|
}
|
|
|
|
flagNames := flagMap[agg.ProductID]
|
|
|
|
// Apply flag filters if provided
|
|
if len(flagFilters) > 0 {
|
|
// Check if any of the flagFilters exist in flagNames
|
|
matched := false
|
|
for _, filter := range flagFilters {
|
|
if containsIgnoreCase(flagNames, filter) {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
if !matched {
|
|
continue // Skip this product if no flag matches
|
|
}
|
|
}
|
|
|
|
results = append(results, ProductUsageRow{
|
|
ProductID: agg.ProductID,
|
|
ProductName: agg.ProductName,
|
|
FlagNames: flagNames,
|
|
TotalQty: agg.TotalQty,
|
|
Price: avgPrice,
|
|
TotalPengeluaran: agg.TotalQty * avgPrice,
|
|
})
|
|
}
|
|
|
|
fmt.Printf("[REPO] After filtering with flagFilters=%v: %d results\n", flagFilters, len(results))
|
|
for i, r := range results {
|
|
fmt.Printf("[REPO] Result[%d]: ProductID=%d, ProductName=%s, FlagNames=%s, TotalQty=%.2f, Price=%.2f, TotalPengeluaran=%.2f\n",
|
|
i, r.ProductID, r.ProductName, r.FlagNames, r.TotalQty, r.Price, r.TotalPengeluaran)
|
|
}
|
|
|
|
// Sort by product name
|
|
sort.Slice(results, func(i, j int) bool {
|
|
return results[i].ProductName < results[j].ProductName
|
|
})
|
|
|
|
fmt.Printf("[REPO] Final sorted results: %d items\n", len(results))
|
|
return results, nil
|
|
}
|
|
|
|
// GetTotalDepletionByProjectFlockKandangID gets total depletion for a specific kandang
|
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalDepletionByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
|
|
var result float64
|
|
err := r.DB().WithContext(ctx).
|
|
Table("recording_depletions").
|
|
Select("COALESCE(SUM(recording_depletions.qty), 0)").
|
|
Joins("JOIN recordings ON recordings.id = recording_depletions.recording_id").
|
|
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = recordings.project_flock_kandangs_id").
|
|
Where("project_flock_kandangs.id = ?", projectFlockKandangID).
|
|
Scan(&result).Error
|
|
return result, err
|
|
}
|
|
|
|
// GetTotalWeightProducedFromUniformityByProjectFlockKandangID calculates total weight produced from uniformity data for a specific kandang
|
|
// Formula: (mean_up / 1.10) * chick_qty_of_weight / 1000
|
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalWeightProducedFromUniformityByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
|
|
if projectFlockKandangID == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
var uniformity struct {
|
|
MeanUp float64
|
|
ChickQtyOfWeight float64
|
|
}
|
|
|
|
err := r.DB().WithContext(ctx).
|
|
Table("project_flock_kandang_uniformity").
|
|
Select("mean_up, chick_qty_of_weight").
|
|
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
|
Order("id DESC").
|
|
Limit(1).
|
|
Scan(&uniformity).Error
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Calculate weight: (mean_up / 1.10) * chick_qty_of_weight / 1000
|
|
totalWeight := (uniformity.MeanUp / 1.10) * uniformity.ChickQtyOfWeight / 1000
|
|
|
|
return totalWeight, nil
|
|
}
|
|
|
|
// containsIgnoreCase checks if a string contains a substring (case-insensitive)
|
|
func containsIgnoreCase(str, substr string) bool {
|
|
return strings.Contains(strings.ToUpper(str), strings.ToUpper(substr))
|
|
}
|