|
- "use client";
-
- import {
- Box,
- Button,
- Dialog,
- DialogActions,
- DialogContent,
- DialogTitle,
- FormControl,
- Grid,
- InputLabel,
- MenuItem,
- Select,
- TextField,
- Typography,
- } from "@mui/material";
- import { useCallback, useEffect, useState, useRef } from "react";
- import { useTranslation } from "react-i18next";
- import {
- GetPickOrderLineInfo,
- PickExecutionIssueData,
- } from "@/app/api/pickOrder/actions";
- import { fetchEscalationCombo } from "@/app/api/user/actions";
- import dayjs from "dayjs";
- import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
-
- interface LotPickData {
- id: number;
- lotId: number;
- lotNo: string;
- expiryDate: string;
- location: string;
- stockUnit: string;
- inQty: number;
- outQty: number;
- holdQty: number;
- totalPickedByAllPickOrders: number;
- availableQty: number;
- requiredQty: number;
- actualPickQty: number;
- lotStatus: string;
- lotAvailability:
- | "available"
- | "insufficient_stock"
- | "expired"
- | "status_unavailable"
- | "rejected";
- stockOutLineId?: number;
- stockOutLineStatus?: string;
- stockOutLineQty?: number;
- }
-
- interface PickExecutionFormProps {
- open: boolean;
- onClose: () => void;
- onSubmit: (data: PickExecutionIssueData) => Promise<void>;
- selectedLot: LotPickData | null;
- selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null;
- pickOrderId?: number;
- pickOrderCreateDate: any;
- }
-
- interface FormErrors {
- actualPickQty?: string;
- missQty?: string;
- badItemQty?: string;
- badReason?: string;
- issueRemark?: string;
- handledBy?: string;
- }
-
- const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
- open,
- onClose,
- onSubmit,
- selectedLot,
- selectedPickOrderLine,
- pickOrderId,
- pickOrderCreateDate,
- }) => {
- const { t } = useTranslation("pickOrder");
- const [formData, setFormData] = useState<Partial<PickExecutionIssueData>>({});
- const [errors, setErrors] = useState<FormErrors>({});
- const [loading, setLoading] = useState(false);
- const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>(
- []
- );
-
- const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => {
- return lot.availableQty || 0;
- }, []);
-
- const calculateRequiredQty = useCallback((lot: LotPickData) => {
- return lot.requiredQty || 0;
- }, []);
-
- useEffect(() => {
- const fetchHandlers = async () => {
- try {
- const escalationCombo = await fetchEscalationCombo();
- setHandlers(escalationCombo);
- } catch (error) {
- console.error("Error fetching handlers:", error);
- }
- };
-
- fetchHandlers();
- }, []);
-
- const initKeyRef = useRef<string | null>(null);
-
- useEffect(() => {
- if (!open || !selectedLot || !selectedPickOrderLine || !pickOrderId) return;
-
- const key = `${selectedPickOrderLine.id}-${selectedLot.lotId}`;
- if (initKeyRef.current === key) return;
-
- const getSafeDate = (dateValue: any): string => {
- if (!dateValue) return dayjs().format(INPUT_DATE_FORMAT);
- try {
- const date = dayjs(dateValue);
- if (!date.isValid()) {
- return dayjs().format(INPUT_DATE_FORMAT);
- }
- return date.format(INPUT_DATE_FORMAT);
- } catch {
- return dayjs().format(INPUT_DATE_FORMAT);
- }
- };
-
- setFormData({
- pickOrderId: pickOrderId,
- pickOrderCode: selectedPickOrderLine.pickOrderCode,
- pickOrderCreateDate: getSafeDate(pickOrderCreateDate),
- pickExecutionDate: dayjs().format(INPUT_DATE_FORMAT),
- pickOrderLineId: selectedPickOrderLine.id,
- itemId: selectedPickOrderLine.itemId,
- itemCode: selectedPickOrderLine.itemCode,
- itemDescription: selectedPickOrderLine.itemName,
- lotId: selectedLot.lotId,
- lotNo: selectedLot.lotNo,
- storeLocation: selectedLot.location,
- requiredQty: selectedLot.requiredQty,
- actualPickQty: selectedLot.actualPickQty || 0,
- missQty: 0,
- badItemQty: 0, // Bad Item Qty
- badPackageQty: 0, // Bad Package Qty (frontend only)
- issueRemark: "",
- pickerName: "",
- handledBy: undefined,
- reason: "",
- badReason: "",
- });
-
- initKeyRef.current = key;
- }, [
- open,
- selectedPickOrderLine?.id,
- selectedLot?.lotId,
- pickOrderId,
- pickOrderCreateDate,
- ]);
-
- const handleInputChange = useCallback(
- (field: keyof PickExecutionIssueData, value: any) => {
- setFormData((prev) => ({ ...prev, [field]: value }));
- if (errors[field as keyof FormErrors]) {
- setErrors((prev) => ({ ...prev, [field]: undefined }));
- }
- },
- [errors]
- );
-
- // Updated validation logic
- const validateForm = (): boolean => {
- const newErrors: FormErrors = {};
- const ap = Number(formData.actualPickQty) || 0;
- const miss = Number(formData.missQty) || 0;
- const badItem = Number(formData.badItemQty) || 0;
- const badPackage = Number((formData as any).badPackageQty) || 0;
- const totalBad = badItem + badPackage;
- const total = ap + miss + totalBad;
- const availableQty = selectedLot?.availableQty || 0;
-
- // 1. Check actualPickQty cannot be negative
- if (ap < 0) {
- newErrors.actualPickQty = t("Qty cannot be negative");
- }
-
- // 2. Check actualPickQty cannot exceed available quantity
- if (ap > availableQty) {
- newErrors.actualPickQty = t("Actual pick qty cannot exceed available qty");
- }
-
- // 3. Check missQty and both bad qtys cannot be negative
- if (miss < 0) {
- newErrors.missQty = t("Invalid qty");
- }
- if (badItem < 0 || badPackage < 0) {
- newErrors.badItemQty = t("Invalid qty");
- }
-
- // 4. Total (actualPickQty + missQty + badItemQty + badPackageQty) cannot exceed lot available qty
- if (total > availableQty) {
- const errorMsg = t(
- "Total qty (actual pick + miss + bad) cannot exceed available qty: {available}",
- { available: availableQty }
- );
- newErrors.actualPickQty = errorMsg;
- newErrors.missQty = errorMsg;
- newErrors.badItemQty = errorMsg;
- }
-
- // 5. At least one field must have a value
- if (ap === 0 && miss === 0 && totalBad === 0) {
- newErrors.actualPickQty = t("Enter pick qty or issue qty");
- }
-
- setErrors(newErrors);
- return Object.keys(newErrors).length === 0;
- };
-
- const handleSubmit = async () => {
- if (!validateForm()) {
- console.error("Form validation failed:", errors);
- return;
- }
-
- if (!formData.pickOrderId) {
- console.error("Missing pickOrderId");
- return;
- }
-
- const badItem = Number(formData.badItemQty) || 0;
- const badPackage = Number((formData as any).badPackageQty) || 0;
- const totalBadQty = badItem + badPackage;
-
- let badReason: string | undefined;
- if (totalBadQty > 0) {
- // assumption: only one of them is > 0
- badReason = badPackage > 0 ? "package_problem" : "quantity_problem";
- }
-
- const submitData: PickExecutionIssueData = {
- ...(formData as PickExecutionIssueData),
- badItemQty: totalBadQty,
- badReason,
- };
-
- setLoading(true);
- try {
- await onSubmit(submitData);
- } catch (error: any) {
- console.error("Error submitting pick execution issue:", error);
- alert(
- t("Failed to submit issue. Please try again.") +
- (error.message ? `: ${error.message}` : "")
- );
- } finally {
- setLoading(false);
- }
- };
-
- const handleClose = () => {
- setFormData({});
- setErrors({});
- onClose();
- };
-
- if (!selectedLot || !selectedPickOrderLine) {
- return null;
- }
-
- const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot);
- const requiredQty = calculateRequiredQty(selectedLot);
-
- return (
- <Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
- <DialogTitle>
- {t("Pick Execution Issue Form") + " - "+selectedPickOrderLine.itemCode+ " "+ selectedPickOrderLine.itemName}
- <br />
- {selectedLot.lotNo}
- </DialogTitle>
- <DialogContent>
- <Box sx={{ mt: 2 }}>
- <Grid container spacing={2}>
- <Grid item xs={6}>
- <TextField
- fullWidth
- label={t("Required Qty")}
- value={requiredQty}
- disabled
- variant="outlined"
- />
- </Grid>
-
- <Grid item xs={6}>
- <TextField
- fullWidth
- label={t("Remaining Available Qty")}
- value={remainingAvailableQty}
- disabled
- variant="outlined"
- />
- </Grid>
-
- <Grid item xs={12}>
- <TextField
- fullWidth
- label={t("Actual Pick Qty")}
- type="number"
- inputProps={{
- inputMode: "numeric",
- pattern: "[0-9]*",
- min: 0,
- }}
- value={formData.actualPickQty ?? ""}
- onChange={(e) =>
- handleInputChange(
- "actualPickQty",
- e.target.value === ""
- ? undefined
-
- : Math.max(0, Number(e.target.value) || 0)
- )
- }
- error={!!errors.actualPickQty}
- helperText={
- errors.actualPickQty || `${t("Max")}: ${remainingAvailableQty}`
- }
- variant="outlined"
- />
- </Grid>
-
- <Grid item xs={12}>
- <FormControl fullWidth>
- <InputLabel>{t("Reason")}</InputLabel>
- <Select
- value={formData.reason || ""}
- onChange={(e) => handleInputChange("reason", e.target.value)}
- label={t("Reason")}
- >
- <MenuItem value="">{t("Select Reason")}</MenuItem>
- <MenuItem value="miss">{t("Edit")}</MenuItem>
- <MenuItem value="bad">{t("Just Complete")}</MenuItem>
- </Select>
- </FormControl>
- </Grid>
-
- <Grid item xs={12}>
- <TextField
- fullWidth
- label={t("Missing item Qty")}
- type="number"
- inputProps={{
- inputMode: "numeric",
- pattern: "[0-9]*",
- min: 0,
- }}
- value={formData.missQty || 0}
- onChange={(e) =>
- handleInputChange(
- "missQty",
- e.target.value === ""
- ? undefined
- : Math.max(0, Number(e.target.value) || 0)
- )
- }
- error={!!errors.missQty}
- variant="outlined"
- />
- </Grid>
-
- <Grid item xs={12}>
- <TextField
- fullWidth
- label={t("Bad Item Qty")}
- type="number"
- inputProps={{
- inputMode: "numeric",
- pattern: "[0-9]*",
- min: 0,
- }}
- value={formData.badItemQty || 0}
- onChange={(e) =>
- handleInputChange(
- "badItemQty",
- e.target.value === ""
- ? undefined
- : Math.max(0, Number(e.target.value) || 0)
- )
- }
- error={!!errors.badItemQty}
- //helperText={t("Quantity Problem")}
- variant="outlined"
- />
- </Grid>
-
- <Grid item xs={12}>
- <TextField
- fullWidth
- label={t("Bad Package Qty")}
- type="number"
- inputProps={{
- inputMode: "numeric",
- pattern: "[0-9]*",
- min: 0,
- }}
- value={(formData as any).badPackageQty || 0}
- onChange={(e) =>
- handleInputChange(
- "badPackageQty",
- e.target.value === ""
- ? undefined
- : Math.max(0, Number(e.target.value) || 0)
- )
- }
- error={!!errors.badItemQty}
- //helperText={t("Package Problem")}
- variant="outlined"
- />
- </Grid>
- </Grid>
- </Box>
- </DialogContent>
- <DialogActions>
- <Button onClick={handleClose} disabled={loading}>
- {t("Cancel")}
- </Button>
- <Button onClick={handleSubmit} variant="contained" disabled={loading}>
- {loading ? t("submitting") : t("submit")}
- </Button>
- </DialogActions>
- </Dialog>
- );
- };
-
- export default PickExecutionForm;
|