From 8a57d439dc5dc200a7e9982c7889d454f0cbc39d Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Wed, 7 Jan 2026 16:09:08 +0700 Subject: [PATCH 1/7] unfinish: seeder and fix migration --- .../migrations/20251117034511_create_expenses_table.down.sql | 3 ++- .../20251210044651_create_so_number_sequence.down.sql | 2 +- internal/database/seed/seeder.go | 1 + internal/entities/product.go | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/database/migrations/20251117034511_create_expenses_table.down.sql b/internal/database/migrations/20251117034511_create_expenses_table.down.sql index bf0ea945..9b06613a 100644 --- a/internal/database/migrations/20251117034511_create_expenses_table.down.sql +++ b/internal/database/migrations/20251117034511_create_expenses_table.down.sql @@ -1 +1,2 @@ -DROP TABLE IF EXISTS expenses; \ No newline at end of file +DROP SEQUENCE IF EXISTS expenses_ref_seq; +DROP TABLE IF EXISTS expenses; diff --git a/internal/database/migrations/20251210044651_create_so_number_sequence.down.sql b/internal/database/migrations/20251210044651_create_so_number_sequence.down.sql index 4d80dd2c..53907ef1 100644 --- a/internal/database/migrations/20251210044651_create_so_number_sequence.down.sql +++ b/internal/database/migrations/20251210044651_create_so_number_sequence.down.sql @@ -1,3 +1,3 @@ -- Drop function and sequence for sales order numbers -DROP FUNCTION IF EXISTS generate_so_number(); DROP SEQUENCE IF EXISTS so_number_seq; +DROP FUNCTION IF EXISTS generate_so_number(); diff --git a/internal/database/seed/seeder.go b/internal/database/seed/seeder.go index b4f6886e..4f666812 100644 --- a/internal/database/seed/seeder.go +++ b/internal/database/seed/seeder.go @@ -299,6 +299,7 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories Tax: tax, ExpiryPeriod: seed.Expiry, CreatedBy: createdBy, + IsVisible: seed.IsVisible, } if err := tx.Create(&product).Error; err != nil { return err diff --git a/internal/entities/product.go b/internal/entities/product.go index d8ce59fc..f86d9a0a 100644 --- a/internal/entities/product.go +++ b/internal/entities/product.go @@ -21,7 +21,7 @@ type Product struct { CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - IsVisible bool `gorm:"column:is_visible;default:true"` + IsVisible bool `` CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` Uom Uom `gorm:"foreignKey:UomId;references:Id"` From b7914e8294bd42cacce9c39eff0e927c4938dd0d Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Fri, 9 Jan 2026 16:03:41 +0700 Subject: [PATCH 2/7] fix(BE): add party account number in payments --- ..._party_account_number_to_payments.down.sql | 6 ++++ ...dd_party_account_number_to_payments.up.sql | 6 ++++ internal/entities/payment.go | 33 +++++++++--------- .../finance/initials/dto/initial.dto.go | 13 ++++--- .../initials/services/initial.service.go | 1 + .../injections/services/injection.service.go | 1 + .../validations/injection.validation.go | 16 ++++----- .../finance/payments/dto/payment.dto.go | 13 ++++--- internal/modules/finance/payments/route.go | 8 ++--- .../payments/services/payment.service.go | 28 ++++++++------- .../validations/payment.validation.go | 34 ++++++++++--------- .../transactions/dto/transaction.dto.go | 13 ++++--- 12 files changed, 104 insertions(+), 68 deletions(-) create mode 100644 internal/database/migrations/20260109074006_add_party_account_number_to_payments.down.sql create mode 100644 internal/database/migrations/20260109074006_add_party_account_number_to_payments.up.sql diff --git a/internal/database/migrations/20260109074006_add_party_account_number_to_payments.down.sql b/internal/database/migrations/20260109074006_add_party_account_number_to_payments.down.sql new file mode 100644 index 00000000..64eb4839 --- /dev/null +++ b/internal/database/migrations/20260109074006_add_party_account_number_to_payments.down.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE payments + DROP COLUMN IF EXISTS party_account_number; + +COMMIT; diff --git a/internal/database/migrations/20260109074006_add_party_account_number_to_payments.up.sql b/internal/database/migrations/20260109074006_add_party_account_number_to_payments.up.sql new file mode 100644 index 00000000..abd80665 --- /dev/null +++ b/internal/database/migrations/20260109074006_add_party_account_number_to_payments.up.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE payments + ADD COLUMN IF NOT EXISTS party_account_number VARCHAR(50); + +COMMIT; diff --git a/internal/entities/payment.go b/internal/entities/payment.go index e48800fb..55575f20 100644 --- a/internal/entities/payment.go +++ b/internal/entities/payment.go @@ -7,22 +7,23 @@ import ( ) type Payment struct { - Id uint `gorm:"primaryKey;autoIncrement"` - PaymentCode string `gorm:"type:varchar(50);not null"` - ReferenceNumber *string `gorm:"type:varchar(100)"` - TransactionType string `gorm:"type:varchar(50)"` - PartyType string `gorm:"type:varchar(50);not null;index:payments_party_polymorphic,priority:1"` - PartyId uint `gorm:"not null;index:payments_party_polymorphic,priority:2"` - PaymentDate time.Time `gorm:"not null"` - PaymentMethod string `gorm:"type:varchar(20);not null"` - BankId *uint `gorm:"not null;index:idx_payments_bank_id"` - Direction string `gorm:"type:varchar(5);not null"` - Nominal float64 `gorm:"type:numeric(15,3);not null"` - Notes string `gorm:"type:text;not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - CreatedBy uint `gorm:"index" json:"-"` + Id uint `gorm:"primaryKey;autoIncrement"` + PaymentCode string `gorm:"type:varchar(50);not null"` + ReferenceNumber *string `gorm:"type:varchar(100)"` + TransactionType string `gorm:"type:varchar(50)"` + PartyType string `gorm:"type:varchar(50);not null;index:payments_party_polymorphic,priority:1"` + PartyId uint `gorm:"not null;index:payments_party_polymorphic,priority:2"` + PartyAccountNumber *string `gorm:"type:varchar(50)"` + PaymentDate time.Time `gorm:"not null"` + PaymentMethod string `gorm:"type:varchar(20);not null"` + BankId *uint `gorm:"not null;index:idx_payments_bank_id"` + Direction string `gorm:"type:varchar(5);not null"` + Nominal float64 `gorm:"type:numeric(15,3);not null"` + Notes string `gorm:"type:text;not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + CreatedBy uint `gorm:"index" json:"-"` BankWarehouse Bank `gorm:"foreignKey:BankId;references:Id"` CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` diff --git a/internal/modules/finance/initials/dto/initial.dto.go b/internal/modules/finance/initials/dto/initial.dto.go index 5eb76e9c..1311024f 100644 --- a/internal/modules/finance/initials/dto/initial.dto.go +++ b/internal/modules/finance/initials/dto/initial.dto.go @@ -101,20 +101,25 @@ func ToInitialDetailDTO(e entity.Payment) InitialDetailDTO { func partyFromInitial(e entity.Payment) Party { party := Party{ - Id: e.PartyId, - Type: e.PartyType, + Id: e.PartyId, + Type: e.PartyType, + } + if e.PartyAccountNumber != nil { + party.AccountNumber = *e.PartyAccountNumber } switch utils.PaymentParty(e.PartyType) { case utils.PaymentPartyCustomer: if e.Customer != nil && e.Customer.Id != 0 { party.Name = e.Customer.Name - party.AccountNumber = e.Customer.AccountNumber + if party.AccountNumber == "" { + party.AccountNumber = e.Customer.AccountNumber + } } case utils.PaymentPartySupplier: if e.Supplier != nil && e.Supplier.Id != 0 { party.Name = e.Supplier.Name - if e.Supplier.AccountNumber != nil { + if party.AccountNumber == "" && e.Supplier.AccountNumber != nil { party.AccountNumber = *e.Supplier.AccountNumber } } diff --git a/internal/modules/finance/initials/services/initial.service.go b/internal/modules/finance/initials/services/initial.service.go index 2eb15d3b..e06e99dd 100644 --- a/internal/modules/finance/initials/services/initial.service.go +++ b/internal/modules/finance/initials/services/initial.service.go @@ -120,6 +120,7 @@ func (s *initialService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit TransactionType: string(utils.TransactionTypeSaldoAwal), PartyType: party, PartyId: req.PartyId, + PartyAccountNumber: nil, PaymentDate: time.Now(), PaymentMethod: string(utils.PaymentMethodSaldo), BankId: req.BankId, diff --git a/internal/modules/finance/injections/services/injection.service.go b/internal/modules/finance/injections/services/injection.service.go index 1b1062b4..8cb80e1c 100644 --- a/internal/modules/finance/injections/services/injection.service.go +++ b/internal/modules/finance/injections/services/injection.service.go @@ -106,6 +106,7 @@ func (s *injectionService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent TransactionType: string(utils.TransactionTypeInjection), PartyType: string(utils.PaymentPartyCustomer), PartyId: 0, + PartyAccountNumber: nil, PaymentDate: adjustmentDate, PaymentMethod: string(utils.PaymentMethodSaldo), BankId: req.BankId, diff --git a/internal/modules/finance/injections/validations/injection.validation.go b/internal/modules/finance/injections/validations/injection.validation.go index eb324525..b5b75087 100644 --- a/internal/modules/finance/injections/validations/injection.validation.go +++ b/internal/modules/finance/injections/validations/injection.validation.go @@ -1,17 +1,17 @@ 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"` - Notes string `json:"notes" validate:"required_strict,max=500"` + 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"` + 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"` - Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` + 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"` + Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } type Query struct { diff --git a/internal/modules/finance/payments/dto/payment.dto.go b/internal/modules/finance/payments/dto/payment.dto.go index 23005e2d..3fbc8ad4 100644 --- a/internal/modules/finance/payments/dto/payment.dto.go +++ b/internal/modules/finance/payments/dto/payment.dto.go @@ -124,20 +124,25 @@ func ToPaymentDetailDTO(e entity.Payment) PaymentDetailDTO { func partyFromPayment(e entity.Payment) Party { party := Party{ - Id: e.PartyId, - Type: e.PartyType, + Id: e.PartyId, + Type: e.PartyType, + } + if e.PartyAccountNumber != nil { + party.AccountNumber = *e.PartyAccountNumber } switch utils.PaymentParty(e.PartyType) { case utils.PaymentPartyCustomer: if e.Customer != nil && e.Customer.Id != 0 { party.Name = e.Customer.Name - party.AccountNumber = e.Customer.AccountNumber + if party.AccountNumber == "" { + party.AccountNumber = e.Customer.AccountNumber + } } case utils.PaymentPartySupplier: if e.Supplier != nil && e.Supplier.Id != 0 { party.Name = e.Supplier.Name - if e.Supplier.AccountNumber != nil { + if party.AccountNumber == "" && e.Supplier.AccountNumber != nil { party.AccountNumber = *e.Supplier.AccountNumber } } diff --git a/internal/modules/finance/payments/route.go b/internal/modules/finance/payments/route.go index c5147fc0..b00de964 100644 --- a/internal/modules/finance/payments/route.go +++ b/internal/modules/finance/payments/route.go @@ -13,9 +13,9 @@ func PaymentRoutes(v1 fiber.Router, u user.UserService, s payment.PaymentService ctrl := controller.NewPaymentController(s) route := v1.Group("/payments") - route.Use(m.Auth(u)) + // route.Use(m.Auth(u)) - route.Post("/",m.RequirePermissions(m.P_Finances_Payments_CreateOne), ctrl.CreateOne) - route.Get("/:id",m.RequirePermissions(m.P_Finances_Payments_GetOne), ctrl.GetOne) - route.Patch("/:id",m.RequirePermissions(m.P_Finances_Payments_UpdateOne), ctrl.UpdateOne) + route.Post("/", ctrl.CreateOne) + route.Get("/:id", m.RequirePermissions(m.P_Finances_Payments_GetOne), ctrl.GetOne) + route.Patch("/:id", m.RequirePermissions(m.P_Finances_Payments_UpdateOne), ctrl.UpdateOne) } diff --git a/internal/modules/finance/payments/services/payment.service.go b/internal/modules/finance/payments/services/payment.service.go index 356288f1..8860f3f4 100644 --- a/internal/modules/finance/payments/services/payment.service.go +++ b/internal/modules/finance/payments/services/payment.service.go @@ -121,18 +121,19 @@ func (s *paymentService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } createBody := &entity.Payment{ - PaymentCode: code, - ReferenceNumber: req.ReferenceNumber, - TransactionType: transactionType, - PartyType: party, - PartyId: req.PartyId, - PaymentDate: paymentDate, - PaymentMethod: method, - BankId: req.BankId, - Direction: directionForParty(party), - Nominal: req.Nominal, - Notes: req.Notes, - CreatedBy: actorID, + PaymentCode: code, + ReferenceNumber: req.ReferenceNumber, + TransactionType: transactionType, + PartyType: party, + PartyId: req.PartyId, + PartyAccountNumber: req.PartyAccountNumber, + PaymentDate: paymentDate, + PaymentMethod: method, + BankId: req.BankId, + Direction: directionForParty(party), + Nominal: req.Nominal, + Notes: req.Notes, + CreatedBy: actorID, } err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { @@ -188,6 +189,9 @@ func (s paymentService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) if req.ReferenceNumber != nil { updateBody["reference_number"] = *req.ReferenceNumber } + if req.PartyAccountNumber != nil { + updateBody["party_account_number"] = *req.PartyAccountNumber + } if req.PaymentMethod != nil { method, err := normalizePaymentMethod(*req.PaymentMethod) if err != nil { diff --git a/internal/modules/finance/payments/validations/payment.validation.go b/internal/modules/finance/payments/validations/payment.validation.go index 14c8f151..a2ab9950 100644 --- a/internal/modules/finance/payments/validations/payment.validation.go +++ b/internal/modules/finance/payments/validations/payment.validation.go @@ -1,25 +1,27 @@ package validation type Create struct { - PartyType string `json:"party_type" validate:"required_strict,min=1,max=50"` - PartyId uint `json:"party_id" validate:"required_strict,number,gt=0"` - PaymentDate string `json:"payment_date" validate:"required_strict,datetime=2006-01-02"` - Nominal float64 `json:"nominal" validate:"required_strict"` - ReferenceNumber *string `json:"reference_number,omitempty"` - PaymentMethod string `json:"payment_method" validate:"required_strict,max=20"` - BankId *uint `json:"bank_id" validate:"omitempty,number,gt=0"` - Notes string `json:"notes" validate:"required_strict,max=500"` + PartyType string `json:"party_type" validate:"required_strict,min=1,max=50"` + PartyId uint `json:"party_id" validate:"required_strict,number,gt=0"` + PartyAccountNumber *string `json:"party_account_number"` + PaymentDate string `json:"payment_date" validate:"required_strict,datetime=2006-01-02"` + Nominal float64 `json:"nominal" validate:"required_strict"` + ReferenceNumber *string `json:"reference_number,omitempty"` + PaymentMethod string `json:"payment_method" validate:"required_strict,max=20"` + BankId *uint `json:"bank_id" validate:"omitempty,number,gt=0"` + Notes string `json:"notes" validate:"required_strict,max=500"` } type Update struct { - PartyType *string `json:"party_type,omitempty" validate:"omitempty,max=50"` - PartyId *uint `json:"party_id,omitempty" validate:"omitempty,number,gt=0"` - PaymentDate *string `json:"payment_date,omitempty" validate:"omitempty,datetime=2006-01-02"` - Nominal *float64 `json:"nominal,omitempty" validate:"omitempty,gt=0"` - ReferenceNumber *string `json:"reference_number,omitempty"` - PaymentMethod *string `json:"payment_method,omitempty" validate:"omitempty,max=20"` - BankId *uint `json:"bank_id,omitempty" validate:"omitempty,number,gt=0"` - Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` + PartyType *string `json:"party_type,omitempty" validate:"omitempty,max=50"` + PartyId *uint `json:"party_id,omitempty" validate:"omitempty,number,gt=0"` + PartyAccountNumber *string `json:"party_account_number,omitempty"` + PaymentDate *string `json:"payment_date,omitempty" validate:"omitempty,datetime=2006-01-02"` + Nominal *float64 `json:"nominal,omitempty" validate:"omitempty,gt=0"` + ReferenceNumber *string `json:"reference_number,omitempty"` + PaymentMethod *string `json:"payment_method,omitempty" validate:"omitempty,max=20"` + BankId *uint `json:"bank_id,omitempty" validate:"omitempty,number,gt=0"` + Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } type Query struct { diff --git a/internal/modules/finance/transactions/dto/transaction.dto.go b/internal/modules/finance/transactions/dto/transaction.dto.go index 25740344..07703fce 100644 --- a/internal/modules/finance/transactions/dto/transaction.dto.go +++ b/internal/modules/finance/transactions/dto/transaction.dto.go @@ -124,20 +124,25 @@ func ToTransactionDetailDTO(e entity.Payment) TransactionDetailDTO { func partyFromPayment(e entity.Payment) Party { party := Party{ - Id: e.PartyId, - Type: e.PartyType, + Id: e.PartyId, + Type: e.PartyType, + } + if e.PartyAccountNumber != nil { + party.AccountNumber = *e.PartyAccountNumber } switch utils.PaymentParty(e.PartyType) { case utils.PaymentPartyCustomer: if e.Customer != nil && e.Customer.Id != 0 { party.Name = e.Customer.Name - party.AccountNumber = e.Customer.AccountNumber + if party.AccountNumber == "" { + party.AccountNumber = e.Customer.AccountNumber + } } case utils.PaymentPartySupplier: if e.Supplier != nil && e.Supplier.Id != 0 { party.Name = e.Supplier.Name - if e.Supplier.AccountNumber != nil { + if party.AccountNumber == "" && e.Supplier.AccountNumber != nil { party.AccountNumber = *e.Supplier.AccountNumber } } From 17d55bd2c0b4b247be1b0e7f48e2798ccb7793e0 Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Fri, 9 Jan 2026 16:43:20 +0700 Subject: [PATCH 3/7] fix(BE): fix code 500 when delete master data foreign to others table --- ...20260109093155_drop_unused_tables.down.sql | 1 + .../20260109093155_drop_unused_tables.up.sql | 1 + ...32_update_master_data_fk_restrict.down.sql | 20 ++++++++++ ...3832_update_master_data_fk_restrict.up.sql | 20 ++++++++++ internal/utils/error.go | 38 +++++++++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 internal/database/migrations/20260109093155_drop_unused_tables.down.sql create mode 100644 internal/database/migrations/20260109093155_drop_unused_tables.up.sql create mode 100644 internal/database/migrations/20260109093832_update_master_data_fk_restrict.down.sql create mode 100644 internal/database/migrations/20260109093832_update_master_data_fk_restrict.up.sql diff --git a/internal/database/migrations/20260109093155_drop_unused_tables.down.sql b/internal/database/migrations/20260109093155_drop_unused_tables.down.sql new file mode 100644 index 00000000..f17c3a80 --- /dev/null +++ b/internal/database/migrations/20260109093155_drop_unused_tables.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS projects; diff --git a/internal/database/migrations/20260109093155_drop_unused_tables.up.sql b/internal/database/migrations/20260109093155_drop_unused_tables.up.sql new file mode 100644 index 00000000..f17c3a80 --- /dev/null +++ b/internal/database/migrations/20260109093155_drop_unused_tables.up.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS projects; diff --git a/internal/database/migrations/20260109093832_update_master_data_fk_restrict.down.sql b/internal/database/migrations/20260109093832_update_master_data_fk_restrict.down.sql new file mode 100644 index 00000000..bc43de5c --- /dev/null +++ b/internal/database/migrations/20260109093832_update_master_data_fk_restrict.down.sql @@ -0,0 +1,20 @@ +-- Revert master data foreign keys to CASCADE delete (except FCR) +ALTER TABLE nonstock_suppliers + DROP CONSTRAINT IF EXISTS nonstock_suppliers_nonstock_id_fkey, + DROP CONSTRAINT IF EXISTS nonstock_suppliers_supplier_id_fkey; + +ALTER TABLE nonstock_suppliers + ADD CONSTRAINT nonstock_suppliers_nonstock_id_fkey FOREIGN KEY (nonstock_id) + REFERENCES nonstocks (id) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT nonstock_suppliers_supplier_id_fkey FOREIGN KEY (supplier_id) + REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE product_suppliers + DROP CONSTRAINT IF EXISTS product_suppliers_product_id_fkey, + DROP CONSTRAINT IF EXISTS product_suppliers_supplier_id_fkey; + +ALTER TABLE product_suppliers + ADD CONSTRAINT product_suppliers_product_id_fkey FOREIGN KEY (product_id) + REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT product_suppliers_supplier_id_fkey FOREIGN KEY (supplier_id) + REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/internal/database/migrations/20260109093832_update_master_data_fk_restrict.up.sql b/internal/database/migrations/20260109093832_update_master_data_fk_restrict.up.sql new file mode 100644 index 00000000..dbb45637 --- /dev/null +++ b/internal/database/migrations/20260109093832_update_master_data_fk_restrict.up.sql @@ -0,0 +1,20 @@ +-- Update master data foreign keys to RESTRICT delete (except FCR) +ALTER TABLE nonstock_suppliers + DROP CONSTRAINT IF EXISTS nonstock_suppliers_nonstock_id_fkey, + DROP CONSTRAINT IF EXISTS nonstock_suppliers_supplier_id_fkey; + +ALTER TABLE nonstock_suppliers + ADD CONSTRAINT nonstock_suppliers_nonstock_id_fkey FOREIGN KEY (nonstock_id) + REFERENCES nonstocks (id) ON DELETE RESTRICT ON UPDATE CASCADE, + ADD CONSTRAINT nonstock_suppliers_supplier_id_fkey FOREIGN KEY (supplier_id) + REFERENCES suppliers (id) ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER TABLE product_suppliers + DROP CONSTRAINT IF EXISTS product_suppliers_product_id_fkey, + DROP CONSTRAINT IF EXISTS product_suppliers_supplier_id_fkey; + +ALTER TABLE product_suppliers + ADD CONSTRAINT product_suppliers_product_id_fkey FOREIGN KEY (product_id) + REFERENCES products (id) ON DELETE RESTRICT ON UPDATE CASCADE, + ADD CONSTRAINT product_suppliers_supplier_id_fkey FOREIGN KEY (supplier_id) + REFERENCES suppliers (id) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/internal/utils/error.go b/internal/utils/error.go index ead06aeb..66537c00 100644 --- a/internal/utils/error.go +++ b/internal/utils/error.go @@ -2,11 +2,14 @@ package utils import ( "errors" + "strings" "gitlab.com/mbugroup/lti-api.git/internal/common/validation" "gitlab.com/mbugroup/lti-api.git/internal/response" "github.com/gofiber/fiber/v2" + "github.com/jackc/pgconn" + pgconnv5 "github.com/jackc/pgx/v5/pgconn" ) func ErrorHandler(c *fiber.Ctx, err error) error { @@ -14,6 +17,10 @@ func ErrorHandler(c *fiber.Ctx, err error) error { return response.Error(c, fiber.StatusBadRequest, message, nil) } + if statusCode, message := mapPgError(err); statusCode != 0 { + return response.Error(c, statusCode, message, nil) + } + var fiberErr *fiber.Error if errors.As(err, &fiberErr) { return response.Error(c, fiberErr.Code, fiberErr.Message, nil) @@ -26,6 +33,37 @@ func NotFoundHandler(c *fiber.Ctx) error { return response.Error(c, fiber.StatusNotFound, "Endpoint Not Found", nil) } +func mapPgError(err error) (int, string) { + code, message := getPgErrorDetails(err) + if code == "" { + return 0, "" + } + + switch code { + case "23503": + return fiber.StatusConflict, "Data tidak bisa dihapus karena masih digunakan oleh data lain." + case "P0001": + if strings.HasPrefix(message, "Cannot soft delete") { + return fiber.StatusConflict, "Data tidak bisa dihapus karena masih digunakan oleh data lain." + } + } + + return 0, "" +} + +func getPgErrorDetails(err error) (string, string) { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + return pgErr.Code, pgErr.Message + } + + var pgErrV5 *pgconnv5.PgError + if errors.As(err, &pgErrV5) { + return pgErrV5.Code, pgErrV5.Message + } + + return "", "" +} func BadRequest(msg string) error { return fiber.NewError(fiber.StatusBadRequest, msg) From bc0bf7fe16bbcb91da187cc865196a8d4d8c1afc Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Sat, 10 Jan 2026 18:25:04 +0700 Subject: [PATCH 4/7] fix(BE): not showed supplier in master data product --- .../master/products/dto/product.dto.go | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/internal/modules/master/products/dto/product.dto.go b/internal/modules/master/products/dto/product.dto.go index dfd4c86f..59f57034 100644 --- a/internal/modules/master/products/dto/product.dto.go +++ b/internal/modules/master/products/dto/product.dto.go @@ -5,6 +5,7 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto" + supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto" uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) @@ -19,6 +20,7 @@ type ProductRelationDTO struct { Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"` Flags *[]string `json:"flags,omitempty"` ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"` + Suppliers []supplierDTO.SupplierRelationDTO `json:"suppliers"` } type ProductListDTO struct { @@ -33,6 +35,7 @@ type ProductListDTO struct { Flags []string `json:"flags"` Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"` ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"` + Suppliers []supplierDTO.SupplierRelationDTO `json:"suppliers"` CreatedUser *userDTO.UserRelationDTO `json:"created_user"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -70,6 +73,7 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO { Flags: &flags, Uom: uomRef, ProductCategory: categoryRef, + Suppliers: toProductSupplierDTOs(e.ProductSuppliers), } } @@ -112,6 +116,7 @@ func ToProductListDTO(e entity.Product) ProductListDTO { UpdatedAt: e.UpdatedAt, CreatedUser: createdUser, ProductCategory: categoryRef, + Suppliers: toProductSupplierDTOs(e.ProductSuppliers), } } @@ -128,3 +133,23 @@ func ToProductDetailDTO(e entity.Product) ProductDetailDTO { ProductListDTO: ToProductListDTO(e), } } + +func toProductSupplierDTOs(relations []entity.ProductSupplier) []supplierDTO.SupplierRelationDTO { + if len(relations) == 0 { + return make([]supplierDTO.SupplierRelationDTO, 0) + } + + result := make([]supplierDTO.SupplierRelationDTO, 0, len(relations)) + for _, relation := range relations { + if relation.Supplier.Id == 0 { + continue + } + result = append(result, supplierDTO.ToSupplierRelationDTO(relation.Supplier)) + } + + if len(result) == 0 { + return make([]supplierDTO.SupplierRelationDTO, 0) + } + + return result +} From 3b2c6f16c339748f9ec3a4560503879d00a42b16 Mon Sep 17 00:00:00 2001 From: ragilap Date: Sat, 10 Jan 2026 21:22:54 +0700 Subject: [PATCH 5/7] feat(BE-74-76-78-278):adjustment project flock,recording,purchase getall --- .../20260110105231_adjust_recording.down.sql | 59 +++++++++++++ .../20260110105231_adjust_recording.up.sql | 57 ++++++++++++ internal/entities/recording.go | 12 +-- .../product_warehouse.repository.go | 36 ++++++++ .../services/projectflock.service.go | 86 ++++++++++--------- .../recordings/dto/recording.dto.go | 42 ++++----- .../modules/production/recordings/route.go | 2 +- .../recordings/services/recording.service.go | 54 ++++++------ .../repositories/purchase.repository.go | 39 +++++++++ .../purchases/services/purchase.service.go | 1 + 10 files changed, 294 insertions(+), 94 deletions(-) create mode 100644 internal/database/migrations/20260110105231_adjust_recording.down.sql create mode 100644 internal/database/migrations/20260110105231_adjust_recording.up.sql diff --git a/internal/database/migrations/20260110105231_adjust_recording.down.sql b/internal/database/migrations/20260110105231_adjust_recording.down.sql new file mode 100644 index 00000000..2bcbcb67 --- /dev/null +++ b/internal/database/migrations/20260110105231_adjust_recording.down.sql @@ -0,0 +1,59 @@ +BEGIN; + +ALTER TABLE recordings + DROP CONSTRAINT IF EXISTS chk_recordings_nonnegatives_v4; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hen_day' + ) THEN + ALTER TABLE recordings RENAME COLUMN hen_day TO hand_day; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hen_house' + ) THEN + ALTER TABLE recordings RENAME COLUMN hen_house TO hand_house; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'egg_mass' + ) THEN + ALTER TABLE recordings RENAME COLUMN egg_mass TO egg_mesh; + END IF; +END $$; + +ALTER TABLE recordings + ADD COLUMN IF NOT EXISTS daily_gain NUMERIC(7,3), + ADD COLUMN IF NOT EXISTS avg_daily_gain NUMERIC(7,3); + +ALTER TABLE recordings + ADD CONSTRAINT chk_recordings_nonnegatives_v3 CHECK ( + (total_depletion_qty IS NULL OR total_depletion_qty >= 0) AND + (cum_depletion_rate IS NULL OR cum_depletion_rate >= 0) AND + (daily_gain IS NULL OR daily_gain >= 0) AND + (avg_daily_gain IS NULL OR avg_daily_gain >= 0) AND + (cum_intake IS NULL OR cum_intake >= 0) AND + (fcr_value IS NULL OR fcr_value >= 0) AND + (total_chick_qty IS NULL OR total_chick_qty >= 0) AND + (hand_day IS NULL OR hand_day >= 0) AND + (hand_house IS NULL OR hand_house >= 0) AND + (feed_intake IS NULL OR feed_intake >= 0) AND + (egg_mesh IS NULL OR egg_mesh >= 0) AND + (egg_weight IS NULL OR egg_weight >= 0) + ); + +COMMIT; diff --git a/internal/database/migrations/20260110105231_adjust_recording.up.sql b/internal/database/migrations/20260110105231_adjust_recording.up.sql new file mode 100644 index 00000000..ac947910 --- /dev/null +++ b/internal/database/migrations/20260110105231_adjust_recording.up.sql @@ -0,0 +1,57 @@ +BEGIN; + +ALTER TABLE recordings + DROP CONSTRAINT IF EXISTS chk_recordings_nonnegatives_v3; + +ALTER TABLE recordings + DROP COLUMN IF EXISTS daily_gain, + DROP COLUMN IF EXISTS avg_daily_gain; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hand_day' + ) THEN + ALTER TABLE recordings RENAME COLUMN hand_day TO hen_day; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hand_house' + ) THEN + ALTER TABLE recordings RENAME COLUMN hand_house TO hen_house; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'egg_mesh' + ) THEN + ALTER TABLE recordings RENAME COLUMN egg_mesh TO egg_mass; + END IF; +END $$; + +ALTER TABLE recordings + ADD CONSTRAINT chk_recordings_nonnegatives_v4 CHECK ( + (total_depletion_qty IS NULL OR total_depletion_qty >= 0) AND + (cum_depletion_rate IS NULL OR cum_depletion_rate >= 0) AND + (cum_intake IS NULL OR cum_intake >= 0) AND + (fcr_value IS NULL OR fcr_value >= 0) AND + (total_chick_qty IS NULL OR total_chick_qty >= 0) AND + (hen_day IS NULL OR hen_day >= 0) AND + (hen_house IS NULL OR hen_house >= 0) AND + (feed_intake IS NULL OR feed_intake >= 0) AND + (egg_mass IS NULL OR egg_mass >= 0) AND + (egg_weight IS NULL OR egg_weight >= 0) + ); + +COMMIT; diff --git a/internal/entities/recording.go b/internal/entities/recording.go index 7f952a62..0cc5dc03 100644 --- a/internal/entities/recording.go +++ b/internal/entities/recording.go @@ -16,10 +16,10 @@ type Recording struct { CumIntake *int `gorm:"column:cum_intake"` FcrValue *float64 `gorm:"column:fcr_value"` TotalChickQty *float64 `gorm:"column:total_chick_qty"` - HandDay *float64 `gorm:"column:hand_day"` - HandHouse *float64 `gorm:"column:hand_house"` + HenDay *float64 `gorm:"column:hen_day"` + HenHouse *float64 `gorm:"column:hen_house"` FeedIntake *float64 `gorm:"column:feed_intake"` - EggMesh *float64 `gorm:"column:egg_mesh"` + EggMass *float64 `gorm:"column:egg_mass"` EggWeight *float64 `gorm:"column:egg_weight"` CreatedBy uint `gorm:"column:created_by"` CreatedAt time.Time `gorm:"autoCreateTime"` @@ -34,11 +34,11 @@ type Recording struct { LatestApproval *Approval `gorm:"-" json:"-"` - StandardHandDay *float64 `gorm:"-"` - StandardHandHouse *float64 `gorm:"-"` + StandardHenDay *float64 `gorm:"-"` + StandardHenHouse *float64 `gorm:"-"` StandardFeedIntake *float64 `gorm:"-"` StandardMaxDepletion *float64 `gorm:"-"` - StandardEggMesh *float64 `gorm:"-"` + StandardEggMass *float64 `gorm:"-"` StandardEggWeight *float64 `gorm:"-"` StandardFcr *float64 `gorm:"-"` } diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index a8a44eb7..6acb4f69 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -23,6 +23,7 @@ type ProductWarehouseRepository interface { GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint, db *gorm.DB) (*entity.ProductWarehouse, error) GetByFlagAndWarehouseID(ctx context.Context, flagName string, warehouseId uint) ([]entity.ProductWarehouse, error) GetFirstProductByFlag(ctx context.Context, flagName string) (*entity.Product, error) + ListProductIDsByFlagPrefixes(ctx context.Context, prefixes []string, visibleStatus *bool) ([]uint, error) ApplyFlagsFilter(db *gorm.DB, flags []string) *gorm.DB AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error GetDetailByID(ctx context.Context, id uint) (*entity.ProductWarehouse, error) @@ -380,3 +381,38 @@ func (r *ProductWarehouseRepositoryImpl) GetFirstProductByFlag(ctx context.Conte } return &product, nil } + +func (r *ProductWarehouseRepositoryImpl) ListProductIDsByFlagPrefixes(ctx context.Context, prefixes []string, visibleStatus *bool) ([]uint, error) { + if len(prefixes) == 0 { + return []uint{}, nil + } + + db := r.DB().WithContext(ctx). + Model(&entity.Product{}). + Distinct("products.id"). + Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", entity.FlagableTypeProduct) + + applied := false + for _, prefix := range prefixes { + if prefix == "" { + continue + } + like := prefix + "%" + if !applied { + db = db.Where("flags.name LIKE ?", like) + applied = true + continue + } + db = db.Or("flags.name LIKE ?", like) + } + + if visibleStatus != nil { + db = db.Where("products.is_visible = ?", *visibleStatus) + } + + var ids []uint + if err := db.Pluck("products.id", &ids).Error; err != nil { + return nil, err + } + return ids, nil +} diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 5f643dee..3dbe3f4b 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -23,6 +23,7 @@ import ( validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations" recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" uniformityRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/uniformities/repositories" + purchaseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories" utils "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" @@ -308,12 +309,12 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* } createBody := &entity.ProjectFlock{ - AreaId: req.AreaId, - Category: cat, - FcrId: req.FcrId, + AreaId: req.AreaId, + Category: cat, + FcrId: req.FcrId, ProductionStandardId: req.ProductionStandardId, - LocationId: req.LocationId, - CreatedBy: actorID, + LocationId: req.LocationId, + CreatedBy: actorID, } err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { @@ -823,22 +824,7 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction * return nil } - blocked, err := s.pivotRepoWithTx(dbTransaction).FindKandangsWithRecordings(ctx, projectFlockID, kandangIDs) - if err != nil { - s.Log.Errorf("Failed to check recordings before detaching kandangs: %+v", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandang detachment") - } - if len(blocked) > 0 { - names := make([]string, 0, len(blocked)) - for _, item := range blocked { - label := fmt.Sprintf("ID %d", item.Id) - if strings.TrimSpace(item.Name) != "" { - label = fmt.Sprintf("%s (%s)", label, item.Name) - } - names = append(names, label) - } - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tidak dapat melepas kandang karena sudah memiliki recording: %s", strings.Join(names, ", "))) - } + // NOTE: Recording constraints are enforced via FK cascade; allow detachment even if recordings exist. pfkIDs, err := s.pivotRepoWithTx(dbTransaction).ListIDsByProjectAndKandang(ctx, projectFlockID, kandangIDs) if err != nil { @@ -854,6 +840,14 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction * return fiber.NewError(fiber.StatusInternalServerError, "Failed to remove uniformity data for project flock kandang") } + db := s.Repository.DB() + if dbTransaction != nil { + db = dbTransaction + } + purchaseRepo := purchaseRepository.NewPurchaseRepository(db) + if err := purchaseRepo.SoftDeleteByProjectFlockKandangIDs(ctx, pfkIDs); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to soft delete purchases for project flock kandang") + } pwRepo := s.ProductWarehouseRepo if dbTransaction != nil { pwRepo = productWarehouseRepository.NewProductWarehouseRepository(dbTransaction) @@ -906,6 +900,11 @@ func (s projectflockService) ensureProjectFlockKandangProductWarehouses(ctx cont return nil } + projectFlockID := records[0].ProjectFlockId + if projectFlockID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Project flock id tidak ditemukan") + } + pwRepo := s.ProductWarehouseRepo if dbTransaction != nil { pwRepo = productWarehouseRepository.NewProductWarehouseRepository(dbTransaction) @@ -920,24 +919,34 @@ func (s projectflockService) ensureProjectFlockKandangProductWarehouses(ctx cont warehouseRepo = warehouseRepository.NewWarehouseRepository(s.Repository.DB()) } - flags := []utils.FlagType{ - utils.FlagAyamAfkir, - utils.FlagAyamCulling, - utils.FlagAyamMati, - utils.FlagTelurPecah, - utils.FlagTelurUtuh, + db := s.Repository.DB() + if dbTransaction != nil { + db = dbTransaction + } + var category string + if err := db.WithContext(ctx). + Model(&entity.ProjectFlock{}). + Select("category"). + Where("id = ?", projectFlockID). + Scan(&category).Error; err != nil { + return err + } + if strings.TrimSpace(category) == "" { + return fiber.NewError(fiber.StatusBadRequest, "Project flock category tidak ditemukan") } - productIDs := make(map[utils.FlagType]uint, len(flags)) - for _, flag := range flags { - product, err := pwRepo.GetFirstProductByFlag(ctx, string(flag)) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product untuk flag %s tidak ditemukan", flag)) - } - return err - } - productIDs[flag] = product.Id + prefixes := []string{"AYAM-"} + if strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying)) { + prefixes = append(prefixes, "TELUR") + } + + invisibleOnly := false + productIDs, err := pwRepo.ListProductIDsByFlagPrefixes(ctx, prefixes, &invisibleOnly) + if err != nil { + return err + } + if len(productIDs) == 0 { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product dengan flag %s tidak ditemukan", strings.Join(prefixes, ", "))) } for _, record := range records { @@ -953,8 +962,7 @@ func (s projectflockService) ensureProjectFlockKandangProductWarehouses(ctx cont return err } - for _, flag := range flags { - productID := productIDs[flag] + for _, productID := range productIDs { if _, err := pwRepo.GetByProductWarehouseAndProjectFlockKandang(ctx, productID, warehouse.Id, record.Id); err == nil { continue } else if !errors.Is(err, gorm.ErrRecordNotFound) { diff --git a/internal/modules/production/recordings/dto/recording.dto.go b/internal/modules/production/recordings/dto/recording.dto.go index c34651ba..f5a04821 100644 --- a/internal/modules/production/recordings/dto/recording.dto.go +++ b/internal/modules/production/recordings/dto/recording.dto.go @@ -25,16 +25,16 @@ type RecordingRelationDTO struct { CumIntake int `json:"cum_intake"` FcrValue float64 `json:"fcr_value"` TotalChickQty float64 `json:"total_chick_qty"` - HandDay float64 `json:"hand_day"` - HandHouse float64 `json:"hand_house"` + HenDay float64 `json:"hen_day"` + HenHouse float64 `json:"hen_house"` FeedIntake float64 `json:"feed_intake"` - EggMesh float64 `json:"egg_mesh"` + EggMass float64 `json:"egg_mass"` EggWeight float64 `json:"egg_weight"` - StandardHandDay *float64 `json:"hand_day_std,omitempty"` - StandardHandHouse *float64 `json:"hand_house_std,omitempty"` + StandardHenDay *float64 `json:"hen_day_std,omitempty"` + StandardHenHouse *float64 `json:"hen_house_std,omitempty"` StandardFeedIntake *float64 `json:"feed_intake_std,omitempty"` StandardMaxDepletion *float64 `json:"max_depletion_std,omitempty"` - StandardEggMesh *float64 `json:"egg_mesh_std,omitempty"` + StandardEggMass *float64 `json:"egg_mass_std,omitempty"` StandardEggWeight *float64 `json:"egg_weight_std,omitempty"` StandardFcr *float64 `json:"fcr_std,omitempty"` Approval approvalDTO.ApprovalRelationDTO `json:"approval"` @@ -94,10 +94,10 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { cumIntake int fcrValue float64 totalChickQty float64 - handDay float64 - handHouse float64 + henDay float64 + henHouse float64 feedIntake float64 - eggMesh float64 + eggMass float64 eggWeight float64 ) @@ -119,17 +119,17 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { if e.TotalChickQty != nil { totalChickQty = *e.TotalChickQty } - if e.HandDay != nil { - handDay = *e.HandDay + if e.HenDay != nil { + henDay = *e.HenDay } - if e.HandHouse != nil { - handHouse = *e.HandHouse + if e.HenHouse != nil { + henHouse = *e.HenHouse } if e.FeedIntake != nil { feedIntake = *e.FeedIntake } - if e.EggMesh != nil { - eggMesh = *e.EggMesh + if e.EggMass != nil { + eggMass = *e.EggMass } if e.EggWeight != nil { eggWeight = *e.EggWeight @@ -157,16 +157,16 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { CumIntake: cumIntake, FcrValue: fcrValue, TotalChickQty: totalChickQty, - HandDay: handDay, - HandHouse: handHouse, + HenDay: henDay, + HenHouse: henHouse, FeedIntake: feedIntake, - EggMesh: eggMesh, + EggMass: eggMass, EggWeight: eggWeight, - StandardHandDay: e.StandardHandDay, - StandardHandHouse: e.StandardHandHouse, + StandardHenDay: e.StandardHenDay, + StandardHenHouse: e.StandardHenHouse, StandardFeedIntake: e.StandardFeedIntake, StandardMaxDepletion: e.StandardMaxDepletion, - StandardEggMesh: e.StandardEggMesh, + StandardEggMass: e.StandardEggMass, StandardEggWeight: e.StandardEggWeight, StandardFcr: e.StandardFcr, Approval: latestApproval, diff --git a/internal/modules/production/recordings/route.go b/internal/modules/production/recordings/route.go index f05d054d..e7f1b081 100644 --- a/internal/modules/production/recordings/route.go +++ b/internal/modules/production/recordings/route.go @@ -16,10 +16,10 @@ func RecordingRoutes(v1 fiber.Router, u user.UserService, s recording.RecordingS route.Use(m.Auth(u)) route.Get("/",m.RequirePermissions(m.P_RecordingGetAll), ctrl.GetAll) + route.Get("/next-day",m.RequirePermissions(m.P_RecordingNextDay), ctrl.GetNextDay) route.Get("/:id",m.RequirePermissions(m.P_RecordingGetOne), ctrl.GetOne) route.Post("/",m.RequirePermissions(m.P_RecordingCreateOne), ctrl.CreateOne) route.Patch("/:id",m.RequirePermissions(m.P_RecordingUpdateOne), ctrl.UpdateOne) route.Delete("/:id",m.RequirePermissions(m.P_RecordingDeleteOne), ctrl.DeleteOne) - route.Get("/next-day",m.RequirePermissions(m.P_RecordingNextDay), ctrl.GetNextDay) route.Post("/approvals",m.RequirePermissions(m.P_RecordingApproval), ctrl.Approve) } diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 54052518..e46056e5 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -1156,34 +1156,34 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm recording.FeedIntake = nil } - var handDay float64 + var henDay float64 if remainingChick > 0 && totalEggQty >= 0 { - handDay = (totalEggQty / remainingChick) * 100 - updates["hand_day"] = handDay - recording.HandDay = &handDay + henDay = (totalEggQty / remainingChick) * 100 + updates["hen_day"] = henDay + recording.HenDay = &henDay } else { - updates["hand_day"] = gorm.Expr("NULL") - recording.HandDay = nil + updates["hen_day"] = gorm.Expr("NULL") + recording.HenDay = nil } - var handHouse float64 + var henHouse float64 if initialChickin > 0 && cumulativeEggQty >= 0 { - handHouse = cumulativeEggQty / initialChickin - updates["hand_house"] = handHouse - recording.HandHouse = &handHouse + henHouse = cumulativeEggQty / initialChickin + updates["hen_house"] = henHouse + recording.HenHouse = &henHouse } else { - updates["hand_house"] = gorm.Expr("NULL") - recording.HandHouse = nil + updates["hen_house"] = gorm.Expr("NULL") + recording.HenHouse = nil } - var eggMesh float64 + var eggMass float64 if remainingChick > 0 && totalEggWeightGrams > 0 { - eggMesh = (totalEggWeightGrams / remainingChick) * 1000 - updates["egg_mesh"] = eggMesh - recording.EggMesh = &eggMesh + eggMass = (totalEggWeightGrams / remainingChick) * 1000 + updates["egg_mass"] = eggMass + recording.EggMass = &eggMass } else { - updates["egg_mesh"] = gorm.Expr("NULL") - recording.EggMesh = nil + updates["egg_mass"] = gorm.Expr("NULL") + recording.EggMass = nil } var eggWeight float64 @@ -1334,11 +1334,11 @@ func (s *recordingService) attachLatestApproval(ctx context.Context, item *entit } type productionStandardValues struct { - HandDay *float64 - HandHouse *float64 + HenDay *float64 + HenHouse *float64 FeedIntake *float64 MaxDepletion *float64 - EggMesh *float64 + EggMass *float64 EggWeight *float64 } @@ -1389,10 +1389,10 @@ func (s *recordingService) attachProductionStandard(ctx context.Context, item *e return err } if detail != nil { - standard.HandDay = detail.TargetHenDayProduction - standard.HandHouse = detail.TargetHenHouseProduction + standard.HenDay = detail.TargetHenDayProduction + standard.HenHouse = detail.TargetHenHouseProduction standard.EggWeight = detail.TargetEggWeight - standard.EggMesh = detail.TargetEggMass + standard.EggMass = detail.TargetEggMass } } @@ -1420,11 +1420,11 @@ func (s *recordingService) attachProductionStandard(ctx context.Context, item *e } } - item.StandardHandDay = standard.HandDay - item.StandardHandHouse = standard.HandHouse + item.StandardHenDay = standard.HenDay + item.StandardHenHouse = standard.HenHouse item.StandardFeedIntake = standard.FeedIntake item.StandardMaxDepletion = standard.MaxDepletion - item.StandardEggMesh = standard.EggMesh + item.StandardEggMass = standard.EggMass item.StandardEggWeight = standard.EggWeight item.StandardFcr = standardFcr diff --git a/internal/modules/purchases/repositories/purchase.repository.go b/internal/modules/purchases/repositories/purchase.repository.go index fc599877..f6e48aeb 100644 --- a/internal/modules/purchases/repositories/purchase.repository.go +++ b/internal/modules/purchases/repositories/purchase.repository.go @@ -25,6 +25,7 @@ type PurchaseRepository interface { NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error) NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error) BackfillProjectFlockKandang(ctx context.Context, purchaseID uint) error + SoftDeleteByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) error GetItemsByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error) GetItemsByWarehouseKandang(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error) } @@ -89,6 +90,44 @@ WHERE pi.purchase_id = ? return r.DB().WithContext(ctx).Exec(query, purchaseID).Error } +func (r *PurchaseRepositoryImpl) SoftDeleteByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) error { + if len(projectFlockKandangIDs) == 0 { + return nil + } + + return r.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var purchaseIDs []uint + query := ` +SELECT pi.purchase_id +FROM purchase_items pi +WHERE pi.project_flock_kandang_id IN (?) +GROUP BY pi.purchase_id +HAVING COUNT(*) = COUNT(CASE WHEN pi.project_flock_kandang_id IN (?) THEN 1 END) +` + if err := tx.Raw(query, projectFlockKandangIDs, projectFlockKandangIDs).Scan(&purchaseIDs).Error; err != nil { + return err + } + + now := time.Now().UTC() + if len(purchaseIDs) > 0 { + if err := tx.Model(&entity.Purchase{}). + Where("id IN (?) AND deleted_at IS NULL", purchaseIDs). + Update("deleted_at", now).Error; err != nil { + return err + } + if err := tx.Where("purchase_id IN (?)", purchaseIDs).Delete(&entity.PurchaseItem{}).Error; err != nil { + return err + } + } + + deleteItems := tx.Where("project_flock_kandang_id IN (?)", projectFlockKandangIDs) + if len(purchaseIDs) > 0 { + deleteItems = deleteItems.Where("purchase_id NOT IN (?)", purchaseIDs) + } + return deleteItems.Delete(&entity.PurchaseItem{}).Error + }) +} + func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error { if len(items) == 0 { return nil diff --git a/internal/modules/purchases/services/purchase.service.go b/internal/modules/purchases/services/purchase.service.go index e46788d8..35ca2f75 100644 --- a/internal/modules/purchases/services/purchase.service.go +++ b/internal/modules/purchases/services/purchase.service.go @@ -133,6 +133,7 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti purchases, total, err := s.PurchaseRepo.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) + db = db.Where("purchases.deleted_at IS NULL") if params.SupplierID > 0 { db = db.Where("supplier_id = ?", params.SupplierID) From b42ca5e6fb17dce9f1a269eae784d84617c4c5f7 Mon Sep 17 00:00:00 2001 From: ragilap Date: Sat, 10 Jan 2026 21:34:19 +0700 Subject: [PATCH 6/7] feat(BE-74-76-78-278):delete unused code recording --- .../recordings/services/recording.service.go | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index e46056e5..819552dc 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -901,47 +901,6 @@ type eggTotals struct { Weight float64 } -type stockTotals struct { - Usage float64 - Pending float64 - Total float64 -} - -func summarizeExistingStocks(stocks []entity.RecordingStock) map[uint]stockTotals { - totals := make(map[uint]stockTotals) - for _, stock := range stocks { - var usage float64 - var pending float64 - if stock.UsageQty != nil { - usage = *stock.UsageQty - } - if stock.PendingQty != nil { - pending = *stock.PendingQty - } - current := totals[stock.ProductWarehouseId] - current.Usage += usage - current.Pending += pending - current.Total += usage + pending - totals[stock.ProductWarehouseId] = current - } - return totals -} - -func summarizeIncomingStocks(stocks []validation.Stock) map[uint]stockTotals { - totals := make(map[uint]stockTotals) - for _, stock := range stocks { - var pending float64 - if stock.PendingQty != nil { - pending = *stock.PendingQty - } - current := totals[stock.ProductWarehouseId] - current.Usage += stock.Qty - current.Pending += pending - current.Total += stock.Qty + pending - totals[stock.ProductWarehouseId] = current - } - return totals -} func stocksMatch(existing []entity.RecordingStock, incoming []validation.Stock) bool { hasPending := false From af79db8726d12aa3eecabc1365260238801774b4 Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Sun, 11 Jan 2026 08:41:45 +0700 Subject: [PATCH 7/7] fix(BE): permission in payment route --- internal/modules/finance/payments/route.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/modules/finance/payments/route.go b/internal/modules/finance/payments/route.go index b00de964..c2931f0a 100644 --- a/internal/modules/finance/payments/route.go +++ b/internal/modules/finance/payments/route.go @@ -13,9 +13,9 @@ func PaymentRoutes(v1 fiber.Router, u user.UserService, s payment.PaymentService ctrl := controller.NewPaymentController(s) route := v1.Group("/payments") - // route.Use(m.Auth(u)) + route.Use(m.Auth(u)) - route.Post("/", ctrl.CreateOne) + route.Post("/", m.RequirePermissions(m.P_Finances_Payments_CreateOne), ctrl.CreateOne) route.Get("/:id", m.RequirePermissions(m.P_Finances_Payments_GetOne), ctrl.GetOne) route.Patch("/:id", m.RequirePermissions(m.P_Finances_Payments_UpdateOne), ctrl.UpdateOne) }