feat(procurement): 統一採購單按鈕樣式與術語更名為「作廢」,並加強權限控管
This commit is contained in:
@@ -447,11 +447,17 @@ class PurchaseOrderController extends Controller
|
||||
$taxAmount = !is_null($inputTax) ? $inputTax : round($totalAmount * 0.05, 2);
|
||||
$grandTotal = $totalAmount + $taxAmount;
|
||||
|
||||
// 狀態轉移權限檢查
|
||||
if (isset($validated['status']) && $order->status !== $validated['status']) {
|
||||
if (!$order->canTransitionTo($validated['status'])) {
|
||||
return back()->withErrors(['error' => '您沒有權限將狀態從 ' . $order->status . ' 變更為 ' . $validated['status']]);
|
||||
}
|
||||
}
|
||||
// 1. 填充屬性但暫不儲存以捕捉變更
|
||||
$order->fill([
|
||||
'vendor_id' => $validated['vendor_id'],
|
||||
'warehouse_id' => $validated['warehouse_id'],
|
||||
'order_date' => $validated['order_date'], // 新增
|
||||
'order_date' => $validated['order_date'],
|
||||
'expected_delivery_date' => $validated['expected_delivery_date'],
|
||||
'total_amount' => $totalAmount,
|
||||
'tax_amount' => $taxAmount,
|
||||
@@ -460,11 +466,22 @@ class PurchaseOrderController extends Controller
|
||||
'status' => $validated['status'],
|
||||
'invoice_number' => $validated['invoice_number'] ?? null,
|
||||
'invoice_date' => $validated['invoice_date'] ?? null,
|
||||
'invoice_amount' => $validated['invoice_amount'] ?? null,
|
||||
'invoice_amount' => (float) ($validated['invoice_amount'] ?? 0),
|
||||
]);
|
||||
|
||||
// 捕捉變更屬性以進行手動記錄
|
||||
// 捕捉變更屬性
|
||||
$dirty = $order->getDirty();
|
||||
|
||||
// 嚴格權限檢查:如果修改了 status 以外的任何欄位,必須具備編輯權限
|
||||
$otherChanges = array_diff(array_keys($dirty), ['status']);
|
||||
if (!empty($otherChanges)) {
|
||||
$canEdit = auth()->user()->hasRole('super-admin') || auth()->user()->can('purchase_orders.edit');
|
||||
if (!$canEdit) {
|
||||
throw new \Exception('您沒有權限修改採購單的基本內容,僅能執行流程異動(如:送審)。');
|
||||
}
|
||||
}
|
||||
|
||||
// 捕捉舊屬性以進行記錄
|
||||
$oldAttributes = [];
|
||||
$newAttributes = [];
|
||||
|
||||
@@ -657,7 +674,7 @@ class PurchaseOrderController extends Controller
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('purchase-orders.index')->with('success', '採購單已刪除');
|
||||
return redirect()->route('purchase-orders.index')->with('success', '採購單已作廢');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return back()->withErrors(['error' => '刪除失敗:' . $e->getMessage()]);
|
||||
|
||||
@@ -70,4 +70,50 @@ class PurchaseOrder extends Model
|
||||
{
|
||||
return $this->hasMany(PurchaseOrderItem::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查是否可以轉移至新狀態,並驗證權限。
|
||||
*/
|
||||
public function canTransitionTo(string $newStatus, $user = null): bool
|
||||
{
|
||||
$user = $user ?? auth()->user();
|
||||
if (!$user) return false;
|
||||
if ($user->hasRole('super-admin')) return true;
|
||||
|
||||
$currentStatus = $this->status;
|
||||
|
||||
// 定義合法的狀態轉移路徑與所需權限
|
||||
$transitions = [
|
||||
'draft' => [
|
||||
'pending' => 'purchase_orders.view', // 基本檢視者即可送審
|
||||
'cancelled' => 'purchase_orders.cancel',
|
||||
],
|
||||
'pending' => [
|
||||
'approved' => 'purchase_orders.approve',
|
||||
'draft' => 'purchase_orders.approve', // 退回草稿
|
||||
'cancelled' => 'purchase_orders.cancel',
|
||||
],
|
||||
'approved' => [
|
||||
'cancelled' => 'purchase_orders.cancel',
|
||||
'partial' => null, // 系統自動轉移,不需手動權限點
|
||||
],
|
||||
'partial' => [
|
||||
'completed' => null, // 系統自動轉移
|
||||
'closed' => 'purchase_orders.approve', // 手動結案通常需要核准權限
|
||||
'cancelled' => 'purchase_orders.cancel',
|
||||
],
|
||||
];
|
||||
|
||||
if (!isset($transitions[$currentStatus])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists($newStatus, $transitions[$currentStatus])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$requiredPermission = $transitions[$currentStatus][$newStatus];
|
||||
|
||||
return $requiredPermission ? $user->can($requiredPermission) : true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ Route::middleware('auth')->group(function () {
|
||||
Route::get('/purchase-orders/{id}', [PurchaseOrderController::class, 'show'])->name('purchase-orders.show');
|
||||
|
||||
Route::get('/purchase-orders/{id}/edit', [PurchaseOrderController::class, 'edit'])->middleware('permission:purchase_orders.edit')->name('purchase-orders.edit');
|
||||
Route::put('/purchase-orders/{id}', [PurchaseOrderController::class, 'update'])->middleware('permission:purchase_orders.edit')->name('purchase-orders.update');
|
||||
Route::match(['PUT', 'PATCH'], '/purchase-orders/{id}', [PurchaseOrderController::class, 'update'])->name('purchase-orders.update');
|
||||
Route::delete('/purchase-orders/{id}', [PurchaseOrderController::class, 'destroy'])->middleware('permission:purchase_orders.delete')->name('purchase-orders.destroy');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user