Browse Source

update

master
CANCERYS\kw093 2 months ago
parent
commit
c8f5d7bf99
6 changed files with 148 additions and 54 deletions
  1. +1
    -8
      src/components/DoDetail/DoInfoCard.tsx
  2. +1
    -1
      src/components/FinishedGoodSearch/FGPickOrderCard.tsx
  3. +24
    -28
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  4. +99
    -15
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  5. +18
    -1
      src/i18n/zh/do.json
  6. +5
    -1
      src/i18n/zh/pickOrder.json

+ 1
- 8
src/components/DoDetail/DoInfoCard.tsx View File

@@ -54,14 +54,7 @@ const DoInfoCard: React.FC<Props> = ({
disabled={true} disabled={true}
/> />
</Grid> </Grid>
<Grid item xs={6}>
<TextField
{...register("currencyCode")}
label={t("Currency Code")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
{...register("orderDate")} {...register("orderDate")}


+ 1
- 1
src/components/FinishedGoodSearch/FGPickOrderCard.tsx View File

@@ -108,7 +108,7 @@ const FGPickOrderCard: React.FC<Props> = ({ fgOrder, onQrCodeClick }) => {
onClick={() => onQrCodeClick(fgOrder.pickOrderId)} onClick={() => onQrCodeClick(fgOrder.pickOrderId)}
sx={{ minWidth: 120 }} sx={{ minWidth: 120 }}
> >
{t("View QR Code")}
{t("Print DN/Label")}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>


+ 24
- 28
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx View File

@@ -87,10 +87,12 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
const remainingQty = lot.inQty - lot.outQty; const remainingQty = lot.inQty - lot.outQty;
return Math.max(0, remainingQty); return Math.max(0, remainingQty);
}, []); }, []);
const calculateRequiredQty = useCallback((lot: LotPickData) => {
const requiredQty = lot.requiredQty-(lot.actualPickQty||0);
return Math.max(0, requiredQty);
}, []);
const calculateRequiredQty = useCallback((lot: LotPickData) => {
// ✅ Use the original required quantity, not subtracting actualPickQty
// The actualPickQty in the form should be independent of the database value
return lot.requiredQty || 0;
}, []);
// 获取处理人员列表 // 获取处理人员列表
useEffect(() => { useEffect(() => {
const fetchHandlers = async () => { const fetchHandlers = async () => {
@@ -166,36 +168,30 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
// ✅ Update form validation to require either missQty > 0 OR badItemQty > 0 // ✅ Update form validation to require either missQty > 0 OR badItemQty > 0
const validateForm = (): boolean => { const validateForm = (): boolean => {
const newErrors: FormErrors = {}; const newErrors: FormErrors = {};
if (formData.actualPickQty === undefined || formData.actualPickQty < 0) { if (formData.actualPickQty === undefined || formData.actualPickQty < 0) {
newErrors.actualPickQty = t('pickOrder.validation.actualPickQtyRequired');
newErrors.actualPickQty = t('Qty is required');
} }
// ✅ FIXED: Check if actual pick qty exceeds remaining available qty
if (formData.actualPickQty && formData.actualPickQty > remainingAvailableQty) {
newErrors.actualPickQty = t('Qty is not allowed to be greater than remaining available qty');
}
// ✅ FIXED: Check if actual pick qty exceeds required qty (use original required qty)
if (formData.actualPickQty && formData.actualPickQty > (selectedLot?.requiredQty || 0)) {
newErrors.actualPickQty = t('Qty is not allowed to be greater than required qty');
}
// ✅ NEW: Require either missQty > 0 OR badItemQty > 0 (at least one issue must be reported) // ✅ NEW: Require either missQty > 0 OR badItemQty > 0 (at least one issue must be reported)
const hasMissQty = formData.missQty && formData.missQty > 0; const hasMissQty = formData.missQty && formData.missQty > 0;
const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0; const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0;
if (!hasMissQty && !hasBadItemQty) { if (!hasMissQty && !hasBadItemQty) {
newErrors.missQty = t('pickOrder.validation.mustReportMissOrBadItems');
newErrors.badItemQty = t('pickOrder.validation.mustReportMissOrBadItems');
newErrors.missQty = t('At least one issue must be reported');
newErrors.badItemQty = t('At least one issue must be reported');
} }
if (formData.missQty && formData.missQty < 0) {
newErrors.missQty = t('pickOrder.validation.missQtyInvalid');
}
if (formData.badItemQty && formData.badItemQty < 0) {
newErrors.badItemQty = t('pickOrder.validation.badItemQtyInvalid');
}
if (formData.badItemQty && formData.badItemQty > 0 && !formData.issueRemark) {
newErrors.issueRemark = t('pickOrder.validation.issueRemarkRequired');
}
if (formData.badItemQty && formData.badItemQty > 0 && !formData.handledBy) {
newErrors.handledBy = t('pickOrder.validation.handlerRequired');
}
setErrors(newErrors); setErrors(newErrors);
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0;
}; };
@@ -251,7 +247,7 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
<TextField <TextField
fullWidth fullWidth
label={t('Required Qty')} label={t('Required Qty')}
value={requiredQty || 0}
value={selectedLot?.requiredQty || 0}
disabled disabled
variant="outlined" variant="outlined"
// helperText={t('Still need to pick')} // helperText={t('Still need to pick')}
@@ -277,7 +273,7 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
value={formData.actualPickQty || 0} value={formData.actualPickQty || 0}
onChange={(e) => handleInputChange('actualPickQty', parseFloat(e.target.value) || 0)} onChange={(e) => handleInputChange('actualPickQty', parseFloat(e.target.value) || 0)}
error={!!errors.actualPickQty} error={!!errors.actualPickQty}
// helperText={errors.actualPickQty || t('Enter the quantity actually picked')}
helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`}
variant="outlined" variant="outlined"
/> />
</Grid> </Grid>


+ 99
- 15
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx View File

@@ -458,7 +458,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
// ✅ Use current data without refreshing to avoid infinite loop // ✅ Use current data without refreshing to avoid infinite loop
const currentLotData = combinedLotData; const currentLotData = combinedLotData;
console.log(`�� Available lots:`, currentLotData.map(lot => lot.lotNo));
console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo));
const matchingLots = currentLotData.filter(lot => const matchingLots = currentLotData.filter(lot =>
lot.lotNo === lotNo || lot.lotNo === lotNo ||
@@ -477,18 +477,17 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
try { try {
let successCount = 0; let successCount = 0;
let existsCount = 0;
let errorCount = 0; let errorCount = 0;
for (const matchingLot of matchingLots) { for (const matchingLot of matchingLots) {
console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`); console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`);
if (matchingLot.stockOutLineId) { if (matchingLot.stockOutLineId) {
// ✅ FIXED: Use matchingLot.stockOutLineId instead of selectedLotForQr.stockOutLineId
// ✅ FIXED: Only update status to 'checked', keep qty at 0
const stockOutLineUpdate = await updateStockOutLineStatus({ const stockOutLineUpdate = await updateStockOutLineStatus({
id: matchingLot.stockOutLineId, // ✅ Use the correct ID
id: matchingLot.stockOutLineId,
status: 'checked', status: 'checked',
qty: matchingLot.stockOutLineQty || matchingLot.requiredQty || 0
qty: 0 // ✅ Keep qty at 0 until user actually submits
}); });
console.log(`Update stock out line result for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate); console.log(`Update stock out line result for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate);
@@ -500,12 +499,12 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
errorCount++; errorCount++;
} }
} else { } else {
// ✅ If no stock out line exists, create one
// ✅ If no stock out line exists, create one with qty = 0
const createStockOutLineData = { const createStockOutLineData = {
consoCode: matchingLot.pickOrderConsoCode, consoCode: matchingLot.pickOrderConsoCode,
pickOrderLineId: matchingLot.pickOrderLineId, pickOrderLineId: matchingLot.pickOrderLineId,
inventoryLotLineId: matchingLot.lotId, inventoryLotLineId: matchingLot.lotId,
qty: matchingLot.requiredQty || 0
qty: 0 // ✅ Create with qty = 0
}; };
const createResult = await createStockOutLine(createStockOutLineData); const createResult = await createStockOutLine(createStockOutLineData);
@@ -719,13 +718,19 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
try { try {
// ✅ FIXED: Calculate cumulative quantity correctly
const currentActualPickQty = lot.actualPickQty || 0; const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + newQty; const cumulativeQty = currentActualPickQty + newQty;
// ✅ FIXED: Determine status based on cumulative quantity vs required quantity
let newStatus = 'partially_completed'; let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) { if (cumulativeQty >= lot.requiredQty) {
newStatus = 'completed'; newStatus = 'completed';
} else if (cumulativeQty > 0) {
newStatus = 'partially_completed';
} else {
newStatus = 'checked'; // QR scanned but no quantity submitted yet
} }
console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`); console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`);
@@ -740,7 +745,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: lot.stockOutLineId, id: lot.stockOutLineId,
status: newStatus, status: newStatus,
qty: cumulativeQty
qty: cumulativeQty // ✅ Use cumulative quantity
}); });
if (newQty > 0) { if (newQty > 0) {
@@ -752,12 +757,11 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
}); });
} }
// ✅ FIXED: Use the proper API function instead of direct fetch
// ✅ Check if pick order is completed when lot status becomes 'completed'
if (newStatus === 'completed' && lot.pickOrderConsoCode) { if (newStatus === 'completed' && lot.pickOrderConsoCode) {
console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try { try {
// ✅ Use the imported API function instead of direct fetch
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
console.log(`✅ Pick order completion check result:`, completionResponse); console.log(`✅ Pick order completion check result:`, completionResponse);
@@ -937,7 +941,83 @@ const paginatedData = useMemo(() => {
const endIndex = startIndex + paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize;
return combinedLotData.slice(startIndex, endIndex); // ✅ No sorting needed return combinedLotData.slice(startIndex, endIndex); // ✅ No sorting needed
}, [combinedLotData, paginationController]); }, [combinedLotData, paginationController]);

const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => {
if (!lot.stockOutLineId) {
console.error("No stock out line found for this lot");
return;
}
try {
// ✅ FIXED: Calculate cumulative quantity correctly
const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty;
// ✅ FIXED: Determine status based on cumulative quantity vs required quantity
let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) {
newStatus = 'completed';
} else if (cumulativeQty > 0) {
newStatus = 'partially_completed';
} else {
newStatus = 'checked'; // QR scanned but no quantity submitted yet
}
console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`);
console.log(`Lot: ${lot.lotNo}`);
console.log(`Required Qty: ${lot.requiredQty}`);
console.log(`Current Actual Pick Qty: ${currentActualPickQty}`);
console.log(`New Submitted Qty: ${submitQty}`);
console.log(`Cumulative Qty: ${cumulativeQty}`);
console.log(`New Status: ${newStatus}`);
console.log(`=====================================`);
await updateStockOutLineStatus({
id: lot.stockOutLineId,
status: newStatus,
qty: cumulativeQty // ✅ Use cumulative quantity
});
if (submitQty > 0) {
await updateInventoryLotLineQuantities({
inventoryLotLineId: lot.lotId,
qty: submitQty,
status: 'available',
operation: 'pick'
});
}
// ✅ Check if pick order is completed when lot status becomes 'completed'
if (newStatus === 'completed' && lot.pickOrderConsoCode) {
console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try {
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
console.log(`✅ Pick order completion check result:`, completionResponse);
if (completionResponse.code === "SUCCESS") {
console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
} else if (completionResponse.message === "not completed") {
console.log(`⏳ Pick order not completed yet, more lines remaining`);
} else {
console.error(`❌ Error checking completion: ${completionResponse.message}`);
}
} catch (error) {
console.error("Error checking pick order completion:", error);
}
}
await fetchAllCombinedLotData();
console.log("Pick quantity submitted successfully!");
setTimeout(() => {
checkAndAutoAssignNext();
}, 1000);
} catch (error) {
console.error("Error submitting pick quantity:", error);
}
}, [fetchAllCombinedLotData, checkAndAutoAssignNext]);




// ✅ Add these functions after line 395 // ✅ Add these functions after line 395
@@ -1117,8 +1197,10 @@ const paginatedData = useMemo(() => {
<TableCell align="right"> <TableCell align="right">
{(() => { {(() => {
const inQty = lot.inQty || 0; const inQty = lot.inQty || 0;
const requiredQty = lot.requiredQty || 0;
const actualPickQty = lot.actualPickQty || 0;
const outQty = lot.outQty || 0; const outQty = lot.outQty || 0;
const result = inQty - outQty;
const result = requiredQty;
return result.toLocaleString()+'('+lot.uomShortDesc+')'; return result.toLocaleString()+'('+lot.uomShortDesc+')';
})()} })()}
</TableCell> </TableCell>
@@ -1143,10 +1225,12 @@ const paginatedData = useMemo(() => {
<Button <Button
variant="contained" variant="contained"
onClick={() => { onClick={() => {
// Submit with default lot required pick qty
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
handlePickQtyChange(lotKey, lot.requiredQty || lot.pickOrderLineRequiredQty);
handleSubmitPickQty(lot);
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
// Submit with default lot required pick qty
handlePickQtyChange(lotKey, submitQty);
handleSubmitPickQtyWithQty(lot, submitQty);
}} }}
disabled={ disabled={
(lot.lotAvailability === 'expired' || (lot.lotAvailability === 'expired' ||


+ 18
- 1
src/i18n/zh/do.json View File

@@ -18,6 +18,23 @@
"Details": "詳情", "Details": "詳情",
"Pending": "待處理", "Pending": "待處理",
"Receiving": "接收中", "Receiving": "接收中",
"Completed": "已完成"
"Completed": "已完成",
"Shop Code": "店鋪編號",
"Supplier Code": "供應商編號",
"Estimated Arrival Date": "預計到貨日期",
"Item No.": "商品編號",
"Item Name": "商品名稱",
"Quantity": "數量",
"uom": "單位",
"Lot No.": "批號",
"Expiry Date": "有效期",
"Location": "庫位",
"Price": "價格",
"Action": "操作",
"Create": "新增",
"Edit": "編輯",
"Delete": "刪除",
"Release": "放單",
"Back": "返回"


} }

+ 5
- 1
src/i18n/zh/pickOrder.json View File

@@ -257,5 +257,9 @@
"Pick Execution Detail":"進行提料詳情", "Pick Execution Detail":"進行提料詳情",
"Submit Required Pick Qty":"提交所需提料數量", "Submit Required Pick Qty":"提交所需提料數量",
"Scan Result":"掃描結果", "Scan Result":"掃描結果",
"Ticket No.":"提票號碼"
"Ticket No.":"提票號碼",
"Start QR Scan":"開始QR掃描",
"Stop QR Scan":"停止QR掃描",
"Scanning...":"掃描中...",
"Print DN/Label":"列印送貨單/標籤"
} }

Loading…
Cancel
Save