[FEAT] 優化庫存分析邏輯,增加銷售 Reference Type 追蹤並修正 InventoryService 閉包變數問題
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 1m20s
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 1m20s
This commit is contained in:
@@ -133,7 +133,10 @@ class SyncOrderAction
|
|||||||
$warehouseId,
|
$warehouseId,
|
||||||
$qty,
|
$qty,
|
||||||
"POS Order: " . $order->external_order_id,
|
"POS Order: " . $order->external_order_id,
|
||||||
true
|
true,
|
||||||
|
null,
|
||||||
|
\App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
$order->id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ class SyncVendingOrderAction
|
|||||||
$warehouseId,
|
$warehouseId,
|
||||||
$qty,
|
$qty,
|
||||||
"Vending Order: " . $order->external_order_id,
|
"Vending Order: " . $order->external_order_id,
|
||||||
true
|
true,
|
||||||
|
null,
|
||||||
|
\App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
$order->id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface InventoryServiceInterface
|
|||||||
* @param string|null $slot
|
* @param string|null $slot
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function decreaseStock(int $productId, int $warehouseId, float $quantity, ?string $reason = null, bool $force = false, ?string $slot = null): void;
|
public function decreaseStock(int $productId, int $warehouseId, float $quantity, ?string $reason = null, bool $force = false, ?string $slot = null, ?string $referenceType = null, $referenceId = null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all active warehouses.
|
* Get all active warehouses.
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ class InventoryService implements InventoryServiceInterface
|
|||||||
return $stock >= $quantity;
|
return $stock >= $quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function decreaseStock(int $productId, int $warehouseId, float $quantity, ?string $reason = null, bool $force = false, ?string $slot = null): void
|
public function decreaseStock(int $productId, int $warehouseId, float $quantity, ?string $reason = null, bool $force = false, ?string $slot = null, ?string $referenceType = null, $referenceId = null): void
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($productId, $warehouseId, $quantity, $reason, $force, $slot) {
|
DB::transaction(function () use ($productId, $warehouseId, $quantity, $reason, $force, $slot, $referenceType, $referenceId) {
|
||||||
$query = Inventory::where('product_id', $productId)
|
$query = Inventory::where('product_id', $productId)
|
||||||
->where('warehouse_id', $warehouseId)
|
->where('warehouse_id', $warehouseId)
|
||||||
->where('quantity', '>', 0);
|
->where('quantity', '>', 0);
|
||||||
@@ -108,7 +108,7 @@ class InventoryService implements InventoryServiceInterface
|
|||||||
if ($remainingToDecrease <= 0) break;
|
if ($remainingToDecrease <= 0) break;
|
||||||
|
|
||||||
$decreaseAmount = min($inventory->quantity, $remainingToDecrease);
|
$decreaseAmount = min($inventory->quantity, $remainingToDecrease);
|
||||||
$this->decreaseInventoryQuantity($inventory->id, $decreaseAmount, $reason);
|
$this->decreaseInventoryQuantity($inventory->id, $decreaseAmount, $reason, $referenceType, $referenceId);
|
||||||
$remainingToDecrease -= $decreaseAmount;
|
$remainingToDecrease -= $decreaseAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ class InventoryService implements InventoryServiceInterface
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->decreaseInventoryQuantity($inventory->id, $remainingToDecrease, $reason);
|
$this->decreaseInventoryQuantity($inventory->id, $remainingToDecrease, $reason, $referenceType, $referenceId);
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception("庫存不足,無法扣除所有請求的數量。");
|
throw new \Exception("庫存不足,無法扣除所有請求的數量。");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ class TurnoverService
|
|||||||
->select('inventories.product_id', DB::raw('ABS(SUM(inventory_transactions.quantity)) as sales_qty_30d'))
|
->select('inventories.product_id', DB::raw('ABS(SUM(inventory_transactions.quantity)) as sales_qty_30d'))
|
||||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||||
->where('inventory_transactions.type', '出庫') // Adjust type as needed based on actual data
|
->where('inventory_transactions.type', '出庫') // Adjust type as needed based on actual data
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereIn('inventory_transactions.reference_type', [
|
||||||
|
\App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
\App\Modules\Sales\Models\SalesImportBatch::class,
|
||||||
|
])->orWhereNull('inventory_transactions.reference_type');
|
||||||
|
})
|
||||||
->where('inventory_transactions.actual_time', '>=', $thirtyDaysAgo)
|
->where('inventory_transactions.actual_time', '>=', $thirtyDaysAgo)
|
||||||
->groupBy('inventories.product_id');
|
->groupBy('inventories.product_id');
|
||||||
|
|
||||||
@@ -87,6 +93,12 @@ class TurnoverService
|
|||||||
->select('inventories.product_id', DB::raw('MAX(actual_time) as last_sale_date'))
|
->select('inventories.product_id', DB::raw('MAX(actual_time) as last_sale_date'))
|
||||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||||
->where('inventory_transactions.type', '出庫')
|
->where('inventory_transactions.type', '出庫')
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereIn('inventory_transactions.reference_type', [
|
||||||
|
\App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
\App\Modules\Sales\Models\SalesImportBatch::class,
|
||||||
|
])->orWhereNull('inventory_transactions.reference_type');
|
||||||
|
})
|
||||||
->groupBy('inventories.product_id');
|
->groupBy('inventories.product_id');
|
||||||
|
|
||||||
if ($warehouseId) {
|
if ($warehouseId) {
|
||||||
@@ -199,6 +211,12 @@ class TurnoverService
|
|||||||
// Get IDs of products sold in last 90 days
|
// Get IDs of products sold in last 90 days
|
||||||
$soldProductIds = InventoryTransaction::query()
|
$soldProductIds = InventoryTransaction::query()
|
||||||
->where('type', '出庫')
|
->where('type', '出庫')
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereIn('reference_type', [
|
||||||
|
\App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
\App\Modules\Sales\Models\SalesImportBatch::class,
|
||||||
|
])->orWhereNull('reference_type');
|
||||||
|
})
|
||||||
->where('actual_time', '>=', $ninetyDaysAgo)
|
->where('actual_time', '>=', $ninetyDaysAgo)
|
||||||
->distinct()
|
->distinct()
|
||||||
->pluck('inventory_id') // Wait, transaction links to inventory, inventory links to product.
|
->pluck('inventory_id') // Wait, transaction links to inventory, inventory links to product.
|
||||||
@@ -214,6 +232,12 @@ class TurnoverService
|
|||||||
$soldProductIdsQuery = DB::table('inventory_transactions')
|
$soldProductIdsQuery = DB::table('inventory_transactions')
|
||||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||||
->where('inventory_transactions.type', '出庫')
|
->where('inventory_transactions.type', '出庫')
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereIn('inventory_transactions.reference_type', [
|
||||||
|
\App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
\App\Modules\Sales\Models\SalesImportBatch::class,
|
||||||
|
])->orWhereNull('inventory_transactions.reference_type');
|
||||||
|
})
|
||||||
->where('inventory_transactions.actual_time', '>=', $ninetyDaysAgo)
|
->where('inventory_transactions.actual_time', '>=', $ninetyDaysAgo)
|
||||||
->select('inventories.product_id')
|
->select('inventories.product_id')
|
||||||
->distinct();
|
->distinct();
|
||||||
@@ -236,6 +260,12 @@ class TurnoverService
|
|||||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||||
->join('products', 'inventories.product_id', '=', 'products.id')
|
->join('products', 'inventories.product_id', '=', 'products.id')
|
||||||
->where('inventory_transactions.type', '出庫')
|
->where('inventory_transactions.type', '出庫')
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereIn('inventory_transactions.reference_type', [
|
||||||
|
\App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
\App\Modules\Sales\Models\SalesImportBatch::class,
|
||||||
|
])->orWhereNull('inventory_transactions.reference_type');
|
||||||
|
})
|
||||||
->where('inventory_transactions.actual_time', '>=', Carbon::now()->subDays($analysisDays))
|
->where('inventory_transactions.actual_time', '>=', Carbon::now()->subDays($analysisDays))
|
||||||
->when($warehouseId, fn($q) => $q->where('inventories.warehouse_id', $warehouseId))
|
->when($warehouseId, fn($q) => $q->where('inventories.warehouse_id', $warehouseId))
|
||||||
->when($categoryId, fn($q) => $q->where('products.category_id', $categoryId))
|
->when($categoryId, fn($q) => $q->where('products.category_id', $categoryId))
|
||||||
|
|||||||
@@ -157,7 +157,9 @@ class SalesImportController extends Controller
|
|||||||
$deduction['quantity'],
|
$deduction['quantity'],
|
||||||
$reason,
|
$reason,
|
||||||
true, // Force deduction
|
true, // Force deduction
|
||||||
$deduction['slot'] // Location/Slot
|
$deduction['slot'], // Location/Slot
|
||||||
|
\App\Modules\Sales\Models\SalesImportBatch::class,
|
||||||
|
$import->id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,9 @@ class PosApiTest extends TestCase
|
|||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
])->postJson('/api/v1/integration/orders', $payload);
|
])->postJson('/api/v1/integration/orders', $payload);
|
||||||
|
|
||||||
|
$response->assertStatus(201)
|
||||||
|
->assertJsonPath('message', 'Order synced and stock deducted successfully');
|
||||||
|
|
||||||
$response->assertStatus(201)
|
$response->assertStatus(201)
|
||||||
->assertJsonPath('message', 'Order synced and stock deducted successfully');
|
->assertJsonPath('message', 'Order synced and stock deducted successfully');
|
||||||
|
|
||||||
@@ -196,6 +199,14 @@ class PosApiTest extends TestCase
|
|||||||
'warehouse_id' => $warehouseId,
|
'warehouse_id' => $warehouseId,
|
||||||
'quantity' => 95,
|
'quantity' => 95,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$order = \App\Modules\Integration\Models\SalesOrder::where('external_order_id', 'ORD-001')->first();
|
||||||
|
$this->assertDatabaseHas('inventory_transactions', [
|
||||||
|
'reference_type' => \App\Modules\Integration\Models\SalesOrder::class,
|
||||||
|
'reference_id' => $order->id,
|
||||||
|
'quantity' => -5,
|
||||||
|
'type' => '出庫',
|
||||||
|
]);
|
||||||
|
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user