Compare commits

..

14 Commits

Author SHA1 Message Date
Giovanni Gabriel Septriadi 7dc4592f08 Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!616
2026-06-09 04:28:22 +00:00
Giovanni Gabriel Septriadi 690db8b485 Merge branch 'fix/kandang-group' into 'development'
fix get list kandang group

See merge request mbugroup/lti-api!615
2026-06-09 04:17:07 +00:00
giovanni 22bf66dbb9 fix get list kandang group 2026-06-09 11:16:06 +07:00
Giovanni Gabriel Septriadi f836685253 Merge branch 'feat/usage-sapronak-price' into 'development'
add response detail recording

See merge request mbugroup/lti-api!614
2026-06-09 02:46:09 +00:00
giovanni 5e9286428f add response detail recording 2026-06-09 09:44:55 +07:00
Giovanni Gabriel Septriadi 61e15dd95d Merge branch 'fix/non-active-phase' into 'development'
Fix/non active phase

See merge request mbugroup/lti-api!613
2026-06-09 01:35:01 +00:00
giovanni 59d72f20b4 non active phase daily checklist 2026-06-08 21:01:20 +07:00
Giovanni Gabriel Septriadi 540434e33b Merge branch 'development' into 'production'
Development

See merge request mbugroup/lti-api!612
2026-06-08 05:51:32 +00:00
Giovanni Gabriel Septriadi 0ebad48348 Merge branch 'fix/depretiatio-response' into 'development'
adjust total bobot laporan keuangan

See merge request mbugroup/lti-api!611
2026-06-08 05:39:09 +00:00
giovanni 9ab4e1a6ef adjust total bobot laporan keuangan 2026-06-08 12:37:39 +07:00
Giovanni Gabriel Septriadi 0a900986e7 Merge branch 'fix/depretiatio-response' into 'development'
adjust response depretitation v2

See merge request mbugroup/lti-api!610
2026-06-08 05:32:28 +00:00
giovanni 217f35b250 adjust response depretitation v2 2026-06-08 12:30:51 +07:00
Giovanni Gabriel Septriadi b3887b8d08 Merge branch 'hotfix/manual-inputs' into 'production'
fix data manual input; remove update manual input from crud recording

See merge request mbugroup/lti-api!608
2026-06-07 15:20:01 +00:00
Giovanni Gabriel Septriadi 2ddfa57aed Merge branch 'hotfix/manual-inputs' into 'development'
Hotfix/manual inputs

See merge request mbugroup/lti-api!609
2026-06-07 15:01:22 +00:00
19 changed files with 425 additions and 75 deletions
@@ -96,6 +96,7 @@ type HppV2FarmDepreciationSnapshotRow struct {
DepreciationPercentEffective float64
DepreciationValue float64
PulletCostDayNTotal float64
Components []byte
}
type HppV2CostRepository interface {
@@ -404,7 +405,7 @@ func (r *HppV2RepositoryImpl) GetFarmDepreciationSnapshotByProjectFlockIDAndPeri
var row HppV2FarmDepreciationSnapshotRow
err := r.db.WithContext(ctx).
Table("farm_depreciation_snapshots").
Select("id, project_flock_id, period_date, depreciation_percent_effective, depreciation_value, pullet_cost_day_n_total").
Select("id, project_flock_id, period_date, depreciation_percent_effective, depreciation_value, pullet_cost_day_n_total, components").
Where("project_flock_id = ?", projectFlockID).
Where("period_date = DATE(?)", periodDate).
Limit(1).
@@ -2,6 +2,7 @@ package service
import (
"context"
"encoding/json"
"time"
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
@@ -1472,6 +1473,18 @@ func (s *hppV2Service) buildFarmSnapshotDepreciationPart(
depreciationPercent = (appliedDepreciation / appliedPulletCostDayN) * 100
}
details := map[string]any{
"basis_total": snapshot.DepreciationValue,
"pullet_cost_day_n": appliedPulletCostDayN,
"depreciation_percent": depreciationPercent,
"snapshot_id": snapshot.ID,
"snapshot_period_date": formatDateOnly(snapshot.PeriodDate),
"snapshot_project_flock": snapshot.ProjectFlockID,
}
for key, value := range farmDepreciationSnapshotMetadata(snapshot.Components, projectFlockKandangId) {
details[key] = value
}
return &HppV2ComponentPart{
Code: hppV2PartDepreciationFarmSnapshot,
Title: "Farm Snapshot",
@@ -1483,14 +1496,7 @@ func (s *hppV2Service) buildFarmSnapshotDepreciationPart(
Denominator: denominator,
Ratio: ratio,
},
Details: map[string]any{
"basis_total": snapshot.DepreciationValue,
"pullet_cost_day_n": appliedPulletCostDayN,
"depreciation_percent": depreciationPercent,
"snapshot_id": snapshot.ID,
"snapshot_period_date": formatDateOnly(snapshot.PeriodDate),
"snapshot_project_flock": snapshot.ProjectFlockID,
},
Details: details,
References: []HppV2Reference{
{
Type: "farm_depreciation_snapshot",
@@ -1504,6 +1510,84 @@ func (s *hppV2Service) buildFarmSnapshotDepreciationPart(
}, nil
}
type farmDepreciationSnapshotComponents struct {
Kandang []farmDepreciationSnapshotKandangComponent `json:"kandang"`
}
type farmDepreciationSnapshotKandangComponent struct {
ProjectFlockKandangID uint `json:"project_flock_kandang_id"`
DayN int `json:"day_n"`
MultiplicationPercent float64 `json:"multiplication_percentage"`
ChickinDate string `json:"chickin_date"`
OriginDate string `json:"origin_date"`
StandardEffectiveDate string `json:"standard_effective_date"`
Population float64 `json:"population"`
}
func farmDepreciationSnapshotMetadata(raw []byte, projectFlockKandangID uint) map[string]any {
result := make(map[string]any)
if len(raw) == 0 {
return result
}
var components farmDepreciationSnapshotComponents
if err := json.Unmarshal(raw, &components); err != nil {
return result
}
var fallback *farmDepreciationSnapshotKandangComponent
for i := range components.Kandang {
component := &components.Kandang[i]
if !component.hasDepreciationMetadata() {
continue
}
if component.ProjectFlockKandangID == projectFlockKandangID {
return component.snapshotDetails()
}
if fallback == nil {
fallback = component
}
}
if fallback != nil {
return fallback.snapshotDetails()
}
return result
}
func (c farmDepreciationSnapshotKandangComponent) hasDepreciationMetadata() bool {
return c.DayN > 0 ||
c.MultiplicationPercent > 0 ||
c.ChickinDate != "" ||
c.OriginDate != "" ||
c.StandardEffectiveDate != "" ||
c.Population > 0
}
func (c farmDepreciationSnapshotKandangComponent) snapshotDetails() map[string]any {
chickinDate := c.ChickinDate
if chickinDate == "" {
chickinDate = c.OriginDate
}
details := map[string]any{
"schedule_day": c.DayN,
"multiplication_percentage": c.MultiplicationPercent,
}
if chickinDate != "" {
details["origin_date"] = chickinDate
details["chickin_date"] = chickinDate
}
if c.StandardEffectiveDate != "" {
details["standard_effective_date"] = c.StandardEffectiveDate
}
if c.Population > 0 {
details["kandang_population"] = c.Population
}
return details
}
func (s *hppV2Service) buildNormalTransferDepreciationPart(
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
transferInput *commonRepo.HppV2LatestTransferInputRow,
@@ -825,6 +825,28 @@ func TestHppV2CalculateHppBreakdown_UsesFarmSnapshotDepreciationProratedByEggPro
DepreciationPercentEffective: 10,
DepreciationValue: 1000,
PulletCostDayNTotal: 10000,
Components: []byte(`{
"kandang_count": 2,
"total_population": 1000,
"kandang": [
{
"project_flock_kandang_id": 71,
"day_n": 5,
"multiplication_percentage": 0.95,
"chickin_date": "2026-01-02",
"standard_effective_date": "2026-06-01",
"population": 800
},
{
"project_flock_kandang_id": 70,
"day_n": 7,
"multiplication_percentage": 0.93,
"chickin_date": "2026-01-01",
"standard_effective_date": "2026-06-02",
"population": 200
}
]
}`),
},
},
eggProductionByPFK: map[uint]struct {
@@ -873,6 +895,21 @@ func TestHppV2CalculateHppBreakdown_UsesFarmSnapshotDepreciationProratedByEggPro
if depreciation.Parts[0].Details["snapshot_id"] != uint(901) {
t.Fatalf("expected snapshot id 901, got %+v", depreciation.Parts[0].Details)
}
if depreciation.Parts[0].Details["schedule_day"] != 7 {
t.Fatalf("expected snapshot schedule_day 7, got %+v", depreciation.Parts[0].Details)
}
if depreciation.Parts[0].Details["multiplication_percentage"] != 0.93 {
t.Fatalf("expected snapshot multiplication_percentage 0.93, got %+v", depreciation.Parts[0].Details)
}
if depreciation.Parts[0].Details["chickin_date"] != "2026-01-01" {
t.Fatalf("expected snapshot chickin_date 2026-01-01, got %+v", depreciation.Parts[0].Details)
}
if depreciation.Parts[0].Details["standard_effective_date"] != "2026-06-02" {
t.Fatalf("expected snapshot standard_effective_date 2026-06-02, got %+v", depreciation.Parts[0].Details)
}
if depreciation.Parts[0].Details["kandang_population"] != float64(200) {
t.Fatalf("expected snapshot kandang_population 200, got %+v", depreciation.Parts[0].Details)
}
}
func stubKey(ids []uint, flags []string) string {
@@ -0,0 +1,2 @@
UPDATE phases SET is_active = true
WHERE id IN (2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26);
@@ -0,0 +1,2 @@
UPDATE phases SET is_active = false
WHERE id IN (2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26);
+13
View File
@@ -7,7 +7,20 @@ type RecordingStock struct {
ProjectFlockKandangId *uint `gorm:"column:project_flock_kandang_id;index"`
UsageQty *float64 `gorm:"column:usage_qty"`
PendingQty *float64 `gorm:"column:pending_qty"`
TotalPrice float64 `gorm:"-"`
Allocations []RecordingStockAlloc `gorm:"-"`
Recording Recording `gorm:"foreignKey:RecordingId;references:Id"`
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
}
type RecordingStockAlloc struct {
SourceType string
SourceId uint
PrNumber string
PoNumber string
AdjNumber string
Qty float64
UnitPrice float64
Subtotal float64
}
@@ -1215,7 +1215,9 @@ func (s dailyChecklistService) AssignPhases(c *fiber.Ctx, id uint, req *validati
}
if len(phaseIDs) > 0 {
phases, err := s.PhaseRepo.GetByIDs(c.Context(), phaseIDs, nil)
phases, err := s.PhaseRepo.GetByIDs(c.Context(), phaseIDs, func(db *gorm.DB) *gorm.DB {
return db.Where("is_active = true")
})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusBadRequest, "Phase not found")
@@ -72,9 +72,9 @@ func (s kandangGroupService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
}
if params.OrderBy == "desc" || params.OrderBy == "" {
db = db.Order(fmt.Sprintf("kandang_groups.%s DESC", params.SortBy))
db = db.Order(fmt.Sprintf("kandang_groups.%s DESC, kandang_groups.id ASC", params.SortBy))
} else {
db = db.Order(fmt.Sprintf("kandang_groups.%s ASC", params.SortBy))
db = db.Order(fmt.Sprintf("kandang_groups.%s ASC, kandang_groups.id ASC", params.SortBy))
}
return db
@@ -20,6 +20,6 @@ type Query struct {
Search string `query:"search" validate:"omitempty,max=50"`
LocationId int `query:"location_id" validate:"omitempty,number,gt=0"`
PicId int `query:"pic_id" validate:"omitempty,number,gt=0"`
SortBy string `query:"sort_by" validate:"omitempty,max=50,oneof=name created_at updated_at" default:"updated_at"`
OrderBy string `query:"order_by" validate:"omitempty,oneof=asc desc" default:"desc"`
SortBy string `query:"sort_by" validate:"omitempty,max=50,oneof=name created_at updated_at" default:"name"`
OrderBy string `query:"order_by" validate:"omitempty,oneof=asc desc" default:"asc"`
}
@@ -50,6 +50,7 @@ func (s phasesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.
phasess, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
db = s.withRelations(db)
db = db.Where("is_active = true")
if params.Search != "" {
return db.Where("name ILIKE ?", "%"+params.Search+"%")
}
@@ -131,10 +131,23 @@ type RecordingDepletionDTO struct {
ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
}
type RecordingStockAllocDTO struct {
SourceType string `json:"source_type"`
SourceId uint `json:"source_id"`
PrNumber string `json:"pr_number"`
PoNumber string `json:"po_number"`
AdjNumber string `json:"adj_number"`
Qty float64 `json:"qty"`
UnitPrice float64 `json:"unit_price"`
Subtotal float64 `json:"subtotal"`
}
type RecordingStockDTO struct {
ProductWarehouseId uint `json:"product_warehouse_id"`
UsageAmount float64 `json:"usage_amount"`
PendingQty float64 `json:"pending_qty"`
TotalPrice float64 `json:"total_price"`
Allocations []RecordingStockAllocDTO `json:"allocations"`
ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
}
@@ -197,10 +210,26 @@ func ToRecordingStockDTOs(stocks []entity.RecordingStock) []RecordingStockDTO {
pendingQty = *s.PendingQty
}
allocs := make([]RecordingStockAllocDTO, len(s.Allocations))
for j, a := range s.Allocations {
allocs[j] = RecordingStockAllocDTO{
SourceType: a.SourceType,
SourceId: a.SourceId,
PrNumber: a.PrNumber,
PoNumber: a.PoNumber,
AdjNumber: a.AdjNumber,
Qty: a.Qty,
UnitPrice: a.UnitPrice,
Subtotal: a.Subtotal,
}
}
result[i] = RecordingStockDTO{
ProductWarehouseId: s.ProductWarehouseId,
UsageAmount: usageAmount,
PendingQty: pendingQty,
TotalPrice: s.TotalPrice,
Allocations: allocs,
ProductWarehouse: mapProductWarehouseDTO(&s.ProductWarehouse),
}
}
@@ -80,6 +80,7 @@ type RecordingRepository interface {
ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error
ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error)
GetProgressRows(ctx context.Context, startDate, endDate time.Time, allowedLocationIDs []uint, restrict bool) ([]exportprogress.Row, error)
GetStockAllocationsByIDs(ctx context.Context, stockIDs []uint) (map[uint][]entity.RecordingStockAlloc, error)
}
type RecordingRepositoryImpl struct {
@@ -1231,3 +1232,71 @@ func (r *RecordingRepositoryImpl) GetTotalWeightProducedFromUniformityByProjectF
return result.TotalWeight, err
}
func (r *RecordingRepositoryImpl) GetStockAllocationsByIDs(ctx context.Context, stockIDs []uint) (map[uint][]entity.RecordingStockAlloc, error) {
if len(stockIDs) == 0 {
return map[uint][]entity.RecordingStockAlloc{}, nil
}
type row struct {
RecordingStockId uint
SourceType string
SourceId uint
PrNumber string
PoNumber string
AdjNumber string
Qty float64
UnitPrice float64
Subtotal float64
}
var rows []row
err := r.DB().WithContext(ctx).Raw(`
SELECT
sa.usable_id AS recording_stock_id,
sa.stockable_type AS source_type,
sa.stockable_id AS source_id,
COALESCE(p.pr_number, '') AS pr_number,
COALESCE(p.po_number, '') AS po_number,
COALESCE(ast.adj_number, '') AS adj_number,
sa.qty AS qty,
COALESCE(CASE
WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN pi.price
WHEN sa.stockable_type = 'ADJUSTMENT_IN' THEN ast.price
END, 0) AS unit_price,
sa.qty * COALESCE(CASE
WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN pi.price
WHEN sa.stockable_type = 'ADJUSTMENT_IN' THEN ast.price
END, 0) AS subtotal
FROM stock_allocations sa
LEFT JOIN purchase_items pi
ON pi.id = sa.stockable_id AND sa.stockable_type = 'PURCHASE_ITEMS'
LEFT JOIN purchases p
ON p.id = pi.purchase_id
LEFT JOIN adjustment_stocks ast
ON ast.id = sa.stockable_id AND sa.stockable_type = 'ADJUSTMENT_IN'
WHERE sa.usable_type = 'RECORDING_STOCK'
AND sa.usable_id IN ?
AND sa.status = 'ACTIVE'
AND sa.allocation_purpose = 'CONSUME'
ORDER BY sa.usable_id, sa.id
`, stockIDs).Scan(&rows).Error
if err != nil {
return nil, err
}
result := make(map[uint][]entity.RecordingStockAlloc)
for _, row := range rows {
result[row.RecordingStockId] = append(result[row.RecordingStockId], entity.RecordingStockAlloc{
SourceType: row.SourceType,
SourceId: row.SourceId,
PrNumber: row.PrNumber,
PoNumber: row.PoNumber,
AdjNumber: row.AdjNumber,
Qty: row.Qty,
UnitPrice: row.UnitPrice,
Subtotal: row.Subtotal,
})
}
return result, nil
}
@@ -278,6 +278,26 @@ func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, erro
s.Log.Errorf("Failed get recording by id: %+v", err)
return nil, err
}
if len(recording.Stocks) > 0 {
stockIDs := make([]uint, len(recording.Stocks))
for i, s := range recording.Stocks {
stockIDs[i] = s.Id
}
if allocMap, err := s.Repository.GetStockAllocationsByIDs(c.Context(), stockIDs); err != nil {
s.Log.Warnf("Failed to get stock allocations for recording %d: %+v", id, err)
} else {
for i := range recording.Stocks {
allocs := allocMap[recording.Stocks[i].Id]
recording.Stocks[i].Allocations = allocs
var total float64
for _, a := range allocs {
total += a.Subtotal
}
recording.Stocks[i].TotalPrice = total
}
}
}
if err := recordingutil.AttachLatestApproval(c.Context(), recording, s.ApprovalSvc, s.Log); err != nil {
return nil, err
}
@@ -50,7 +50,7 @@ type ExpenseDepreciationV2MetaDTO struct {
}
type ExpenseDepreciationV2RowDTO struct {
Date string `json:"date"`
Date string `json:"date"`
DepreciationPercentEffective float64 `json:"depreciation_percent_effective"`
DepreciationValue float64 `json:"depreciation_value"`
PulletCostDayNTotal float64 `json:"pullet_cost_day_n_total"`
@@ -60,7 +60,6 @@ type ExpenseDepreciationV2RowDTO struct {
TotalValuePulletAfterDepreciation float64 `json:"total_value_pullet_after_depreciation"`
StandardEffectiveDate string `json:"standard_effective_date,omitempty"`
TotalPopulation float64 `json:"total_population"`
Components any `json:"components"`
}
func NewExpenseDepreciationFiltersDTO(area, location, projectFlockID, period string) ExpenseDepreciationFiltersDTO {
@@ -0,0 +1,51 @@
package dto
import (
"encoding/json"
"testing"
)
func TestExpenseDepreciationRowDTOComponentsJSONContract(t *testing.T) {
v1 := ExpenseDepreciationRowDTO{
ProjectFlockID: 1,
FarmName: "Farm A",
Period: "2026-06-05",
Components: map[string]any{"kandang_count": 1},
}
rawV1, err := json.Marshal(v1)
if err != nil {
t.Fatalf("marshal v1 dto: %v", err)
}
var decodedV1 map[string]any
if err := json.Unmarshal(rawV1, &decodedV1); err != nil {
t.Fatalf("unmarshal v1 dto: %v", err)
}
if _, ok := decodedV1["components"]; !ok {
t.Fatalf("expected v1 components to be present, got %s", string(rawV1))
}
v2 := ExpenseDepreciationV2RowDTO{
Date: "2026-06-05",
DepreciationPercentEffective: 10,
DepreciationValue: 100,
PulletCostDayNTotal: 1000,
MultiplicationPercentage: 0.9,
DayN: 2,
ChickinDate: "2026-01-01",
TotalValuePulletAfterDepreciation: 900,
TotalPopulation: 100,
}
rawV2, err := json.Marshal(v2)
if err != nil {
t.Fatalf("marshal v2 dto: %v", err)
}
var decodedV2 map[string]any
if err := json.Unmarshal(rawV2, &decodedV2); err != nil {
t.Fatalf("unmarshal v2 dto: %v", err)
}
if _, ok := decodedV2["components"]; ok {
t.Fatalf("expected v2 components to be omitted, got %s", string(rawV2))
}
}
@@ -83,7 +83,7 @@ func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppByDeliver
realizationDate = *mdp.DeliveryDate
}
totalWeightKg := mdp.UsageQty * mdp.AvgWeight
totalWeightKg := mdp.TotalWeight
salesAmount := totalWeightKg * mdp.UnitPrice
var hpp float64
@@ -0,0 +1,71 @@
package dto
import (
"math"
"testing"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
)
func TestToMarketingReportItemsUsesDeliveryProductTotalWeight(t *testing.T) {
mdps := []entity.MarketingDeliveryProduct{
{
Id: 1,
UsageQty: 10,
AvgWeight: 2.5,
TotalWeight: 17.75,
UnitPrice: 1000,
},
}
got := ToMarketingReportItems(mdps, nil, nil, nil)
if len(got) != 1 {
t.Fatalf("expected 1 marketing report item, got %d", len(got))
}
if got[0].TotalWeightKg != 17.75 {
t.Fatalf("expected total_weight_kg to use delivery product total_weight 17.75, got %.2f", got[0].TotalWeightKg)
}
if got[0].Qty != 10 {
t.Fatalf("expected qty to stay from usage_qty, got %.2f", got[0].Qty)
}
if got[0].AverageWeightKg != 2.5 {
t.Fatalf("expected average_weight_kg to stay from avg_weight, got %.2f", got[0].AverageWeightKg)
}
if got[0].SalesAmount != 17750 {
t.Fatalf("expected sales_amount to use delivery product total_weight, got %.2f", got[0].SalesAmount)
}
}
func TestMarketingSummaryUsesReportItemTotalWeight(t *testing.T) {
items := []RepportMarketingItemDTO{
{
Qty: 10,
TotalWeightKg: 17.75,
SalesAmount: 17750,
},
{
Qty: 5,
TotalWeightKg: 8.25,
SalesAmount: 8250,
},
}
got := ToSummaryFromDTOItems(items)
if got == nil {
t.Fatal("expected summary, got nil")
}
if got.TotalWeightKg != 26 {
t.Fatalf("expected summary total_weight_kg to sum item total weights, got %.2f", got.TotalWeightKg)
}
if diff := math.Abs(got.AverageWeightKg - (26.0 / 15.0)); diff > 0.000001 {
t.Fatalf("expected summary average_weight_kg to use total_weight_kg / total_qty, got %.6f", got.AverageWeightKg)
}
if got.TotalQty != 15 {
t.Fatalf("expected total qty 15, got %d", got.TotalQty)
}
if got.TotalSalesAmount != 26000 {
t.Fatalf("expected total sales amount 26000, got %d", got.TotalSalesAmount)
}
}
@@ -90,6 +90,12 @@ func (m *expenseDepreciationRepoMock) DeleteSnapshotsFromDate(_ context.Context,
return nil
}
func (m *expenseDepreciationRepoMock) DeleteSnapshotsByFarmIDs(_ context.Context, farmIDs []uint) error {
m.deleteCalled = true
m.deleteFarmIDs = append([]uint{}, farmIDs...)
return nil
}
func (m *expenseDepreciationRepoMock) GetLatestManualInputsByFarms(_ context.Context, _ []int64, _ []int64, _ []int64) ([]repportRepo.FarmDepreciationManualInputRow, error) {
return append([]repportRepo.FarmDepreciationManualInputRow{}, m.manualInputs...), nil
}
@@ -423,7 +423,10 @@ func (s *repportService) GetExpenseDepreciationV2(ctx *fiber.Ctx) ([]dto.Expense
var totalDepreciationValue float64
var totalPulletCostDayN float64
var totalPopulation float64
var allKandangComponents []depreciationKandangComponent
var multiplicationPercentage float64
var dayN int
var chickinDate string
var standardEffectiveDate string
for _, kandangID := range kandangIDs {
breakdown, err := s.HppV2Svc.CalculateHppBreakdown(kandangID, &dayDate)
@@ -444,72 +447,33 @@ func (s *repportService) GetExpenseDepreciationV2(ctx *fiber.Ctx) ([]dto.Expense
continue
}
houseType := approvalService.NormalizeDepreciationHouseType(breakdown.HouseType)
component := depreciationKandangComponent{
ProjectFlockKandangID: breakdown.ProjectFlockKandangID,
KandangID: breakdown.KandangID,
KandangName: breakdown.KandangName,
SourceProjectFlockID: hppV2DetailUint(part.Details, "source_project_flock_id"),
HouseType: houseType,
DayN: hppV2DetailInt(part.Details, "schedule_day"),
DepreciationPercent: hppV2DetailFloat(part.Details, "depreciation_percent"),
MultiplicationPercentage: hppV2DetailFloat(part.Details, "multiplication_percentage"),
PulletCostDayN: hppV2DetailFloat(part.Details, "pullet_cost_day_n"),
DepreciationValue: part.Total,
TotalValuePulletAfterDepreciation: hppV2DetailFloat(part.Details, "total_value_pullet_after_depreciation"),
DepreciationSource: part.Code,
OriginDate: hppV2DetailString(part.Details, "origin_date"),
ChickinDate: hppV2DetailString(part.Details, "origin_date"),
StandardEffectiveDate: hppV2DetailString(part.Details, "standard_effective_date"),
Population: hppV2DetailFloat(part.Details, "kandang_population"),
partPulletCostDayN := hppV2DetailFloat(part.Details, "pullet_cost_day_n")
partPopulation := hppV2DetailFloat(part.Details, "kandang_population")
partDayN := hppV2DetailInt(part.Details, "schedule_day")
partMultiplicationPercentage := hppV2DetailFloat(part.Details, "multiplication_percentage")
partChickinDate := hppV2DetailString(part.Details, "chickin_date")
if partChickinDate == "" {
partChickinDate = hppV2DetailString(part.Details, "origin_date")
}
if component.HouseType == "" {
component.HouseType = approvalService.NormalizeDepreciationHouseType(hppV2DetailString(part.Details, "house_type"))
}
totalPulletCostDayN += partPulletCostDayN
totalDepreciationValue += part.Total
totalPopulation += partPopulation
if ref := hppV2FindReference(part.References, "laying_transfer"); ref != nil {
component.TransferID = ref.ID
component.TransferDate = ref.Date
component.TransferQty = ref.Qty
if dayN == 0 && multiplicationPercentage == 0 && chickinDate == "" &&
(partDayN > 0 || partMultiplicationPercentage > 0 || partChickinDate != "") {
dayN = partDayN
multiplicationPercentage = partMultiplicationPercentage
chickinDate = partChickinDate
standardEffectiveDate = hppV2DetailString(part.Details, "standard_effective_date")
}
if part.Code == "manual_cutover" {
if startDay := hppV2DetailInt(part.Details, "start_schedule_day"); startDay > 0 {
component.StartScheduleDay = &startDay
}
component.CutoverDate = hppV2DetailString(part.Details, "cutover_date")
if manualID := hppV2DetailUint(part.Details, "manual_input_id"); manualID > 0 {
component.ManualInputID = &manualID
}
if component.ManualInputID == nil {
if ref := hppV2FindReference(part.References, "farm_depreciation_manual_input"); ref != nil && ref.ID > 0 {
manualID := ref.ID
component.ManualInputID = &manualID
}
}
}
totalPulletCostDayN += component.PulletCostDayN
totalDepreciationValue += component.DepreciationValue
totalPopulation += component.Population
allKandangComponents = append(allKandangComponents, component)
}
}
effectivePercent := approvalService.CalculateEffectiveDepreciationPercent(totalDepreciationValue, totalPulletCostDayN)
components := depreciationFarmComponents{
KandangCount: len(allKandangComponents),
TotalPopulation: totalPopulation,
Kandang: allKandangComponents,
}
componentsJSON, _ := json.Marshal(components)
multiplicationPercentage, dayN, chickinDate, standardEffectiveDate := depreciationSnapshotInfo(parseSnapshotComponents(componentsJSON))
rows = append(rows, dto.ExpenseDepreciationV2RowDTO{
Date: dayStr,
Date: dayStr,
DepreciationPercentEffective: effectivePercent,
DepreciationValue: totalDepreciationValue,
PulletCostDayNTotal: totalPulletCostDayN,
@@ -519,7 +483,6 @@ func (s *repportService) GetExpenseDepreciationV2(ctx *fiber.Ctx) ([]dto.Expense
TotalValuePulletAfterDepreciation: totalPulletCostDayN - totalDepreciationValue,
StandardEffectiveDate: standardEffectiveDate,
TotalPopulation: totalPopulation,
Components: parseSnapshotComponents(componentsJSON),
})
actualDays++
}