feat(BE-278): unrestrict feat warehouse purchase,adding purchase upload document

This commit is contained in:
ragilap
2025-12-31 03:50:58 +07:00
parent 0396aa0255
commit fd5f83ca58
10 changed files with 159 additions and 63 deletions
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"math"
"mime/multipart"
"strings"
"time"
@@ -57,6 +58,7 @@ type purchaseService struct {
ApprovalSvc commonSvc.ApprovalService
ExpenseBridge PurchaseExpenseBridge
FifoSvc commonSvc.FifoService
DocumentSvc commonSvc.DocumentService
approvalWorkflow approvalutils.ApprovalWorkflowKey
}
@@ -76,6 +78,7 @@ func NewPurchaseService(
approvalSvc commonSvc.ApprovalService,
expenseBridge PurchaseExpenseBridge,
fifoSvc commonSvc.FifoService,
documentSvc commonSvc.DocumentService,
) PurchaseService {
return &purchaseService{
Log: utils.Log,
@@ -89,6 +92,7 @@ func NewPurchaseService(
ApprovalSvc: approvalSvc,
ExpenseBridge: expenseBridge,
FifoSvc: fifoSvc,
DocumentSvc: documentSvc,
approvalWorkflow: utils.ApprovalWorkflowPurchase,
}
}
@@ -615,9 +619,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
ctx := c.Context()
action, err := parseApprovalActionInput(req.Action)
if err != nil {
return nil, err
@@ -664,6 +666,30 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
return updated, nil
}
if action == entity.ApprovalActionApproved && len(req.TravelDocuments) > 0 {
if len(req.TravelDocuments) > len(req.Items) {
return nil, utils.BadRequest("Travel documents exceed total receiving items")
}
for idx, file := range req.TravelDocuments {
if file == nil {
continue
}
if idx >= len(req.Items) {
break
}
itemID := req.Items[idx].PurchaseItemID
if itemID == 0 {
return nil, utils.BadRequest("Purchase item id is required for travel document upload")
}
uploadedURL, err := s.uploadTravelDocument(ctx, actorID, itemID, file)
if err != nil {
s.Log.Errorf("Failed to upload travel document for item %d: %+v", itemID, err)
return nil, utils.Internal("Failed to upload travel document")
}
req.Items[idx].TravelDocumentPath = &uploadedURL
}
}
itemMap := make(map[uint]*entity.PurchaseItem, len(purchase.Items))
for i := range purchase.Items {
itemMap[purchase.Items[i].Id] = &purchase.Items[i]
@@ -807,32 +833,20 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
for _, prep := range prepared {
item := prep.item
var oldPWID *uint
if item.ProductWarehouseId != nil {
idCopy := uint(*item.ProductWarehouseId)
oldPWID = &idCopy
}
var newPWID *uint
clearPW := false
// Always ensure PW when qty > 0 so stockable has target.
if prep.receivedQty > 0 {
pwID, err := pwRepoTx.EnsureProductWarehouse(
c.Context(),
uint(item.ProductId),
prep.warehouseID,
item.ProjectFlockKandangId,
purchase.CreatedBy,
)
if err != nil {
return err
}
newPWID = &pwID
} else if oldPWID != nil {
newPWID = oldPWID
clearPW = true
// Always ensure PW after receiving so linkage stays stable.
pwID, err := pwRepoTx.EnsureProductWarehouse(
c.Context(),
uint(item.ProductId),
prep.warehouseID,
item.ProjectFlockKandangId,
purchase.CreatedBy,
)
if err != nil {
return err
}
newPWID = &pwID
deltaQty := prep.receivedQty - item.TotalQty
switch {
@@ -857,7 +871,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
VehicleNumber: prep.payload.VehicleNumber,
ReceivedQty: &qtyCopy,
ProductWarehouseID: newPWID,
ClearProductWarehouse: clearPW,
ClearProductWarehouse: false,
}
if prep.overrideWarehouse || uint(item.WarehouseId) != prep.warehouseID {
@@ -972,6 +986,38 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
return updated, nil
}
func (s *purchaseService) uploadTravelDocument(
ctx context.Context,
actorID uint,
itemID uint,
file *multipart.FileHeader,
) (string, error) {
if file == nil {
return "", errors.New("travel document file is required")
}
if s.DocumentSvc == nil {
return "", errors.New("document service not available")
}
documentFiles := []commonSvc.DocumentFile{{
File: file,
Type: string(utils.DocumentTypePurchaseTravel),
}}
results, err := s.DocumentSvc.UploadDocuments(ctx, commonSvc.DocumentUploadRequest{
DocumentableType: string(utils.DocumentableTypePurchaseItem),
DocumentableID: uint64(itemID),
CreatedBy: &actorID,
Files: documentFiles,
})
if err != nil {
return "", err
}
if len(results) == 0 {
return "", errors.New("upload result is empty")
}
return results[0].URL, nil
}
func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint, req *validation.DeletePurchaseItemsRequest) (*entity.Purchase, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err