feat: 補齊生產管理與進貨單權限、功能實作及 UI 優化
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 50s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

This commit is contained in:
2026-01-27 17:40:56 +08:00
parent 95d8dc2e84
commit 4c959efc8b
9 changed files with 100 additions and 15 deletions

View File

@@ -23,6 +23,11 @@ class ActivityLogController extends Controller
'App\Modules\Inventory\Models\Warehouse' => '倉庫',
'App\Modules\Inventory\Models\Inventory' => '庫存',
'App\Modules\Finance\Models\UtilityFee' => '公共事業費',
'App\Modules\Inventory\Models\GoodsReceipt' => '進貨單',
'App\Modules\Production\Models\ProductionOrder' => '生產工單',
'App\Modules\Production\Models\Recipe' => '生產配方',
'App\Modules\Production\Models\RecipeItem' => '配方品項',
'App\Modules\Production\Models\ProductionOrderItem' => '工單品項',
];
}

View File

@@ -178,8 +178,12 @@ class RoleController extends Controller
'inventory' => '庫存管理',
'vendors' => '廠商資料管理',
'purchase_orders' => '採購單管理',
'goods_receipts' => '進貨單管理',
'production_orders' => '生產工單管理',
'recipes' => '配方管理',
'users' => '使用者管理',
'roles' => '角色與權限',
'system' => '系統管理',
'utility_fees' => '公共事業費管理',
'accounting' => '會計報表',
];

View File

@@ -228,4 +228,21 @@ class GoodsReceiptController extends Controller
return response()->json($vendors);
}
/**
* 刪除進貨單
*/
public function destroy(GoodsReceipt $goodsReceipt)
{
// 只有有權限的人可以刪除
if (!auth()->user()->can('goods_receipts.delete')) {
return redirect()->back()->with('error', '您沒有權限刪除進貨單');
}
// 簡單刪除邏輯:刪除進貨單(品項由資料庫級聯刪除或手動處理)
// 注意:實務上可能需要處理已入庫的庫存回滾,但在這個簡易 ERP 中通常是行政刪除
$goodsReceipt->delete();
return redirect()->route('goods-receipts.index')->with('success', '進貨單已刪除');
}
}

View File

@@ -37,10 +37,25 @@ class PermissionSeeder extends Seeder
'inventory.view_cost', // 查看成本與價值
'inventory.adjust',
'inventory.transfer',
'inventory.delete',
// 進貨單管理
'goods_receipts.view',
'goods_receipts.create',
'goods_receipts.edit',
'goods_receipts.delete',
// 生產工單管理
'production_orders.view',
'production_orders.create',
'production_orders.edit',
'production_orders.delete',
// 配方管理
'recipes.view',
'recipes.create',
'recipes.edit',
'recipes.delete',
// 供應商管理
'vendors.view',
@@ -101,8 +116,10 @@ class PermissionSeeder extends Seeder
'products.view', 'products.create', 'products.edit', 'products.delete',
'purchase_orders.view', 'purchase_orders.create', 'purchase_orders.edit',
'purchase_orders.delete', 'purchase_orders.publish',
'inventory.view', 'inventory.view_cost', 'inventory.adjust', 'inventory.transfer',
'goods_receipts.view', 'goods_receipts.create',
'inventory.view', 'inventory.view_cost', 'inventory.adjust', 'inventory.transfer', 'inventory.delete',
'goods_receipts.view', 'goods_receipts.create', 'goods_receipts.edit', 'goods_receipts.delete',
'production_orders.view', 'production_orders.create', 'production_orders.edit', 'production_orders.delete',
'recipes.view', 'recipes.create', 'recipes.edit', 'recipes.delete',
'vendors.view', 'vendors.create', 'vendors.edit', 'vendors.delete',
'warehouses.view', 'warehouses.create', 'warehouses.edit', 'warehouses.delete',
'users.view', 'users.create', 'users.edit',
@@ -115,8 +132,9 @@ class PermissionSeeder extends Seeder
// warehouse-manager 管理庫存與倉庫
$warehouseManager->givePermissionTo([
'products.view',
'inventory.view', 'inventory.adjust', 'inventory.transfer',
'goods_receipts.view', 'goods_receipts.create',
'inventory.view', 'inventory.adjust', 'inventory.transfer', 'inventory.delete',
'goods_receipts.view', 'goods_receipts.create', 'goods_receipts.edit', 'goods_receipts.delete',
'production_orders.view', 'production_orders.create', 'production_orders.edit',
'warehouses.view', 'warehouses.create', 'warehouses.edit',
]);

View File

@@ -114,10 +114,24 @@ const fieldLabels: Record<string, string> = {
transaction_date: '費用日期',
category: '費用類別',
amount: '金額',
// 進貨單欄位
gr_number: '進貨單號',
received_date: '入庫日期',
type: '入庫類型',
remarks: '備註',
// 生產管理欄位
production_number: '工單編號',
production_date: '生產日期',
actual_quantity: '實際產量',
consumption_status: '物料消耗狀態',
recipe_id: '生產配方',
recipe_name: '配方名稱',
yield_quantity: '預期產量',
};
// 採購單狀態對照表
// 狀態翻譯對照表
const statusMap: Record<string, string> = {
// 採購單狀態
draft: '草稿',
pending: '待審核',
approved: '已核准',
@@ -125,6 +139,10 @@ const statusMap: Record<string, string> = {
received: '已收貨',
cancelled: '已取消',
completed: '已完成',
// 生產工單狀態
planned: '已計畫',
in_progress: '生產中',
// completed 已定義
};
// 庫存品質狀態對照表

View File

@@ -71,10 +71,13 @@ export default function RoleCreate({ groupedPermissions }: Props) {
'edit': '編輯',
'delete': '刪除',
'publish': '發布',
'adjust': '新增 / 調整',
'adjust': '調整',
'transfer': '調撥',
'safety_stock': '安全庫存設定',
'export': '匯出',
'complete': '完成',
'view_cost': '檢視成本',
'view_logs': '檢視日誌',
};
return map[action] || action;

View File

@@ -78,10 +78,13 @@ export default function RoleEdit({ role, groupedPermissions, currentPermissions
'edit': '編輯',
'delete': '刪除',
'publish': '發布',
'adjust': '新增 / 調整',
'adjust': '調整',
'transfer': '調撥',
'safety_stock': '安全庫存設定',
'export': '匯出',
'complete': '完成',
'view_cost': '檢視成本',
'view_logs': '檢視日誌',
};
return map[action] || action;

View File

@@ -3,7 +3,7 @@
*/
import { useState, useEffect } from "react";
import { Plus, Factory, Search, RotateCcw, Eye, Pencil } from 'lucide-react';
import { Plus, Factory, Search, RotateCcw, Eye, Pencil, Trash2 } from 'lucide-react';
import { Button } from "@/Components/ui/button";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, router, Link } from "@inertiajs/react";
@@ -266,6 +266,21 @@ export default function ProductionIndex({ productionOrders, filters }: Props) {
</Button>
</Link>
</Can>
<Can permission="production_orders.delete">
<Button
variant="outline"
size="sm"
className="button-outlined-error"
title="刪除"
onClick={() => {
if (confirm('確定要刪除此生產工單嗎?')) {
router.delete(route('production-orders.destroy', order.id));
}
}}
>
<Trash2 className="h-4 w-4" />
</Button>
</Can>
</div>
</TableCell>
</TableRow>
@@ -298,6 +313,6 @@ export default function ProductionIndex({ productionOrders, filters }: Props) {
</div>
</div>
</div>
</AuthenticatedLayout>
</AuthenticatedLayout >
);
}

View File

@@ -110,12 +110,14 @@ export default function RecipeIndex({ recipes, filters }: Props) {
</p>
</div>
<div className="flex gap-2">
<Link href={route('recipes.create')}>
<Button className="gap-2 button-filled-primary">
<Plus className="h-4 w-4" />
</Button>
</Link>
<Can permission="recipes.create">
<Link href={route('recipes.create')}>
<Button className="gap-2 button-filled-primary">
<Plus className="h-4 w-4" />
</Button>
</Link>
</Can>
</div>
</div>