[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,
|
||||
$qty,
|
||||
"POS Order: " . $order->external_order_id,
|
||||
true
|
||||
true,
|
||||
null,
|
||||
\App\Modules\Integration\Models\SalesOrder::class,
|
||||
$order->id
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,10 @@ class SyncVendingOrderAction
|
||||
$warehouseId,
|
||||
$qty,
|
||||
"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
|
||||
* @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.
|
||||
|
||||
@@ -87,9 +87,9 @@ class InventoryService implements InventoryServiceInterface
|
||||
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)
|
||||
->where('warehouse_id', $warehouseId)
|
||||
->where('quantity', '>', 0);
|
||||
@@ -108,7 +108,7 @@ class InventoryService implements InventoryServiceInterface
|
||||
if ($remainingToDecrease <= 0) break;
|
||||
|
||||
$decreaseAmount = min($inventory->quantity, $remainingToDecrease);
|
||||
$this->decreaseInventoryQuantity($inventory->id, $decreaseAmount, $reason);
|
||||
$this->decreaseInventoryQuantity($inventory->id, $decreaseAmount, $reason, $referenceType, $referenceId);
|
||||
$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 {
|
||||
throw new \Exception("庫存不足,無法扣除所有請求的數量。");
|
||||
}
|
||||
|
||||
@@ -69,6 +69,12 @@ class TurnoverService
|
||||
->select('inventories.product_id', DB::raw('ABS(SUM(inventory_transactions.quantity)) as sales_qty_30d'))
|
||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||
->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)
|
||||
->groupBy('inventories.product_id');
|
||||
|
||||
@@ -87,6 +93,12 @@ class TurnoverService
|
||||
->select('inventories.product_id', DB::raw('MAX(actual_time) as last_sale_date'))
|
||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||
->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');
|
||||
|
||||
if ($warehouseId) {
|
||||
@@ -199,6 +211,12 @@ class TurnoverService
|
||||
// Get IDs of products sold in last 90 days
|
||||
$soldProductIds = InventoryTransaction::query()
|
||||
->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)
|
||||
->distinct()
|
||||
->pluck('inventory_id') // Wait, transaction links to inventory, inventory links to product.
|
||||
@@ -214,6 +232,12 @@ class TurnoverService
|
||||
$soldProductIdsQuery = DB::table('inventory_transactions')
|
||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||
->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)
|
||||
->select('inventories.product_id')
|
||||
->distinct();
|
||||
@@ -236,6 +260,12 @@ class TurnoverService
|
||||
->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id')
|
||||
->join('products', 'inventories.product_id', '=', 'products.id')
|
||||
->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))
|
||||
->when($warehouseId, fn($q) => $q->where('inventories.warehouse_id', $warehouseId))
|
||||
->when($categoryId, fn($q) => $q->where('products.category_id', $categoryId))
|
||||
|
||||
@@ -157,7 +157,9 @@ class SalesImportController extends Controller
|
||||
$deduction['quantity'],
|
||||
$reason,
|
||||
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',
|
||||
])->postJson('/api/v1/integration/orders', $payload);
|
||||
|
||||
$response->assertStatus(201)
|
||||
->assertJsonPath('message', 'Order synced and stock deducted successfully');
|
||||
|
||||
$response->assertStatus(201)
|
||||
->assertJsonPath('message', 'Order synced and stock deducted successfully');
|
||||
|
||||
@@ -197,6 +200,14 @@ class PosApiTest extends TestCase
|
||||
'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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user