| @@ -19,6 +19,8 @@ import { | |||||
| DialogContentText, | DialogContentText, | ||||
| DialogActions, | DialogActions, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { SessionWithTokens } from "@/config/authConfig"; | |||||
| import { useSession } from "next-auth/react"; | |||||
| import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox"; | import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox"; | ||||
| import { useState, useCallback, useEffect } from "react"; | import { useState, useCallback, useEffect } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -33,7 +35,7 @@ import { | |||||
| import { fetchStockTakeSections } from "@/app/api/warehouse/actions"; | import { fetchStockTakeSections } from "@/app/api/warehouse/actions"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
| import { AUTH } from "@/authorities"; | |||||
| const PER_PAGE = 6; | const PER_PAGE = 6; | ||||
| interface PickerCardListProps { | interface PickerCardListProps { | ||||
| @@ -59,6 +61,9 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ | |||||
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| const [stockTakeSessions, setStockTakeSessions] = useState<AllPickedStockTakeListReponse[]>([]); | const [stockTakeSessions, setStockTakeSessions] = useState<AllPickedStockTakeListReponse[]>([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||||
| const abilities = session?.abilities ?? session?.user?.abilities ?? []; | |||||
| const canManageStockTake = abilities.some((a) => a.trim() === AUTH.ADMIN); | |||||
| /** 建立盤點後若仍在 page 0,仍強制重新載入 */ | /** 建立盤點後若仍在 page 0,仍強制重新載入 */ | ||||
| const [listRefreshNonce, setListRefreshNonce] = useState(0); | const [listRefreshNonce, setListRefreshNonce] = useState(0); | ||||
| const [creating, setCreating] = useState(false); | const [creating, setCreating] = useState(false); | ||||
| @@ -303,7 +308,7 @@ const handleResetSearch = () => { | |||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| onClick={() => setOpenConfirmDialog(true)} | onClick={() => setOpenConfirmDialog(true)} | ||||
| disabled={creating} | |||||
| disabled={creating || !canManageStockTake} | |||||
| > | > | ||||
| {creating ? <CircularProgress size={20} /> : t("Create Stock Take for All Sections")} | {creating ? <CircularProgress size={20} /> : t("Create Stock Take for All Sections")} | ||||
| </Button> | </Button> | ||||
| @@ -123,7 +123,7 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({ | |||||
| setLoadingDetails(false); | setLoadingDetails(false); | ||||
| } | } | ||||
| }, [selectedSession, total]); | }, [selectedSession, total]); | ||||
| {/* | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const inputs: Record<number, { firstQty: string; secondQty: string; firstBadQty: string; secondBadQty: string; remark: string }> = {}; | const inputs: Record<number, { firstQty: string; secondQty: string; firstBadQty: string; secondBadQty: string; remark: string }> = {}; | ||||
| inventoryLotDetails.forEach((detail) => { | inventoryLotDetails.forEach((detail) => { | ||||
| @@ -143,7 +143,31 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({ | |||||
| }); | }); | ||||
| setRecordInputs(inputs); | setRecordInputs(inputs); | ||||
| }, [inventoryLotDetails]); | }, [inventoryLotDetails]); | ||||
| */} | |||||
| useEffect(() => { | |||||
| setRecordInputs((prev) => { | |||||
| const next: Record<number, { firstQty: string; secondQty: string; firstBadQty: string; secondBadQty: string; remark: string }> = {}; | |||||
| inventoryLotDetails.forEach((detail) => { | |||||
| const hasServerFirst = detail.firstStockTakeQty != null; | |||||
| const hasServerSecond = detail.secondStockTakeQty != null; | |||||
| const firstTotal = hasServerFirst | |||||
| ? (detail.firstStockTakeQty! + (detail.firstBadQty ?? 0)).toString() | |||||
| : ""; | |||||
| const secondTotal = hasServerSecond | |||||
| ? (detail.secondStockTakeQty! + (detail.secondBadQty ?? 0)).toString() | |||||
| : ""; | |||||
| const existing = prev[detail.id]; | |||||
| next[detail.id] = { | |||||
| firstQty: hasServerFirst ? firstTotal : (existing?.firstQty ?? firstTotal), | |||||
| secondQty: hasServerSecond ? secondTotal : (existing?.secondQty ?? secondTotal), | |||||
| firstBadQty: hasServerFirst ? (detail.firstBadQty?.toString() || "") : (existing?.firstBadQty ?? ""), | |||||
| secondBadQty: hasServerSecond ? (detail.secondBadQty?.toString() || "") : (existing?.secondBadQty ?? ""), | |||||
| remark: hasServerSecond ? (detail.remarks || "") : (existing?.remark ?? detail.remarks ?? ""), | |||||
| }; | |||||
| }); | |||||
| return next; | |||||
| }); | |||||
| }, [inventoryLotDetails]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| loadDetails(page, pageSize); | loadDetails(page, pageSize); | ||||
| }, [page, pageSize, loadDetails]); | }, [page, pageSize, loadDetails]); | ||||
| @@ -93,7 +93,7 @@ export const REPORTS: ReportDefinition[] = [ | |||||
| { label: "物料編號 Item Code", name: "itemCode", type: "text", required: false}, | { label: "物料編號 Item Code", name: "itemCode", type: "text", required: false}, | ||||
| ] | ] | ||||
| }, | }, | ||||
| /* | |||||
| { id: "rep-012", | { id: "rep-012", | ||||
| title: "庫存盤點報告", | title: "庫存盤點報告", | ||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-take-variance`, | apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-take-variance`, | ||||
| @@ -103,7 +103,24 @@ export const REPORTS: ReportDefinition[] = [ | |||||
| { label: "物料編號 Item Code", name: "itemCode", type: "text", required: false}, | { label: "物料編號 Item Code", name: "itemCode", type: "text", required: false}, | ||||
| ] | ] | ||||
| }, | }, | ||||
| */ | |||||
| { | |||||
| id: "rep-012", | |||||
| title: "庫存盤點報告", | |||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-take-variance-v2`, | |||||
| fields: [ | |||||
| { | |||||
| label: "盤點輪次 Stock Take Round", | |||||
| name: "stockTakeRoundId", | |||||
| type: "select", | |||||
| required: true, | |||||
| dynamicOptions: true, | |||||
| dynamicOptionsEndpoint: `${NEXT_PUBLIC_API_URL}/report/stock-take-rounds`, | |||||
| options: [] | |||||
| }, | |||||
| { label: "物料編號 Item Code", name: "itemCode", type: "text", required: false}, | |||||
| ] | |||||
| }, | |||||
| { id: "rep-011", | { id: "rep-011", | ||||
| title: "庫存明細報告", | title: "庫存明細報告", | ||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-ledger`, | apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-ledger`, | ||||