From b988f45a0b402c3a8d42a13131293d1b03641f56 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 30 Dec 2025 19:30:42 +0700 Subject: [PATCH 1/4] feat(BE): update expense DTO and service to directly use location from expense --- internal/modules/expenses/dto/expense.dto.go | 8 +++----- .../modules/expenses/services/expense.service.go | 12 +++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/modules/expenses/dto/expense.dto.go b/internal/modules/expenses/dto/expense.dto.go index 6402f8fd..129c2e96 100644 --- a/internal/modules/expenses/dto/expense.dto.go +++ b/internal/modules/expenses/dto/expense.dto.go @@ -105,11 +105,9 @@ func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO { realizationDate = &e.RealizationDate } - if len(e.Nonstocks) > 0 && e.Nonstocks[0].Kandang != nil { - if e.Nonstocks[0].Kandang.Location.Id != 0 { - mapped := locationDTO.ToLocationRelationDTO(e.Nonstocks[0].Kandang.Location) - location = &mapped - } + if e.Location != nil && e.Location.Id != 0 { + mapped := locationDTO.ToLocationRelationDTO(*e.Location) + location = &mapped } if e.Supplier != nil && e.Supplier.Id != 0 { diff --git a/internal/modules/expenses/services/expense.service.go b/internal/modules/expenses/services/expense.service.go index b4753451..20d6b568 100644 --- a/internal/modules/expenses/services/expense.service.go +++ b/internal/modules/expenses/services/expense.service.go @@ -30,7 +30,7 @@ type ExpenseService interface { GetOne(ctx *fiber.Ctx, id uint) (*expenseDto.ExpenseDetailDTO, error) CreateOne(ctx *fiber.Ctx, req *validation.Create) (*expenseDto.ExpenseDetailDTO, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*expenseDto.ExpenseDetailDTO, error) - DeleteOne(ctx *fiber.Ctx, id uint) error + DeleteOne(ctx *fiber.Ctx, id uint64) error CreateRealization(ctx *fiber.Ctx, expenseID uint, req *validation.CreateRealization) (*expenseDto.ExpenseDetailDTO, error) CompleteExpense(ctx *fiber.Ctx, id uint, notes *string) (*expenseDto.ExpenseDetailDTO, error) UpdateRealization(ctx *fiber.Ctx, expenseID uint, req *validation.UpdateRealization) (*expenseDto.ExpenseDetailDTO, error) @@ -68,6 +68,7 @@ func (s expenseService) withRelations(db *gorm.DB) *gorm.DB { return db. Preload("CreatedUser"). Preload("Supplier"). + Preload("Location"). Preload("Nonstocks.Nonstock"). Preload("Nonstocks.Realization"). Preload("Nonstocks.ProjectFlockKandang.Kandang.Location"). @@ -621,14 +622,15 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) return responseDTO, nil } -func (s expenseService) DeleteOne(c *fiber.Ctx, id uint) error { +func (s expenseService) DeleteOne(c *fiber.Ctx, id uint64) error { + idUint := uint(id) if err := commonSvc.EnsureRelations(c.Context(), - commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: s.Repository.IdExists}, + commonSvc.RelationCheck{Name: "Expense", ID: &idUint, Exists: s.Repository.IdExists}, ); err != nil { return err } - expense, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { + expense, err := s.Repository.GetByID(c.Context(), idUint, func(db *gorm.DB) *gorm.DB { return db.Preload("Nonstocks") }) if err != nil { @@ -643,7 +645,7 @@ func (s expenseService) DeleteOne(c *fiber.Ctx, id uint) error { if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), expense); err != nil { return err } - if err := s.Repository.DeleteOne(c.Context(), id); err != nil { + if err := s.Repository.DeleteOne(c.Context(), idUint); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { s.Log.Errorf("Expense not found for ID %d: %+v", id, err) return fiber.NewError(fiber.StatusNotFound, "Expense not found") From 3ecea6741f6ded980be7963e7593f19f97e6d312 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 30 Dec 2025 19:39:10 +0700 Subject: [PATCH 2/4] feat(BE): update DeleteOne method to use uint64 for ID and implement soft delete logic --- .../controllers/expense.controller.go | 4 ++-- .../repositories/expense.repository.go | 23 ++++++++++++++++++ internal/modules/expenses/route.go | 24 +++++++++---------- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/internal/modules/expenses/controllers/expense.controller.go b/internal/modules/expenses/controllers/expense.controller.go index 49642231..666642ca 100644 --- a/internal/modules/expenses/controllers/expense.controller.go +++ b/internal/modules/expenses/controllers/expense.controller.go @@ -203,12 +203,12 @@ func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error { func (u *ExpenseController) DeleteOne(c *fiber.Ctx) error { param := c.Params("id") - id, err := strconv.Atoi(param) + id64, err := strconv.ParseUint(param, 10, 64) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") } - if err := u.ExpenseService.DeleteOne(c, uint(id)); err != nil { + if err := u.ExpenseService.DeleteOne(c, id64); err != nil { return err } diff --git a/internal/modules/expenses/repositories/expense.repository.go b/internal/modules/expenses/repositories/expense.repository.go index 844a6409..8796c761 100644 --- a/internal/modules/expenses/repositories/expense.repository.go +++ b/internal/modules/expenses/repositories/expense.repository.go @@ -3,6 +3,8 @@ package repository import ( "context" "errors" + "fmt" + "time" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -17,6 +19,7 @@ type ExpenseRepository interface { GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error) WithProjectFlockKandangFilter(pfkID, kandangID uint) func(*gorm.DB) *gorm.DB CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID, kandangID uint, isFinished func(*entity.Approval) bool) (int64, error) + DeleteOne(ctx context.Context, id uint) error } type ExpenseRepositoryImpl struct { @@ -107,3 +110,23 @@ func (r *ExpenseRepositoryImpl) CountUnfinishedByProjectFlockKandang(ctx context } return unfinished, nil } + +func (r *ExpenseRepositoryImpl) DeleteOne(ctx context.Context, id uint) error { + // Cast to uint64 to match entity.Id type + id64 := uint64(id) + deletedAt := time.Now() + + // Use raw SQL with interpolated integer to avoid type issues + // Interpolate id directly as integer literal (safe because it's uint64) + result := r.DB().WithContext(ctx). + Exec(`UPDATE "expenses" SET "deleted_at" = $1 WHERE "id" = `+fmt.Sprintf("%d", id64)+` AND "deleted_at" IS NULL`, + deletedAt) + + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} diff --git a/internal/modules/expenses/route.go b/internal/modules/expenses/route.go index fa3191fa..9c22bde3 100644 --- a/internal/modules/expenses/route.go +++ b/internal/modules/expenses/route.go @@ -22,16 +22,16 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService // route.Patch("/:id", m.Auth(u), ctrl.UpdateOne) // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) - route.Get("/",m.RequirePermissions(m.P_ExpenseGetAll), ctrl.GetAll) - route.Post("/",m.RequirePermissions(m.P_ExpenseCreateOne), ctrl.CreateOne) - route.Get("/:id",m.RequirePermissions(m.P_ExpenseGetOne), ctrl.GetOne) - route.Patch("/:id",m.RequirePermissions(m.P_ExpenseUpdateOne), ctrl.UpdateOne) - route.Delete("/:id",m.RequirePermissions(m.P_ExpenseDeleteOne), ctrl.DeleteOne) - route.Post("/approvals/manager",m.RequirePermissions(m.P_ExpenseApprovalManager), ctrl.Approval) - route.Post("/approvals/finance",m.RequirePermissions(m.P_ExpenseApprovalFinance), ctrl.Approval) - route.Post("/:id/realizations",m.RequirePermissions(m.P_ExpenseCreateRealizations), ctrl.CreateRealization) - route.Patch("/:id/realizations",m.RequirePermissions(m.P_ExpenseUpdateRealizations), ctrl.UpdateRealization) - route.Post("/:id/complete",m.RequirePermissions(m.P_ExpenseCompleteExpense), ctrl.CompleteExpense) - route.Delete("/:id/documents/:documentId",m.RequirePermissions(m.P_ExpenseDocument), ctrl.DeleteDocument) - route.Delete("/:id/realization-documents/:documentId",m.RequirePermissions(m.P_ExpenseDocumentRealizations), ctrl.DeleteRealizationDocument) + route.Get("/", m.RequirePermissions(m.P_ExpenseGetAll), ctrl.GetAll) + route.Post("/", m.RequirePermissions(m.P_ExpenseCreateOne), ctrl.CreateOne) + route.Get("/:id", m.RequirePermissions(m.P_ExpenseGetOne), ctrl.GetOne) + route.Patch("/:id", m.RequirePermissions(m.P_ExpenseUpdateOne), ctrl.UpdateOne) + route.Delete("/:id", m.RequirePermissions(m.P_ExpenseDeleteOne), ctrl.DeleteOne) + route.Post("/approvals/manager", m.RequirePermissions(m.P_ExpenseApprovalManager), ctrl.Approval) + route.Post("/approvals/finance", m.RequirePermissions(m.P_ExpenseApprovalFinance), ctrl.Approval) + route.Post("/:id/realizations", m.RequirePermissions(m.P_ExpenseCreateRealizations), ctrl.CreateRealization) + route.Patch("/:id/realizations", m.RequirePermissions(m.P_ExpenseUpdateRealizations), ctrl.UpdateRealization) + route.Post("/:id/complete", m.RequirePermissions(m.P_ExpenseCompleteExpense), ctrl.CompleteExpense) + route.Delete("/:id/documents/:documentId", m.RequirePermissions(m.P_ExpenseDocument), ctrl.DeleteDocument) + route.Delete("/:id/realization-documents/:documentId", m.RequirePermissions(m.P_ExpenseDocumentRealizations), ctrl.DeleteRealizationDocument) } From d91ff7a4c295385ea06c1d9b73f2babba68d9bec Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 30 Dec 2025 20:03:23 +0700 Subject: [PATCH 3/4] feat(BE): add supplier_id filter to GetAll method and update validation for query parameters --- .../modules/master/nonstocks/services/nonstock.service.go | 8 ++++++++ .../master/nonstocks/validations/nonstock.validation.go | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/modules/master/nonstocks/services/nonstock.service.go b/internal/modules/master/nonstocks/services/nonstock.service.go index c0001a52..e201b1f1 100644 --- a/internal/modules/master/nonstocks/services/nonstock.service.go +++ b/internal/modules/master/nonstocks/services/nonstock.service.go @@ -59,6 +59,14 @@ func (s nonstockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit nonstocks, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) + + if params.SupplierID != nil { + supplierID := *params.SupplierID + db = db.Joins("JOIN nonstock_suppliers ON nonstock_suppliers.nonstock_id = nonstocks.id"). + Where("nonstock_suppliers.supplier_id = ?", supplierID). + Group("nonstocks.id") // Prevent duplicates from join + } + if params.Search != "" { return db.Where("name LIKE ?", "%"+params.Search+"%") } diff --git a/internal/modules/master/nonstocks/validations/nonstock.validation.go b/internal/modules/master/nonstocks/validations/nonstock.validation.go index c421b7ec..b58370d5 100644 --- a/internal/modules/master/nonstocks/validations/nonstock.validation.go +++ b/internal/modules/master/nonstocks/validations/nonstock.validation.go @@ -15,7 +15,8 @@ type Update struct { } type Query struct { - Page int `query:"page" validate:"omitempty,number,min=1"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` - Search string `query:"search" validate:"omitempty,max=50"` + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + Search string `query:"search" validate:"omitempty,max=50"` + SupplierID *uint `query:"supplier_id" validate:"omitempty,gt=0"` } From 91fd8a253bae28486425bde9850279bbf3c2eb4a Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 30 Dec 2025 20:16:40 +0700 Subject: [PATCH 4/4] feat(BE): update foreign key constraints for project_chickins and adjust service logic for project flock kandang retrieval --- ...alter_project_chickins_fk_cascade.down.sql | 20 +++++++++++++++++++ ...9_alter_project_chickins_fk_cascade.up.sql | 20 +++++++++++++++++++ .../services/adjustment.service.go | 7 +++---- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.down.sql create mode 100644 internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.up.sql diff --git a/internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.down.sql b/internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.down.sql new file mode 100644 index 00000000..1314087c --- /dev/null +++ b/internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.down.sql @@ -0,0 +1,20 @@ +-- Drop CASCADE constraint +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_project_chickins_kandang' + AND conrelid = 'project_chickins'::regclass + ) THEN + ALTER TABLE project_chickins + DROP CONSTRAINT fk_project_chickins_kandang; + END IF; +END $$; + +-- Recreate foreign key constraint with RESTRICT (original behavior) +ALTER TABLE project_chickins +ADD CONSTRAINT fk_project_chickins_kandang +FOREIGN KEY (project_flock_kandang_id) +REFERENCES project_flock_kandangs(id) +ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.up.sql b/internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.up.sql new file mode 100644 index 00000000..ad07b8e0 --- /dev/null +++ b/internal/database/migrations/20251230201439_alter_project_chickins_fk_cascade.up.sql @@ -0,0 +1,20 @@ +-- Drop existing foreign key constraint with RESTRICT +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_project_chickins_kandang' + AND conrelid = 'project_chickins'::regclass + ) THEN + ALTER TABLE project_chickins + DROP CONSTRAINT fk_project_chickins_kandang; + END IF; +END $$; + +-- Add new foreign key constraint with CASCADE delete +ALTER TABLE project_chickins +ADD CONSTRAINT fk_project_chickins_kandang +FOREIGN KEY (project_flock_kandang_id) +REFERENCES project_flock_kandangs(id) +ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index edf5f72b..f15f37df 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -118,10 +118,9 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e var createdLogId uint var projectFlockKandangID *uint - pfk, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(ctx, uint(req.WarehouseID)) - if err == nil && pfk != nil { - idCopy := uint(pfk.Id) - projectFlockKandangID = &idCopy + pfkID, err := s.getActiveProjectFlockKandangID(ctx, uint(req.WarehouseID)) + if err == nil && pfkID > 0 { + projectFlockKandangID = &pfkID } pw, err := s.ProductWarehouseRepo.FindByProductWarehouseAndPfk(