mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
178 lines
4.0 KiB
Go
178 lines
4.0 KiB
Go
package fifo_stock_v2
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func (s *fifoStockV2Service) Recalculate(ctx context.Context, req RecalculateRequest) (*RecalculateResult, error) {
|
|
result := &RecalculateResult{Drifts: make([]WarehouseDrift, 0)}
|
|
|
|
err := s.withTransaction(ctx, req.Tx, func(tx *gorm.DB) error {
|
|
hash := requestHash(map[string]any{
|
|
"product_warehouse_ids": req.ProductWarehouseIDs,
|
|
"flag_group_codes": req.FlagGroupCodes,
|
|
"as_of": req.AsOf,
|
|
"fix_drift": req.FixDrift,
|
|
})
|
|
logRow, reused, err := s.beginOperation(
|
|
tx,
|
|
OperationRecalculate,
|
|
req.IdempotencyKey,
|
|
hash,
|
|
0,
|
|
"RECALCULATE",
|
|
"",
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if reused {
|
|
if len(logRow.ResultPayload) == 0 {
|
|
return fmt.Errorf("idempotent recalculate has empty payload")
|
|
}
|
|
if err := json.Unmarshal(logRow.ResultPayload, result); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
if logRow != nil {
|
|
defer func() {
|
|
if err != nil {
|
|
s.failOperation(tx, logRow, err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
warehouseIDs, err := s.resolveRecalculateWarehouseIDs(ctx, tx, req.ProductWarehouseIDs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
groupCodes, err := s.resolveRecalculateGroupCodes(ctx, tx, req.FlagGroupCodes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, warehouseID := range warehouseIDs {
|
|
expected := 0.0
|
|
for _, flagGroup := range groupCodes {
|
|
available, calcErr := s.calculateWarehouseAvailableForGroup(ctx, tx, warehouseID, flagGroup, req.AsOf)
|
|
if calcErr != nil {
|
|
return calcErr
|
|
}
|
|
expected += available
|
|
}
|
|
|
|
actual, actualErr := s.loadWarehouseQty(ctx, tx, warehouseID)
|
|
if actualErr != nil {
|
|
return actualErr
|
|
}
|
|
|
|
delta := expected - actual
|
|
result.Checked++
|
|
if math.Abs(delta) < 1e-6 {
|
|
continue
|
|
}
|
|
|
|
drift := WarehouseDrift{
|
|
ProductWarehouseID: warehouseID,
|
|
ExpectedQty: expected,
|
|
ActualQty: actual,
|
|
Delta: delta,
|
|
}
|
|
result.Drifts = append(result.Drifts, drift)
|
|
|
|
if req.FixDrift {
|
|
if err := s.adjustProductWarehouseQty(tx, warehouseID, delta); err != nil {
|
|
return err
|
|
}
|
|
result.Fixed++
|
|
}
|
|
}
|
|
|
|
if err := s.finishOperation(tx, logRow, result); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *fifoStockV2Service) resolveRecalculateWarehouseIDs(ctx context.Context, tx *gorm.DB, provided []uint) ([]uint, error) {
|
|
if len(provided) > 0 {
|
|
return provided, nil
|
|
}
|
|
var ids []uint
|
|
err := tx.WithContext(ctx).Table("product_warehouses").Select("id").Order("id ASC").Scan(&ids).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
func (s *fifoStockV2Service) resolveRecalculateGroupCodes(ctx context.Context, tx *gorm.DB, provided []string) ([]string, error) {
|
|
if len(provided) > 0 {
|
|
return provided, nil
|
|
}
|
|
var groups []string
|
|
err := tx.WithContext(ctx).
|
|
Table("fifo_stock_v2_flag_groups").
|
|
Select("code").
|
|
Where("is_active = TRUE").
|
|
Order("priority ASC, code ASC").
|
|
Scan(&groups).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
func (s *fifoStockV2Service) calculateWarehouseAvailableForGroup(
|
|
ctx context.Context,
|
|
tx *gorm.DB,
|
|
warehouseID uint,
|
|
flagGroupCode string,
|
|
asOf *time.Time,
|
|
) (float64, error) {
|
|
rows, err := s.gatherRows(ctx, tx, GatherRequest{
|
|
FlagGroupCode: flagGroupCode,
|
|
Lane: LaneStockable,
|
|
ProductWarehouseID: warehouseID,
|
|
AsOf: asOf,
|
|
Limit: 50000,
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
total := 0.0
|
|
for _, row := range rows {
|
|
total += row.AvailableQuantity
|
|
}
|
|
return total, nil
|
|
}
|
|
|
|
func (s *fifoStockV2Service) loadWarehouseQty(ctx context.Context, tx *gorm.DB, warehouseID uint) (float64, error) {
|
|
type row struct {
|
|
Qty float64 `gorm:"column:qty"`
|
|
}
|
|
var out row
|
|
err := tx.WithContext(ctx).
|
|
Table("product_warehouses").
|
|
Select("COALESCE(qty,0) AS qty").
|
|
Where("id = ?", warehouseID).
|
|
Take(&out).Error
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return out.Qty, nil
|
|
}
|