|
|
@@ -35,6 +35,7 @@ import { |
|
|
Accordion, |
|
|
Accordion, |
|
|
AccordionSummary, |
|
|
AccordionSummary, |
|
|
AccordionDetails, |
|
|
AccordionDetails, |
|
|
|
|
|
Alert, |
|
|
} from "@mui/material"; |
|
|
} from "@mui/material"; |
|
|
import SearchIcon from "@mui/icons-material/Search"; |
|
|
import SearchIcon from "@mui/icons-material/Search"; |
|
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; |
|
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; |
|
|
@@ -53,7 +54,9 @@ import { |
|
|
getLatestStockTakeRoundMeta, |
|
|
getLatestStockTakeRoundMeta, |
|
|
getBusyStockTakeSections, |
|
|
getBusyStockTakeSections, |
|
|
getAvailableLotsForStockTakeCreate, |
|
|
getAvailableLotsForStockTakeCreate, |
|
|
|
|
|
getSectionCreateItemOverlaps, |
|
|
searchItemsForStockTakeCreate, |
|
|
searchItemsForStockTakeCreate, |
|
|
|
|
|
type SectionCreateItemOverlap, |
|
|
type StockTakeCreateLotOption, |
|
|
type StockTakeCreateLotOption, |
|
|
} from "@/app/api/stockTake/actions"; |
|
|
} from "@/app/api/stockTake/actions"; |
|
|
import { fetchStockTakeSections } from "@/app/api/warehouse/actions"; |
|
|
import { fetchStockTakeSections } from "@/app/api/warehouse/actions"; |
|
|
@@ -357,6 +360,8 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ |
|
|
const [createDialogSelectedLotLineIds, setCreateDialogSelectedLotLineIds] = useState<number[]>([]); |
|
|
const [createDialogSelectedLotLineIds, setCreateDialogSelectedLotLineIds] = useState<number[]>([]); |
|
|
const [createDialogLotsLoading, setCreateDialogLotsLoading] = useState(false); |
|
|
const [createDialogLotsLoading, setCreateDialogLotsLoading] = useState(false); |
|
|
const [createDialogItemSearching, setCreateDialogItemSearching] = useState(false); |
|
|
const [createDialogItemSearching, setCreateDialogItemSearching] = useState(false); |
|
|
|
|
|
const [sectionItemOverlaps, setSectionItemOverlaps] = useState<SectionCreateItemOverlap[]>([]); |
|
|
|
|
|
const [sectionOverlapsLoading, setSectionOverlapsLoading] = useState(false); |
|
|
const [sectionsLoading, setSectionsLoading] = useState(false); |
|
|
const [sectionsLoading, setSectionsLoading] = useState(false); |
|
|
const [stockTakeSectionRows, setStockTakeSectionRows] = useState<StockTakeSectionInfo[]>([]); |
|
|
const [stockTakeSectionRows, setStockTakeSectionRows] = useState<StockTakeSectionInfo[]>([]); |
|
|
const createStockTakeInFlightRef = useRef(false); |
|
|
const createStockTakeInFlightRef = useRef(false); |
|
|
@@ -1197,6 +1202,39 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ |
|
|
}; |
|
|
}; |
|
|
}, [openConfirmDialog, createMode, createDialogSelectedItems, createDialogLotOrigin]); |
|
|
}, [openConfirmDialog, createMode, createDialogSelectedItems, createDialogLotOrigin]); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
if (!openConfirmDialog || createMode !== "section") { |
|
|
|
|
|
setSectionItemOverlaps([]); |
|
|
|
|
|
setSectionOverlapsLoading(false); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const sections = createDialogSelectedSections.map((s) => s.trim()).filter((s) => s.length > 0); |
|
|
|
|
|
if (sections.length === 0) { |
|
|
|
|
|
setSectionItemOverlaps([]); |
|
|
|
|
|
setSectionOverlapsLoading(false); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
let cancelled = false; |
|
|
|
|
|
const timer = window.setTimeout(() => { |
|
|
|
|
|
setSectionOverlapsLoading(true); |
|
|
|
|
|
getSectionCreateItemOverlaps(sections) |
|
|
|
|
|
.then((rows) => { |
|
|
|
|
|
if (!cancelled) setSectionItemOverlaps(Array.isArray(rows) ? rows : []); |
|
|
|
|
|
}) |
|
|
|
|
|
.catch((e) => { |
|
|
|
|
|
console.error("Failed to load section create item overlaps:", e); |
|
|
|
|
|
if (!cancelled) setSectionItemOverlaps([]); |
|
|
|
|
|
}) |
|
|
|
|
|
.finally(() => { |
|
|
|
|
|
if (!cancelled) setSectionOverlapsLoading(false); |
|
|
|
|
|
}); |
|
|
|
|
|
}, 250); |
|
|
|
|
|
return () => { |
|
|
|
|
|
cancelled = true; |
|
|
|
|
|
window.clearTimeout(timer); |
|
|
|
|
|
}; |
|
|
|
|
|
}, [openConfirmDialog, createMode, createDialogSelectedSections]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
setSectionsLoading(true); |
|
|
setSectionsLoading(true); |
|
|
fetchStockTakeSections() |
|
|
fetchStockTakeSections() |
|
|
@@ -1318,8 +1356,26 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ |
|
|
setCreateDialogSelectedItems([]); |
|
|
setCreateDialogSelectedItems([]); |
|
|
setCreateDialogLotOptions([]); |
|
|
setCreateDialogLotOptions([]); |
|
|
setCreateDialogSelectedLotLineIds([]); |
|
|
setCreateDialogSelectedLotLineIds([]); |
|
|
|
|
|
setSectionItemOverlaps([]); |
|
|
|
|
|
setSectionOverlapsLoading(false); |
|
|
onCreateDialogClose?.(); |
|
|
onCreateDialogClose?.(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const formatSectionItemOverlapLine = useCallback( |
|
|
|
|
|
(row: SectionCreateItemOverlap) => { |
|
|
|
|
|
const itemLabel = [row.itemCode, row.itemName].filter(Boolean).join(" ").trim() || `#${row.itemId}`; |
|
|
|
|
|
const roundName = row.stockTakeRoundName?.trim() |
|
|
|
|
|
? `「${row.stockTakeRoundName.trim()}」` |
|
|
|
|
|
: ""; |
|
|
|
|
|
return t("Section create item overlap line", { |
|
|
|
|
|
item: itemLabel, |
|
|
|
|
|
round: row.stockTakeRoundId, |
|
|
|
|
|
roundName, |
|
|
|
|
|
section: row.stockTakeSection || "-", |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
[t], |
|
|
|
|
|
); |
|
|
return ( |
|
|
return ( |
|
|
<Box> |
|
|
<Box> |
|
|
{!hideSectionList && ( |
|
|
{!hideSectionList && ( |
|
|
@@ -2010,6 +2066,30 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ |
|
|
</Typography>{" "} |
|
|
</Typography>{" "} |
|
|
{createMode === "item" ? t("lots unit") : t("sections unit")} |
|
|
{createMode === "item" ? t("lots unit") : t("sections unit")} |
|
|
</Typography> |
|
|
</Typography> |
|
|
|
|
|
{createMode === "section" && sectionOverlapsLoading ? ( |
|
|
|
|
|
<Typography variant="caption" color="text.secondary"> |
|
|
|
|
|
… |
|
|
|
|
|
</Typography> |
|
|
|
|
|
) : null} |
|
|
|
|
|
{createMode === "section" && sectionItemOverlaps.length > 0 ? ( |
|
|
|
|
|
<Alert severity="warning" sx={{ py: 0.5, alignItems: "flex-start" }}> |
|
|
|
|
|
<Typography variant="caption" fontWeight={700} display="block" sx={{ mb: 0.5 }}> |
|
|
|
|
|
{t("Section create item overlap title")} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
<Box component="ul" sx={{ m: 0, pl: 2, maxHeight: 140, overflowY: "auto" }}> |
|
|
|
|
|
{sectionItemOverlaps.map((row) => ( |
|
|
|
|
|
<Typography |
|
|
|
|
|
component="li" |
|
|
|
|
|
variant="caption" |
|
|
|
|
|
key={`${row.itemId}-${row.stockTakeRoundId}-${row.stockTakeSection}`} |
|
|
|
|
|
sx={{ display: "list-item" }} |
|
|
|
|
|
> |
|
|
|
|
|
{formatSectionItemOverlapLine(row)} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
))} |
|
|
|
|
|
</Box> |
|
|
|
|
|
</Alert> |
|
|
|
|
|
) : null} |
|
|
</Box> |
|
|
</Box> |
|
|
|
|
|
|
|
|
{/* 右側:區域卡片網格 */} |
|
|
{/* 右側:區域卡片網格 */} |
|
|
@@ -2495,6 +2575,25 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ |
|
|
</Typography>{" "} |
|
|
</Typography>{" "} |
|
|
{createMode === "item" ? t("lots unit") : t("sections unit")} |
|
|
{createMode === "item" ? t("lots unit") : t("sections unit")} |
|
|
</Typography> |
|
|
</Typography> |
|
|
|
|
|
{createMode === "section" && sectionItemOverlaps.length > 0 ? ( |
|
|
|
|
|
<Alert severity="warning" sx={{ py: 0.75 }}> |
|
|
|
|
|
<Typography variant="body2" fontWeight={700} sx={{ mb: 0.5 }}> |
|
|
|
|
|
{t("Section create item overlap title")} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
<Box component="ul" sx={{ m: 0, pl: 2, maxHeight: 160, overflowY: "auto" }}> |
|
|
|
|
|
{sectionItemOverlaps.map((row) => ( |
|
|
|
|
|
<Typography |
|
|
|
|
|
component="li" |
|
|
|
|
|
variant="body2" |
|
|
|
|
|
key={`confirm-${row.itemId}-${row.stockTakeRoundId}-${row.stockTakeSection}`} |
|
|
|
|
|
sx={{ display: "list-item", mb: 0.5 }} |
|
|
|
|
|
> |
|
|
|
|
|
{formatSectionItemOverlapLine(row)} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
))} |
|
|
|
|
|
</Box> |
|
|
|
|
|
</Alert> |
|
|
|
|
|
) : null} |
|
|
</Stack> |
|
|
</Stack> |
|
|
<Box |
|
|
<Box |
|
|
sx={{ |
|
|
sx={{ |
|
|
|