Merge branch 'dev/teguh' into 'development'

fix(BE): fix error get location id when only attach to location in expense

See merge request mbugroup/lti-api!119
This commit is contained in:
Hafizh A. Y.
2025-12-31 02:18:27 +00:00
10 changed files with 102 additions and 31 deletions
@@ -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;
@@ -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;
@@ -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
}
+3 -5
View File
@@ -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 {
@@ -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
}
+12 -12
View File
@@ -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)
}
@@ -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")
@@ -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(
@@ -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+"%")
}
@@ -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"`
}