mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
init depresiasi
This commit is contained in:
@@ -90,6 +90,75 @@ func (c *RepportController) GetExpense(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RepportController) GetExpenseDepreciation(ctx *fiber.Ctx) error {
|
||||
rows, meta, err := c.RepportService.GetExpenseDepreciation(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
Code int `json:"code"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Meta dto.ExpenseDepreciationMetaDTO `json:"meta"`
|
||||
Data []dto.ExpenseDepreciationRowDTO `json:"data"`
|
||||
}{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get expense depreciation report successfully",
|
||||
Meta: *meta,
|
||||
Data: rows,
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(resp)
|
||||
}
|
||||
|
||||
func (c *RepportController) GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) error {
|
||||
rows, meta, err := c.RepportService.GetExpenseDepreciationManualInputs(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
Code int `json:"code"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Meta dto.ExpenseDepreciationMetaDTO `json:"meta"`
|
||||
Data []dto.ExpenseDepreciationManualInputRowDTO `json:"data"`
|
||||
}{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get expense depreciation manual inputs successfully",
|
||||
Meta: *meta,
|
||||
Data: rows,
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(resp)
|
||||
}
|
||||
|
||||
func (c *RepportController) UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx) error {
|
||||
req := new(validation.ExpenseDepreciationManualInputUpsert)
|
||||
if err := ctx.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if err := m.EnsureProjectFlockAccess(ctx, c.RepportService.DB(), req.ProjectFlockID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := c.RepportService.UpsertExpenseDepreciationManualInput(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Upsert expense depreciation manual input successfully",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
|
||||
query := &validation.MarketingQuery{
|
||||
Page: ctx.QueryInt("page", 1),
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package dto
|
||||
|
||||
type ExpenseDepreciationFiltersDTO struct {
|
||||
AreaID string `json:"area_id"`
|
||||
LocationID string `json:"location_id"`
|
||||
ProjectFlockID string `json:"project_flock_id"`
|
||||
Period string `json:"period"`
|
||||
}
|
||||
|
||||
type ExpenseDepreciationMetaDTO struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int64 `json:"total_pages"`
|
||||
TotalResults int64 `json:"total_results"`
|
||||
Filters ExpenseDepreciationFiltersDTO `json:"filters"`
|
||||
}
|
||||
|
||||
type ExpenseDepreciationRowDTO struct {
|
||||
ProjectFlockID int64 `json:"project_flock_id"`
|
||||
FarmName string `json:"farm_name"`
|
||||
Period string `json:"period"`
|
||||
DepreciationPercentEffective float64 `json:"depreciation_percent_effective"`
|
||||
DepreciationValue float64 `json:"depreciation_value"`
|
||||
PulletCostDayNTotal float64 `json:"pullet_cost_day_n_total"`
|
||||
Components any `json:"components"`
|
||||
}
|
||||
|
||||
type ExpenseDepreciationManualInputRowDTO struct {
|
||||
ID int64 `json:"id"`
|
||||
ProjectFlockID int64 `json:"project_flock_id"`
|
||||
FarmName string `json:"farm_name"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
Note *string `json:"note"`
|
||||
}
|
||||
|
||||
func NewExpenseDepreciationFiltersDTO(area, location, projectFlockID, period string) ExpenseDepreciationFiltersDTO {
|
||||
return ExpenseDepreciationFiltersDTO{
|
||||
AreaID: area,
|
||||
LocationID: location,
|
||||
ProjectFlockID: projectFlockID,
|
||||
Period: period,
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
purchaseSupplierRepository := repportRepo.NewPurchaseSupplierRepository(db)
|
||||
debtSupplierRepository := repportRepo.NewDebtSupplierRepository(db)
|
||||
hppPerKandangRepository := repportRepo.NewHppPerKandangRepository(db)
|
||||
expenseDepreciationRepository := repportRepo.NewExpenseDepreciationRepository(db)
|
||||
productionResultRepository := repportRepo.NewProductionResultRepository(db)
|
||||
customerPaymentRepository := repportRepo.NewCustomerPaymentRepository(db)
|
||||
customerRepository := customerRepo.NewCustomerRepository(db)
|
||||
@@ -45,7 +46,27 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
|
||||
approvalSvc := approvalService.NewApprovalService(approvalRepository)
|
||||
hppSvc := approvalService.NewHppService(hppCostRepository)
|
||||
repportService := sRepport.NewRepportService(db, validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, hppSvc, purchaseSupplierRepository, debtSupplierRepository, hppPerKandangRepository, productionResultRepository, customerPaymentRepository, customerRepository, standardGrowthDetailRepository, productionStandardDetailRepository)
|
||||
repportService := sRepport.NewRepportService(
|
||||
db,
|
||||
validate,
|
||||
expenseRealizationRepository,
|
||||
expenseDepreciationRepository,
|
||||
marketingDeliveryProductRepository,
|
||||
purchaseRepository,
|
||||
chickinRepository,
|
||||
recordingRepository,
|
||||
approvalSvc,
|
||||
hppSvc,
|
||||
hppCostRepository,
|
||||
purchaseSupplierRepository,
|
||||
debtSupplierRepository,
|
||||
hppPerKandangRepository,
|
||||
productionResultRepository,
|
||||
customerPaymentRepository,
|
||||
customerRepository,
|
||||
standardGrowthDetailRepository,
|
||||
productionStandardDetailRepository,
|
||||
)
|
||||
userService := sUser.NewUserService(userRepository, validate)
|
||||
|
||||
RepportRoutes(router, userService, repportService)
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
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
|
||||
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.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,
|
||||
"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, note").
|
||||
Where("project_flock_id = ?", row.ProjectFlockId).
|
||||
Take(row).Error
|
||||
}
|
||||
@@ -16,6 +16,9 @@ func RepportRoutes(v1 fiber.Router, u user.UserService, s repport.RepportService
|
||||
route.Use(m.Auth(u))
|
||||
|
||||
route.Get("/expense", m.RequirePermissions(m.P_ReportExpenseGetAll), ctrl.GetExpense)
|
||||
route.Get("/expense/depreciation", ctrl.GetExpenseDepreciation)
|
||||
route.Get("/expense/depreciation/manual-inputs", ctrl.GetExpenseDepreciationManualInputs)
|
||||
route.Put("/expense/depreciation/manual-inputs", ctrl.UpsertExpenseDepreciationManualInput)
|
||||
route.Get("/marketing", m.RequirePermissions(m.P_ReportDeliveryGetAll), ctrl.GetMarketing)
|
||||
route.Get("/purchase-supplier", m.RequirePermissions(m.P_ReportPurchaseSupplierGetAll), ctrl.GetPurchaseSupplier)
|
||||
route.Get("/debt-supplier", m.RequirePermissions(m.P_ReportDebtSupplierGetAll), ctrl.GetDebtSupplier)
|
||||
|
||||
@@ -42,6 +42,9 @@ import (
|
||||
|
||||
type RepportService interface {
|
||||
GetExpense(ctx *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error)
|
||||
GetExpenseDepreciation(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationRowDTO, *dto.ExpenseDepreciationMetaDTO, error)
|
||||
GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationManualInputRowDTO, *dto.ExpenseDepreciationMetaDTO, error)
|
||||
UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx, req *validation.ExpenseDepreciationManualInputUpsert) (*dto.ExpenseDepreciationManualInputRowDTO, error)
|
||||
GetMarketing(ctx *fiber.Ctx, params *validation.MarketingQuery) ([]dto.RepportMarketingItemDTO, int64, error)
|
||||
GetPurchaseSupplier(ctx *fiber.Ctx, params *validation.PurchaseSupplierQuery) ([]dto.PurchaseSupplierDTO, int64, error)
|
||||
GetDebtSupplier(ctx *fiber.Ctx, params *validation.DebtSupplierQuery) ([]dto.DebtSupplierDTO, int64, error)
|
||||
@@ -56,12 +59,14 @@ type repportService struct {
|
||||
Validate *validator.Validate
|
||||
db *gorm.DB
|
||||
ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository
|
||||
ExpenseDepreciationRepo repportRepo.ExpenseDepreciationRepository
|
||||
MarketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository
|
||||
PurchaseRepo purchaseRepo.PurchaseRepository
|
||||
ChickinRepo chickinRepo.ProjectChickinRepository
|
||||
RecordingRepo recordingRepo.RecordingRepository
|
||||
ApprovalSvc approvalService.ApprovalService
|
||||
HppSvc approvalService.HppService
|
||||
HppCostRepo commonRepo.HppCostRepository
|
||||
PurchaseSupplierRepo repportRepo.PurchaseSupplierRepository
|
||||
DebtSupplierRepo repportRepo.DebtSupplierRepository
|
||||
HppPerKandangRepo repportRepo.HppPerKandangRepository
|
||||
@@ -85,12 +90,14 @@ func NewRepportService(
|
||||
db *gorm.DB,
|
||||
validate *validator.Validate,
|
||||
expenseRealizationRepo expenseRepo.ExpenseRealizationRepository,
|
||||
expenseDepreciationRepo repportRepo.ExpenseDepreciationRepository,
|
||||
marketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository,
|
||||
purchaseRepo purchaseRepo.PurchaseRepository,
|
||||
chickinRepo chickinRepo.ProjectChickinRepository,
|
||||
recordingRepo recordingRepo.RecordingRepository,
|
||||
approvalSvc approvalService.ApprovalService,
|
||||
hppSvc approvalService.HppService,
|
||||
hppCostRepo commonRepo.HppCostRepository,
|
||||
purchaseSupplierRepo repportRepo.PurchaseSupplierRepository,
|
||||
debtSupplierRepo repportRepo.DebtSupplierRepository,
|
||||
hppPerKandangRepo repportRepo.HppPerKandangRepository,
|
||||
@@ -105,12 +112,14 @@ func NewRepportService(
|
||||
Validate: validate,
|
||||
db: db,
|
||||
ExpenseRealizationRepo: expenseRealizationRepo,
|
||||
ExpenseDepreciationRepo: expenseDepreciationRepo,
|
||||
MarketingDeliveryRepo: marketingDeliveryRepo,
|
||||
PurchaseRepo: purchaseRepo,
|
||||
ChickinRepo: chickinRepo,
|
||||
RecordingRepo: recordingRepo,
|
||||
ApprovalSvc: approvalSvc,
|
||||
HppSvc: hppSvc,
|
||||
HppCostRepo: hppCostRepo,
|
||||
PurchaseSupplierRepo: purchaseSupplierRepo,
|
||||
DebtSupplierRepo: debtSupplierRepo,
|
||||
HppPerKandangRepo: hppPerKandangRepo,
|
||||
@@ -164,6 +173,495 @@ func (s *repportService) GetExpense(c *fiber.Ctx, params *validation.ExpenseQuer
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
func (s *repportService) GetExpenseDepreciation(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationRowDTO, *dto.ExpenseDepreciationMetaDTO, error) {
|
||||
params, filters, err := s.parseExpenseDepreciationQuery(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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
candidateRows, err := s.ExpenseDepreciationRepo.GetCandidateFarms(
|
||||
ctx.Context(),
|
||||
periodDate,
|
||||
params.AreaIDs,
|
||||
params.LocationIDs,
|
||||
params.ProjectFlockIDs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
limit := params.Limit
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
if len(candidateRows) == 0 {
|
||||
meta := &dto.ExpenseDepreciationMetaDTO{
|
||||
Page: params.Page,
|
||||
Limit: limit,
|
||||
TotalPages: 1,
|
||||
TotalResults: 0,
|
||||
Filters: filters,
|
||||
}
|
||||
return []dto.ExpenseDepreciationRowDTO{}, meta, nil
|
||||
}
|
||||
|
||||
farmIDs := make([]uint, 0, len(candidateRows))
|
||||
farmNameByID := make(map[uint]string, len(candidateRows))
|
||||
for _, row := range candidateRows {
|
||||
farmIDs = append(farmIDs, row.ProjectFlockID)
|
||||
farmNameByID[row.ProjectFlockID] = row.FarmName
|
||||
}
|
||||
|
||||
snapshots, err := s.ExpenseDepreciationRepo.GetSnapshotsByPeriodAndFarmIDs(ctx.Context(), periodDate, farmIDs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
snapshotByFarmID := make(map[uint]entity.FarmDepreciationSnapshot, len(snapshots))
|
||||
for _, row := range snapshots {
|
||||
snapshotByFarmID[row.ProjectFlockId] = row
|
||||
}
|
||||
|
||||
missingFarmIDs := make([]uint, 0)
|
||||
for _, farmID := range farmIDs {
|
||||
if _, exists := snapshotByFarmID[farmID]; exists {
|
||||
continue
|
||||
}
|
||||
missingFarmIDs = append(missingFarmIDs, farmID)
|
||||
}
|
||||
|
||||
if len(missingFarmIDs) > 0 {
|
||||
computedSnapshots, computeErr := s.computeExpenseDepreciationSnapshots(ctx.Context(), periodDate, missingFarmIDs, farmNameByID)
|
||||
if computeErr != nil {
|
||||
return nil, nil, computeErr
|
||||
}
|
||||
if len(computedSnapshots) > 0 {
|
||||
if err := s.ExpenseDepreciationRepo.UpsertSnapshots(ctx.Context(), computedSnapshots); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, row := range computedSnapshots {
|
||||
snapshotByFarmID[row.ProjectFlockId] = row
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rows := make([]dto.ExpenseDepreciationRowDTO, 0, len(candidateRows))
|
||||
for _, candidate := range candidateRows {
|
||||
snapshot, exists := snapshotByFarmID[candidate.ProjectFlockID]
|
||||
if !exists {
|
||||
rows = append(rows, dto.ExpenseDepreciationRowDTO{
|
||||
ProjectFlockID: int64(candidate.ProjectFlockID),
|
||||
FarmName: candidate.FarmName,
|
||||
Period: params.Period,
|
||||
DepreciationPercentEffective: 0,
|
||||
DepreciationValue: 0,
|
||||
PulletCostDayNTotal: 0,
|
||||
Components: map[string]any{},
|
||||
})
|
||||
continue
|
||||
}
|
||||
rows = append(rows, dto.ExpenseDepreciationRowDTO{
|
||||
ProjectFlockID: int64(snapshot.ProjectFlockId),
|
||||
FarmName: candidate.FarmName,
|
||||
Period: params.Period,
|
||||
DepreciationPercentEffective: snapshot.DepreciationPercentEffective,
|
||||
DepreciationValue: snapshot.DepreciationValue,
|
||||
PulletCostDayNTotal: snapshot.PulletCostDayNTotal,
|
||||
Components: parseSnapshotComponents(snapshot.Components),
|
||||
})
|
||||
}
|
||||
|
||||
totalResults := int64(len(rows))
|
||||
totalPages := int64(0)
|
||||
if totalResults > 0 {
|
||||
totalPages = int64(math.Ceil(float64(totalResults) / float64(limit)))
|
||||
}
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * limit
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if offset > len(rows) {
|
||||
offset = len(rows)
|
||||
}
|
||||
end := offset + limit
|
||||
if end > len(rows) {
|
||||
end = len(rows)
|
||||
}
|
||||
|
||||
meta := &dto.ExpenseDepreciationMetaDTO{
|
||||
Page: params.Page,
|
||||
Limit: limit,
|
||||
TotalPages: totalPages,
|
||||
TotalResults: totalResults,
|
||||
Filters: filters,
|
||||
}
|
||||
|
||||
return rows[offset:end], meta, nil
|
||||
}
|
||||
|
||||
func (s *repportService) GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) ([]dto.ExpenseDepreciationManualInputRowDTO, *dto.ExpenseDepreciationMetaDTO, error) {
|
||||
params, filters, err := s.parseExpenseDepreciationQuery(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if s.ExpenseDepreciationRepo == nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "expense depreciation repository is not configured")
|
||||
}
|
||||
|
||||
repoRows, err := s.ExpenseDepreciationRepo.GetLatestManualInputsByFarms(
|
||||
ctx.Context(),
|
||||
params.AreaIDs,
|
||||
params.LocationIDs,
|
||||
params.ProjectFlockIDs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rows := make([]dto.ExpenseDepreciationManualInputRowDTO, 0, len(repoRows))
|
||||
for _, row := range repoRows {
|
||||
rows = append(rows, dto.ExpenseDepreciationManualInputRowDTO{
|
||||
ID: int64(row.Id),
|
||||
ProjectFlockID: int64(row.ProjectFlockID),
|
||||
FarmName: row.FarmName,
|
||||
TotalCost: row.TotalCost,
|
||||
Note: row.Note,
|
||||
})
|
||||
}
|
||||
|
||||
limit := params.Limit
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
totalResults := int64(len(rows))
|
||||
totalPages := int64(0)
|
||||
if totalResults > 0 {
|
||||
totalPages = int64(math.Ceil(float64(totalResults) / float64(limit)))
|
||||
}
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * limit
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if offset > len(rows) {
|
||||
offset = len(rows)
|
||||
}
|
||||
end := offset + limit
|
||||
if end > len(rows) {
|
||||
end = len(rows)
|
||||
}
|
||||
|
||||
meta := &dto.ExpenseDepreciationMetaDTO{
|
||||
Page: params.Page,
|
||||
Limit: limit,
|
||||
TotalPages: totalPages,
|
||||
TotalResults: totalResults,
|
||||
Filters: filters,
|
||||
}
|
||||
|
||||
return rows[offset:end], meta, nil
|
||||
}
|
||||
|
||||
func (s *repportService) UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx, req *validation.ExpenseDepreciationManualInputUpsert) (*dto.ExpenseDepreciationManualInputRowDTO, error) {
|
||||
if req == nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "request is required")
|
||||
}
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
if s.ExpenseDepreciationRepo == nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "expense depreciation repository is not configured")
|
||||
}
|
||||
|
||||
row := entity.FarmDepreciationManualInput{
|
||||
ProjectFlockId: req.ProjectFlockID,
|
||||
TotalCost: req.TotalCost,
|
||||
Note: req.Note,
|
||||
}
|
||||
if err := s.ExpenseDepreciationRepo.UpsertManualInput(ctx.Context(), &row); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &dto.ExpenseDepreciationManualInputRowDTO{
|
||||
ID: int64(row.Id),
|
||||
ProjectFlockID: int64(row.ProjectFlockId),
|
||||
TotalCost: row.TotalCost,
|
||||
Note: row.Note,
|
||||
}
|
||||
|
||||
listRows, listErr := s.ExpenseDepreciationRepo.GetLatestManualInputsByFarms(
|
||||
ctx.Context(),
|
||||
nil,
|
||||
nil,
|
||||
[]int64{int64(row.ProjectFlockId)},
|
||||
)
|
||||
if listErr == nil {
|
||||
for _, listRow := range listRows {
|
||||
if listRow.ProjectFlockID == row.ProjectFlockId {
|
||||
response.FarmName = listRow.FarmName
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type depreciationKandangComponent struct {
|
||||
ProjectFlockKandangID uint `json:"project_flock_kandang_id"`
|
||||
KandangID uint `json:"kandang_id"`
|
||||
KandangName string `json:"kandang_name"`
|
||||
TransferID uint `json:"transfer_id"`
|
||||
TransferDate string `json:"transfer_date"`
|
||||
SourceProjectFlockID uint `json:"source_project_flock_id"`
|
||||
HouseType string `json:"house_type"`
|
||||
DayN int `json:"day_n"`
|
||||
DepreciationPercent float64 `json:"depreciation_percent"`
|
||||
TransferQty float64 `json:"transfer_qty"`
|
||||
PulletCostDayN float64 `json:"pullet_cost_day_n"`
|
||||
DepreciationValue float64 `json:"depreciation_value"`
|
||||
}
|
||||
|
||||
type depreciationFarmComponents struct {
|
||||
KandangCount int `json:"kandang_count"`
|
||||
Kandang []depreciationKandangComponent `json:"kandang"`
|
||||
}
|
||||
|
||||
func (s *repportService) computeExpenseDepreciationSnapshots(
|
||||
ctx context.Context,
|
||||
periodDate time.Time,
|
||||
farmIDs []uint,
|
||||
farmNameByID map[uint]string,
|
||||
) ([]entity.FarmDepreciationSnapshot, error) {
|
||||
if len(farmIDs) == 0 {
|
||||
return []entity.FarmDepreciationSnapshot{}, nil
|
||||
}
|
||||
|
||||
inputRows, err := s.ExpenseDepreciationRepo.GetLatestTransferInputsByFarms(ctx, periodDate, farmIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupedByFarm := make(map[uint][]repportRepo.FarmDepreciationLatestTransferRow, len(farmIDs))
|
||||
houseTypeSet := make(map[string]struct{})
|
||||
maxDay := 0
|
||||
|
||||
for _, row := range inputRows {
|
||||
groupedByFarm[row.ProjectFlockID] = append(groupedByFarm[row.ProjectFlockID], row)
|
||||
dayN := depreciationDayNumber(row.TransferDate, periodDate)
|
||||
if dayN > maxDay {
|
||||
maxDay = dayN
|
||||
}
|
||||
houseType := strings.TrimSpace(strings.ToLower(valueOrEmptyString(row.HouseType)))
|
||||
if houseType != "" {
|
||||
houseTypeSet[houseType] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
houseTypes := make([]string, 0, len(houseTypeSet))
|
||||
for houseType := range houseTypeSet {
|
||||
houseTypes = append(houseTypes, houseType)
|
||||
}
|
||||
sort.Strings(houseTypes)
|
||||
|
||||
percentByHouseType, err := s.ExpenseDepreciationRepo.GetDepreciationPercents(ctx, houseTypes, maxDay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type sourceCostCacheItem struct {
|
||||
totalDepCost float64
|
||||
}
|
||||
sourceCostCache := make(map[string]sourceCostCacheItem)
|
||||
sourcePopulationCache := make(map[uint]float64)
|
||||
|
||||
result := make([]entity.FarmDepreciationSnapshot, 0, len(farmIDs))
|
||||
for _, farmID := range farmIDs {
|
||||
farmRows := groupedByFarm[farmID]
|
||||
components := depreciationFarmComponents{
|
||||
KandangCount: len(farmRows),
|
||||
Kandang: make([]depreciationKandangComponent, 0, len(farmRows)),
|
||||
}
|
||||
|
||||
totalDepreciationValue := 0.0
|
||||
totalPulletCostDayN := 0.0
|
||||
for _, row := range farmRows {
|
||||
dayN := depreciationDayNumber(row.TransferDate, periodDate)
|
||||
houseType := strings.TrimSpace(strings.ToLower(valueOrEmptyString(row.HouseType)))
|
||||
|
||||
transferDateKey := row.TransferDate.Format("2006-01-02")
|
||||
cacheKey := fmt.Sprintf("%d|%s", row.SourceProjectFlockID, transferDateKey)
|
||||
cached, exists := sourceCostCache[cacheKey]
|
||||
if !exists {
|
||||
endOfDay := row.TransferDate.Add(24 * time.Hour)
|
||||
sourceDepCost, calcErr := s.HppSvc.GetTotalDepresiasiFlockGrowing(row.SourceProjectFlockID, &endOfDay)
|
||||
if calcErr != nil {
|
||||
return nil, calcErr
|
||||
}
|
||||
cached = sourceCostCacheItem{totalDepCost: sourceDepCost}
|
||||
sourceCostCache[cacheKey] = cached
|
||||
}
|
||||
|
||||
sourcePopulation, popExists := sourcePopulationCache[row.SourceProjectFlockID]
|
||||
if !popExists {
|
||||
if s.HppCostRepo == nil {
|
||||
sourcePopulation = 0
|
||||
} else {
|
||||
kandangIDs, idsErr := s.HppCostRepo.GetProjectFlockKandangIDs(ctx, row.SourceProjectFlockID)
|
||||
if idsErr != nil {
|
||||
return nil, idsErr
|
||||
}
|
||||
population, popErr := s.HppCostRepo.GetTotalPopulation(ctx, kandangIDs)
|
||||
if popErr != nil {
|
||||
return nil, popErr
|
||||
}
|
||||
sourcePopulation = population
|
||||
}
|
||||
sourcePopulationCache[row.SourceProjectFlockID] = sourcePopulation
|
||||
}
|
||||
|
||||
initialPulletCost := 0.0
|
||||
if sourcePopulation > 0 {
|
||||
initialPulletCost = (cached.totalDepCost * row.TransferQty) / sourcePopulation
|
||||
}
|
||||
|
||||
pulletCostDayN, depreciationValue, depreciationPercent := calculateDepreciationAtDayN(
|
||||
initialPulletCost,
|
||||
dayN,
|
||||
houseType,
|
||||
percentByHouseType,
|
||||
)
|
||||
|
||||
totalPulletCostDayN += pulletCostDayN
|
||||
totalDepreciationValue += depreciationValue
|
||||
|
||||
components.Kandang = append(components.Kandang, depreciationKandangComponent{
|
||||
ProjectFlockKandangID: row.ProjectFlockKandangID,
|
||||
KandangID: row.KandangID,
|
||||
KandangName: row.KandangName,
|
||||
TransferID: row.TransferID,
|
||||
TransferDate: row.TransferDate.Format("2006-01-02"),
|
||||
SourceProjectFlockID: row.SourceProjectFlockID,
|
||||
HouseType: houseType,
|
||||
DayN: dayN,
|
||||
DepreciationPercent: depreciationPercent,
|
||||
TransferQty: row.TransferQty,
|
||||
PulletCostDayN: pulletCostDayN,
|
||||
DepreciationValue: depreciationValue,
|
||||
})
|
||||
}
|
||||
|
||||
effectivePercent := 0.0
|
||||
effectivePercent = calculateEffectiveDepreciationPercent(totalDepreciationValue, totalPulletCostDayN)
|
||||
|
||||
componentsJSON, marshalErr := json.Marshal(components)
|
||||
if marshalErr != nil {
|
||||
return nil, marshalErr
|
||||
}
|
||||
|
||||
result = append(result, entity.FarmDepreciationSnapshot{
|
||||
ProjectFlockId: farmID,
|
||||
PeriodDate: periodDate,
|
||||
DepreciationPercentEffective: effectivePercent,
|
||||
DepreciationValue: totalDepreciationValue,
|
||||
PulletCostDayNTotal: totalPulletCostDayN,
|
||||
Components: componentsJSON,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func depreciationDayNumber(transferDate time.Time, periodDate time.Time) int {
|
||||
transfer := time.Date(transferDate.Year(), transferDate.Month(), transferDate.Day(), 0, 0, 0, 0, transferDate.Location())
|
||||
period := time.Date(periodDate.Year(), periodDate.Month(), periodDate.Day(), 0, 0, 0, 0, periodDate.Location())
|
||||
if period.Before(transfer) {
|
||||
return 0
|
||||
}
|
||||
return int(period.Sub(transfer).Hours()/24) + 1
|
||||
}
|
||||
|
||||
func calculateDepreciationAtDayN(
|
||||
initialPulletCost float64,
|
||||
dayN int,
|
||||
houseType string,
|
||||
percentByHouseType map[string]map[int]float64,
|
||||
) (float64, float64, float64) {
|
||||
if initialPulletCost <= 0 || dayN <= 0 || houseType == "" {
|
||||
return 0, 0, 0
|
||||
}
|
||||
|
||||
housePercent, exists := percentByHouseType[houseType]
|
||||
if !exists {
|
||||
return 0, 0, 0
|
||||
}
|
||||
|
||||
current := initialPulletCost
|
||||
pulletCostDayN := 0.0
|
||||
depreciationValue := 0.0
|
||||
depreciationPercent := 0.0
|
||||
for day := 1; day <= dayN; day++ {
|
||||
pct := housePercent[day]
|
||||
dep := current * (pct / 100)
|
||||
if day == dayN {
|
||||
pulletCostDayN = current
|
||||
depreciationValue = dep
|
||||
depreciationPercent = pct
|
||||
}
|
||||
current -= dep
|
||||
if current < 0 {
|
||||
current = 0
|
||||
}
|
||||
}
|
||||
return pulletCostDayN, depreciationValue, depreciationPercent
|
||||
}
|
||||
|
||||
func calculateEffectiveDepreciationPercent(totalDepreciationValue, totalPulletCostDayN float64) float64 {
|
||||
if totalPulletCostDayN <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (totalDepreciationValue / totalPulletCostDayN) * 100
|
||||
}
|
||||
|
||||
func parseSnapshotComponents(raw []byte) any {
|
||||
if len(raw) == 0 {
|
||||
return map[string]any{}
|
||||
}
|
||||
var out any
|
||||
if err := json.Unmarshal(raw, &out); err != nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func valueOrEmptyString(v *string) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return *v
|
||||
}
|
||||
|
||||
func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.MarketingQuery) ([]dto.RepportMarketingItemDTO, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
@@ -2133,6 +2631,84 @@ func (s *repportService) parseHppPerKandangQuery(ctx *fiber.Ctx) (*validation.Hp
|
||||
return params, filters, nil
|
||||
}
|
||||
|
||||
func (s *repportService) parseExpenseDepreciationQuery(ctx *fiber.Ctx) (*validation.ExpenseDepreciationQuery, dto.ExpenseDepreciationFiltersDTO, error) {
|
||||
page := ctx.QueryInt("page", 1)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
limit := ctx.QueryInt("limit", 10)
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
rawArea := ctx.Query("area_id", "")
|
||||
rawLocation := ctx.Query("location_id", "")
|
||||
rawProjectFlock := ctx.Query("project_flock_id", "")
|
||||
period := strings.TrimSpace(ctx.Query("period", ""))
|
||||
|
||||
areaIDs, err := parseCommaSeparatedInt64s(rawArea)
|
||||
if err != nil {
|
||||
return nil, dto.ExpenseDepreciationFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
locationIDs, err := parseCommaSeparatedInt64s(rawLocation)
|
||||
if err != nil {
|
||||
return nil, dto.ExpenseDepreciationFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
projectFlockIDs, err := parseCommaSeparatedInt64s(rawProjectFlock)
|
||||
if err != nil {
|
||||
return nil, dto.ExpenseDepreciationFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
locationScope, err := m.ResolveLocationScope(ctx, s.ExpenseRealizationRepo.DB())
|
||||
if err != nil {
|
||||
return nil, dto.ExpenseDepreciationFiltersDTO{}, err
|
||||
}
|
||||
areaScope, err := m.ResolveAreaScope(ctx, s.ExpenseRealizationRepo.DB())
|
||||
if err != nil {
|
||||
return nil, dto.ExpenseDepreciationFiltersDTO{}, err
|
||||
}
|
||||
|
||||
if locationScope.Restrict {
|
||||
allowed := toInt64Slice(locationScope.IDs)
|
||||
if len(allowed) == 0 {
|
||||
locationIDs = []int64{-1}
|
||||
} else if len(locationIDs) > 0 {
|
||||
locationIDs = intersectInt64(locationIDs, allowed)
|
||||
} else {
|
||||
locationIDs = allowed
|
||||
}
|
||||
}
|
||||
|
||||
if areaScope.Restrict {
|
||||
allowed := toInt64Slice(areaScope.IDs)
|
||||
if len(allowed) == 0 {
|
||||
areaIDs = []int64{-1}
|
||||
} else if len(areaIDs) > 0 {
|
||||
areaIDs = intersectInt64(areaIDs, allowed)
|
||||
} else {
|
||||
areaIDs = allowed
|
||||
}
|
||||
}
|
||||
|
||||
params := &validation.ExpenseDepreciationQuery{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Period: period,
|
||||
ProjectFlockIDs: projectFlockIDs,
|
||||
AreaIDs: areaIDs,
|
||||
LocationIDs: locationIDs,
|
||||
}
|
||||
|
||||
filters := dto.NewExpenseDepreciationFiltersDTO(
|
||||
rawArea,
|
||||
rawLocation,
|
||||
rawProjectFlock,
|
||||
period,
|
||||
)
|
||||
|
||||
return params, filters, nil
|
||||
}
|
||||
|
||||
func parseCommaSeparatedInt64s(raw string) ([]int64, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
|
||||
@@ -75,6 +75,21 @@ type HppPerKandangQuery struct {
|
||||
WeightMax *float64 `query:"-"`
|
||||
}
|
||||
|
||||
type ExpenseDepreciationQuery struct {
|
||||
Page int `query:"page" validate:"omitempty,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,min=1,max=1000,gt=0"`
|
||||
Period string `query:"period" validate:"required,datetime=2006-01-02"`
|
||||
ProjectFlockIDs []int64 `query:"-"`
|
||||
AreaIDs []int64 `query:"-"`
|
||||
LocationIDs []int64 `query:"-"`
|
||||
}
|
||||
|
||||
type ExpenseDepreciationManualInputUpsert struct {
|
||||
ProjectFlockID uint `json:"project_flock_id" validate:"required,gt=0"`
|
||||
TotalCost float64 `json:"total_cost" validate:"required,gte=0"`
|
||||
Note *string `json:"note" validate:"omitempty,max=1000"`
|
||||
}
|
||||
|
||||
type ProductionResultQuery struct {
|
||||
Page int `query:"page" validate:"omitempty,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,min=1,gt=0"`
|
||||
|
||||
Reference in New Issue
Block a user