From eefc9850e1226839d3251cfaeba637b59d829cc3 Mon Sep 17 00:00:00 2001 From: Adnan Zahir Date: Sat, 25 Apr 2026 22:47:52 +0700 Subject: [PATCH] feat: editable po_date --- .../controllers/purchase.controller.go | 26 +++++++++++++++ internal/modules/purchases/route.go | 1 + .../purchases/services/purchase.service.go | 33 +++++++++++++++++++ .../validations/purchase.validation.go | 4 +++ 4 files changed, 64 insertions(+) diff --git a/internal/modules/purchases/controllers/purchase.controller.go b/internal/modules/purchases/controllers/purchase.controller.go index 6c627cb2..e3bce86f 100644 --- a/internal/modules/purchases/controllers/purchase.controller.go +++ b/internal/modules/purchases/controllers/purchase.controller.go @@ -283,6 +283,32 @@ func validatePurchaseDocumentSizes(files []*multipart.FileHeader) error { return nil } +func (ctrl *PurchaseController) UpdatePoDate(c *fiber.Ctx) error { + param := c.Params("id") + id, err := strconv.Atoi(param) + if err != nil || id == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") + } + + req := new(validation.UpdatePoDateRequest) + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := ctrl.service.UpdatePoDate(c, uint(id), req) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Purchase PO date updated successfully", + Data: dto.ToPurchaseDetailDTO(*result), + }) +} + func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error { param := c.Params("id") id, err := strconv.Atoi(param) diff --git a/internal/modules/purchases/route.go b/internal/modules/purchases/route.go index ed0c74f1..f07213af 100644 --- a/internal/modules/purchases/route.go +++ b/internal/modules/purchases/route.go @@ -21,6 +21,7 @@ func Routes(router fiber.Router, purchaseService service.PurchaseService, userSe route.Post("/:id/approvals/staff", m.RequirePermissions(m.P_PurchaseApprovalStaff), ctrl.ApproveStaffPurchase) route.Post("/:id/approvals/manager", m.RequirePermissions(m.P_PurchaseApprovalManager), ctrl.ApproveManagerPurchase) route.Post("/:id/receipts", m.RequirePermissions(m.P_PurchaseReceive), ctrl.ReceiveProducts) + route.Patch("/:id/po-date", m.RequirePermissions(m.P_PurchaseUpdateOne), ctrl.UpdatePoDate) route.Delete("/:id", m.RequirePermissions(m.P_PurchaseDeleteOne), ctrl.DeletePurchase) route.Delete("/:id/items", m.RequirePermissions(m.P_PurchaseItemDeleteOne), ctrl.DeleteItems) } diff --git a/internal/modules/purchases/services/purchase.service.go b/internal/modules/purchases/services/purchase.service.go index 728c0e2b..61285a6c 100644 --- a/internal/modules/purchases/services/purchase.service.go +++ b/internal/modules/purchases/services/purchase.service.go @@ -45,6 +45,7 @@ type PurchaseService interface { DeleteItems(ctx *fiber.Ctx, id uint, req *validation.DeletePurchaseItemsRequest) (*entity.Purchase, error) DeletePurchase(ctx *fiber.Ctx, id uint) error GetProgressRows(ctx *fiber.Ctx, query *exportprogress.Query) ([]exportprogress.Row, error) + UpdatePoDate(ctx *fiber.Ctx, id uint, req *validation.UpdatePoDateRequest) (*entity.Purchase, error) } const ( @@ -798,6 +799,38 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *val return updated, nil } +func (s *purchaseService) UpdatePoDate(c *fiber.Ctx, id uint, req *validation.UpdatePoDateRequest) (*entity.Purchase, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + parsed, err := utils.ParseDateString(strings.TrimSpace(req.PoDate)) + if err != nil { + return nil, utils.BadRequest("po_date must use format YYYY-MM-DD") + } + poDate := parsed.UTC() + + purchase, err := s.loadPurchase(c.Context(), id) + if err != nil { + return nil, err + } + + if err := s.PurchaseRepo.PatchOne(c.Context(), id, map[string]any{"po_date": poDate}, nil); err != nil { + return nil, utils.Internal("Failed to update po_date") + } + + purchase.PoDate = &poDate + + updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations) + if err != nil { + return nil, utils.Internal("Failed to reload purchase") + } + if err := s.attachLatestApproval(c.Context(), updated); err != nil { + s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", updated.Id, err) + } + return updated, nil +} + func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation.ReceivePurchaseRequest) (*entity.Purchase, error) { if err := s.Validate.Struct(req); err != nil { return nil, err diff --git a/internal/modules/purchases/validations/purchase.validation.go b/internal/modules/purchases/validations/purchase.validation.go index 780b2911..54dea5bd 100644 --- a/internal/modules/purchases/validations/purchase.validation.go +++ b/internal/modules/purchases/validations/purchase.validation.go @@ -61,6 +61,10 @@ type DeletePurchaseItemsRequest struct { ItemIDs []uint `json:"item_ids" validate:"required,min=1,dive,gt=0"` } +type UpdatePoDateRequest struct { + PoDate string `json:"po_date" validate:"required,datetime=2006-01-02"` +} + type Query struct { Page int `query:"page" validate:"omitempty,number,min=1"` Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`