feat(procurement): 統一採購單按鈕樣式與術語更名為「作廢」,並加強權限控管
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user