mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-06-09 15:07:49 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33bae94d43 |
@@ -195,9 +195,12 @@ func (s *fifoStockV2Service) allocateInternal(ctx context.Context, tx *gorm.DB,
|
|||||||
|
|
||||||
if remaining > 0 {
|
if remaining > 0 {
|
||||||
if !allowOverConsume {
|
if !allowOverConsume {
|
||||||
return nil, fmt.Errorf("%w: requested %.3f, allocated %.3f", ErrInsufficientStock, req.NeedQty, result.AllocatedQty)
|
s.logger.Warnf("FIFO v2: clearing historical pending (%.3f) for %s/%d at PW=%d — over-consume is blocked by rule",
|
||||||
|
remaining, req.Usable.LegacyTypeKey, req.Usable.ID, req.ProductWarehouseID)
|
||||||
|
result.PendingQty = 0
|
||||||
|
} else {
|
||||||
|
result.PendingQty = remaining
|
||||||
}
|
}
|
||||||
result.PendingQty = remaining
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.applyUsableDeltas(tx, *usableRule, req.Usable.ID, result.AllocatedQty, result.PendingQty); err != nil {
|
if err := s.applyUsableDeltas(tx, *usableRule, req.Usable.ID, result.AllocatedQty, result.PendingQty); err != nil {
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Rollback: re-insert TELUR/TELUR_GRADE block rules yang dihapus oleh migration ini.
|
||||||
|
|
||||||
|
INSERT INTO fifo_stock_v2_overconsume_rules(flag_group_code, function_code, lane, allow_overconsume, priority, reason, is_active)
|
||||||
|
SELECT 'TELUR', 'MARKETING_OUT', 'USABLE', FALSE, 20, 'fifo_v2_exception_marketing_block_telur', TRUE
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM fifo_stock_v2_overconsume_rules
|
||||||
|
WHERE lane = 'USABLE'
|
||||||
|
AND function_code = 'MARKETING_OUT'
|
||||||
|
AND flag_group_code = 'TELUR'
|
||||||
|
AND reason = 'fifo_v2_exception_marketing_block_telur'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO fifo_stock_v2_overconsume_rules(flag_group_code, function_code, lane, allow_overconsume, priority, reason, is_active)
|
||||||
|
SELECT 'TELUR_GRADE', 'MARKETING_OUT', 'USABLE', FALSE, 20, 'fifo_v2_exception_marketing_block_telur_grade', TRUE
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM fifo_stock_v2_overconsume_rules
|
||||||
|
WHERE lane = 'USABLE'
|
||||||
|
AND function_code = 'MARKETING_OUT'
|
||||||
|
AND flag_group_code = 'TELUR_GRADE'
|
||||||
|
AND reason = 'fifo_v2_exception_marketing_block_telur_grade'
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Revert rules yang ditambahkan oleh migration 20260603031237_block_marketing_overconsume_telur.
|
||||||
|
-- TELUR/TELUR_GRADE kembali fallback ke default allow rule (allow_overconsume=TRUE)
|
||||||
|
-- karena validasi stok sekarang ditangani di service layer (code validation) bukan lewat
|
||||||
|
-- config overconsume FIFO v2.
|
||||||
|
|
||||||
|
DELETE FROM fifo_stock_v2_overconsume_rules
|
||||||
|
WHERE lane = 'USABLE'
|
||||||
|
AND function_code = 'MARKETING_OUT'
|
||||||
|
AND flag_group_code IN ('TELUR', 'TELUR_GRADE')
|
||||||
|
AND reason IN (
|
||||||
|
'fifo_v2_exception_marketing_block_telur',
|
||||||
|
'fifo_v2_exception_marketing_block_telur_grade'
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -972,6 +972,19 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
|||||||
if err := deliveryProductRepo.UpdateOne(ctx, deliveryProduct.Id, deliveryProduct, nil); err != nil {
|
if err := deliveryProductRepo.UpdateOne(ctx, deliveryProduct.Id, deliveryProduct, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||||
}
|
}
|
||||||
|
if requestedQty > 0 {
|
||||||
|
available, err := s.checkAvailableStockQty(ctx, tx, marketingProduct.ProductWarehouseId)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memeriksa ketersediaan stok")
|
||||||
|
}
|
||||||
|
if requestedQty > available {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf(
|
||||||
|
"Stok tidak mencukupi: dibutuhkan %g, tersedia %g",
|
||||||
|
requestedQty, available,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := reflowMarketingScope(
|
if err := reflowMarketingScope(
|
||||||
ctx,
|
ctx,
|
||||||
s.FifoStockV2Svc,
|
s.FifoStockV2Svc,
|
||||||
@@ -1505,3 +1518,28 @@ func uniqueUintIDs(ids []uint) []uint {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkAvailableStockQty returns the net available qty for a product warehouse:
|
||||||
|
// gross qty (product_warehouses.qty) minus the sum of active CONSUME allocations
|
||||||
|
// in stock_allocations. This gives the true available stock accounting for all
|
||||||
|
// other delivery orders that have already consumed from the same warehouse.
|
||||||
|
func (s deliveryOrdersService) checkAvailableStockQty(ctx context.Context, tx *gorm.DB, productWarehouseId uint) (float64, error) {
|
||||||
|
var pw entity.ProductWarehouse
|
||||||
|
if err := tx.WithContext(ctx).Select("qty").First(&pw, productWarehouseId).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var usedQty float64
|
||||||
|
if err := tx.WithContext(ctx).Raw(`
|
||||||
|
SELECT COALESCE(SUM(qty), 0)
|
||||||
|
FROM stock_allocations
|
||||||
|
WHERE stockable_type = 'product_warehouses'
|
||||||
|
AND stockable_id = ?
|
||||||
|
AND status = 'ACTIVE'
|
||||||
|
AND allocation_purpose = 'CONSUME'
|
||||||
|
`, productWarehouseId).Scan(&usedQty).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pw.Quantity - usedQty, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -152,29 +152,6 @@ func (c *RepportController) GetExpenseDepreciation(ctx *fiber.Ctx) error {
|
|||||||
return ctx.Status(fiber.StatusOK).JSON(resp)
|
return ctx.Status(fiber.StatusOK).JSON(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RepportController) GetExpenseDepreciationV2(ctx *fiber.Ctx) error {
|
|
||||||
rows, meta, err := c.RepportService.GetExpenseDepreciationV2(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Meta dto.ExpenseDepreciationV2MetaDTO `json:"meta"`
|
|
||||||
Data []dto.ExpenseDepreciationV2RowDTO `json:"data"`
|
|
||||||
}{
|
|
||||||
Code: fiber.StatusOK,
|
|
||||||
Status: "success",
|
|
||||||
Message: "Get expense depreciation report v2 successfully",
|
|
||||||
Meta: *meta,
|
|
||||||
Data: rows,
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.Status(fiber.StatusOK).JSON(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RepportController) GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) error {
|
func (c *RepportController) GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) error {
|
||||||
rows, meta, err := c.RepportService.GetExpenseDepreciationManualInputs(ctx)
|
rows, meta, err := c.RepportService.GetExpenseDepreciationManualInputs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -40,29 +40,6 @@ type ExpenseDepreciationManualInputRowDTO struct {
|
|||||||
Note *string `json:"note"`
|
Note *string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseDepreciationV2MetaDTO struct {
|
|
||||||
ProjectFlockID int64 `json:"project_flock_id"`
|
|
||||||
FarmName string `json:"farm_name"`
|
|
||||||
LocationID int64 `json:"location_id"`
|
|
||||||
Period string `json:"period"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
TotalDays int `json:"total_days"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExpenseDepreciationV2RowDTO struct {
|
|
||||||
Date string `json:"date"`
|
|
||||||
DepreciationPercentEffective float64 `json:"depreciation_percent_effective"`
|
|
||||||
DepreciationValue float64 `json:"depreciation_value"`
|
|
||||||
PulletCostDayNTotal float64 `json:"pullet_cost_day_n_total"`
|
|
||||||
MultiplicationPercentage float64 `json:"multiplication_percentage"`
|
|
||||||
DayN int `json:"day_n"`
|
|
||||||
ChickinDate string `json:"chickin_date"`
|
|
||||||
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 {
|
func NewExpenseDepreciationFiltersDTO(area, location, projectFlockID, period string) ExpenseDepreciationFiltersDTO {
|
||||||
return ExpenseDepreciationFiltersDTO{
|
return ExpenseDepreciationFiltersDTO{
|
||||||
AreaID: area,
|
AreaID: area,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ func RepportRoutes(v1 fiber.Router, u user.UserService, s repport.RepportService
|
|||||||
|
|
||||||
route.Get("/expense", m.RequirePermissions(m.P_ReportExpenseGetAll), ctrl.GetExpense)
|
route.Get("/expense", m.RequirePermissions(m.P_ReportExpenseGetAll), ctrl.GetExpense)
|
||||||
route.Get("/expense/depreciation", ctrl.GetExpenseDepreciation)
|
route.Get("/expense/depreciation", ctrl.GetExpenseDepreciation)
|
||||||
route.Get("/expense/v2/depreciation", ctrl.GetExpenseDepreciationV2)
|
|
||||||
route.Get("/expense/depreciation/manual-inputs", ctrl.GetExpenseDepreciationManualInputs)
|
route.Get("/expense/depreciation/manual-inputs", ctrl.GetExpenseDepreciationManualInputs)
|
||||||
route.Put("/expense/depreciation/manual-inputs", ctrl.UpsertExpenseDepreciationManualInput)
|
route.Put("/expense/depreciation/manual-inputs", ctrl.UpsertExpenseDepreciationManualInput)
|
||||||
route.Get("/marketing", m.RequirePermissions(m.P_ReportDeliveryGetAll), ctrl.GetMarketing)
|
route.Get("/marketing", m.RequirePermissions(m.P_ReportDeliveryGetAll), ctrl.GetMarketing)
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import (
|
|||||||
type RepportService interface {
|
type RepportService interface {
|
||||||
GetExpense(ctx *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error)
|
GetExpense(ctx *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error)
|
||||||
GetExpenseDepreciation(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationRowDTO, *dto.ExpenseDepreciationMetaDTO, error)
|
GetExpenseDepreciation(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationRowDTO, *dto.ExpenseDepreciationMetaDTO, error)
|
||||||
GetExpenseDepreciationV2(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationV2RowDTO, *dto.ExpenseDepreciationV2MetaDTO, error)
|
|
||||||
GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationManualInputRowDTO, *dto.ExpenseDepreciationMetaDTO, error)
|
GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationManualInputRowDTO, *dto.ExpenseDepreciationMetaDTO, error)
|
||||||
UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx, req *validation.ExpenseDepreciationManualInputUpsert) (*dto.ExpenseDepreciationManualInputRowDTO, error)
|
UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx, req *validation.ExpenseDepreciationManualInputUpsert) (*dto.ExpenseDepreciationManualInputRowDTO, error)
|
||||||
GetMarketing(ctx *fiber.Ctx, params *validation.MarketingQuery) ([]dto.RepportMarketingItemDTO, int64, error)
|
GetMarketing(ctx *fiber.Ctx, params *validation.MarketingQuery) ([]dto.RepportMarketingItemDTO, int64, error)
|
||||||
@@ -356,182 +355,6 @@ func (s *repportService) GetExpenseDepreciation(ctx *fiber.Ctx) ([]dto.ExpenseDe
|
|||||||
return rows[offset:end], meta, nil
|
return rows[offset:end], meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *repportService) GetExpenseDepreciationV2(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationV2RowDTO, *dto.ExpenseDepreciationV2MetaDTO, error) {
|
|
||||||
params, err := s.parseExpenseDepreciationV2Query(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())
|
|
||||||
}
|
|
||||||
if s.ExpenseDepreciationRepo == nil {
|
|
||||||
return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "expense depreciation repository is not configured")
|
|
||||||
}
|
|
||||||
if s.HppCostRepo == nil {
|
|
||||||
return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "hpp cost repository is not configured")
|
|
||||||
}
|
|
||||||
if s.HppV2Svc == nil {
|
|
||||||
return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "hpp v2 service is not configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
limit := params.Limit
|
|
||||||
if limit <= 0 {
|
|
||||||
limit = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
farmID := uint(params.ProjectFlockID)
|
|
||||||
kandangIDs, err := s.HppCostRepo.GetProjectFlockKandangIDs(ctx.Context(), farmID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(kandangIDs) == 0 {
|
|
||||||
return nil, nil, fiber.NewError(fiber.StatusNotFound, "project flock has no kandangs")
|
|
||||||
}
|
|
||||||
|
|
||||||
var farmName string
|
|
||||||
if err := s.db.WithContext(ctx.Context()).
|
|
||||||
Table("project_flocks").
|
|
||||||
Select("flock_name").
|
|
||||||
Where("id = ? AND deleted_at IS NULL", farmID).
|
|
||||||
Scan(&farmName).Error; err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if farmName == "" {
|
|
||||||
return nil, nil, fiber.NewError(fiber.StatusNotFound, "project flock not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := make([]dto.ExpenseDepreciationV2RowDTO, 0, limit)
|
|
||||||
actualDays := 0
|
|
||||||
|
|
||||||
for i := 0; i < limit; i++ {
|
|
||||||
dayDate := periodDate.AddDate(0, 0, i)
|
|
||||||
dayStr := dayDate.Format("2006-01-02")
|
|
||||||
|
|
||||||
var totalDepreciationValue float64
|
|
||||||
var totalPulletCostDayN float64
|
|
||||||
var totalPopulation float64
|
|
||||||
var allKandangComponents []depreciationKandangComponent
|
|
||||||
|
|
||||||
for _, kandangID := range kandangIDs {
|
|
||||||
breakdown, err := s.HppV2Svc.CalculateHppBreakdown(kandangID, &dayDate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if breakdown == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
depreciationComponent := hppV2FindDepreciationComponent(breakdown)
|
|
||||||
if depreciationComponent == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, part := range depreciationComponent.Parts {
|
|
||||||
if part.Total <= 0 {
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if component.HouseType == "" {
|
|
||||||
component.HouseType = approvalService.NormalizeDepreciationHouseType(hppV2DetailString(part.Details, "house_type"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref := hppV2FindReference(part.References, "laying_transfer"); ref != nil {
|
|
||||||
component.TransferID = ref.ID
|
|
||||||
component.TransferDate = ref.Date
|
|
||||||
component.TransferQty = ref.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
DepreciationPercentEffective: effectivePercent,
|
|
||||||
DepreciationValue: totalDepreciationValue,
|
|
||||||
PulletCostDayNTotal: totalPulletCostDayN,
|
|
||||||
MultiplicationPercentage: multiplicationPercentage,
|
|
||||||
DayN: dayN,
|
|
||||||
ChickinDate: chickinDate,
|
|
||||||
TotalValuePulletAfterDepreciation: totalPulletCostDayN - totalDepreciationValue,
|
|
||||||
StandardEffectiveDate: standardEffectiveDate,
|
|
||||||
TotalPopulation: totalPopulation,
|
|
||||||
Components: parseSnapshotComponents(componentsJSON),
|
|
||||||
})
|
|
||||||
actualDays++
|
|
||||||
}
|
|
||||||
|
|
||||||
meta := &dto.ExpenseDepreciationV2MetaDTO{
|
|
||||||
ProjectFlockID: params.ProjectFlockID,
|
|
||||||
FarmName: farmName,
|
|
||||||
LocationID: params.LocationID,
|
|
||||||
Period: params.Period,
|
|
||||||
Limit: limit,
|
|
||||||
TotalDays: actualDays,
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows, meta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *repportService) GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationManualInputRowDTO, *dto.ExpenseDepreciationMetaDTO, error) {
|
func (s *repportService) GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationManualInputRowDTO, *dto.ExpenseDepreciationMetaDTO, error) {
|
||||||
params, filters, err := s.parseExpenseDepreciationQuery(ctx)
|
params, filters, err := s.parseExpenseDepreciationQuery(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3202,45 +3025,6 @@ func (s *repportService) parseExpenseDepreciationQuery(ctx *fiber.Ctx) (*validat
|
|||||||
return params, filters, nil
|
return params, filters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *repportService) parseExpenseDepreciationV2Query(ctx *fiber.Ctx) (*validation.ExpenseDepreciationV2Query, error) {
|
|
||||||
limit := ctx.QueryInt("limit", 10)
|
|
||||||
if limit < 1 {
|
|
||||||
limit = 10
|
|
||||||
}
|
|
||||||
period := strings.TrimSpace(ctx.Query("period", ""))
|
|
||||||
locationID := ctx.QueryInt("location_id", 0)
|
|
||||||
projectFlockID := ctx.QueryInt("project_flock_id", 0)
|
|
||||||
|
|
||||||
locationScope, err := m.ResolveLocationScope(ctx, s.ExpenseRealizationRepo.DB())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if locationScope.Restrict && locationID > 0 {
|
|
||||||
allowed := toInt64Slice(locationScope.IDs)
|
|
||||||
if len(allowed) == 0 {
|
|
||||||
return nil, fiber.NewError(fiber.StatusForbidden, "no location access")
|
|
||||||
}
|
|
||||||
found := false
|
|
||||||
for _, id := range allowed {
|
|
||||||
if id == int64(locationID) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return nil, fiber.NewError(fiber.StatusForbidden, "location not in scope")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &validation.ExpenseDepreciationV2Query{
|
|
||||||
Limit: limit,
|
|
||||||
Period: period,
|
|
||||||
LocationID: int64(locationID),
|
|
||||||
ProjectFlockID: int64(projectFlockID),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCommaSeparatedInt64s(raw string) ([]int64, error) {
|
func parseCommaSeparatedInt64s(raw string) ([]int64, error) {
|
||||||
raw = strings.TrimSpace(raw)
|
raw = strings.TrimSpace(raw)
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
|
|||||||
@@ -93,13 +93,6 @@ type ExpenseDepreciationQuery struct {
|
|||||||
LocationIDs []int64 `query:"-"`
|
LocationIDs []int64 `query:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseDepreciationV2Query struct {
|
|
||||||
Limit int `query:"limit" validate:"omitempty,min=1,max=90"`
|
|
||||||
Period string `query:"period" validate:"required,datetime=2006-01-02"`
|
|
||||||
LocationID int64 `query:"location_id" validate:"omitempty,gt=0"`
|
|
||||||
ProjectFlockID int64 `query:"project_flock_id" validate:"required,gt=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExpenseDepreciationManualInputUpsert struct {
|
type ExpenseDepreciationManualInputUpsert struct {
|
||||||
ProjectFlockID uint `json:"project_flock_id" validate:"required,gt=0"`
|
ProjectFlockID uint `json:"project_flock_id" validate:"required,gt=0"`
|
||||||
TotalCost float64 `json:"total_cost" validate:"required,gte=0"`
|
TotalCost float64 `json:"total_cost" validate:"required,gte=0"`
|
||||||
|
|||||||
Reference in New Issue
Block a user