mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +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
|
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 {
|
func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.Atoi(param)
|
||||||
|
|||||||
@@ -78,12 +78,13 @@ func setPurchaseExportColumns(file *excelize.File, sheet string) error {
|
|||||||
"A": 16,
|
"A": 16,
|
||||||
"B": 16,
|
"B": 16,
|
||||||
"C": 14,
|
"C": 14,
|
||||||
"D": 22,
|
"D": 14,
|
||||||
"E": 22,
|
"E": 22,
|
||||||
"F": 18,
|
"F": 22,
|
||||||
"G": 18,
|
"G": 18,
|
||||||
"H": 52,
|
"H": 18,
|
||||||
"I": 24,
|
"I": 52,
|
||||||
|
"J": 24,
|
||||||
}
|
}
|
||||||
|
|
||||||
for col, width := range columnWidths {
|
for col, width := range columnWidths {
|
||||||
@@ -103,6 +104,7 @@ func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
|
|||||||
"PR Number",
|
"PR Number",
|
||||||
"PO Number",
|
"PO Number",
|
||||||
"Tanggal PO",
|
"Tanggal PO",
|
||||||
|
"Tanggal Terima",
|
||||||
"Supplier",
|
"Supplier",
|
||||||
"Lokasi",
|
"Lokasi",
|
||||||
"Status",
|
"Status",
|
||||||
@@ -136,7 +138,7 @@ func setPurchaseExportHeaders(file *excelize.File, sheet string) error {
|
|||||||
return err
|
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 {
|
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 {
|
if err := file.SetCellValue(sheet, "C"+row, formatPurchaseExportDate(item.PoDate)); err != nil {
|
||||||
return err
|
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
|
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
|
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
|
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
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +197,7 @@ func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.Purcha
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +217,7 @@ func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.Purcha
|
|||||||
return err
|
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 {
|
func buildPurchaseGrandTotalMap(items []entity.Purchase) map[uint]float64 {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ type PurchaseListDTO struct {
|
|||||||
PurchaseRelationDTO
|
PurchaseRelationDTO
|
||||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
||||||
DueDate *time.Time `json:"due_date"`
|
DueDate *time.Time `json:"due_date"`
|
||||||
|
ReceivedDate *time.Time `json:"received_date"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||||
RequesterName string `json:"requester_name"`
|
RequesterName string `json:"requester_name"`
|
||||||
PoExpedition []PoExpeditionDTO `json:"po_expedition"`
|
PoExpedition []PoExpeditionDTO `json:"po_expedition"`
|
||||||
@@ -174,6 +175,7 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
|||||||
poExpedition = make([]PoExpeditionDTO, 0)
|
poExpedition = make([]PoExpeditionDTO, 0)
|
||||||
location *locationDTO.LocationRelationDTO
|
location *locationDTO.LocationRelationDTO
|
||||||
area *areaDTO.AreaRelationDTO
|
area *areaDTO.AreaRelationDTO
|
||||||
|
receivedDate *time.Time
|
||||||
)
|
)
|
||||||
productMap := make(map[uint]productDTO.ProductRelationDTO)
|
productMap := make(map[uint]productDTO.ProductRelationDTO)
|
||||||
expeditionRefSet := make(map[uint64]struct{})
|
expeditionRefSet := make(map[uint64]struct{})
|
||||||
@@ -205,6 +207,12 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
|||||||
ar := areaDTO.ToAreaRelationDTO(item.Warehouse.Area)
|
ar := areaDTO.ToAreaRelationDTO(item.Warehouse.Area)
|
||||||
area = &ar
|
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))
|
products := make([]productDTO.ProductRelationDTO, 0, len(productMap))
|
||||||
for _, prod := range productMap {
|
for _, prod := range productMap {
|
||||||
@@ -215,6 +223,7 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
|||||||
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
|
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
|
||||||
Supplier: supplier,
|
Supplier: supplier,
|
||||||
DueDate: p.DueDate,
|
DueDate: p.DueDate,
|
||||||
|
ReceivedDate: receivedDate,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
RequesterName: requesterName,
|
RequesterName: requesterName,
|
||||||
PoExpedition: poExpedition,
|
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/staff", m.RequirePermissions(m.P_PurchaseApprovalStaff), ctrl.ApproveStaffPurchase)
|
||||||
route.Post("/:id/approvals/manager", m.RequirePermissions(m.P_PurchaseApprovalManager), ctrl.ApproveManagerPurchase)
|
route.Post("/:id/approvals/manager", m.RequirePermissions(m.P_PurchaseApprovalManager), ctrl.ApproveManagerPurchase)
|
||||||
route.Post("/:id/receipts", m.RequirePermissions(m.P_PurchaseReceive), ctrl.ReceiveProducts)
|
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", m.RequirePermissions(m.P_PurchaseDeleteOne), ctrl.DeletePurchase)
|
||||||
route.Delete("/:id/items", m.RequirePermissions(m.P_PurchaseItemDeleteOne), ctrl.DeleteItems)
|
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)
|
DeleteItems(ctx *fiber.Ctx, id uint, req *validation.DeletePurchaseItemsRequest) (*entity.Purchase, error)
|
||||||
DeletePurchase(ctx *fiber.Ctx, id uint) error
|
DeletePurchase(ctx *fiber.Ctx, id uint) error
|
||||||
GetProgressRows(ctx *fiber.Ctx, query *exportprogress.Query) ([]exportprogress.Row, error)
|
GetProgressRows(ctx *fiber.Ctx, query *exportprogress.Query) ([]exportprogress.Row, error)
|
||||||
|
UpdatePoDate(ctx *fiber.Ctx, id uint, req *validation.UpdatePoDateRequest) (*entity.Purchase, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -713,6 +714,12 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *val
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
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) != ""
|
hasExistingPO := purchase.PoNumber != nil && strings.TrimSpace(*purchase.PoNumber) != ""
|
||||||
var generatedNumber string
|
var generatedNumber string
|
||||||
|
|
||||||
@@ -725,7 +732,7 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *val
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updateData["po_number"] = code
|
updateData["po_number"] = code
|
||||||
updateData["po_date"] = now
|
updateData["po_date"] = poDateToSet
|
||||||
generatedNumber = code
|
generatedNumber = code
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,7 +777,7 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *val
|
|||||||
|
|
||||||
if generatedNumber != "" {
|
if generatedNumber != "" {
|
||||||
purchase.PoNumber = &generatedNumber
|
purchase.PoNumber = &generatedNumber
|
||||||
purchase.PoDate = &now
|
purchase.PoDate = &poDateToSet
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations)
|
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
|
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) {
|
func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation.ReceivePurchaseRequest) (*entity.Purchase, error) {
|
||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type ApproveStaffPurchaseRequest struct {
|
|||||||
type ApproveManagerPurchaseRequest struct {
|
type ApproveManagerPurchaseRequest struct {
|
||||||
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
|
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
|
||||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
|
PoDate *string `json:"po_date,omitempty" validate:"omitempty,datetime=2006-01-02"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceivePurchaseItemRequest struct {
|
type ReceivePurchaseItemRequest struct {
|
||||||
@@ -60,6 +61,10 @@ type DeletePurchaseItemsRequest struct {
|
|||||||
ItemIDs []uint `json:"item_ids" validate:"required,min=1,dive,gt=0"`
|
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 {
|
type Query struct {
|
||||||
Page int `query:"page" validate:"omitempty,number,min=1"`
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
|
|||||||
Reference in New Issue
Block a user