From 8b1831fc73b611b2dc189ad2fa08901e31e16b7a Mon Sep 17 00:00:00 2001 From: giovanni Date: Sat, 24 Jan 2026 12:03:09 +0700 Subject: [PATCH 1/5] adjust take weight remaining --- .../repports/services/repport.service.go | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 9c976138..98905b66 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -1576,18 +1576,18 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes return nil, nil, err } - eggMap, err := s.HppPerKandangRepo.GetWeightRemainingByProjectFlockKandangIDs(ctx.Context(), startOfDay, endOfDay, validPfkIDs) - if err != nil { - return nil, nil, err - } - for pfkID, egg := range eggMap { - if rowIdx, ok := pfkIndex[pfkID]; ok { - repoRows[rowIdx].EggProductionWeightKgRemaining = egg.EggProductionWeightKgRemaining - // repoRows[rowIdx].EggProductionPiecesRemaining = egg.EggProductionPiecesRemaining - // repoRows[rowIdx].EggProductionTotalWeightKg = egg.EggProductionTotalWeightKg - // repoRows[rowIdx].EggProductionTotalPieces = egg.EggProductionTotalPieces - } - } + // eggMap, err := s.HppPerKandangRepo.GetWeightRemainingByProjectFlockKandangIDs(ctx.Context(), startOfDay, endOfDay, validPfkIDs) + // if err != nil { + // return nil, nil, err + // } + // for pfkID, egg := range eggMap { + // if rowIdx, ok := pfkIndex[pfkID]; ok { + // repoRows[rowIdx].EggProductionWeightKgRemaining = egg.EggProductionWeightKgRemaining + // // repoRows[rowIdx].EggProductionPiecesRemaining = egg.EggProductionPiecesRemaining + // // repoRows[rowIdx].EggProductionTotalWeightKg = egg.EggProductionTotalWeightKg + // // repoRows[rowIdx].EggProductionTotalPieces = egg.EggProductionTotalPieces + // } + // } } costMap := make(map[uint]HppCostAggregate, len(costRows)) @@ -1673,7 +1673,7 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes } var eggPiecesFloatRemaining float64 - eggRemainingWeightFloatRemaining := row.EggProductionWeightKgRemaining + var eggRemainingWeightFloatRemaining float64 var eggTotalPiecesFloat float64 var eggWeightFloat float64 eggHpp := 0.0 @@ -1683,7 +1683,7 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes return nil, nil, err } if hppCost != nil { - // eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg + eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg eggPiecesFloatRemaining = hppCost.Estimation.Butir - hppCost.Real.Butir eggHpp = hppCost.Estimation.HargaKg eggTotalPiecesFloat = hppCost.Estimation.Butir From 4646bf557726c790a0fd35e4efa0b6712b290487 Mon Sep 17 00:00:00 2001 From: giovanni Date: Sat, 24 Jan 2026 13:34:21 +0700 Subject: [PATCH 2/5] add filter location id --- .../master/warehouses/controllers/warehouse.controller.go | 1 + .../modules/master/warehouses/services/warehouse.service.go | 3 +++ .../master/warehouses/validations/warehouse.validation.go | 1 + 3 files changed, 5 insertions(+) diff --git a/internal/modules/master/warehouses/controllers/warehouse.controller.go b/internal/modules/master/warehouses/controllers/warehouse.controller.go index a7cfac94..4e93cb52 100644 --- a/internal/modules/master/warehouses/controllers/warehouse.controller.go +++ b/internal/modules/master/warehouses/controllers/warehouse.controller.go @@ -28,6 +28,7 @@ func (u *WarehouseController) GetAll(c *fiber.Ctx) error { Limit: c.QueryInt("limit", 10), Search: c.Query("search", ""), AreaId: c.QueryInt("area_id", 0), + LocationId: c.QueryInt("location_id", 0), ActiveProjectFlockOnly: c.QueryBool("active_project_flock", false), } diff --git a/internal/modules/master/warehouses/services/warehouse.service.go b/internal/modules/master/warehouses/services/warehouse.service.go index 0b9dfc18..0730bc48 100644 --- a/internal/modules/master/warehouses/services/warehouse.service.go +++ b/internal/modules/master/warehouses/services/warehouse.service.go @@ -58,6 +58,9 @@ func (s warehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti if params.AreaId != 0 { db = db.Where("area_id = ?", params.AreaId) } + if params.LocationId != 0 { + db = db.Where("location_id = ?", params.LocationId) + } if params.ActiveProjectFlockOnly { db = db.Where(` EXISTS ( diff --git a/internal/modules/master/warehouses/validations/warehouse.validation.go b/internal/modules/master/warehouses/validations/warehouse.validation.go index 1e305520..be796082 100644 --- a/internal/modules/master/warehouses/validations/warehouse.validation.go +++ b/internal/modules/master/warehouses/validations/warehouse.validation.go @@ -21,5 +21,6 @@ type Query struct { Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` Search string `query:"search" validate:"omitempty,max=50"` AreaId int `query:"area_id" validate:"omitempty,number,gt=0"` + LocationId int `query:"location_id" validate:"omitempty,number,gt=0"` ActiveProjectFlockOnly bool `query:"active_project_flock"` } From 458c8e0a9128952909fdc7caa9811d695507f514 Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Sat, 24 Jan 2026 13:35:13 +0700 Subject: [PATCH 3/5] fix(BE): edit customer, finance: bank optional, nominal minus, and filter --- ...3_alter_payments_bank_id_nullable.down.sql | 6 ++ ...853_alter_payments_bank_id_nullable.up.sql | 6 ++ .../finance/initials/dto/initial.dto.go | 11 +-- .../initials/services/initial.service.go | 58 +++++++++---- .../validations/initial.validation.go | 2 +- .../injections/services/injection.service.go | 10 ++- .../validations/injection.validation.go | 4 +- .../controllers/transaction.controller.go | 43 +++++++++- .../transactions/dto/transaction.dto.go | 11 +-- .../services/transaction.service.go | 83 ++++++++++++++++++- .../validations/transaction.validation.go | 13 ++- .../customers/services/customer.service.go | 16 ++++ 12 files changed, 226 insertions(+), 37 deletions(-) create mode 100644 internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.down.sql create mode 100644 internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.up.sql diff --git a/internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.down.sql b/internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.down.sql new file mode 100644 index 00000000..1b1c7f6f --- /dev/null +++ b/internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.down.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE payments + ALTER COLUMN bank_id SET NOT NULL; + +COMMIT; diff --git a/internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.up.sql b/internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.up.sql new file mode 100644 index 00000000..b95dcbf0 --- /dev/null +++ b/internal/database/migrations/20260124050853_alter_payments_bank_id_nullable.up.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE payments + ALTER COLUMN bank_id DROP NOT NULL; + +COMMIT; diff --git a/internal/modules/finance/initials/dto/initial.dto.go b/internal/modules/finance/initials/dto/initial.dto.go index 1311024f..454422fa 100644 --- a/internal/modules/finance/initials/dto/initial.dto.go +++ b/internal/modules/finance/initials/dto/initial.dto.go @@ -20,7 +20,7 @@ type InitialRelationDTO struct { InitialBalanceType string `json:"initial_balance_type"` InitialBalanceTypeLabel string `json:"initial_balance_type_label"` Party Party `json:"party"` - Bank bankDTO.BankRelationDTO `json:"bank,omitempty"` + Bank *bankDTO.BankRelationDTO `json:"bank"` Direction string `json:"direction"` Nominal float64 `json:"nominal"` Notes string `json:"notes"` @@ -128,11 +128,12 @@ func partyFromInitial(e entity.Payment) Party { return party } -func bankFromInitial(e entity.Payment) bankDTO.BankRelationDTO { +func bankFromInitial(e entity.Payment) *bankDTO.BankRelationDTO { if e.BankWarehouse.Id == 0 { - return bankDTO.BankRelationDTO{} + return nil } - return bankDTO.ToBankRelationDTO(e.BankWarehouse) + bank := bankDTO.ToBankRelationDTO(e.BankWarehouse) + return &bank } func userFromInitial(e entity.Payment) userDTO.UserRelationDTO { @@ -161,7 +162,7 @@ func initialBalanceLabel(balanceType string) string { } func initialBalanceTypeFromPayment(e entity.Payment) string { - if strings.EqualFold(e.Direction, "OUT") || e.Nominal < 0 { + if e.Nominal < 0 { return "NEGATIVE" } return "POSITIVE" diff --git a/internal/modules/finance/initials/services/initial.service.go b/internal/modules/finance/initials/services/initial.service.go index e06e99dd..14ab1f54 100644 --- a/internal/modules/finance/initials/services/initial.service.go +++ b/internal/modules/finance/initials/services/initial.service.go @@ -82,6 +82,7 @@ func (s initialService) GetOne(c *fiber.Ctx, id uint) (*entity.Payment, error) { } func (s *initialService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Payment, error) { + normalizeOptionalBankId(&req.BankId) if err := s.Validate.Struct(req); err != nil { return nil, err } @@ -124,7 +125,7 @@ func (s *initialService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit PaymentDate: time.Now(), PaymentMethod: string(utils.PaymentMethodSaldo), BankId: req.BankId, - Direction: directionForInitialType(balanceType), + Direction: directionForInitialType(party, balanceType), Nominal: signedNominal(balanceType, req.Nominal), Notes: req.Note, CreatedBy: actorID, @@ -164,6 +165,7 @@ func (s *initialService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } func (s initialService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Payment, error) { + normalizeOptionalBankId(&req.BankId) if err := s.Validate.Struct(req); err != nil { return nil, err } @@ -186,6 +188,8 @@ func (s initialService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) requiresExisting := req.PartyType != nil || req.PartyId != nil || req.InitialBalanceType != nil || req.Nominal != nil requiresVerification := requiresExisting || req.ReferenceNumber != nil || req.Note != nil || req.BankId != nil var existing *entity.Payment + var resolvedPartyType string + var resolvedPartyId uint if requiresVerification { current, err := s.Repository.GetByID(c.Context(), id, nil) if errors.Is(err, gorm.ErrRecordNotFound) { @@ -199,26 +203,25 @@ func (s initialService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) return nil, fiber.NewError(fiber.StatusNotFound, "Initial not found") } existing = current + resolvedPartyType = existing.PartyType + resolvedPartyId = existing.PartyId } if req.PartyType != nil || req.PartyId != nil { - partyType := existing.PartyType - partyId := existing.PartyId - if req.PartyType != nil { normalized, err := normalizePartyType(*req.PartyType) if err != nil { return nil, err } - partyType = normalized - updateBody["party_type"] = partyType + resolvedPartyType = normalized + updateBody["party_type"] = resolvedPartyType } if req.PartyId != nil { - partyId = *req.PartyId - updateBody["party_id"] = partyId + resolvedPartyId = *req.PartyId + updateBody["party_id"] = resolvedPartyId } - if err := s.ensurePartyExists(c.Context(), partyType, partyId); err != nil { + if err := s.ensurePartyExists(c.Context(), resolvedPartyType, resolvedPartyId); err != nil { return nil, err } } @@ -238,8 +241,11 @@ func (s initialService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) nominal = *req.Nominal } - updateBody["direction"] = directionForInitialType(balanceType) + updateBody["direction"] = directionForInitialType(resolvedPartyType, balanceType) updateBody["nominal"] = signedNominal(balanceType, nominal) + } else if req.PartyType != nil { + balanceType := balanceTypeFromPayment(existing) + updateBody["direction"] = directionForInitialType(resolvedPartyType, balanceType) } if len(updateBody) == 0 { @@ -262,7 +268,7 @@ func isInitialTransaction(transactionType string) bool { } func balanceTypeFromPayment(payment *entity.Payment) string { - if strings.EqualFold(payment.Direction, "OUT") || payment.Nominal < 0 { + if payment.Nominal < 0 { return "NEGATIVE" } return "POSITIVE" @@ -286,11 +292,24 @@ func normalizeInitialBalanceType(balanceType string) (string, error) { } } -func directionForInitialType(balanceType string) string { - if strings.EqualFold(balanceType, "NEGATIVE") { - return "OUT" +func directionForInitialType(partyType string, balanceType string) string { + switch utils.PaymentParty(strings.ToUpper(strings.TrimSpace(partyType))) { + case utils.PaymentPartySupplier: + if strings.EqualFold(balanceType, "POSITIVE") { + return "OUT" + } + return "IN" + case utils.PaymentPartyCustomer: + if strings.EqualFold(balanceType, "NEGATIVE") { + return "OUT" + } + return "IN" + default: + if strings.EqualFold(balanceType, "NEGATIVE") { + return "OUT" + } + return "IN" } - return "IN" } func signedNominal(balanceType string, nominal float64) float64 { @@ -335,3 +354,12 @@ func (s initialService) ensureBankExists(ctx context.Context, bankId *uint) erro commonSvc.RelationCheck{Name: "Bank", ID: bankId, Exists: s.Repository.BankExists}, ) } + +func normalizeOptionalBankId(bankId **uint) { + if bankId == nil || *bankId == nil { + return + } + if **bankId == 0 { + *bankId = nil + } +} diff --git a/internal/modules/finance/initials/validations/initial.validation.go b/internal/modules/finance/initials/validations/initial.validation.go index 27df2eea..d3e1df54 100644 --- a/internal/modules/finance/initials/validations/initial.validation.go +++ b/internal/modules/finance/initials/validations/initial.validation.go @@ -3,7 +3,7 @@ package validation type Create struct { PartyType string `json:"party_type" validate:"required_strict,max=50"` PartyId uint `json:"party_id" validate:"required_strict,number,gt=0"` - BankId *uint `json:"bank_id" validate:"required_strict,number,gt=0"` + BankId *uint `json:"bank_id,omitempty" validate:"omitempty,number,gt=0"` ReferenceNumber string `json:"reference_number" validate:"required_strict,max=100"` InitialBalanceType string `json:"initial_balance_type" validate:"required_strict,oneof=NEGATIVE POSITIVE"` Nominal float64 `json:"nominal" validate:"required_strict,gt=0"` diff --git a/internal/modules/finance/injections/services/injection.service.go b/internal/modules/finance/injections/services/injection.service.go index 8cb80e1c..99b0e488 100644 --- a/internal/modules/finance/injections/services/injection.service.go +++ b/internal/modules/finance/injections/services/injection.service.go @@ -110,7 +110,7 @@ func (s *injectionService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent PaymentDate: adjustmentDate, PaymentMethod: string(utils.PaymentMethodSaldo), BankId: req.BankId, - Direction: "IN", + Direction: directionForInjectionNominal(req.Nominal), Nominal: req.Nominal, Notes: req.Notes, CreatedBy: actorID, @@ -186,6 +186,7 @@ func (s injectionService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin } if req.Nominal != nil { updateBody["nominal"] = *req.Nominal + updateBody["direction"] = directionForInjectionNominal(*req.Nominal) } if req.Notes != nil { updateBody["notes"] = *req.Notes @@ -210,6 +211,13 @@ func isInjectionTransaction(transactionType string) bool { return strings.EqualFold(transactionType, string(utils.TransactionTypeInjection)) } +func directionForInjectionNominal(nominal float64) string { + if nominal < 0 { + return "OUT" + } + return "IN" +} + func (s injectionService) generateInjectionCode(ctx context.Context) (string, error) { sequence, err := s.Repository.NextPaymentSequence(ctx) if err != nil { diff --git a/internal/modules/finance/injections/validations/injection.validation.go b/internal/modules/finance/injections/validations/injection.validation.go index b5b75087..744256a1 100644 --- a/internal/modules/finance/injections/validations/injection.validation.go +++ b/internal/modules/finance/injections/validations/injection.validation.go @@ -3,14 +3,14 @@ package validation type Create struct { BankId *uint `json:"bank_id" validate:"required_strict,number,gt=0"` AdjustmentDate string `json:"adjustment_date" validate:"required_strict"` - Nominal float64 `json:"nominal" validate:"required_strict,gt=0"` + Nominal float64 `json:"nominal" validate:"required_strict"` Notes string `json:"notes" validate:"required_strict,max=500"` } type Update struct { BankId *uint `json:"bank_id,omitempty" validate:"omitempty,number,gt=0"` AdjustmentDate *string `json:"adjustment_date,omitempty" validate:"omitempty"` - Nominal *float64 `json:"nominal,omitempty" validate:"omitempty,gt=0"` + Nominal *float64 `json:"nominal,omitempty"` Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } diff --git a/internal/modules/finance/transactions/controllers/transaction.controller.go b/internal/modules/finance/transactions/controllers/transaction.controller.go index fa3e1369..5c25cbcd 100644 --- a/internal/modules/finance/transactions/controllers/transaction.controller.go +++ b/internal/modules/finance/transactions/controllers/transaction.controller.go @@ -3,6 +3,7 @@ package controller import ( "math" "strconv" + "strings" "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/dto" service "gitlab.com/mbugroup/lti-api.git/internal/modules/finance/transactions/services" @@ -23,10 +24,46 @@ func NewTransactionController(transactionService service.TransactionService) *Tr } func (u *TransactionController) GetAll(c *fiber.Ctx) error { + parseOptionalUint := func(key string) (*uint, error) { + raw := strings.TrimSpace(c.Query(key, "")) + if raw == "" { + return nil, nil + } + parsed, err := strconv.ParseUint(raw, 10, 64) + if err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid "+key) + } + if parsed == 0 { + return nil, nil + } + value := uint(parsed) + return &value, nil + } + + bankId, err := parseOptionalUint("bank_id") + if err != nil { + return err + } + customerId, err := parseOptionalUint("customer_id") + if err != nil { + return err + } + supplierId, err := parseOptionalUint("supplier_id") + if err != nil { + return err + } + query := &validation.Query{ - Page: c.QueryInt("page", 1), - Limit: c.QueryInt("limit", 10), - Search: c.Query("search", ""), + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + Search: c.Query("search", ""), + TransactionType: c.Query("transaction_type", ""), + BankId: bankId, + CustomerId: customerId, + SupplierId: supplierId, + SortDate: c.Query("sort_date", ""), + StartDate: c.Query("start_date", ""), + EndDate: c.Query("end_date", ""), } if query.Page < 1 || query.Limit < 1 { diff --git a/internal/modules/finance/transactions/dto/transaction.dto.go b/internal/modules/finance/transactions/dto/transaction.dto.go index 07703fce..89b63ecf 100644 --- a/internal/modules/finance/transactions/dto/transaction.dto.go +++ b/internal/modules/finance/transactions/dto/transaction.dto.go @@ -21,7 +21,7 @@ type TransactionRelationDTO struct { Party Party `json:"party"` PaymentDate time.Time `json:"payment_date"` PaymentMethod string `json:"payment_method"` - Bank bankDTO.BankRelationDTO `json:"bank,omitempty"` + Bank *bankDTO.BankRelationDTO `json:"bank"` ExpenseAmount float64 `json:"expense_amount"` IncomeAmount float64 `json:"income_amount"` Nominal float64 `json:"nominal"` @@ -37,7 +37,7 @@ type TransactionListDTO struct { Party Party `json:"party"` PaymentDate time.Time `json:"payment_date"` PaymentMethod string `json:"payment_method"` - Bank bankDTO.BankRelationDTO `json:"bank"` + Bank *bankDTO.BankRelationDTO `json:"bank"` ExpenseAmount float64 `json:"expense_amount"` IncomeAmount float64 `json:"income_amount"` Nominal float64 `json:"nominal"` @@ -151,11 +151,12 @@ func partyFromPayment(e entity.Payment) Party { return party } -func bankFromPayment(e entity.Payment) bankDTO.BankRelationDTO { +func bankFromPayment(e entity.Payment) *bankDTO.BankRelationDTO { if e.BankWarehouse.Id == 0 { - return bankDTO.BankRelationDTO{} + return nil } - return bankDTO.ToBankRelationDTO(e.BankWarehouse) + bank := bankDTO.ToBankRelationDTO(e.BankWarehouse) + return &bank } func userFromPayment(e entity.Payment) userDTO.UserRelationDTO { diff --git a/internal/modules/finance/transactions/services/transaction.service.go b/internal/modules/finance/transactions/services/transaction.service.go index f7398d43..f422320f 100644 --- a/internal/modules/finance/transactions/services/transaction.service.go +++ b/internal/modules/finance/transactions/services/transaction.service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "strings" + "time" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -61,13 +62,19 @@ func (s transactionService) GetAll(c *fiber.Ctx, params *validation.Query) ([]en return nil, 0, err } + startDate, endDate, err := parseTransactionDateRange(params.StartDate, params.EndDate) + if err != nil { + return nil, 0, err + } + offset := (params.Page - 1) * params.Limit transactions, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) + if params.Search != "" { like := "%" + strings.ToLower(strings.TrimSpace(params.Search)) + "%" - return db.Where( + db = db.Where( `LOWER(payment_code) LIKE ? OR LOWER(COALESCE(reference_number, '')) LIKE ? OR LOWER(COALESCE(transaction_type, '')) LIKE ? OR @@ -75,7 +82,35 @@ func (s transactionService) GetAll(c *fiber.Ctx, params *validation.Query) ([]en like, like, like, like, ) } - return db.Order("payment_date DESC").Order("created_at DESC") + + if strings.TrimSpace(params.TransactionType) != "" { + db = db.Where("transaction_type = ?", strings.ToUpper(strings.TrimSpace(params.TransactionType))) + } + + if params.BankId != nil { + db = db.Where("bank_id = ?", *params.BankId) + } + + if params.CustomerId != nil && params.SupplierId != nil { + db = db.Where( + "(party_type = ? AND party_id = ?) OR (party_type = ? AND party_id = ?)", + string(utils.PaymentPartyCustomer), *params.CustomerId, + string(utils.PaymentPartySupplier), *params.SupplierId, + ) + } else if params.CustomerId != nil { + db = db.Where("party_type = ? AND party_id = ?", string(utils.PaymentPartyCustomer), *params.CustomerId) + } else if params.SupplierId != nil { + db = db.Where("party_type = ? AND party_id = ?", string(utils.PaymentPartySupplier), *params.SupplierId) + } + + if startDate != nil { + db = db.Where("payment_date >= ?", *startDate) + } + if endDate != nil { + db = db.Where("payment_date < ?", *endDate) + } + + return applyTransactionSort(db, params.SortDate) }) if err != nil { @@ -173,3 +208,47 @@ func (s transactionService) approvalQueryModifier() func(*gorm.DB) *gorm.DB { return db.Preload("ActionUser") } } + +func parseTransactionDateRange(startDate, endDate string) (*time.Time, *time.Time, error) { + start := strings.TrimSpace(startDate) + end := strings.TrimSpace(endDate) + + var startPtr *time.Time + var endPtr *time.Time + var endValue *time.Time + + if start != "" { + parsed, err := utils.ParseDateString(start) + if err != nil { + return nil, nil, utils.BadRequest("start_date must use format YYYY-MM-DD") + } + startPtr = &parsed + } + + if end != "" { + parsed, err := utils.ParseDateString(end) + if err != nil { + return nil, nil, utils.BadRequest("end_date must use format YYYY-MM-DD") + } + endValue = &parsed + nextDay := parsed.AddDate(0, 0, 1) + endPtr = &nextDay + } + + if startPtr != nil && endValue != nil && startPtr.After(*endValue) { + return nil, nil, utils.BadRequest("start_date must be earlier than end_date") + } + + return startPtr, endPtr, nil +} + +func applyTransactionSort(db *gorm.DB, sortDate string) *gorm.DB { + switch strings.ToLower(strings.TrimSpace(sortDate)) { + case "created_at": + return db.Order("created_at DESC").Order("payment_date DESC") + case "payment_date": + return db.Order("payment_date DESC").Order("created_at DESC") + default: + return db.Order("payment_date DESC").Order("created_at DESC") + } +} diff --git a/internal/modules/finance/transactions/validations/transaction.validation.go b/internal/modules/finance/transactions/validations/transaction.validation.go index 7d16d3ee..f367dda1 100644 --- a/internal/modules/finance/transactions/validations/transaction.validation.go +++ b/internal/modules/finance/transactions/validations/transaction.validation.go @@ -9,7 +9,14 @@ type Update struct { } type Query struct { - Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` - Search string `query:"search" validate:"omitempty,max=50"` + Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` + Search string `query:"search" validate:"omitempty,max=50"` + TransactionType string `query:"transaction_type" validate:"omitempty,max=50"` + BankId *uint `query:"bank_id" validate:"omitempty,number,gt=0"` + CustomerId *uint `query:"customer_id" validate:"omitempty,number,gt=0"` + SupplierId *uint `query:"supplier_id" validate:"omitempty,number,gt=0"` + SortDate string `query:"sort_date" validate:"omitempty,oneof=created_at payment_date"` + StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"` + EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"` } diff --git a/internal/modules/master/customers/services/customer.service.go b/internal/modules/master/customers/services/customer.service.go index fe4cb41e..6156dc8c 100644 --- a/internal/modules/master/customers/services/customer.service.go +++ b/internal/modules/master/customers/services/customer.service.go @@ -156,6 +156,22 @@ func (s customerService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint updateBody["type"] = typ } + if req.Address != nil { + updateBody["address"] = *req.Address + } + + if req.Phone != nil { + updateBody["phone"] = *req.Phone + } + + if req.Email != nil { + updateBody["email"] = *req.Email + } + + if req.AccountNumber != nil { + updateBody["account_number"] = *req.AccountNumber + } + if len(updateBody) == 0 { return s.GetOne(c, id) } From 8c849818129911ff4855498ee8dac97243bd8518 Mon Sep 17 00:00:00 2001 From: giovanni Date: Sat, 24 Jan 2026 14:17:50 +0700 Subject: [PATCH 4/5] adjust get weight remaining --- .../repositories/hpp_per_kandang.repository.go | 11 ++++++----- .../modules/repports/services/repport.service.go | 12 ++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/internal/modules/repports/repositories/hpp_per_kandang.repository.go b/internal/modules/repports/repositories/hpp_per_kandang.repository.go index 03d56fc6..eeb09e92 100644 --- a/internal/modules/repports/repositories/hpp_per_kandang.repository.go +++ b/internal/modules/repports/repositories/hpp_per_kandang.repository.go @@ -23,6 +23,7 @@ type HppPerKandangRow struct { // RemainingChickenBirds float64 // RemainingChickenWeight float64 EggProductionWeightKgRemaining float64 + // AverageWeightEggPerPiece float64 // EggProductionPiecesRemaining float64 // EggProductionTotalWeightKg float64 // EggProductionTotalPieces float64 @@ -229,8 +230,8 @@ func (r *hppPerKandangRepository) GetWeightRemainingByProjectFlockKandangIDs(ctx ) type eggRow struct { - ProjectFlockKandangID uint - EggProductionWeightKgRemaining float64 + ProjectFlockKandangID uint + AverageWeightEggPerPiece float64 // EggProductionPiecesRemaining float64 // EggProductionTotalWeightKg float64 // EggProductionTotalPieces float64 @@ -241,7 +242,7 @@ func (r *hppPerKandangRepository) GetWeightRemainingByProjectFlockKandangIDs(ctx Table("recordings AS r"). Select(` r.project_flock_kandangs_id AS project_flock_kandang_id, - COALESCE((SUM(re.weight) / NULLIF(SUM(re.total_qty), 0)) * SUM(re.total_qty - re.total_used), 0) AS egg_production_weight_kg_remaining`). + COALESCE(SUM(re.weight) / NULLIF(SUM(re.total_qty), 0), 0) AS average_weight_egg_per_piece`). Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval). Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id"). Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs). @@ -257,8 +258,8 @@ func (r *hppPerKandangRepository) GetWeightRemainingByProjectFlockKandangIDs(ctx result := make(map[uint]HppPerKandangRow, len(eggRows)) for _, row := range eggRows { result[row.ProjectFlockKandangID] = HppPerKandangRow{ - ProjectFlockKandangID: row.ProjectFlockKandangID, - EggProductionWeightKgRemaining: row.EggProductionWeightKgRemaining, + ProjectFlockKandangID: row.ProjectFlockKandangID, + // AverageWeightEggPerPiece: row.AverageWeightEggPerPiece, // EggProductionPiecesRemaining: row.EggProductionPiecesRemaining, // EggProductionTotalWeightKg: row.EggProductionTotalWeightKg, // EggProductionTotalPieces: row.EggProductionTotalPieces, diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 98905b66..67bf7339 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -1583,9 +1583,7 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes // for pfkID, egg := range eggMap { // if rowIdx, ok := pfkIndex[pfkID]; ok { // repoRows[rowIdx].EggProductionWeightKgRemaining = egg.EggProductionWeightKgRemaining - // // repoRows[rowIdx].EggProductionPiecesRemaining = egg.EggProductionPiecesRemaining - // // repoRows[rowIdx].EggProductionTotalWeightKg = egg.EggProductionTotalWeightKg - // // repoRows[rowIdx].EggProductionTotalPieces = egg.EggProductionTotalPieces + // repoRows[rowIdx].AverageWeightEggPerPiece = egg.AverageWeightEggPerPiece // } // } } @@ -1676,6 +1674,7 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes var eggRemainingWeightFloatRemaining float64 var eggTotalPiecesFloat float64 var eggWeightFloat float64 + var avgWeight float64 eggHpp := 0.0 if s.HppSvc != nil { hppCost, err := s.HppSvc.CalculateHppCost(row.ProjectFlockKandangID, &endOfDay) @@ -1683,11 +1682,12 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes return nil, nil, err } if hppCost != nil { - eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg eggPiecesFloatRemaining = hppCost.Estimation.Butir - hppCost.Real.Butir eggHpp = hppCost.Estimation.HargaKg eggTotalPiecesFloat = hppCost.Estimation.Butir eggWeightFloat = hppCost.Estimation.Kg + avgWeight = eggWeightFloat / eggTotalPiecesFloat + eggRemainingWeightFloatRemaining = avgWeight * eggPiecesFloatRemaining } } if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) { @@ -1703,10 +1703,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes eggWeightFloat = 0 } - avgWeight := 0.0 - if eggTotalPiecesFloat > 0 { - avgWeight = eggWeightFloat / eggTotalPiecesFloat - } if params.WeightMin != nil && avgWeight < *params.WeightMin { continue } From f4b2408698b7034b4e16492ec390bb0969e2ab03 Mon Sep 17 00:00:00 2001 From: ragilap Date: Sat, 24 Jan 2026 14:20:14 +0700 Subject: [PATCH 5/5] [FIX/BE-US] fix recording stock and dashboard filtering --- internal/modules/dashboards/module.go | 7 ++- .../repositories/dashboard.repository.go | 1 + .../dashboard_stats.repository.go | 26 +++++++- .../dashboards/services/dashboard.service.go | 59 +++++++++++++++++-- .../recordings/services/recording.service.go | 42 +++++++++++++ 5 files changed, 125 insertions(+), 10 deletions(-) diff --git a/internal/modules/dashboards/module.go b/internal/modules/dashboards/module.go index 24574dc7..d7d0d477 100644 --- a/internal/modules/dashboards/module.go +++ b/internal/modules/dashboards/module.go @@ -5,6 +5,8 @@ import ( "github.com/gofiber/fiber/v2" "gorm.io/gorm" + commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + commonService "gitlab.com/mbugroup/lti-api.git/internal/common/service" rDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories" sDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/services" @@ -16,11 +18,12 @@ type DashboardModule struct{} func (DashboardModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { dashboardRepo := rDashboard.NewDashboardRepository(db) + hppCostRepo := commonRepo.NewHppCostRepository(db) userRepo := rUser.NewUserRepository(db) - dashboardService := sDashboard.NewDashboardService(dashboardRepo, validate) + hppSvc := commonService.NewHppService(hppCostRepo) + dashboardService := sDashboard.NewDashboardService(dashboardRepo, validate, hppSvc) userService := sUser.NewUserService(userRepo, validate) DashboardRoutes(router, userService, dashboardService) } - diff --git a/internal/modules/dashboards/repositories/dashboard.repository.go b/internal/modules/dashboards/repositories/dashboard.repository.go index 90ee3bf8..eb9f6060 100644 --- a/internal/modules/dashboards/repositories/dashboard.repository.go +++ b/internal/modules/dashboards/repositories/dashboard.repository.go @@ -21,6 +21,7 @@ type DashboardRepository interface { SumSellingPrice(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (SellingPriceAggregate, error) SumEggProductionWeightGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) SumEggProductionWeightKg(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) + ListProjectFlockKandangIDsByEggProduction(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]uint, error) GetRecordingWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]RecordingWeeklyMetric, error) GetUniformityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]UniformityWeeklyMetric, error) GetStandardWeeklyMetrics(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyMetric, error) diff --git a/internal/modules/dashboards/repositories/dashboard_stats.repository.go b/internal/modules/dashboards/repositories/dashboard_stats.repository.go index 828dd96c..493851e5 100644 --- a/internal/modules/dashboards/repositories/dashboard_stats.repository.go +++ b/internal/modules/dashboards/repositories/dashboard_stats.repository.go @@ -309,6 +309,27 @@ func (r *DashboardRepositoryImpl) SumEggProductionWeightKg(ctx context.Context, return grams / 1000, nil } +func (r *DashboardRepositoryImpl) ListProjectFlockKandangIDsByEggProduction(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]uint, error) { + var ids []uint + + db := r.DB().WithContext(ctx). + Table("recording_eggs AS re"). + Select("DISTINCT r.project_flock_kandangs_id"). + Joins("JOIN recordings AS r ON r.id = re.recording_id"). + Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). + Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). + Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). + Where("r.deleted_at IS NULL") + + db = applyDashboardFilters(db, filters) + + if err := db.Scan(&ids).Error; err != nil { + return nil, err + } + + return ids, nil +} + func (r *DashboardRepositoryImpl) GetFeedUsageByUom(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]FeedUsageByUom, error) { var rows []FeedUsageByUom @@ -553,7 +574,7 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context var rows []ComparisonWeeklyMetric db := r.DB().WithContext(ctx). Table("recordings AS r"). - Select(fmt.Sprintf(`((r.day - 1) / 7 + 1) AS week, + Select(fmt.Sprintf(`(CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 ELSE ((r.day - 1) / 7 + 1) END) AS week, %s AS series_id, COALESCE(AVG(%s), 0) AS value`, seriesExpr, metricExpr)). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). @@ -561,8 +582,7 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id"). Joins("JOIN locations AS loc ON loc.id = k.location_id"). Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). - Where("r.deleted_at IS NULL"). - Where("r.day IS NOT NULL AND r.day > 0") + Where("r.deleted_at IS NULL") db = applyDashboardFilters(db, filters) diff --git a/internal/modules/dashboards/services/dashboard.service.go b/internal/modules/dashboards/services/dashboard.service.go index b4635b2e..ce2cc0f8 100644 --- a/internal/modules/dashboards/services/dashboard.service.go +++ b/internal/modules/dashboards/services/dashboard.service.go @@ -10,6 +10,7 @@ import ( "strings" "time" + commonService "gitlab.com/mbugroup/lti-api.git/internal/common/service" "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/dto" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations" @@ -27,13 +28,15 @@ type dashboardService struct { Log *logrus.Logger Validate *validator.Validate Repository repository.DashboardRepository + HppSvc commonService.HppService } -func NewDashboardService(repo repository.DashboardRepository, validate *validator.Validate) DashboardService { +func NewDashboardService(repo repository.DashboardRepository, validate *validator.Validate, hppSvc commonService.HppService) DashboardService { return &dashboardService{ Log: utils.Log, Validate: validate, Repository: repo, + HppSvc: hppSvc, } } @@ -592,13 +595,13 @@ func buildAggregateComparisonPercent(weeks []int, seriesRows []repository.Compar count++ } - if count == 0 { - continue - } - if result[week] == nil { result[week] = map[uint]float64{} } + if count == 0 { + result[week][series.Id] = 0 + continue + } result[week][series.Id] = sum / count } } @@ -846,6 +849,21 @@ func percentDelta(current, last float64) float64 { } func (s dashboardService) calculateHppGlobal(ctx context.Context, startDate, endExclusive, endDate time.Time, location *time.Location) (float64, float64, error) { + if s.HppSvc != nil { + currentHpp, err := s.hppGlobalForPeriod(ctx, startDate, endExclusive) + if err != nil { + return 0, 0, err + } + + lastMonthStart, lastMonthEndExclusive := monthRange(endDate.AddDate(0, -1, 0), location) + lastHpp, err := s.hppGlobalForPeriod(ctx, lastMonthStart, lastMonthEndExclusive) + if err != nil { + return 0, 0, err + } + + return currentHpp, lastHpp, nil + } + totalEggKg, err := s.Repository.SumEggProductionWeightKg(ctx, startDate, endExclusive, nil) if err != nil { return 0, 0, err @@ -878,6 +896,37 @@ func (s dashboardService) calculateHppGlobal(ctx context.Context, startDate, end return hppCurrent, hppLast, nil } +func (s dashboardService) hppGlobalForPeriod(ctx context.Context, startDate, endExclusive time.Time) (float64, error) { + kandangIDs, err := s.Repository.ListProjectFlockKandangIDsByEggProduction(ctx, startDate, endExclusive, nil) + if err != nil { + return 0, err + } + if len(kandangIDs) == 0 { + return 0, nil + } + + endOfPeriod := endExclusive.Add(-time.Nanosecond) + totalCost := 0.0 + totalWeightKg := 0.0 + for _, kandangID := range kandangIDs { + hppCost, err := s.HppSvc.CalculateHppCost(kandangID, &endOfPeriod) + if err != nil { + return 0, err + } + if hppCost == nil { + continue + } + totalCost += hppCost.Estimation.Total + totalWeightKg += hppCost.Estimation.Kg + } + + if totalWeightKg <= 0 { + return 0, nil + } + + return totalCost / totalWeightKg, nil +} + func (s dashboardService) calculateSellingPrice(ctx context.Context, endDate time.Time, location *time.Location) (float64, float64, error) { startPrevMonth, endPrevMonthExclusive := monthRange(endDate.AddDate(0, -1, 0), location) currentEndExclusive := endDate.AddDate(0, 0, 1) diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index d490185d..25103c2f 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -287,6 +287,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent } mappedDepletions := recordingutil.MapDepletions(createdRecording.Id, req.Depletions) + depletionDesired := resetDepletionQuantitiesForFIFO(mappedDepletions, s.FifoSvc != nil) if s.FifoSvc != nil && len(mappedDepletions) > 0 { sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, req.ProjectFlockKandangId) if err != nil { @@ -301,6 +302,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent return err } if s.FifoSvc != nil { + applyDepletionDesiredQuantities(mappedDepletions, depletionDesired, true) note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id) if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil { return err @@ -465,6 +467,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin } mappedDepletions := recordingutil.MapDepletions(recordingEntity.Id, req.Depletions) + depletionDesired := resetDepletionQuantitiesForFIFO(mappedDepletions, s.FifoSvc != nil) if s.FifoSvc != nil && len(mappedDepletions) > 0 { sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, recordingEntity.ProjectFlockKandangId) if err != nil { @@ -480,6 +483,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin } if s.FifoSvc != nil { + applyDepletionDesiredQuantities(mappedDepletions, depletionDesired, true) note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id) if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil { return err @@ -929,6 +933,9 @@ func (s *recordingService) consumeRecordingDepletions( destDelta := depletion.Qty + depletion.PendingQty if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 { + if depletion.ProductWarehouseId == sourceWarehouseID { + continue + } log := &entity.StockLog{ ProductWarehouseId: depletion.ProductWarehouseId, CreatedBy: actorID, @@ -1066,6 +1073,9 @@ func (s *recordingService) releaseRecordingDepletions( destDelta := depletion.Qty + depletion.PendingQty if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 { + if depletion.ProductWarehouseId == sourceWarehouseID { + continue + } log := &entity.StockLog{ ProductWarehouseId: depletion.ProductWarehouseId, CreatedBy: actorID, @@ -1235,6 +1245,11 @@ type desiredStock struct { Pending float64 } +type desiredDepletion struct { + Qty float64 + Pending float64 +} + func resetStockQuantitiesForFIFO(stocks []entity.RecordingStock, enabled bool) []desiredStock { desired := make([]desiredStock, len(stocks)) for i := range stocks { @@ -1269,6 +1284,33 @@ func applyStockDesiredQuantities(stocks []entity.RecordingStock, desired []desir } } +func resetDepletionQuantitiesForFIFO(depletions []entity.RecordingDepletion, enabled bool) []desiredDepletion { + desired := make([]desiredDepletion, len(depletions)) + for i := range depletions { + desired[i].Qty = depletions[i].Qty + desired[i].Pending = depletions[i].PendingQty + if !enabled { + continue + } + depletions[i].Qty = 0 + depletions[i].PendingQty = 0 + } + return desired +} + +func applyDepletionDesiredQuantities(depletions []entity.RecordingDepletion, desired []desiredDepletion, enabled bool) { + if !enabled { + return + } + for i := range depletions { + if i >= len(desired) { + break + } + depletions[i].Qty = desired[i].Qty + depletions[i].PendingQty = desired[i].Pending + } +} + func (s *recordingService) syncRecordingStocks( ctx context.Context, tx *gorm.DB,