Merge branch 'development' into 'staging'

Development

See merge request mbugroup/lti-api!201
This commit is contained in:
Hafizh A. Y.
2026-01-15 11:55:47 +00:00
8 changed files with 219 additions and 84 deletions
@@ -318,6 +318,7 @@ func (r *ClosingRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlo
Joins("JOIN suppliers s ON s.id = e.supplier_id"). Joins("JOIN suppliers s ON s.id = e.supplier_id").
Where("pfk.project_flock_id = ?", projectFlockID). Where("pfk.project_flock_id = ?", projectFlockID).
Where("e.category = ?", "BOP"). Where("e.category = ?", "BOP").
Where("e.realization_date IS NOT NULL").
Where("UPPER(f.name) = ?", strings.ToUpper(string(utils.FlagEkspedisi))) Where("UPPER(f.name) = ?", strings.ToUpper(string(utils.FlagEkspedisi)))
if projectFlockKandangID != nil && *projectFlockKandangID != 0 { if projectFlockKandangID != nil && *projectFlockKandangID != 0 {
@@ -889,17 +890,58 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka
} }
func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) { func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) {
rows, err := r.fetchStockLogs(ctx, kandangID, string(utils.StockLogTypeTransfer), true) incomingQuery := r.withCtx(ctx).
Table("stock_transfer_details AS std").
Select(`
std.product_id AS product_id,
p.name AS product_name,
f.name AS flag,
st.transfer_date::timestamp AS date,
COALESCE(st.movement_number, '') AS reference,
COALESCE(std.total_qty, 0) AS qty_in,
0 AS qty_out,
COALESCE(p.product_price, 0) AS price
`).
Joins("JOIN stock_transfers st ON st.id = std.stock_transfer_id").
Joins("JOIN product_warehouses pw ON pw.id = std.dest_product_warehouse_id").
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
Joins("JOIN products p ON p.id = std.product_id").
Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
Where("w.kandang_id = ?", kandangID).
Where("f.name IN ?", sapronakFlagsAll)
incoming, err := scanAndGroupDetails(incomingQuery)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
in, out := splitStockLogs(rows, func(row stockLogSapronakRow) string {
if ref := strings.TrimSpace(row.MovementNumber); ref != "" { outgoingQuery := r.withCtx(ctx).
return ref Table("stock_allocations AS sa").
} Select(`
return fmt.Sprintf("TRF-%d", row.ID) std.product_id AS product_id,
}) p.name AS product_name,
return in, out, nil f.name AS flag,
st.transfer_date::timestamp AS date,
COALESCE(st.movement_number, '') AS reference,
0 AS qty_in,
COALESCE(SUM(sa.qty), 0) AS qty_out,
COALESCE(p.product_price, 0) AS price
`).
Joins("JOIN stock_transfer_details std ON std.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyStockTransferOut.String()).
Joins("JOIN stock_transfers st ON st.id = std.stock_transfer_id").
Joins("JOIN product_warehouses pw ON pw.id = sa.product_warehouse_id").
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
Joins("JOIN products p ON p.id = std.product_id").
Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
Where("sa.status = ?", entity.StockAllocationStatusActive).
Where("w.kandang_id = ?", kandangID).
Where("f.name IN ?", sapronakFlagsAll).
Group("std.id, std.product_id, p.name, f.name, st.transfer_date, st.movement_number, p.product_price")
outgoing, err := scanAndGroupDetails(outgoingQuery)
if err != nil {
return nil, nil, err
}
return incoming, outgoing, nil
} }
type ActualUsageCostRow struct { type ActualUsageCostRow struct {
@@ -3,7 +3,6 @@ package service
import ( import (
"context" "context"
"strings" "strings"
"time"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@@ -112,7 +111,7 @@ func (s sapronakService) computeSapronakReports(ctx context.Context, params *val
} }
// We no longer filter by date for closing sapronak report; pass nil pointers. // We no longer filter by date for closing sapronak report; pass nil pointers.
items, groups, totalIncoming, totalUsage, err := s.buildSapronakItems(ctx, pfk, nil, nil, params.Flag) items, groups, totalIncoming, totalUsage, err := s.buildSapronakItems(ctx, pfk, params.Flag)
if err != nil { if err != nil {
s.Log.Errorf("Failed to build sapronak items for pfk %d: %+v", pfk.Id, err) s.Log.Errorf("Failed to build sapronak items for pfk %d: %+v", pfk.Id, err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to calculate sapronak report") return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to calculate sapronak report")
@@ -126,8 +125,6 @@ func (s sapronakService) computeSapronakReports(ctx context.Context, params *val
KandangName: pfk.Kandang.Name, KandangName: pfk.Kandang.Name,
Period: pfk.Period, Period: pfk.Period,
Status: status, Status: status,
StartDate: nil,
EndDate: nil,
TotalIncomingValue: totalIncoming, TotalIncomingValue: totalIncoming,
TotalUsageValue: totalUsage, TotalUsageValue: totalUsage,
Items: items, Items: items,
@@ -318,7 +315,7 @@ func buildSapronakDetails(
return result return result
} }
func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.ProjectFlockKandang, start, end *time.Time, flagFilter string) ([]dto.SapronakItemDTO, []dto.SapronakGroupDTO, float64, float64, error) { func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.ProjectFlockKandang, flagFilter string) ([]dto.SapronakItemDTO, []dto.SapronakGroupDTO, float64, float64, error) {
// For sapronak closing report we intentionally ignore date range // For sapronak closing report we intentionally ignore date range
// and aggregate all historical transactions for the kandang/project. // and aggregate all historical transactions for the kandang/project.
incomingRows, err := s.Repository.FetchSapronakIncoming(ctx, pfk.KandangId) incomingRows, err := s.Repository.FetchSapronakIncoming(ctx, pfk.KandangId)
@@ -419,6 +416,22 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
return groupMap[flag] return groupMap[flag]
} }
resolveFlagName := func(productID uint, details []dto.SapronakDetailDTO) (string, string) {
flag := ""
name := ""
if item, ok := itemMap[productID]; ok {
flag = item.Flag
name = item.ProductName
}
if flag == "" && len(details) > 0 {
flag = details[0].Flag
}
if name == "" && len(details) > 0 {
name = details[0].ProductName
}
return flag, name
}
for _, row := range incoming { for _, row := range incoming {
if !matchesFlag(row.Flag) { if !matchesFlag(row.Flag) {
continue continue
@@ -554,19 +567,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
} }
for productID, details := range incomingDetails { for productID, details := range incomingDetails {
flag := "" flag, name := resolveFlagName(productID, details)
name := ""
if item, ok := itemMap[productID]; ok {
flag = item.Flag
name = item.ProductName
}
if !matchesFlag(flag) { if !matchesFlag(flag) {
continue continue
} }
group := ensureGroup(flag) group := ensureGroup(flag)
for _, d := range details { for _, d := range details {
d.Flag = flag if d.Flag == "" {
d.ProductName = name d.Flag = flag
}
if d.ProductName == "" {
d.ProductName = name
}
group.Items = append(group.Items, d) group.Items = append(group.Items, d)
group.TotalMasuk += d.QtyMasuk group.TotalMasuk += d.QtyMasuk
group.TotalNilai += d.Nilai group.TotalNilai += d.Nilai
@@ -575,19 +587,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
} }
for productID, details := range adjIncoming { for productID, details := range adjIncoming {
flag := "" flag, name := resolveFlagName(productID, details)
name := ""
if item, ok := itemMap[productID]; ok {
flag = item.Flag
name = item.ProductName
}
if !matchesFlag(flag) { if !matchesFlag(flag) {
continue continue
} }
group := ensureGroup(flag) group := ensureGroup(flag)
for _, d := range details { for _, d := range details {
d.Flag = flag if d.Flag == "" {
d.ProductName = name d.Flag = flag
}
if d.ProductName == "" {
d.ProductName = name
}
group.Items = append(group.Items, d) group.Items = append(group.Items, d)
group.TotalMasuk += d.QtyMasuk group.TotalMasuk += d.QtyMasuk
group.TotalNilai += d.Nilai group.TotalNilai += d.Nilai
@@ -596,19 +607,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
} }
for productID, details := range usageDetails { for productID, details := range usageDetails {
flag := "" flag, name := resolveFlagName(productID, details)
name := ""
if item, ok := itemMap[productID]; ok {
flag = item.Flag
name = item.ProductName
}
if !matchesFlag(flag) { if !matchesFlag(flag) {
continue continue
} }
group := ensureGroup(flag) group := ensureGroup(flag)
for _, d := range details { for _, d := range details {
d.Flag = flag if d.Flag == "" {
d.ProductName = name d.Flag = flag
}
if d.ProductName == "" {
d.ProductName = name
}
group.Items = append(group.Items, d) group.Items = append(group.Items, d)
group.TotalKeluar += d.QtyKeluar group.TotalKeluar += d.QtyKeluar
group.SaldoAkhir -= d.QtyKeluar group.SaldoAkhir -= d.QtyKeluar
@@ -616,19 +626,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
} }
for productID, details := range adjOutgoing { for productID, details := range adjOutgoing {
flag := "" flag, name := resolveFlagName(productID, details)
name := ""
if item, ok := itemMap[productID]; ok {
flag = item.Flag
name = item.ProductName
}
if !matchesFlag(flag) { if !matchesFlag(flag) {
continue continue
} }
group := ensureGroup(flag) group := ensureGroup(flag)
for _, d := range details { for _, d := range details {
d.Flag = flag if d.Flag == "" {
d.ProductName = name d.Flag = flag
}
if d.ProductName == "" {
d.ProductName = name
}
group.Items = append(group.Items, d) group.Items = append(group.Items, d)
group.TotalKeluar += d.QtyKeluar group.TotalKeluar += d.QtyKeluar
group.SaldoAkhir -= d.QtyKeluar group.SaldoAkhir -= d.QtyKeluar
@@ -636,19 +645,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
} }
for productID, details := range transIncoming { for productID, details := range transIncoming {
flag := "" flag, name := resolveFlagName(productID, details)
name := ""
if item, ok := itemMap[productID]; ok {
flag = item.Flag
name = item.ProductName
}
if !matchesFlag(flag) { if !matchesFlag(flag) {
continue continue
} }
group := ensureGroup(flag) group := ensureGroup(flag)
for _, d := range details { for _, d := range details {
d.Flag = flag if d.Flag == "" {
d.ProductName = name d.Flag = flag
}
if d.ProductName == "" {
d.ProductName = name
}
group.Items = append(group.Items, d) group.Items = append(group.Items, d)
group.TotalMasuk += d.QtyMasuk group.TotalMasuk += d.QtyMasuk
group.TotalNilai += d.Nilai group.TotalNilai += d.Nilai
@@ -657,19 +665,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
} }
for productID, details := range transOutgoing { for productID, details := range transOutgoing {
flag := "" flag, name := resolveFlagName(productID, details)
name := ""
if item, ok := itemMap[productID]; ok {
flag = item.Flag
name = item.ProductName
}
if !matchesFlag(flag) { if !matchesFlag(flag) {
continue continue
} }
group := ensureGroup(flag) group := ensureGroup(flag)
for _, d := range details { for _, d := range details {
d.Flag = flag if d.Flag == "" {
d.ProductName = name d.Flag = flag
}
if d.ProductName == "" {
d.ProductName = name
}
group.Items = append(group.Items, d) group.Items = append(group.Items, d)
group.TotalKeluar += d.QtyKeluar group.TotalKeluar += d.QtyKeluar
group.SaldoAkhir -= d.QtyKeluar group.SaldoAkhir -= d.QtyKeluar
@@ -28,6 +28,7 @@ func (u *SupplierController) GetAll(c *fiber.Ctx) error {
Limit: c.QueryInt("limit", 10), Limit: c.QueryInt("limit", 10),
Search: c.Query("search", ""), Search: c.Query("search", ""),
Category: c.Query("category", ""), Category: c.Query("category", ""),
Flag: c.Query("flag", ""),
} }
if query.Page < 1 || query.Limit < 1 { if query.Page < 1 || query.Limit < 1 {
@@ -47,6 +47,10 @@ func (s supplierService) withRelations(db *gorm.DB) *gorm.DB {
Preload("NonstockSuppliers.Nonstock.Flags") Preload("NonstockSuppliers.Nonstock.Flags")
} }
func (s supplierService) withListRelations(db *gorm.DB) *gorm.DB {
return db.Preload("CreatedUser")
}
func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Supplier, int64, error) { func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Supplier, int64, error) {
if err := s.Validate.Struct(params); err != nil { if err := s.Validate.Struct(params); err != nil {
return nil, 0, err return nil, 0, err
@@ -63,7 +67,7 @@ func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit
offset := (params.Page - 1) * params.Limit offset := (params.Page - 1) * params.Limit
suppliers, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { suppliers, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
db = s.withRelations(db) db = s.withListRelations(db)
if params.Search != "" { if params.Search != "" {
return db.Where("name ILIKE ?", "%"+params.Search+"%") return db.Where("name ILIKE ?", "%"+params.Search+"%")
} }
@@ -72,7 +76,23 @@ func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit
db = db.Where("category ILIKE ?", "%"+params.Category+"%") db = db.Where("category ILIKE ?", "%"+params.Category+"%")
} }
return db.Order("created_at DESC").Order("updated_at DESC") if params.Flag != "" {
flag := strings.ToUpper(params.Flag)
db = db.Where(`
EXISTS (
SELECT 1
FROM nonstock_suppliers nsup
JOIN nonstocks n ON n.id = nsup.nonstock_id
JOIN flags f ON f.flagable_id = n.id AND f.flagable_type = ?
WHERE nsup.supplier_id = suppliers.id
AND UPPER(f.name) = ?
)`,
entity.FlagableTypeNonstock,
flag,
)
}
return db.Order("suppliers.created_at DESC").Order("suppliers.updated_at DESC")
}) })
if err != nil { if err != nil {
@@ -32,7 +32,8 @@ type Update struct {
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"`
Flag string `query:"flag" validate:"omitempty"`
Search string `query:"search" validate:"omitempty,max=50"` Search string `query:"search" validate:"omitempty,max=50"`
Category string `query:"category" validate:"omitempty,max=50"` Category string `query:"category" validate:"omitempty,max=50"`
} }
@@ -110,7 +110,11 @@ func (s *warehouseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
} }
typ := strings.ToUpper(req.Type) typ := strings.ToUpper(req.Type)
if err := validateWarehouseTypeRequirements(typ, &req.AreaId, req.LocationId, req.KandangId); err != nil { createValidationOpts := WarehouseTypeValidationOptions{
LocationProvided: req.LocationId != nil,
KandangProvided: req.KandangId != nil,
}
if err := validateWarehouseTypeRequirements(typ, &req.AreaId, &req.LocationId, &req.KandangId, createValidationOpts); err != nil {
return nil, err return nil, err
} }
@@ -208,9 +212,22 @@ func (s warehouseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
finalKandangId = req.KandangId finalKandangId = req.KandangId
} }
if err := validateWarehouseTypeRequirements(finalType, &finalAreaId, finalLocationId, finalKandangId); err != nil { originalLocationId := finalLocationId
originalKandangId := finalKandangId
updateValidationOpts := WarehouseTypeValidationOptions{
AutoClear: true,
LocationProvided: req.LocationId != nil,
KandangProvided: req.KandangId != nil,
}
if err := validateWarehouseTypeRequirements(finalType, &finalAreaId, &finalLocationId, &finalKandangId, updateValidationOpts); err != nil {
return nil, err return nil, err
} }
if originalLocationId != finalLocationId {
updateBody["location_id"] = nil
}
if originalKandangId != finalKandangId {
updateBody["kandang_id"] = nil
}
if len(updateBody) == 0 { if len(updateBody) == 0 {
return s.GetOne(c, id) return s.GetOne(c, id)
@@ -238,47 +255,65 @@ func (s warehouseService) DeleteOne(c *fiber.Ctx, id uint) error {
return nil return nil
} }
func validateWarehouseTypeRequirements(typ string, areaID *uint, locationID *uint, kandangID *uint) error { type WarehouseTypeValidationOptions struct {
AutoClear bool
LocationProvided bool
KandangProvided bool
}
func validateWarehouseTypeRequirements(typ string, areaID *uint, locationID **uint, kandangID **uint, opts WarehouseTypeValidationOptions) error {
switch utils.WarehouseType(typ) { switch utils.WarehouseType(typ) {
case utils.WarehouseTypeArea: case utils.WarehouseTypeArea:
if areaID == nil || *areaID == 0 { if areaID == nil || *areaID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is AREA") return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is AREA")
} }
if locationID != nil { if locationID != nil && *locationID != nil {
return fiber.NewError(fiber.StatusBadRequest, "location_id must not be provided when type is AREA") if opts.AutoClear && !opts.LocationProvided {
*locationID = nil
} else {
return fiber.NewError(fiber.StatusBadRequest, "location_id must not be provided when type is AREA")
}
} }
if kandangID != nil { if kandangID != nil && *kandangID != nil {
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must not be provided when type is AREA") if opts.AutoClear && !opts.KandangProvided {
*kandangID = nil
} else {
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must not be provided when type is AREA")
}
} }
return nil return nil
case utils.WarehouseTypeLokasi: case utils.WarehouseTypeLokasi:
if areaID == nil || *areaID == 0 { if areaID == nil || *areaID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is LOCATION") return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is LOCATION")
} }
if locationID == nil { if locationID == nil || *locationID == nil {
return fiber.NewError(fiber.StatusBadRequest, "location_id is required when type is LOCATION") return fiber.NewError(fiber.StatusBadRequest, "location_id is required when type is LOCATION")
} }
if *locationID == 0 { if **locationID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "location_id must be greater than 0 when type is LOCATION") return fiber.NewError(fiber.StatusBadRequest, "location_id must be greater than 0 when type is LOCATION")
} }
if kandangID != nil { if kandangID != nil && *kandangID != nil {
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must not be provided when type is LOCATION") if opts.AutoClear && !opts.KandangProvided {
*kandangID = nil
} else {
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must not be provided when type is LOCATION")
}
} }
return nil return nil
case utils.WarehouseTypeKandang: case utils.WarehouseTypeKandang:
if areaID == nil || *areaID == 0 { if areaID == nil || *areaID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is KANDANG") return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is KANDANG")
} }
if locationID == nil { if locationID == nil || *locationID == nil {
return fiber.NewError(fiber.StatusBadRequest, "location_id is required when type is KANDANG") return fiber.NewError(fiber.StatusBadRequest, "location_id is required when type is KANDANG")
} }
if *locationID == 0 { if **locationID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "location_id must be greater than 0 when type is KANDANG") return fiber.NewError(fiber.StatusBadRequest, "location_id must be greater than 0 when type is KANDANG")
} }
if kandangID == nil { if kandangID == nil || *kandangID == nil {
return fiber.NewError(fiber.StatusBadRequest, "kandang_id is required when type is KANDANG") return fiber.NewError(fiber.StatusBadRequest, "kandang_id is required when type is KANDANG")
} }
if *kandangID == 0 { if **kandangID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must be greater than 0 when type is KANDANG") return fiber.NewError(fiber.StatusBadRequest, "kandang_id must be greater than 0 when type is KANDANG")
} }
return nil return nil
@@ -36,6 +36,7 @@ type ExpenseReceivingPayload struct {
TransportPerItem *float64 TransportPerItem *float64
ReceivedQty float64 ReceivedQty float64
ReceivedDate *time.Time ReceivedDate *time.Time
VehicleNumber *string
} }
type groupedItem struct { type groupedItem struct {
@@ -182,6 +183,22 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
} }
ctx := c.Context() ctx := c.Context()
filtered := make([]ExpenseReceivingPayload, 0, len(updates))
for _, upd := range updates {
if upd.SupplierID == 0 {
continue
}
if upd.TransportPerItem == nil || *upd.TransportPerItem <= 0 {
continue
}
if upd.VehicleNumber == nil || strings.TrimSpace(*upd.VehicleNumber) == "" {
continue
}
filtered = append(filtered, upd)
}
if len(filtered) == 0 {
return nil
}
// Load current links to decide whether to update in place or recreate. // Load current links to decide whether to update in place or recreate.
type itemLink struct { type itemLink struct {
@@ -205,9 +222,9 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
itemLinks := make(map[uint]itemLink) itemLinks := make(map[uint]itemLink)
updatedExpenses := make(map[uint64]struct{}) updatedExpenses := make(map[uint64]struct{})
if len(updates) > 0 { if len(filtered) > 0 {
ids := make([]uint, 0, len(updates)) ids := make([]uint, 0, len(filtered))
for _, upd := range updates { for _, upd := range filtered {
if upd.PurchaseItemID != 0 { if upd.PurchaseItemID != 0 {
ids = append(ids, upd.PurchaseItemID) ids = append(ids, upd.PurchaseItemID)
} }
@@ -252,7 +269,7 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
groups := make(map[string][]groupedItem) groups := make(map[string][]groupedItem)
for _, payload := range updates { for _, payload := range filtered {
if payload.ReceivedDate == nil { if payload.ReceivedDate == nil {
return fiber.NewError(fiber.StatusBadRequest, "received_date is required") return fiber.NewError(fiber.StatusBadRequest, "received_date is required")
} }
@@ -702,6 +702,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
warehouseID uint warehouseID uint
supplierID uint supplierID uint
transportPerItem *float64 transportPerItem *float64
vehicleNumber *string
overrideWarehouse bool overrideWarehouse bool
receivedQty float64 receivedQty float64
} }
@@ -756,7 +757,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
} }
visitedItems[payload.PurchaseItemID] = struct{}{} visitedItems[payload.PurchaseItemID] = struct{}{}
supplierID := purchase.SupplierId var supplierID uint
if payload.ExpeditionVendorID != nil && *payload.ExpeditionVendorID != 0 { if payload.ExpeditionVendorID != nil && *payload.ExpeditionVendorID != 0 {
supplierID = *payload.ExpeditionVendorID supplierID = *payload.ExpeditionVendorID
} }
@@ -770,6 +771,15 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
transportPerItem = &val transportPerItem = &val
} }
var vehicleNumber *string
if payload.VehicleNumber != nil && strings.TrimSpace(*payload.VehicleNumber) != "" {
val := strings.TrimSpace(*payload.VehicleNumber)
vehicleNumber = &val
} else if item.VehicleNumber != nil && strings.TrimSpace(*item.VehicleNumber) != "" {
val := strings.TrimSpace(*item.VehicleNumber)
vehicleNumber = &val
}
prepared = append(prepared, preparedReceiving{ prepared = append(prepared, preparedReceiving{
item: item, item: item,
payload: payload, payload: payload,
@@ -777,6 +787,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
warehouseID: warehouseID, warehouseID: warehouseID,
supplierID: supplierID, supplierID: supplierID,
transportPerItem: transportPerItem, transportPerItem: transportPerItem,
vehicleNumber: vehicleNumber,
overrideWarehouse: overrideWarehouse, overrideWarehouse: overrideWarehouse,
receivedQty: receivedQty, receivedQty: receivedQty,
}) })
@@ -964,6 +975,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
TransportPerItem: prep.transportPerItem, TransportPerItem: prep.transportPerItem,
ReceivedQty: prep.receivedQty, ReceivedQty: prep.receivedQty,
ReceivedDate: &date, ReceivedDate: &date,
VehicleNumber: prep.vehicleNumber,
} }
receivingPayloads = append(receivingPayloads, payload) receivingPayloads = append(receivingPayloads, payload)
} }