mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
Merge branch 'codex/filter-improvement' into 'development'
Codex/po date See merge request mbugroup/lti-api!479
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -78,12 +78,13 @@ func setPurchaseExportColumns(file *excelize.File, sheet string) error {
|
||||
"A": 16,
|
||||
"B": 16,
|
||||
"C": 14,
|
||||
"D": 22,
|
||||
"D": 14,
|
||||
"E": 22,
|
||||
"F": 18,
|
||||
"F": 22,
|
||||
"G": 18,
|
||||
"H": 52,
|
||||
"I": 24,
|
||||
"H": 18,
|
||||
"I": 52,
|
||||
"J": 24,
|
||||
}
|
||||
|
||||
for col, width := range columnWidths {
|
||||
@@ -103,6 +104,7 @@ func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
|
||||
"PR Number",
|
||||
"PO Number",
|
||||
"Tanggal PO",
|
||||
"Tanggal Terima",
|
||||
"Supplier",
|
||||
"Lokasi",
|
||||
"Status",
|
||||
@@ -136,7 +138,7 @@ func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return file.SetCellStyle(sheet, "A1", "I1", headerStyle)
|
||||
return file.SetCellStyle(sheet, "A1", "J1", headerStyle)
|
||||
}
|
||||
|
||||
func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.PurchaseListDTO, grandTotals map[uint]float64) error {
|
||||
@@ -155,22 +157,25 @@ func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.Purcha
|
||||
if err := file.SetCellValue(sheet, "C"+row, formatPurchaseExportDate(item.PoDate)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "D"+row, safePurchaseSupplierName(item)); err != nil {
|
||||
if err := file.SetCellValue(sheet, "D"+row, formatPurchaseExportDate(item.ReceivedDate)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "E"+row, safePurchaseLocationName(item)); err != nil {
|
||||
if err := file.SetCellValue(sheet, "E"+row, safePurchaseSupplierName(item)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "F"+row, formatPurchaseExportStatus(item)); err != nil {
|
||||
if err := file.SetCellValue(sheet, "F"+row, safePurchaseLocationName(item)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "G"+row, formatPurchaseRupiah(grandTotals[item.Id])); err != nil {
|
||||
if err := file.SetCellValue(sheet, "G"+row, formatPurchaseExportStatus(item)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "H"+row, formatPurchaseProducts(item)); err != nil {
|
||||
if err := file.SetCellValue(sheet, "H"+row, formatPurchaseRupiah(grandTotals[item.Id])); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "I"+row, safePurchaseExportPointerText(item.Notes)); err != nil {
|
||||
if err := file.SetCellValue(sheet, "I"+row, formatPurchaseProducts(item)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "J"+row, safePurchaseExportPointerText(item.Notes)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -192,7 +197,7 @@ func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.Purcha
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellStyle(sheet, "A2", "I"+strconv.Itoa(lastRow), dataStyle); err != nil {
|
||||
if err := file.SetCellStyle(sheet, "A2", "J"+strconv.Itoa(lastRow), dataStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -212,7 +217,7 @@ func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.Purcha
|
||||
return err
|
||||
}
|
||||
|
||||
return file.SetCellStyle(sheet, "G2", "G"+strconv.Itoa(lastRow), moneyStyle)
|
||||
return file.SetCellStyle(sheet, "H2", "H"+strconv.Itoa(lastRow), moneyStyle)
|
||||
}
|
||||
|
||||
func buildPurchaseGrandTotalMap(items []entity.Purchase) map[uint]float64 {
|
||||
|
||||
@@ -27,6 +27,7 @@ type PurchaseListDTO struct {
|
||||
PurchaseRelationDTO
|
||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
||||
DueDate *time.Time `json:"due_date"`
|
||||
ReceivedDate *time.Time `json:"received_date"`
|
||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||
RequesterName string `json:"requester_name"`
|
||||
PoExpedition []PoExpeditionDTO `json:"po_expedition"`
|
||||
@@ -174,6 +175,7 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
||||
poExpedition = make([]PoExpeditionDTO, 0)
|
||||
location *locationDTO.LocationRelationDTO
|
||||
area *areaDTO.AreaRelationDTO
|
||||
receivedDate *time.Time
|
||||
)
|
||||
productMap := make(map[uint]productDTO.ProductRelationDTO)
|
||||
expeditionRefSet := make(map[uint64]struct{})
|
||||
@@ -205,6 +207,12 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
||||
ar := areaDTO.ToAreaRelationDTO(item.Warehouse.Area)
|
||||
area = &ar
|
||||
}
|
||||
if item.ReceivedDate != nil && !item.ReceivedDate.IsZero() {
|
||||
if receivedDate == nil || item.ReceivedDate.Before(*receivedDate) {
|
||||
t := *item.ReceivedDate
|
||||
receivedDate = &t
|
||||
}
|
||||
}
|
||||
}
|
||||
products := make([]productDTO.ProductRelationDTO, 0, len(productMap))
|
||||
for _, prod := range productMap {
|
||||
@@ -215,6 +223,7 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
||||
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
|
||||
Supplier: supplier,
|
||||
DueDate: p.DueDate,
|
||||
ReceivedDate: receivedDate,
|
||||
CreatedUser: createdUser,
|
||||
RequesterName: requesterName,
|
||||
PoExpedition: poExpedition,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
@@ -713,6 +714,12 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *val
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
poDateToSet := now
|
||||
if req.PoDate != nil && strings.TrimSpace(*req.PoDate) != "" {
|
||||
if parsed, parseErr := utils.ParseDateString(strings.TrimSpace(*req.PoDate)); parseErr == nil {
|
||||
poDateToSet = parsed.UTC()
|
||||
}
|
||||
}
|
||||
hasExistingPO := purchase.PoNumber != nil && strings.TrimSpace(*purchase.PoNumber) != ""
|
||||
var generatedNumber string
|
||||
|
||||
@@ -725,7 +732,7 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *val
|
||||
return err
|
||||
}
|
||||
updateData["po_number"] = code
|
||||
updateData["po_date"] = now
|
||||
updateData["po_date"] = poDateToSet
|
||||
generatedNumber = code
|
||||
}
|
||||
|
||||
@@ -770,7 +777,7 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *val
|
||||
|
||||
if generatedNumber != "" {
|
||||
purchase.PoNumber = &generatedNumber
|
||||
purchase.PoDate = &now
|
||||
purchase.PoDate = &poDateToSet
|
||||
}
|
||||
|
||||
updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations)
|
||||
@@ -792,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
|
||||
|
||||
@@ -35,6 +35,7 @@ type ApproveStaffPurchaseRequest struct {
|
||||
type ApproveManagerPurchaseRequest struct {
|
||||
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||
PoDate *string `json:"po_date,omitempty" validate:"omitempty,datetime=2006-01-02"`
|
||||
}
|
||||
|
||||
type ReceivePurchaseItemRequest struct {
|
||||
@@ -60,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"`
|
||||
|
||||
Reference in New Issue
Block a user