mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'fix/BE/Purchase-bop' into 'development'
[FIX/BE-US] adjustment purchase,closing hpp expedition,supplier filter flags See merge request mbugroup/lti-api!199
This commit is contained in:
@@ -318,6 +318,7 @@ func (r *ClosingRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlo
|
||||
Joins("JOIN suppliers s ON s.id = e.supplier_id").
|
||||
Where("pfk.project_flock_id = ?", projectFlockID).
|
||||
Where("e.category = ?", "BOP").
|
||||
Where("e.realization_date IS NOT NULL").
|
||||
Where("UPPER(f.name) = ?", strings.ToUpper(string(utils.FlagEkspedisi)))
|
||||
|
||||
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) {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
in, out := splitStockLogs(rows, func(row stockLogSapronakRow) string {
|
||||
if ref := strings.TrimSpace(row.MovementNumber); ref != "" {
|
||||
return ref
|
||||
}
|
||||
return fmt.Sprintf("TRF-%d", row.ID)
|
||||
})
|
||||
return in, out, nil
|
||||
|
||||
outgoingQuery := r.withCtx(ctx).
|
||||
Table("stock_allocations AS sa").
|
||||
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,
|
||||
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 {
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"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.
|
||||
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 {
|
||||
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")
|
||||
@@ -126,8 +125,6 @@ func (s sapronakService) computeSapronakReports(ctx context.Context, params *val
|
||||
KandangName: pfk.Kandang.Name,
|
||||
Period: pfk.Period,
|
||||
Status: status,
|
||||
StartDate: nil,
|
||||
EndDate: nil,
|
||||
TotalIncomingValue: totalIncoming,
|
||||
TotalUsageValue: totalUsage,
|
||||
Items: items,
|
||||
@@ -318,7 +315,7 @@ func buildSapronakDetails(
|
||||
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
|
||||
// and aggregate all historical transactions for the kandang/project.
|
||||
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]
|
||||
}
|
||||
|
||||
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 {
|
||||
if !matchesFlag(row.Flag) {
|
||||
continue
|
||||
@@ -554,19 +567,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
||||
}
|
||||
|
||||
for productID, details := range incomingDetails {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
flag, name := resolveFlagName(productID, details)
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
if d.Flag == "" {
|
||||
d.Flag = flag
|
||||
}
|
||||
if d.ProductName == "" {
|
||||
d.ProductName = name
|
||||
}
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalMasuk += d.QtyMasuk
|
||||
group.TotalNilai += d.Nilai
|
||||
@@ -575,19 +587,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
||||
}
|
||||
|
||||
for productID, details := range adjIncoming {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
flag, name := resolveFlagName(productID, details)
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
if d.Flag == "" {
|
||||
d.Flag = flag
|
||||
}
|
||||
if d.ProductName == "" {
|
||||
d.ProductName = name
|
||||
}
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalMasuk += d.QtyMasuk
|
||||
group.TotalNilai += d.Nilai
|
||||
@@ -596,19 +607,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
||||
}
|
||||
|
||||
for productID, details := range usageDetails {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
flag, name := resolveFlagName(productID, details)
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
if d.Flag == "" {
|
||||
d.Flag = flag
|
||||
}
|
||||
if d.ProductName == "" {
|
||||
d.ProductName = name
|
||||
}
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalKeluar += 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 {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
flag, name := resolveFlagName(productID, details)
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
if d.Flag == "" {
|
||||
d.Flag = flag
|
||||
}
|
||||
if d.ProductName == "" {
|
||||
d.ProductName = name
|
||||
}
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalKeluar += 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 {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
flag, name := resolveFlagName(productID, details)
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
if d.Flag == "" {
|
||||
d.Flag = flag
|
||||
}
|
||||
if d.ProductName == "" {
|
||||
d.ProductName = name
|
||||
}
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalMasuk += d.QtyMasuk
|
||||
group.TotalNilai += d.Nilai
|
||||
@@ -657,19 +665,18 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
||||
}
|
||||
|
||||
for productID, details := range transOutgoing {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
flag, name := resolveFlagName(productID, details)
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
if d.Flag == "" {
|
||||
d.Flag = flag
|
||||
}
|
||||
if d.ProductName == "" {
|
||||
d.ProductName = name
|
||||
}
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalKeluar += d.QtyKeluar
|
||||
group.SaldoAkhir -= d.QtyKeluar
|
||||
|
||||
@@ -28,6 +28,7 @@ func (u *SupplierController) GetAll(c *fiber.Ctx) error {
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Search: c.Query("search", ""),
|
||||
Category: c.Query("category", ""),
|
||||
Flag: c.Query("flag", ""),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
|
||||
@@ -47,6 +47,10 @@ func (s supplierService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
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) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
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
|
||||
|
||||
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 != "" {
|
||||
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+"%")
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -32,7 +32,8 @@ type Update struct {
|
||||
|
||||
type Query struct {
|
||||
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"`
|
||||
Category string `query:"category" validate:"omitempty,max=50"`
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type ExpenseReceivingPayload struct {
|
||||
TransportPerItem *float64
|
||||
ReceivedQty float64
|
||||
ReceivedDate *time.Time
|
||||
VehicleNumber *string
|
||||
}
|
||||
|
||||
type groupedItem struct {
|
||||
@@ -182,6 +183,22 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
}
|
||||
|
||||
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.
|
||||
type itemLink struct {
|
||||
@@ -205,9 +222,9 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
|
||||
itemLinks := make(map[uint]itemLink)
|
||||
updatedExpenses := make(map[uint64]struct{})
|
||||
if len(updates) > 0 {
|
||||
ids := make([]uint, 0, len(updates))
|
||||
for _, upd := range updates {
|
||||
if len(filtered) > 0 {
|
||||
ids := make([]uint, 0, len(filtered))
|
||||
for _, upd := range filtered {
|
||||
if upd.PurchaseItemID != 0 {
|
||||
ids = append(ids, upd.PurchaseItemID)
|
||||
}
|
||||
@@ -252,7 +269,7 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
|
||||
groups := make(map[string][]groupedItem)
|
||||
|
||||
for _, payload := range updates {
|
||||
for _, payload := range filtered {
|
||||
if payload.ReceivedDate == nil {
|
||||
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
|
||||
supplierID uint
|
||||
transportPerItem *float64
|
||||
vehicleNumber *string
|
||||
overrideWarehouse bool
|
||||
receivedQty float64
|
||||
}
|
||||
@@ -756,7 +757,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
}
|
||||
visitedItems[payload.PurchaseItemID] = struct{}{}
|
||||
|
||||
supplierID := purchase.SupplierId
|
||||
var supplierID uint
|
||||
if payload.ExpeditionVendorID != nil && *payload.ExpeditionVendorID != 0 {
|
||||
supplierID = *payload.ExpeditionVendorID
|
||||
}
|
||||
@@ -770,6 +771,15 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
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{
|
||||
item: item,
|
||||
payload: payload,
|
||||
@@ -777,6 +787,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
warehouseID: warehouseID,
|
||||
supplierID: supplierID,
|
||||
transportPerItem: transportPerItem,
|
||||
vehicleNumber: vehicleNumber,
|
||||
overrideWarehouse: overrideWarehouse,
|
||||
receivedQty: receivedQty,
|
||||
})
|
||||
@@ -964,6 +975,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
TransportPerItem: prep.transportPerItem,
|
||||
ReceivedQty: prep.receivedQty,
|
||||
ReceivedDate: &date,
|
||||
VehicleNumber: prep.vehicleNumber,
|
||||
}
|
||||
receivingPayloads = append(receivingPayloads, payload)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user