Files
lti-api/internal/modules/repports/repositories/expense_depreciation.repository.go
T
2026-04-19 15:10:53 +07:00

330 lines
9.3 KiB
Go

package repositories
import (
"context"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type FarmDepreciationCandidateRow struct {
ProjectFlockID uint
FarmName string
}
type FarmDepreciationLatestTransferRow struct {
ProjectFlockID uint
FarmName string
ProjectFlockKandangID uint
KandangID uint
KandangName string
HouseType *string
SourceProjectFlockID uint
TransferDate time.Time
TransferQty float64
TransferID uint
}
type FarmDepreciationManualInputRow struct {
Id uint
ProjectFlockID uint
FarmName string
TotalCost float64
CutoverDate time.Time
Note *string
}
type houseDepreciationPercentRow struct {
HouseType string
Day int
DepreciationPercent float64
}
type ExpenseDepreciationRepository interface {
GetCandidateFarms(ctx context.Context, period time.Time, areaIDs, locationIDs, projectFlockIDs []int64) ([]FarmDepreciationCandidateRow, error)
GetSnapshotsByPeriodAndFarmIDs(ctx context.Context, period time.Time, farmIDs []uint) ([]entity.FarmDepreciationSnapshot, error)
UpsertSnapshots(ctx context.Context, rows []entity.FarmDepreciationSnapshot) error
DeleteSnapshotsFromDate(ctx context.Context, fromDate time.Time, farmIDs []uint) error
GetLatestTransferInputsByFarms(ctx context.Context, period time.Time, farmIDs []uint) ([]FarmDepreciationLatestTransferRow, error)
GetDepreciationPercents(ctx context.Context, houseTypes []string, maxDay int) (map[string]map[int]float64, error)
GetLatestManualInputsByFarms(ctx context.Context, areaIDs, locationIDs, projectFlockIDs []int64) ([]FarmDepreciationManualInputRow, error)
UpsertManualInput(ctx context.Context, row *entity.FarmDepreciationManualInput) error
DB() *gorm.DB
}
type expenseDepreciationRepository struct {
db *gorm.DB
}
func NewExpenseDepreciationRepository(db *gorm.DB) ExpenseDepreciationRepository {
return &expenseDepreciationRepository{db: db}
}
func (r *expenseDepreciationRepository) DB() *gorm.DB {
return r.db
}
func (r *expenseDepreciationRepository) GetCandidateFarms(
ctx context.Context,
period time.Time,
areaIDs, locationIDs, projectFlockIDs []int64,
) ([]FarmDepreciationCandidateRow, error) {
rows := make([]FarmDepreciationCandidateRow, 0)
query := r.db.WithContext(ctx).
Table("project_flocks AS pf").
Select("DISTINCT pf.id AS project_flock_id, pf.flock_name AS farm_name").
Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
Where("pf.deleted_at IS NULL").
Where("pf.category = ?", utils.ProjectFlockCategoryLaying).
Where("(pfk.closed_at IS NULL OR DATE(pfk.closed_at) >= DATE(?))", period)
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 len(projectFlockIDs) > 0 {
query = query.Where("pf.id IN ?", projectFlockIDs)
}
if err := query.Order("pf.id ASC").Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func (r *expenseDepreciationRepository) GetSnapshotsByPeriodAndFarmIDs(
ctx context.Context,
period time.Time,
farmIDs []uint,
) ([]entity.FarmDepreciationSnapshot, error) {
if len(farmIDs) == 0 {
return []entity.FarmDepreciationSnapshot{}, nil
}
rows := make([]entity.FarmDepreciationSnapshot, 0)
if err := r.db.WithContext(ctx).
Where("project_flock_id IN ?", farmIDs).
Where("period_date = DATE(?)", period).
Find(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func (r *expenseDepreciationRepository) UpsertSnapshots(ctx context.Context, rows []entity.FarmDepreciationSnapshot) error {
if len(rows) == 0 {
return nil
}
return r.db.WithContext(ctx).
Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "project_flock_id"},
{Name: "period_date"},
},
DoUpdates: clause.AssignmentColumns([]string{
"depreciation_percent_effective",
"depreciation_value",
"pullet_cost_day_n_total",
"components",
"updated_at",
}),
}).
Create(&rows).Error
}
func (r *expenseDepreciationRepository) DeleteSnapshotsFromDate(
ctx context.Context,
fromDate time.Time,
farmIDs []uint,
) error {
if fromDate.IsZero() {
return nil
}
query := r.db.WithContext(ctx).
Table("farm_depreciation_snapshots").
Where("period_date >= DATE(?)", fromDate)
if len(farmIDs) > 0 {
query = query.Where("project_flock_id IN ?", farmIDs)
}
return query.Delete(nil).Error
}
func (r *expenseDepreciationRepository) GetLatestTransferInputsByFarms(
ctx context.Context,
period time.Time,
farmIDs []uint,
) ([]FarmDepreciationLatestTransferRow, error) {
if len(farmIDs) == 0 {
return []FarmDepreciationLatestTransferRow{}, nil
}
rows := make([]FarmDepreciationLatestTransferRow, 0)
query := `
WITH latest_transfer_approval AS (
SELECT a.approvable_id, a.action
FROM approvals a
JOIN (
SELECT approvable_id, MAX(action_at) AS latest_action_at
FROM approvals
WHERE approvable_type = @approval_type
GROUP BY approvable_id
) la
ON la.approvable_id = a.approvable_id
AND la.latest_action_at = a.action_at
WHERE a.approvable_type = @approval_type
),
approved_transfers AS (
SELECT
lt.id,
lt.from_project_flock_id,
lt.to_project_flock_id,
COALESCE(DATE(lt.effective_move_date), DATE(lt.economic_cutoff_date), DATE(lt.transfer_date)) AS effective_date
FROM laying_transfers lt
JOIN latest_transfer_approval lta ON lta.approvable_id = lt.id
WHERE lt.deleted_at IS NULL
AND lt.executed_at IS NOT NULL
AND lta.action = 'APPROVED'
)
SELECT DISTINCT ON (ltt.target_project_flock_kandang_id)
pf.id AS project_flock_id,
pf.flock_name AS farm_name,
pfk.id AS project_flock_kandang_id,
k.id AS kandang_id,
k.name AS kandang_name,
k.house_type::text AS house_type,
at.from_project_flock_id AS source_project_flock_id,
at.effective_date AS transfer_date,
ltt.total_qty AS transfer_qty,
at.id AS transfer_id
FROM laying_transfer_targets ltt
JOIN approved_transfers at ON at.id = ltt.laying_transfer_id
JOIN project_flock_kandangs pfk ON pfk.id = ltt.target_project_flock_kandang_id
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
JOIN kandangs k ON k.id = pfk.kandang_id
WHERE ltt.deleted_at IS NULL
AND pf.id IN @farm_ids
AND at.effective_date <= DATE(@period_date)
ORDER BY ltt.target_project_flock_kandang_id, at.effective_date DESC, at.id DESC
`
if err := r.db.WithContext(ctx).Raw(query, map[string]any{
"approval_type": utils.ApprovalWorkflowTransferToLaying.String(),
"farm_ids": farmIDs,
"period_date": period,
}).Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func (r *expenseDepreciationRepository) GetDepreciationPercents(
ctx context.Context,
houseTypes []string,
maxDay int,
) (map[string]map[int]float64, error) {
result := make(map[string]map[int]float64)
if len(houseTypes) == 0 || maxDay <= 0 {
return result, nil
}
rows := make([]houseDepreciationPercentRow, 0)
if err := r.db.WithContext(ctx).
Table("house_depreciation_standards").
Select("house_type::text AS house_type, day, depreciation_percent").
Where("house_type::text IN ?", houseTypes).
Where("day <= ?", maxDay).
Order("house_type ASC, day ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
for _, row := range rows {
if _, exists := result[row.HouseType]; !exists {
result[row.HouseType] = make(map[int]float64)
}
result[row.HouseType][row.Day] = row.DepreciationPercent
}
return result, nil
}
func (r *expenseDepreciationRepository) GetLatestManualInputsByFarms(
ctx context.Context,
areaIDs, locationIDs, projectFlockIDs []int64,
) ([]FarmDepreciationManualInputRow, error) {
rows := make([]FarmDepreciationManualInputRow, 0)
query := r.db.WithContext(ctx).
Table("farm_depreciation_manual_inputs AS fdmi").
Select(`
fdmi.id AS id,
fdmi.project_flock_id AS project_flock_id,
pf.flock_name AS farm_name,
fdmi.total_cost AS total_cost,
fdmi.cutover_date AS cutover_date,
fdmi.note AS note
`).
Joins("JOIN project_flocks AS pf ON pf.id = fdmi.project_flock_id").
Where("pf.deleted_at IS NULL").
Where("pf.category = ?", utils.ProjectFlockCategoryLaying)
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 len(projectFlockIDs) > 0 {
query = query.Where("pf.id IN ?", projectFlockIDs)
}
if err := query.
Order("pf.id ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func (r *expenseDepreciationRepository) UpsertManualInput(ctx context.Context, row *entity.FarmDepreciationManualInput) error {
if row == nil {
return nil
}
now := time.Now().UTC()
err := r.db.WithContext(ctx).
Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "project_flock_id"},
},
DoUpdates: clause.Assignments(map[string]any{
"total_cost": row.TotalCost,
"cutover_date": row.CutoverDate,
"note": row.Note,
"updated_at": now,
}),
}).
Create(row).Error
if err != nil {
return err
}
return r.db.WithContext(ctx).
Table("farm_depreciation_manual_inputs").
Select("id, project_flock_id, total_cost, cutover_date, note").
Where("project_flock_id = ?", row.ProjectFlockId).
Take(row).Error
}