Merge branch 'fix/BE/US-278-purchase_adjustment_createOne' into 'development'

Fix/be/us 278 purchase adjustment createone

See merge request mbugroup/lti-api!123
This commit is contained in:
Hafizh A. Y.
2025-12-31 03:01:35 +00:00
10 changed files with 226 additions and 96 deletions
+1
View File
@@ -44,6 +44,7 @@ const (
P_ReportExpenseGetAll = "lti.repport.expense.list" P_ReportExpenseGetAll = "lti.repport.expense.list"
P_ReportDeliveryGetAll = "lti.repport.delivery.list" P_ReportDeliveryGetAll = "lti.repport.delivery.list"
P_ReportPurchaseSupplierGetAll = "lti.repport.purchasesupplier.list" P_ReportPurchaseSupplierGetAll = "lti.repport.purchasesupplier.list"
P_ReportHppPerKandangGetAll = "lti.repport.gethppperkandang.list"
) )
const ( const (
@@ -215,21 +215,19 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get active project flocks for location") return fiber.NewError(fiber.StatusInternalServerError, "Failed to get active project flocks for location")
} }
if len(activeProjectFlocks) == 0 { if len(activeProjectFlocks) > 0 {
return fiber.NewError(fiber.StatusBadRequest, "No active project flocks found for this location") projectFlockIDs := make([]uint64, len(activeProjectFlocks))
} for i, pf := range activeProjectFlocks {
projectFlockIDs[i] = uint64(pf.Id)
}
projectFlockIDs := make([]uint64, len(activeProjectFlocks)) projectFlockIdsJSON, err := json.Marshal(projectFlockIDs)
for i, pf := range activeProjectFlocks { if err != nil {
projectFlockIDs[i] = uint64(pf.Id) return fiber.NewError(fiber.StatusInternalServerError, "Failed to marshal project_flock_ids")
}
jsonStr := string(projectFlockIdsJSON)
projectFlockIdJSON = &jsonStr
} }
projectFlockIdsJSON, err := json.Marshal(projectFlockIDs)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to marshal project_flock_ids")
}
jsonStr := string(projectFlockIdsJSON)
projectFlockIdJSON = &jsonStr
} }
expense = &entity.Expense{ expense = &entity.Expense{
@@ -216,6 +216,31 @@ func (r *ProductWarehouseRepositoryImpl) CleanupEmpty(ctx context.Context, affec
return nil return nil
} }
var inUseIDs []uint
if err := r.DB().WithContext(ctx).
Model(&entity.PurchaseItem{}).
Where("product_warehouse_id IN ?", emptyIDs).
Distinct().
Pluck("product_warehouse_id", &inUseIDs).Error; err != nil {
return err
}
if len(inUseIDs) > 0 {
inUse := make(map[uint]struct{}, len(inUseIDs))
for _, id := range inUseIDs {
inUse[id] = struct{}{}
}
filtered := make([]uint, 0, len(emptyIDs))
for _, id := range emptyIDs {
if _, exists := inUse[id]; !exists {
filtered = append(filtered, id)
}
}
emptyIDs = filtered
}
if len(emptyIDs) == 0 {
return nil
}
if err := r.DB().WithContext(ctx). if err := r.DB().WithContext(ctx).
Model(&entity.PurchaseItem{}). Model(&entity.PurchaseItem{}).
Where("product_warehouse_id IN ?", emptyIDs). Where("product_warehouse_id IN ?", emptyIDs).
@@ -1,6 +1,7 @@
package controller package controller
import ( import (
"encoding/json"
"fmt" "fmt"
"math" "math"
"strconv" "strconv"
@@ -24,13 +25,13 @@ func NewPurchaseController(s service.PurchaseService) *PurchaseController {
func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error { func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
query := &validation.Query{ query := &validation.Query{
Page: c.QueryInt("page", 1), Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10), Limit: c.QueryInt("limit", 10),
CreatedFrom: strings.TrimSpace(c.Query("created_from")), CreatedFrom: strings.TrimSpace(c.Query("created_from")),
CreatedTo: strings.TrimSpace(c.Query("created_to")), CreatedTo: strings.TrimSpace(c.Query("created_to")),
SupplierID: uint(c.QueryInt("supplier_id", 0)), SupplierID: uint(c.QueryInt("supplier_id", 0)),
AreaID: uint(c.QueryInt("area_id", 0)), AreaID: uint(c.QueryInt("area_id", 0)),
LocationID: uint(c.QueryInt("location_id", 0)), LocationID: uint(c.QueryInt("location_id", 0)),
ProductCategoryID: uint(c.QueryInt("product_category_id", 0)), ProductCategoryID: uint(c.QueryInt("product_category_id", 0)),
} }
@@ -86,7 +87,6 @@ func (ctrl *PurchaseController) CreateOne(c *fiber.Ctx) error {
if err := c.BodyParser(req); err != nil { if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
result, err := ctrl.service.CreateOne(c, req) result, err := ctrl.service.CreateOne(c, req)
if err != nil { if err != nil {
return err return err
@@ -161,10 +161,26 @@ func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
} }
req := new(validation.ReceivePurchaseRequest) req := new(validation.ReceivePurchaseRequest)
if err := c.BodyParser(req); err != nil { form, err := c.MultipartForm()
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
}
req.Action = c.FormValue("action")
if notes := strings.TrimSpace(c.FormValue("notes")); notes != "" {
req.Notes = &notes
} }
itemsJSON := c.FormValue("items")
if strings.TrimSpace(itemsJSON) != "" {
if err := json.Unmarshal([]byte(itemsJSON), &req.Items); err != nil {
var singleItem validation.ReceivePurchaseItemRequest
if err := json.Unmarshal([]byte(itemsJSON), &singleItem); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid items JSON")
}
req.Items = []validation.ReceivePurchaseItemRequest{singleItem}
}
}
req.TravelDocuments = form.File["documents"]
result, err := ctrl.service.ReceiveProducts(c, uint(id), req) result, err := ctrl.service.ReceiveProducts(c, uint(id), req)
if err != nil { if err != nil {
return err return err
+1
View File
@@ -98,6 +98,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
approvalService, approvalService,
expenseBridge, expenseBridge,
fifoService, fifoService,
documentSvc,
) )
userRepo := rUser.NewUserRepository(db) userRepo := rUser.NewUserRepository(db)
@@ -310,9 +310,6 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
return err return err
} }
if cnt == 1 { if cnt == 1 {
if item.Warehouse == nil || item.Warehouse.KandangId == nil || *item.Warehouse.KandangId == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Warehouse not connect to kandangs")
}
newNonstockID, err := b.findExpeditionNonstockID(ctx, supplierID) newNonstockID, err := b.findExpeditionNonstockID(ctx, supplierID)
if err != nil { if err != nil {
return err return err
@@ -332,7 +329,9 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
"price": pricePerItem, "price": pricePerItem,
"notes": note, "notes": note,
"nonstock_id": newNonstockID, "nonstock_id": newNonstockID,
"kandang_id": uint64(*item.Warehouse.KandangId), }
if item.Warehouse != nil && item.Warehouse.KandangId != nil && *item.Warehouse.KandangId != 0 {
updateBody["kandang_id"] = uint64(*item.Warehouse.KandangId)
} }
if err := b.db.WithContext(ctx). if err := b.db.WithContext(ctx).
Model(&entity.ExpenseNonstock{}). Model(&entity.ExpenseNonstock{}).
@@ -395,9 +394,13 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
} }
if kandangID != nil { if kandangID != nil {
updateBody["kandang_id"] = uint64(*kandangID) updateBody["kandang_id"] = uint64(*kandangID)
} else {
updateBody["kandang_id"] = nil
} }
if projectFK != nil { if projectFK != nil {
updateBody["project_flock_kandang_id"] = uint64(*projectFK) updateBody["project_flock_kandang_id"] = uint64(*projectFK)
} else {
updateBody["project_flock_kandang_id"] = nil
} }
if err := b.db.WithContext(ctx). if err := b.db.WithContext(ctx).
@@ -550,18 +553,27 @@ func (b *expenseBridge) createExpenseViaService(
} }
kandangID := items[0].kandangID kandangID := items[0].kandangID
if kandangID == nil || *kandangID == 0 { var locationID uint64
return nil, fiber.NewError(fiber.StatusBadRequest, "Warehouse not connect to kandangs") var expenseKandangID *uint64
} if kandangID != nil && *kandangID != 0 {
kandang, err := b.kandangRepo.GetByID(ctx, *kandangID, func(db *gorm.DB) *gorm.DB {
kandang, err := b.kandangRepo.GetByID(ctx, *kandangID, func(db *gorm.DB) *gorm.DB { return db.Select("id, location_id")
return db.Select("id, location_id") })
}) if err != nil {
if err != nil { return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Kandang not found: %d", *kandangID))
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Kandang not found: %d", *kandangID)) }
} if kandang == nil {
if kandang == nil { return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Kandang not found: %d", *kandangID))
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Kandang not found: %d", *kandangID)) }
locationID = uint64(kandang.LocationId)
id := uint64(*kandangID)
expenseKandangID = &id
} else {
warehouse := items[0].item.Warehouse
if warehouse == nil || warehouse.LocationId == nil || *warehouse.LocationId == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Warehouse location is required for expense")
}
locationID = uint64(*warehouse.LocationId)
} }
costItems := make([]expenseValidation.CostItem, 0, len(items)) costItems := make([]expenseValidation.CostItem, 0, len(items))
@@ -584,9 +596,9 @@ func (b *expenseBridge) createExpenseViaService(
TransactionDate: utils.FormatDate(expenseDate), TransactionDate: utils.FormatDate(expenseDate),
Category: "BOP", Category: "BOP",
SupplierID: uint64(supplierID), SupplierID: uint64(supplierID),
LocationID: uint64(kandang.LocationId), LocationID: locationID,
ExpenseNonstocks: []expenseValidation.ExpenseNonstock{{ ExpenseNonstocks: []expenseValidation.ExpenseNonstock{{
KandangID: func() *uint64 { id := uint64(*kandangID); return &id }(), KandangID: expenseKandangID,
CostItems: costItems, CostItems: costItems,
}}, }},
} }
@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"mime/multipart"
"strings" "strings"
"time" "time"
@@ -57,6 +58,7 @@ type purchaseService struct {
ApprovalSvc commonSvc.ApprovalService ApprovalSvc commonSvc.ApprovalService
ExpenseBridge PurchaseExpenseBridge ExpenseBridge PurchaseExpenseBridge
FifoSvc commonSvc.FifoService FifoSvc commonSvc.FifoService
DocumentSvc commonSvc.DocumentService
approvalWorkflow approvalutils.ApprovalWorkflowKey approvalWorkflow approvalutils.ApprovalWorkflowKey
} }
@@ -76,6 +78,7 @@ func NewPurchaseService(
approvalSvc commonSvc.ApprovalService, approvalSvc commonSvc.ApprovalService,
expenseBridge PurchaseExpenseBridge, expenseBridge PurchaseExpenseBridge,
fifoSvc commonSvc.FifoService, fifoSvc commonSvc.FifoService,
documentSvc commonSvc.DocumentService,
) PurchaseService { ) PurchaseService {
return &purchaseService{ return &purchaseService{
Log: utils.Log, Log: utils.Log,
@@ -89,6 +92,7 @@ func NewPurchaseService(
ApprovalSvc: approvalSvc, ApprovalSvc: approvalSvc,
ExpenseBridge: expenseBridge, ExpenseBridge: expenseBridge,
FifoSvc: fifoSvc, FifoSvc: fifoSvc,
DocumentSvc: documentSvc,
approvalWorkflow: utils.ApprovalWorkflowPurchase, approvalWorkflow: utils.ApprovalWorkflowPurchase,
} }
} }
@@ -246,22 +250,25 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
s.Log.Errorf("Failed to get warehouse %d: %+v", id, err) s.Log.Errorf("Failed to get warehouse %d: %+v", id, err)
return nil, nil, utils.Internal("Failed to get warehouse") return nil, nil, utils.Internal("Failed to get warehouse")
} }
if warehouse.KandangId == nil || *warehouse.KandangId == 0 {
return nil, nil, utils.BadRequest(fmt.Sprintf("%s is not linked to a kandang", warehouse.Name))
}
var pfkID *uint var pfkID *uint
if s.ProjectFlockKandangRepo != nil { isKandang := strings.EqualFold(strings.TrimSpace(warehouse.Type), "KANDANG")
if pfk, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(c.Context(), uint(*warehouse.KandangId)); err == nil && pfk != nil { if isKandang {
if pfk.ClosedAt != nil { if warehouse.KandangId == nil || *warehouse.KandangId == 0 {
return nil, nil, utils.BadRequest("Project sudah closing") return nil, nil, utils.BadRequest(fmt.Sprintf("%s is not linked to a kandang", warehouse.Name))
}
if s.ProjectFlockKandangRepo != nil {
if pfk, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(c.Context(), uint(*warehouse.KandangId)); err == nil && pfk != nil {
if pfk.ClosedAt != nil {
return nil, nil, utils.BadRequest("Project sudah closing")
}
idCopy := uint(pfk.Id)
pfkID = &idCopy
} else if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil, utils.BadRequest(fmt.Sprintf("%s has no active project flock", warehouse.Name))
} else if err != nil {
s.Log.Errorf("Failed to validate project flock for warehouse %d: %+v", id, err)
return nil, nil, utils.Internal("Failed to validate project flock")
} }
idCopy := uint(pfk.Id)
pfkID = &idCopy
} else if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil, utils.BadRequest(fmt.Sprintf("%s has no active project flock", warehouse.Name))
} else if err != nil {
s.Log.Errorf("Failed to validate project flock for warehouse %d: %+v", id, err)
return nil, nil, utils.Internal("Failed to validate project flock")
} }
} }
@@ -612,9 +619,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
if err := s.Validate.Struct(req); err != nil { if err := s.Validate.Struct(req); err != nil {
return nil, err return nil, err
} }
ctx := c.Context() ctx := c.Context()
action, err := parseApprovalActionInput(req.Action) action, err := parseApprovalActionInput(req.Action)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -661,6 +666,30 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
return updated, nil 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)) itemMap := make(map[uint]*entity.PurchaseItem, len(purchase.Items))
for i := range purchase.Items { for i := range purchase.Items {
itemMap[purchase.Items[i].Id] = &purchase.Items[i] itemMap[purchase.Items[i].Id] = &purchase.Items[i]
@@ -804,32 +833,20 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
for _, prep := range prepared { for _, prep := range prepared {
item := prep.item item := prep.item
var oldPWID *uint
if item.ProductWarehouseId != nil {
idCopy := uint(*item.ProductWarehouseId)
oldPWID = &idCopy
}
var newPWID *uint var newPWID *uint
clearPW := false
// Always ensure PW when qty > 0 so stockable has target. // Always ensure PW after receiving so linkage stays stable.
if prep.receivedQty > 0 { pwID, err := pwRepoTx.EnsureProductWarehouse(
pwID, err := pwRepoTx.EnsureProductWarehouse( c.Context(),
c.Context(), uint(item.ProductId),
uint(item.ProductId), prep.warehouseID,
prep.warehouseID, item.ProjectFlockKandangId,
item.ProjectFlockKandangId, purchase.CreatedBy,
purchase.CreatedBy, )
) if err != nil {
if err != nil { return err
return err
}
newPWID = &pwID
} else if oldPWID != nil {
newPWID = oldPWID
clearPW = true
} }
newPWID = &pwID
deltaQty := prep.receivedQty - item.TotalQty deltaQty := prep.receivedQty - item.TotalQty
switch { switch {
@@ -854,7 +871,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
VehicleNumber: prep.payload.VehicleNumber, VehicleNumber: prep.payload.VehicleNumber,
ReceivedQty: &qtyCopy, ReceivedQty: &qtyCopy,
ProductWarehouseID: newPWID, ProductWarehouseID: newPWID,
ClearProductWarehouse: clearPW, ClearProductWarehouse: false,
} }
if prep.overrideWarehouse || uint(item.WarehouseId) != prep.warehouseID { if prep.overrideWarehouse || uint(item.WarehouseId) != prep.warehouseID {
@@ -969,6 +986,38 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
return updated, nil 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) { func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint, req *validation.DeletePurchaseItemsRequest) (*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
@@ -1048,6 +1097,10 @@ func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint, req *validation.Del
return nil, utils.Internal("Failed to delete purchase items") return nil, utils.Internal("Failed to delete purchase items")
} }
if err := s.deletePurchaseItemDocuments(ctx, itemsToDelete); err != nil {
return nil, utils.Internal("Failed to delete purchase documents")
}
if len(itemsToDelete) > 0 { if len(itemsToDelete) > 0 {
if err := s.notifyExpenseItemsDeleted(ctx, purchase.Id, itemsToDelete); err != nil { if err := s.notifyExpenseItemsDeleted(ctx, purchase.Id, itemsToDelete); err != nil {
s.Log.Errorf("Failed to sync expense deletion for purchase %d: %+v", purchase.Id, err) s.Log.Errorf("Failed to sync expense deletion for purchase %d: %+v", purchase.Id, err)
@@ -1107,6 +1160,10 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
return utils.Internal("Failed to delete purchase") return utils.Internal("Failed to delete purchase")
} }
if err := s.deletePurchaseItemDocuments(ctx, itemsToDelete); err != nil {
return utils.Internal("Failed to delete purchase documents")
}
if len(itemsToDelete) > 0 { if len(itemsToDelete) > 0 {
if err := s.notifyExpenseItemsDeleted(ctx, uint(id), itemsToDelete); err != nil { if err := s.notifyExpenseItemsDeleted(ctx, uint(id), itemsToDelete); err != nil {
s.Log.Errorf("Failed to sync expense deletion for purchase %d: %+v", id, err) s.Log.Errorf("Failed to sync expense deletion for purchase %d: %+v", id, err)
@@ -1190,6 +1247,21 @@ func (s *purchaseService) notifyExpenseItemsDeleted(ctx context.Context, purchas
} }
func (s *purchaseService) deletePurchaseItemDocuments(ctx context.Context, items []entity.PurchaseItem) error {
if s.DocumentSvc == nil || len(items) == 0 {
return nil
}
for _, item := range items {
if item.Id == 0 {
continue
}
if err := s.DocumentSvc.DeleteByTarget(ctx, string(utils.DocumentableTypePurchaseItem), uint64(item.Id), true); err != nil {
return err
}
}
return nil
}
func (s *purchaseService) buildStaffAdjustmentPayload( func (s *purchaseService) buildStaffAdjustmentPayload(
ctx context.Context, ctx context.Context,
purchase *entity.Purchase, purchase *entity.Purchase,
@@ -1462,5 +1534,5 @@ func (s *purchaseService) ensureProjectFlockNotClosedForPurchase(
return utils.Internal("DB not available for project flock validation") return utils.Internal("DB not available for project flock validation")
} }
return commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(ctx, db, pfkIDs) return commonSvc.EnsureProjectFlockNotClosedByProjectFlockKandangID(ctx, db, pfkIDs)
} }
@@ -1,5 +1,7 @@
package validation package validation
import "mime/multipart"
type PurchaseItemPayload struct { type PurchaseItemPayload struct {
WarehouseID uint `json:"warehouse_id" validate:"required,gt=0"` WarehouseID uint `json:"warehouse_id" validate:"required,gt=0"`
ProductID uint `json:"product_id" validate:"required,gt=0"` ProductID uint `json:"product_id" validate:"required,gt=0"`
@@ -26,7 +28,7 @@ type StaffPurchaseApprovalItem struct {
type ApproveStaffPurchaseRequest struct { type ApproveStaffPurchaseRequest struct {
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"` Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
Items []StaffPurchaseApprovalItem `json:"items,omitempty" validate:"omitempty,min=1,dive"` Items []StaffPurchaseApprovalItem `json:"items" validate:"omitempty,min=1,dive"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
} }
@@ -36,21 +38,22 @@ type ApproveManagerPurchaseRequest struct {
} }
type ReceivePurchaseItemRequest struct { type ReceivePurchaseItemRequest struct {
PurchaseItemID uint `json:"purchase_item_id" validate:"required,gt=0"` PurchaseItemID uint `form:"purchase_item_id" json:"purchase_item_id" validate:"required,gt=0"`
WarehouseID *uint `json:"warehouse_id" validate:"omitempty,gt=0"` WarehouseID *uint `form:"warehouse_id" json:"warehouse_id" validate:"omitempty,gt=0"`
ReceivedDate string `json:"received_date" validate:"required,datetime=2006-01-02"` ReceivedDate string `form:"received_date" json:"received_date" validate:"required,datetime=2006-01-02"`
ExpeditionVendorID *uint `json:"expedition_vendor_id,omitempty" validate:"omitempty,gt=0"` ExpeditionVendorID *uint `form:"expedition_vendor_id" json:"expedition_vendor_id,omitempty" validate:"omitempty,gt=0"`
TransportPerItem *float64 `json:"transport_per_item,omitempty" validate:"omitempty,gte=0"` TransportPerItem *float64 `form:"transport_per_item" json:"transport_per_item,omitempty" validate:"omitempty,gte=0"`
TravelNumber *string `json:"travel_number" validate:"omitempty,max=100"` TravelNumber *string `form:"travel_number" json:"travel_number" validate:"omitempty,max=100"`
TravelDocumentPath *string `json:"travel_document_path" validate:"omitempty,max=255"` TravelDocumentPath *string `form:"travel_document_path" json:"travel_document_path" validate:"omitempty,max=1024"`
VehicleNumber *string `json:"vehicle_number" validate:"omitempty,max=100"` VehicleNumber *string `form:"vehicle_number" json:"vehicle_number" validate:"omitempty,max=100"`
ReceivedQty *float64 `json:"received_qty" validate:"omitempty,gte=0"` ReceivedQty *float64 `form:"received_qty" json:"received_qty" validate:"omitempty,gte=0"`
} }
type ReceivePurchaseRequest struct { type ReceivePurchaseRequest struct {
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"` Action string `form:"action" json:"action" validate:"required,oneof=APPROVED REJECTED"`
Items []ReceivePurchaseItemRequest `json:"items,omitempty" validate:"omitempty,min=1,dive"` Items []ReceivePurchaseItemRequest `form:"items" json:"items" validate:"min=1,dive"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` TravelDocuments []*multipart.FileHeader `form:"travel_documents" json:"-" validate:"omitempty,dive"`
Notes *string `form:"notes" json:"notes,omitempty" validate:"omitempty,max=500"`
} }
type DeletePurchaseItemsRequest struct { type DeletePurchaseItemsRequest struct {
+1 -1
View File
@@ -18,6 +18,6 @@ func RepportRoutes(v1 fiber.Router, u user.UserService, s repport.RepportService
route.Get("/expense", m.RequirePermissions(m.P_ReportExpenseGetAll), ctrl.GetExpense) route.Get("/expense", m.RequirePermissions(m.P_ReportExpenseGetAll), ctrl.GetExpense)
route.Get("/marketing", m.RequirePermissions(m.P_ReportDeliveryGetAll), ctrl.GetMarketing) route.Get("/marketing", m.RequirePermissions(m.P_ReportDeliveryGetAll), ctrl.GetMarketing)
route.Get("/purchase-supplier", m.RequirePermissions(m.P_ReportPurchaseSupplierGetAll), ctrl.GetPurchaseSupplier) route.Get("/purchase-supplier", m.RequirePermissions(m.P_ReportPurchaseSupplierGetAll), ctrl.GetPurchaseSupplier)
route.Get("/hpp-per-kandang", ctrl.GetHppPerKandang) route.Get("/hpp-per-kandang", m.RequirePermissions(m.P_ReportHppPerKandangGetAll),ctrl.GetHppPerKandang)
} }
+2
View File
@@ -426,10 +426,12 @@ const (
DocumentTypeTransfer DocumentType = "STOCK_TRANSFER_DOCUMENT" DocumentTypeTransfer DocumentType = "STOCK_TRANSFER_DOCUMENT"
DocumentTypeExpense DocumentType = "EXPENSE_DOCUMENT" DocumentTypeExpense DocumentType = "EXPENSE_DOCUMENT"
DocumentTypeExpenseRealization DocumentType = "EXPENSE_REALIZATION_DOCUMENT" DocumentTypeExpenseRealization DocumentType = "EXPENSE_REALIZATION_DOCUMENT"
DocumentTypePurchaseTravel DocumentType = "PURCHASE_TRAVEL_DOCUMENT"
DocumentableTypeTransfer DocumentableType = "STOCK_TRANSFER" DocumentableTypeTransfer DocumentableType = "STOCK_TRANSFER"
DocumentableTypeExpense DocumentableType = "EXPENSE" DocumentableTypeExpense DocumentableType = "EXPENSE"
DocumentableTypeExpenseRealization DocumentableType = "EXPENSE_REALIZATION" DocumentableTypeExpenseRealization DocumentableType = "EXPENSE_REALIZATION"
DocumentableTypePurchaseItem DocumentableType = "PURCHASE_ITEM"
) )
// ------------------------------------------------------------------- // -------------------------------------------------------------------