Compare commits
2 Commits
197df3bec4
...
adf13410ba
| Author | SHA1 | Date | |
|---|---|---|---|
| adf13410ba | |||
| d52a215916 |
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Star ERP 系統提供外部整合 API (Integration API) 供電商前台、POS
|
|||||||
|
|
||||||
| 參數名稱 | 類型 | 必填 | 說明 |
|
| 參數名稱 | 類型 | 必填 | 說明 |
|
||||||
| :--- | :--- | :---: | :--- |
|
| :--- | :--- | :---: | :--- |
|
||||||
| `warehouse_code` | String | **是** | 要查詢的倉庫代碼 (例如:`STORE-001`) |
|
| `warehouse_code` | String | **是** | 要查詢的倉庫代碼 (例如:`STORE-001`,測試可使用預設建立之 `api-test-01`) |
|
||||||
|
|
||||||
### Response
|
### Response
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ Star ERP 系統提供外部整合 API (Integration API) 供電商前台、POS
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"warehouse_code": "STORE-001",
|
"warehouse_code": "api-test-01",
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"external_pos_id": "POS-ITEM-001",
|
"external_pos_id": "POS-ITEM-001",
|
||||||
@@ -136,7 +136,7 @@ Star ERP 系統提供外部整合 API (Integration API) 供電商前台、POS
|
|||||||
| 欄位名稱 | 型態 | 必填 | 說明 |
|
| 欄位名稱 | 型態 | 必填 | 說明 |
|
||||||
| :--- | :--- | :---: | :--- |
|
| :--- | :--- | :---: | :--- |
|
||||||
| `external_order_id` | String | **是** | 第三方系統中的唯一訂單編號,不可重複 (Unique) |
|
| `external_order_id` | String | **是** | 第三方系統中的唯一訂單編號,不可重複 (Unique) |
|
||||||
| `warehouse_code` | String | **是** | 指定扣除庫存的倉庫代碼 (例如:`STORE-001`)。若找不到對應倉庫將直接拒絕請求 |
|
| `warehouse_code` | String | **是** | 指定扣除庫存的倉庫代碼 (例如:`api-test-01` 測試倉)。若找不到對應倉庫將直接拒絕請求 |
|
||||||
| `payment_method` | String | 否 | 付款方式,僅接受:`cash`, `credit_card`, `line_pay`, `ecpay`, `transfer`, `other`。預設為 `cash` |
|
| `payment_method` | String | 否 | 付款方式,僅接受:`cash`, `credit_card`, `line_pay`, `ecpay`, `transfer`, `other`。預設為 `cash` |
|
||||||
| `sold_at` | String(Date) | 否 | 交易發生時間,預設為當下時間 (格式: YYYY-MM-DD HH:mm:ss) |
|
| `sold_at` | String(Date) | 否 | 交易發生時間,預設為當下時間 (格式: YYYY-MM-DD HH:mm:ss) |
|
||||||
| `items` | Array | **是** | 訂單明細陣列,至少需包含一筆商品 |
|
| `items` | Array | **是** | 訂單明細陣列,至少需包含一筆商品 |
|
||||||
@@ -153,7 +153,7 @@ Star ERP 系統提供外部整合 API (Integration API) 供電商前台、POS
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"external_order_id": "ORD-20231026-0001",
|
"external_order_id": "ORD-20231026-0001",
|
||||||
"warehouse_code": "STORE-001",
|
"warehouse_code": "api-test-01",
|
||||||
"payment_method": "credit_card",
|
"payment_method": "credit_card",
|
||||||
"sold_at": "2023-10-26 14:30:00",
|
"sold_at": "2023-10-26 14:30:00",
|
||||||
"items": [
|
"items": [
|
||||||
|
|||||||
@@ -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