feat(procurement): 統一採購單按鈕樣式與術語更名為「作廢」,並加強權限控管
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 1m28s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

This commit is contained in:
2026-02-06 15:32:12 +08:00
parent 70f1709bd0
commit 6bfdd92347
11 changed files with 318 additions and 73 deletions

View File

@@ -2,10 +2,10 @@
* 查看採購單詳情頁面
*/
import { ArrowLeft, ShoppingCart } from "lucide-react";
import { ArrowLeft, ShoppingCart, Send, CheckCircle, XCircle, RotateCcw } from "lucide-react";
import { Button } from "@/Components/ui/button";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, Link } from "@inertiajs/react";
import { Head, Link, useForm, usePage, router } from "@inertiajs/react";
import { StatusProgressBar } from "@/Components/PurchaseOrder/StatusProgressBar";
import PurchaseOrderStatusBadge from "@/Components/PurchaseOrder/PurchaseOrderStatusBadge";
import CopyButton from "@/Components/shared/CopyButton";
@@ -13,6 +13,8 @@ import { PurchaseOrderItemsTable } from "@/Components/PurchaseOrder/PurchaseOrde
import type { PurchaseOrder } from "@/types/purchase-order";
import { formatCurrency, formatDateTime } from "@/utils/format";
import { getShowBreadcrumbs } from "@/utils/breadcrumb";
import { toast } from "sonner";
import { PageProps } from "@/types/global";
interface Props {
order: PurchaseOrder;
@@ -44,11 +46,6 @@ export default function ViewPurchaseOrderPage({ order }: Props) {
<p className="text-gray-500 mt-1">{order.poNumber}</p>
</div>
<div className="flex items-center gap-3">
<Link href={`/purchase-orders/${order.id}/edit`}>
<Button variant="outline" className="button-outlined-primary">
</Button>
</Link>
<PurchaseOrderStatusBadge status={order.status} />
</div>
</div>
@@ -171,9 +168,111 @@ export default function ViewPurchaseOrderPage({ order }: Props) {
</div>
</div>
</div>
{/* 操作按鈕 (底部) */}
<div className="flex justify-end pt-4">
<PurchaseOrderActions order={order} />
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
function PurchaseOrderActions({ order }: { order: PurchaseOrder }) {
const { auth } = usePage<PageProps>().props;
const permissions = auth.user?.permissions || [];
const { processing } = useForm({
status: order.status,
});
const handleUpdateStatus = (newStatus: string, actionName: string) => {
const formData = {
vendor_id: order.supplierId,
warehouse_id: order.warehouse_id,
order_date: order.orderDate,
expected_delivery_date: order.expectedDate ? new Date(order.expectedDate).toISOString().split('T')[0] : null,
items: order.items.map((item: any) => ({
productId: item.productId,
quantity: item.quantity,
unitId: item.unitId,
subtotal: item.subtotal,
})),
tax_amount: order.taxAmount,
status: newStatus,
remark: order.remark || "",
};
router.patch(route('purchase-orders.update', order.id), formData, {
onSuccess: () => toast.success(`採購單已${actionName === '取消' ? '作廢' : actionName}`),
onError: (errors: any) => {
console.error("Status Update Error:", errors);
toast.error(errors.error || "操作失敗");
}
});
};
// 權限判斷 (包含超級管理員檢查)
const isSuperAdmin = auth.user?.roles?.some((r: any) => r.name === 'super-admin');
const canApprove = isSuperAdmin || permissions.includes('purchase_orders.approve');
const canCancel = isSuperAdmin || permissions.includes('purchase_orders.cancel');
const canEdit = isSuperAdmin || permissions.includes('purchase_orders.edit');
const canView = isSuperAdmin || permissions.includes('purchase_orders.view');
// 送審權限:擁有檢視或編輯權限的人都可以送審
const canSubmit = canEdit || canView;
return (
<div className="flex items-center gap-4">
{['draft', 'pending', 'approved'].includes(order.status) && canCancel && (
<Button
onClick={() => handleUpdateStatus('cancelled', '作廢')}
disabled={processing}
variant="outline"
size="xl"
className="button-outlined-error shadow-red-200/20 border-red-600 text-red-600 hover:bg-red-50"
>
<XCircle className="h-5 w-5" />
</Button>
)}
{order.status === 'pending' && canApprove && (
<Button
onClick={() => handleUpdateStatus('draft', '退回')}
disabled={processing}
variant="outline"
size="xl"
className="button-outlined-warning shadow-amber-200/20"
>
<RotateCcw className="h-5 w-5" /> 退
</Button>
)}
<div className="flex-1" />
{order.status === 'draft' && canSubmit && (
<Button
onClick={() => handleUpdateStatus('pending', '送出審核')}
disabled={processing}
size="xl"
className="button-filled-primary shadow-primary/20"
>
<Send className="h-5 w-5" />
</Button>
)}
{order.status === 'pending' && canApprove && (
<Button
onClick={() => handleUpdateStatus('approved', '核准')}
disabled={processing}
size="xl"
className="button-filled-primary shadow-primary/20"
>
<CheckCircle className="h-5 w-5" />
</Button>
)}
</div>
);
}