|
|
|
@@ -17,6 +17,7 @@ import { |
|
|
|
TextField, |
|
|
|
Radio, |
|
|
|
TablePagination, |
|
|
|
TableSortLabel, |
|
|
|
} from "@mui/material"; |
|
|
|
import { useState, useCallback, useEffect, useMemo } from "react"; |
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
@@ -45,6 +46,25 @@ interface ApproverStockTakeAllProps { |
|
|
|
|
|
|
|
type QtySelectionType = "first" | "second" | "approver"; |
|
|
|
|
|
|
|
type ApprovedSortKey = |
|
|
|
| "stockTakeEndTime" |
|
|
|
| "stockTakeSection" |
|
|
|
| "item" |
|
|
|
| "stockTakerName" |
|
|
|
| "variance"; |
|
|
|
|
|
|
|
function parseDateTimeMs( |
|
|
|
v: string | string[] | null | undefined |
|
|
|
): number { |
|
|
|
if (v == null) return 0; |
|
|
|
if (Array.isArray(v)) { |
|
|
|
const arr = v as unknown as number[]; |
|
|
|
const [y, m, d, h = 0, min = 0, s = 0] = arr; |
|
|
|
return dayjs(`${y}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")} ${h}:${min}:${s}`).valueOf(); |
|
|
|
} |
|
|
|
return dayjs(v as string).valueOf(); |
|
|
|
} |
|
|
|
|
|
|
|
const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ |
|
|
|
selectedSession, |
|
|
|
mode, |
|
|
|
@@ -66,6 +86,8 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ |
|
|
|
const [page, setPage] = useState(0); |
|
|
|
const [pageSize, setPageSize] = useState<number | string>(50); |
|
|
|
const [total, setTotal] = useState(0); |
|
|
|
const [approvedSortKey, setApprovedSortKey] = useState<ApprovedSortKey | null>(null); |
|
|
|
const [approvedSortDir, setApprovedSortDir] = useState<"asc" | "desc">("asc"); |
|
|
|
|
|
|
|
const currentUserId = session?.id ? parseInt(session.id) : undefined; |
|
|
|
|
|
|
|
@@ -200,12 +222,62 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ |
|
|
|
]); |
|
|
|
|
|
|
|
const sortedDetails = useMemo(() => { |
|
|
|
return [...filteredDetails].sort((a, b) => { |
|
|
|
const sectionA = (a.stockTakeSection || "").trim(); |
|
|
|
const sectionB = (b.stockTakeSection || "").trim(); |
|
|
|
return sectionA.localeCompare(sectionB, undefined, { numeric: true, sensitivity: "base" }); |
|
|
|
const list = [...filteredDetails]; |
|
|
|
if (mode !== "approved") { |
|
|
|
return list.sort((a, b) => |
|
|
|
(a.stockTakeSection || "").localeCompare(b.stockTakeSection || "", undefined, { |
|
|
|
numeric: true, |
|
|
|
sensitivity: "base", |
|
|
|
}) |
|
|
|
); |
|
|
|
} |
|
|
|
const key = approvedSortKey ?? "stockTakeSection"; |
|
|
|
const mul = approvedSortDir === "asc" ? 1 : -1; |
|
|
|
return list.sort((a, b) => { |
|
|
|
let cmp = 0; |
|
|
|
switch (key) { |
|
|
|
case "stockTakeEndTime": |
|
|
|
cmp = |
|
|
|
parseDateTimeMs(a.approverTime ?? a.stockTakeEndTime) - |
|
|
|
parseDateTimeMs(b.approverTime ?? b.stockTakeEndTime); |
|
|
|
break; |
|
|
|
case "stockTakeSection": |
|
|
|
cmp = (a.stockTakeSection || "").localeCompare(b.stockTakeSection || "", undefined, { |
|
|
|
numeric: true, |
|
|
|
sensitivity: "base", |
|
|
|
}); |
|
|
|
break; |
|
|
|
case "item": |
|
|
|
cmp = `${a.itemCode || ""} ${a.itemName || ""}`.localeCompare( |
|
|
|
`${b.itemCode || ""} ${b.itemName || ""}`, |
|
|
|
undefined, |
|
|
|
{ numeric: true, sensitivity: "base" } |
|
|
|
); |
|
|
|
break; |
|
|
|
case "stockTakerName": |
|
|
|
cmp = (a.stockTakerName || "").localeCompare(b.stockTakerName || "", undefined, { |
|
|
|
numeric: true, |
|
|
|
sensitivity: "base", |
|
|
|
}); |
|
|
|
break; |
|
|
|
case "variance": |
|
|
|
cmp = Number(a.varianceQty ?? 0) - Number(b.varianceQty ?? 0); |
|
|
|
break; |
|
|
|
default: |
|
|
|
cmp = 0; |
|
|
|
} |
|
|
|
return cmp * mul; |
|
|
|
}); |
|
|
|
}, [filteredDetails]); |
|
|
|
}, [filteredDetails, mode, approvedSortKey, approvedSortDir]); |
|
|
|
|
|
|
|
const handleApprovedSort = useCallback((property: ApprovedSortKey) => { |
|
|
|
if (approvedSortKey === property) { |
|
|
|
setApprovedSortDir((d) => (d === "asc" ? "desc" : "asc")); |
|
|
|
} else { |
|
|
|
setApprovedSortKey(property); |
|
|
|
setApprovedSortDir("asc"); |
|
|
|
} |
|
|
|
}, [approvedSortKey]); |
|
|
|
|
|
|
|
const handleSaveApproverStockTake = useCallback( |
|
|
|
async (detail: InventoryLotDetailResponse) => { |
|
|
|
@@ -410,6 +482,12 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ |
|
|
|
[inventoryLotDetails] |
|
|
|
); |
|
|
|
|
|
|
|
const formatRecordEndTime = (detail: InventoryLotDetailResponse) => { |
|
|
|
const ms = parseDateTimeMs(detail.approverTime ?? detail.stockTakeEndTime); |
|
|
|
if (!ms) return "-"; |
|
|
|
return dayjs(ms).format("YYYY-MM-DD HH:mm"); |
|
|
|
}; |
|
|
|
|
|
|
|
return ( |
|
|
|
<Box> |
|
|
|
{onBack && ( |
|
|
|
@@ -482,23 +560,110 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ |
|
|
|
<Table> |
|
|
|
<TableHead> |
|
|
|
<TableRow> |
|
|
|
<TableCell>{t("Warehouse Location")}</TableCell> |
|
|
|
<TableCell>{t("Item-lotNo-ExpiryDate")}</TableCell> |
|
|
|
{mode === "approved" && ( |
|
|
|
<TableCell |
|
|
|
sortDirection={ |
|
|
|
approvedSortKey === "stockTakeEndTime" ? approvedSortDir : false |
|
|
|
} |
|
|
|
> |
|
|
|
<TableSortLabel |
|
|
|
active={approvedSortKey === "stockTakeEndTime"} |
|
|
|
direction={ |
|
|
|
approvedSortKey === "stockTakeEndTime" ? approvedSortDir : "asc" |
|
|
|
} |
|
|
|
onClick={() => handleApprovedSort("stockTakeEndTime")} |
|
|
|
> |
|
|
|
{t("Approver Time")} |
|
|
|
</TableSortLabel> |
|
|
|
</TableCell> |
|
|
|
)} |
|
|
|
<TableCell |
|
|
|
sortDirection={ |
|
|
|
mode === "approved" && (approvedSortKey === "stockTakeSection" || approvedSortKey === null) |
|
|
|
? approvedSortDir |
|
|
|
: false |
|
|
|
} |
|
|
|
> |
|
|
|
{mode === "approved" ? ( |
|
|
|
<TableSortLabel |
|
|
|
active={ |
|
|
|
approvedSortKey === "stockTakeSection" || approvedSortKey === null |
|
|
|
} |
|
|
|
direction={approvedSortDir} |
|
|
|
onClick={() => handleApprovedSort("stockTakeSection")} |
|
|
|
> |
|
|
|
{t("Warehouse Location")} |
|
|
|
</TableSortLabel> |
|
|
|
) : ( |
|
|
|
t("Warehouse Location") |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
<TableCell |
|
|
|
sortDirection={ |
|
|
|
mode === "approved" && approvedSortKey === "item" ? approvedSortDir : false |
|
|
|
} |
|
|
|
> |
|
|
|
{mode === "approved" ? ( |
|
|
|
<TableSortLabel |
|
|
|
active={approvedSortKey === "item"} |
|
|
|
direction={approvedSortKey === "item" ? approvedSortDir : "asc"} |
|
|
|
onClick={() => handleApprovedSort("item")} |
|
|
|
> |
|
|
|
{t("Item-lotNo-ExpiryDate")} |
|
|
|
</TableSortLabel> |
|
|
|
) : ( |
|
|
|
t("Item-lotNo-ExpiryDate") |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
<TableCell>{t("UOM")}</TableCell> |
|
|
|
<TableCell> |
|
|
|
{t("Stock Take Qty(include Bad Qty)= Available Qty")} |
|
|
|
</TableCell> |
|
|
|
|
|
|
|
{mode === "approved" && ( |
|
|
|
<TableCell |
|
|
|
sortDirection={ |
|
|
|
approvedSortKey === "variance" ? approvedSortDir : false |
|
|
|
} |
|
|
|
> |
|
|
|
<TableSortLabel |
|
|
|
active={approvedSortKey === "variance"} |
|
|
|
direction={approvedSortKey === "variance" ? approvedSortDir : "asc"} |
|
|
|
onClick={() => handleApprovedSort("variance")} |
|
|
|
> |
|
|
|
{t("Variance")} |
|
|
|
</TableSortLabel> |
|
|
|
</TableCell> |
|
|
|
)} |
|
|
|
<TableCell>{t("Remark")}</TableCell> |
|
|
|
<TableCell>{t("Record Status")}</TableCell> |
|
|
|
<TableCell>{t("Picker")}</TableCell> |
|
|
|
<TableCell |
|
|
|
sortDirection={ |
|
|
|
mode === "approved" && approvedSortKey === "stockTakerName" |
|
|
|
? approvedSortDir |
|
|
|
: false |
|
|
|
} |
|
|
|
> |
|
|
|
{mode === "approved" ? ( |
|
|
|
<TableSortLabel |
|
|
|
active={approvedSortKey === "stockTakerName"} |
|
|
|
direction={ |
|
|
|
approvedSortKey === "stockTakerName" ? approvedSortDir : "asc" |
|
|
|
} |
|
|
|
onClick={() => handleApprovedSort("stockTakerName")} |
|
|
|
> |
|
|
|
{t("Picker")} |
|
|
|
</TableSortLabel> |
|
|
|
) : ( |
|
|
|
t("Picker") |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
<TableCell>{t("Action")}</TableCell> |
|
|
|
</TableRow> |
|
|
|
</TableHead> |
|
|
|
<TableBody> |
|
|
|
{sortedDetails.length === 0 ? ( |
|
|
|
<TableRow> |
|
|
|
<TableCell colSpan={8} align="center"> |
|
|
|
<TableCell colSpan={mode === "approved" ? 10 : 8} align="center"> |
|
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
|
{t("No data")} |
|
|
|
</Typography> |
|
|
|
@@ -515,6 +680,16 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ |
|
|
|
|
|
|
|
return ( |
|
|
|
<TableRow key={detail.id}> |
|
|
|
{mode === "approved" && ( |
|
|
|
<TableCell> |
|
|
|
<Stack spacing={0.5}> |
|
|
|
|
|
|
|
<Typography variant="caption" color="text.secondary"> |
|
|
|
{formatRecordEndTime(detail)} |
|
|
|
</Typography> |
|
|
|
</Stack> |
|
|
|
</TableCell> |
|
|
|
)} |
|
|
|
<TableCell> |
|
|
|
<Stack spacing={0.5}> |
|
|
|
<Typography variant="body2"><strong>{detail.stockTakeSection || "-"} {detail.stockTakeSectionDescription || "-"}</strong></Typography> |
|
|
|
@@ -759,6 +934,14 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
|
|
|
|
{mode === "approved" && ( |
|
|
|
<TableCell> |
|
|
|
<Typography variant="body2"> |
|
|
|
{formatNumber(detail.varianceQty)} |
|
|
|
</Typography> |
|
|
|
</TableCell> |
|
|
|
)} |
|
|
|
|
|
|
|
<TableCell> |
|
|
|
<Typography variant="body2"> |
|
|
|
{detail.remarks || "-"} |
|
|
|
|