feat: 完成進貨單自動拋轉應付帳款流程與AP介面優化
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 1m8s
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 1m8s
1. 新增 AccountPayable (應付帳款) 模組,包含 Migration、Model、Service 與 Controller 2. 修改 GoodsReceipt (進貨單) 流程,在確認進貨時自動產生對應的應付帳款單 (AP-YYYYMMDD-XX) 3. 實作應付帳款詳細頁面 (Show.tsx),包含發票登記與標記付款功能 4. 修正應付帳款 Show 頁面的排版,將發票資訊套用標準的綠色背景區塊,並調整按鈕位置 5. 更新相關的 Service Provider 與 Routes
This commit is contained in:
@@ -7,8 +7,10 @@ use App\Modules\Inventory\Models\GoodsReceiptItem;
|
||||
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
|
||||
use App\Modules\Procurement\Contracts\ProcurementServiceInterface;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Modules\Inventory\Events\GoodsReceiptApprovedEvent;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class GoodsReceiptService
|
||||
class GoodsReceiptService implements \App\Modules\Inventory\Contracts\GoodsReceiptServiceInterface
|
||||
{
|
||||
protected $inventoryService;
|
||||
protected $procurementService;
|
||||
@@ -22,7 +24,7 @@ class GoodsReceiptService
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new Goods Receipt and process inventory.
|
||||
* Store a new Goods Receipt (Draft state).
|
||||
*
|
||||
* @param array $data
|
||||
* @return GoodsReceipt
|
||||
@@ -34,7 +36,7 @@ class GoodsReceiptService
|
||||
// 1. Generate Code
|
||||
$data['code'] = $this->generateCode($data['received_date']);
|
||||
$data['user_id'] = auth()->id();
|
||||
$data['status'] = 'completed'; // Direct completion for now
|
||||
$data['status'] = GoodsReceipt::STATUS_DRAFT; // 預設草稿
|
||||
|
||||
// 2. Create Header
|
||||
$goodsReceipt = GoodsReceipt::create($data);
|
||||
@@ -52,8 +54,76 @@ class GoodsReceiptService
|
||||
'expiry_date' => $itemData['expiry_date'] ?? null,
|
||||
]);
|
||||
$goodsReceipt->items()->save($grItem);
|
||||
}
|
||||
|
||||
// 4. Update Inventory
|
||||
return $goodsReceipt;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing Goods Receipt.
|
||||
*
|
||||
* @param GoodsReceipt $goodsReceipt
|
||||
* @param array $data
|
||||
* @return GoodsReceipt
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update(GoodsReceipt $goodsReceipt, array $data)
|
||||
{
|
||||
if (!in_array($goodsReceipt->status, [GoodsReceipt::STATUS_DRAFT, GoodsReceipt::STATUS_REJECTED])) {
|
||||
throw new \Exception('只有草稿或被退回的進貨單可以修改。');
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($goodsReceipt, $data) {
|
||||
$goodsReceipt->update([
|
||||
'vendor_id' => $data['vendor_id'] ?? $goodsReceipt->vendor_id,
|
||||
'received_date' => $data['received_date'] ?? $goodsReceipt->received_date,
|
||||
'remarks' => $data['remarks'] ?? $goodsReceipt->remarks,
|
||||
]);
|
||||
|
||||
if (isset($data['items'])) {
|
||||
// Simple strategy: delete existing items and recreate
|
||||
$goodsReceipt->items()->delete();
|
||||
|
||||
foreach ($data['items'] as $itemData) {
|
||||
$grItem = new GoodsReceiptItem([
|
||||
'product_id' => $itemData['product_id'],
|
||||
'purchase_order_item_id' => $itemData['purchase_order_item_id'] ?? null,
|
||||
'quantity_received' => $itemData['quantity_received'],
|
||||
'unit_price' => $itemData['unit_price'],
|
||||
'total_amount' => $itemData['quantity_received'] * $itemData['unit_price'],
|
||||
'batch_number' => $itemData['batch_number'] ?? null,
|
||||
'expiry_date' => $itemData['expiry_date'] ?? null,
|
||||
]);
|
||||
$goodsReceipt->items()->save($grItem);
|
||||
}
|
||||
}
|
||||
|
||||
return $goodsReceipt->fresh('items');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit for audit (Confirm receipt by warehouse staff).
|
||||
* This will increase inventory and update PO.
|
||||
*
|
||||
* @param GoodsReceipt $goodsReceipt
|
||||
* @return GoodsReceipt
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function submit(GoodsReceipt $goodsReceipt)
|
||||
{
|
||||
if (!in_array($goodsReceipt->status, [GoodsReceipt::STATUS_DRAFT, GoodsReceipt::STATUS_REJECTED])) {
|
||||
throw new \Exception('只有草稿或被退回的進貨單可以確認點收。');
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($goodsReceipt) {
|
||||
$goodsReceipt->status = GoodsReceipt::STATUS_COMPLETED;
|
||||
$goodsReceipt->save();
|
||||
|
||||
// Process Inventory and PO updates
|
||||
foreach ($goodsReceipt->items as $grItem) {
|
||||
// 1. Update Inventory
|
||||
$reason = match($goodsReceipt->type) {
|
||||
'standard' => '採購進貨',
|
||||
'miscellaneous' => '雜項入庫',
|
||||
@@ -75,7 +145,7 @@ class GoodsReceiptService
|
||||
'arrival_date' => $goodsReceipt->received_date,
|
||||
]);
|
||||
|
||||
// 5. Update PO if linked and type is standard
|
||||
// 2. Update PO if linked and type is standard
|
||||
if ($goodsReceipt->type === 'standard' && $goodsReceipt->purchase_order_id && $grItem->purchase_order_item_id) {
|
||||
$this->procurementService->updateReceivedQuantity(
|
||||
$grItem->purchase_order_item_id,
|
||||
@@ -84,10 +154,14 @@ class GoodsReceiptService
|
||||
}
|
||||
}
|
||||
|
||||
// Fire event to let Finance module create AP
|
||||
event(new GoodsReceiptApprovedEvent($goodsReceipt->id));
|
||||
|
||||
return $goodsReceipt;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private function generateCode(string $date)
|
||||
{
|
||||
// Format: GR-YYYYMMDD-NN
|
||||
@@ -106,4 +180,22 @@ class GoodsReceiptService
|
||||
|
||||
return $prefix . str_pad($seq, 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取指定的進貨單資訊 (實作 GoodsReceiptServiceInterface)
|
||||
*
|
||||
* @param int $goodsReceiptId
|
||||
* @return array|null
|
||||
*/
|
||||
public function getGoodsReceiptData(int $goodsReceiptId): ?array
|
||||
{
|
||||
$receipt = GoodsReceipt::with('items')->find($goodsReceiptId);
|
||||
|
||||
if (!$receipt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 以陣列形式回傳資料,避免外部模組產生 Model 依賴
|
||||
return $receipt->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user