Sfoglia il codice sorgente

if rules same different warehouse,slot can combine

controllQRcodescanWarehouse
CANCERYS\kw093 20 ore fa
parent
commit
8035b97b29
5 ha cambiato i file con 292 aggiunte e 34 eliminazioni
  1. +8
    -2
      src/app/api/settings/deliveryOrderFloor/client.ts
  2. +102
    -4
      src/app/api/settings/deliveryOrderFloor/constants.ts
  3. +172
    -28
      src/components/DeliveryOrderFloorSettings/ExcludeWarehouseRuleList.tsx
  4. +5
    -0
      src/i18n/en/deliveryOrderFloor.json
  5. +5
    -0
      src/i18n/zh/deliveryOrderFloor.json

+ 8
- 2
src/app/api/settings/deliveryOrderFloor/client.ts Vedi File

@@ -29,6 +29,7 @@ import {
SETTING_PO_AUTO_PUTAWAY_WAREHOUSE, SETTING_PO_AUTO_PUTAWAY_WAREHOUSE,
codesToExcludeRules, codesToExcludeRules,
compactExcludeRules, compactExcludeRules,
ensureExcludeCardIds,
defaultJoAutoPutAwayRules, defaultJoAutoPutAwayRules,
defaultPoAutoPutAwayRules, defaultPoAutoPutAwayRules,
type AutoPutAwayException, type AutoPutAwayException,
@@ -277,6 +278,7 @@ function normalizeExcludeRule(row: unknown, fallbackMode: ExcludePrintMode): Exc
slots: text("slots"), slots: text("slots"),
printMode: mode === "hideQr" ? "hideQr" : mode === "hideList" ? "hideList" : fallbackMode, printMode: mode === "hideQr" ? "hideQr" : mode === "hideList" ? "hideList" : fallbackMode,
enabled: value.enabled !== false, enabled: value.enabled !== false,
cardId: text("cardId").trim(),
}; };
} }


@@ -287,14 +289,18 @@ function resolveExcludeRules(stored: string | undefined, csv: string, fallbackMo
const parsed = JSON.parse(trimmed) as unknown; const parsed = JSON.parse(trimmed) as unknown;
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
return compactExcludeRules( return compactExcludeRules(
parsed.map((row) => normalizeExcludeRule(row, fallbackMode)).filter((row): row is ExcludeWarehouseRule => row != null),
ensureExcludeCardIds(
parsed.map((row) => normalizeExcludeRule(row, fallbackMode)).filter((row): row is ExcludeWarehouseRule => row != null),
),
); );
} }
} catch { } catch {
// Fall through to the warehouse-code list. // Fall through to the warehouse-code list.
} }
} }
return compactExcludeRules(codesToExcludeRules(csv).map((rule) => ({ ...rule, printMode: fallbackMode })));
return compactExcludeRules(
ensureExcludeCardIds(codesToExcludeRules(csv).map((rule) => ({ ...rule, printMode: fallbackMode }))),
);
} }


/** Missing hide-QR row means the old single print mode still applies. `-` means no hide-QR warehouses. */ /** Missing hide-QR row means the old single print mode still applies. `-` means no hide-QR warehouses. */


+ 102
- 4
src/app/api/settings/deliveryOrderFloor/constants.ts Vedi File

@@ -151,8 +151,17 @@ export type ExcludeWarehouseRule = {
slots: string; slots: string;
printMode: ExcludePrintMode; printMode: ExcludePrintMode;
enabled: boolean; enabled: boolean;
/** Same id stays one card. A new id stays separate until the user agrees to combine. */
cardId: string;
}; };


let excludeCardSeq = 0;

export function newExcludeCardId(): string {
excludeCardSeq += 1;
return `c${Date.now().toString(36)}${excludeCardSeq.toString(36)}`;
}

function listTokens(raw: string): string[] { function listTokens(raw: string): string[] {
return raw return raw
.split(",") .split(",")
@@ -192,8 +201,9 @@ export function codesToExcludeRules(csv: string): ExcludeWarehouseRule[] {
warehouse: group?.warehouse ?? "", warehouse: group?.warehouse ?? "",
areas: group?.area ?? "", areas: group?.area ?? "",
slots: group?.slots.join(",") ?? "", slots: group?.slots.join(",") ?? "",
printMode: "hideList",
printMode: "hideList" as const,
enabled: true, enabled: true,
cardId: "",
}; };
}); });
} }
@@ -209,6 +219,7 @@ export function compactExcludeRules(rules: ExcludeWarehouseRule[]): ExcludeWareh
listTokens(rule.slots).sort().join(","), listTokens(rule.slots).sort().join(","),
rule.printMode === "hideQr" ? "hideQr" : "hideList", rule.printMode === "hideQr" ? "hideQr" : "hideList",
rule.enabled === false ? "off" : "on", rule.enabled === false ? "off" : "on",
rule.cardId,
].join("|"); ].join("|");
const existing = grouped.get(key); const existing = grouped.get(key);
const area = rule.areas.trim(); const area = rule.areas.trim();
@@ -268,7 +279,15 @@ export function partitionExcludeCodes(
} }


export function emptyExcludeWarehouseRule(): ExcludeWarehouseRule { export function emptyExcludeWarehouseRule(): ExcludeWarehouseRule {
return { floor: "", warehouse: "", areas: "", slots: "", printMode: "hideList", enabled: true };
return {
floor: "",
warehouse: "",
areas: "",
slots: "",
printMode: "hideList",
enabled: true,
cardId: newExcludeCardId(),
};
} }


function excludePlaceKey(rule: ExcludeWarehouseRule): string { function excludePlaceKey(rule: ExcludeWarehouseRule): string {
@@ -309,8 +328,7 @@ function excludeRulesOverlap(a: ExcludeWarehouseRule, b: ExcludeWarehouseRule):
return excludeSlotsOverlap(a.slots, b.slots); return excludeSlotsOverlap(a.slots, b.slots);
} }


/** Same floor, warehouse, and list handling stay one card when their areas do not overlap. */
export function groupExcludeRuleIndexes(rules: ExcludeWarehouseRule[]): number[][] {
function groupExcludeRuleIndexesInternal(rules: ExcludeWarehouseRule[], respectCardId: boolean): number[][] {
const groups: number[][] = []; const groups: number[][] = [];
rules.forEach((rule, index) => { rules.forEach((rule, index) => {
const incomplete = !rule.floor.trim() || !rule.warehouse.trim(); const incomplete = !rule.floor.trim() || !rule.warehouse.trim();
@@ -319,6 +337,7 @@ export function groupExcludeRuleIndexes(rules: ExcludeWarehouseRule[]): number[]
: groups.find((indexes) => { : groups.find((indexes) => {
const first = rules[indexes[0]]; const first = rules[indexes[0]];
if (!first || excludePlaceKey(first) !== excludePlaceKey(rule)) return false; if (!first || excludePlaceKey(first) !== excludePlaceKey(rule)) return false;
if (respectCardId && first.cardId !== rule.cardId) return false;
return indexes.every((earlier) => { return indexes.every((earlier) => {
const other = rules[earlier]; const other = rules[earlier];
return other != null && !excludeAreasIntersect(other, rule); return other != null && !excludeAreasIntersect(other, rule);
@@ -330,6 +349,85 @@ export function groupExcludeRuleIndexes(rules: ExcludeWarehouseRule[]): number[]
return groups; return groups;
} }


/** Same card stays together. A different card id is never combined. */
export function groupExcludeRuleIndexes(rules: ExcludeWarehouseRule[]): number[][] {
return groupExcludeRuleIndexesInternal(rules, true);
}

/** Rules saved before card ids keep the card they already share. */
export function ensureExcludeCardIds(rules: ExcludeWarehouseRule[]): ExcludeWarehouseRule[] {
if (rules.every((rule) => rule.cardId)) return rules;
const missingIndexes = rules.flatMap((rule, index) => (rule.cardId ? [] : [index]));
const missingGroups = groupExcludeRuleIndexesInternal(
missingIndexes.map((index) => rules[index] as ExcludeWarehouseRule),
false,
);
const idByMissingPos = new Map<number, string>();
missingGroups.forEach((group) => {
const cardId = newExcludeCardId();
group.forEach((position) => idByMissingPos.set(position, cardId));
});
return rules.map((rule, index) => {
if (rule.cardId) return rule;
const missingPos = missingIndexes.indexOf(index);
return { ...rule, cardId: idByMissingPos.get(missingPos) || newExcludeCardId() };
});
}

export function excludeCombinePairKey(leftId: string, rightId: string, rule: ExcludeWarehouseRule): string {
const [left, right] = [leftId, rightId].sort();
return `${left}|${right}|${excludePlaceKey(rule)}`;
}

function areaSlotMap(card: ExcludeWarehouseRule[]): Map<string, string> {
const map = new Map<string, string>();
for (const rule of card) {
for (const area of listTokens(rule.areas)) map.set(area, rule.slots);
}
return map;
}

/** Shared areas are allowed when their slots do not overlap. `""` slots mean every slot. */
function cardsCanCombine(left: ExcludeWarehouseRule[], right: ExcludeWarehouseRule[]): boolean {
const a = areaSlotMap(left);
const b = areaSlotMap(right);
const starA = a.get(EXCLUDE_ALL_AREAS);
const starB = b.get(EXCLUDE_ALL_AREAS);
if (starA != null && starB != null) return !excludeSlotsOverlap(starA, starB);
if (starA != null) return [...b.values()].every((slots) => !excludeSlotsOverlap(starA, slots));
if (starB != null) return [...a.values()].every((slots) => !excludeSlotsOverlap(starB, slots));
for (const [area, slots] of a) {
const other = b.get(area);
if (other != null && excludeSlotsOverlap(slots, other)) return false;
}
return true;
}

/** Another card on the same floor, warehouse, and list handling, with no overlapping slots. */
export function combineCandidate(
rules: ExcludeWarehouseRule[],
index: number,
declined: ReadonlySet<string>,
): { cardId: string; groupNumber: number; fromNumber: number } | null {
const rule = rules[index];
if (!rule?.floor.trim() || !rule.warehouse.trim() || !rule.cardId) return null;
const groups = groupExcludeRuleIndexes(rules);
const fromNumber = groups.findIndex((group) => group.includes(index)) + 1;
const myCard = rules.filter((item) => item.cardId === rule.cardId);
for (let otherIndex = 0; otherIndex < rules.length; otherIndex += 1) {
const other = rules[otherIndex];
if (!other?.cardId || other.cardId === rule.cardId) continue;
if (excludePlaceKey(other) !== excludePlaceKey(rule)) continue;
const sameCard = rules.filter((item) => item.cardId === other.cardId);
if (!cardsCanCombine(myCard, sameCard)) continue;
if (declined.has(excludeCombinePairKey(rule.cardId, other.cardId, rule))) continue;
const groupNumber = groups.findIndex((group) => group.includes(otherIndex)) + 1;
if (fromNumber <= 0 || groupNumber <= 0) continue;
return { cardId: other.cardId, groupNumber, fromNumber };
}
return null;
}

export function earlierOverlappingExcludeGroups(rules: ExcludeWarehouseRule[], groupIndex: number): number[] { export function earlierOverlappingExcludeGroups(rules: ExcludeWarehouseRule[], groupIndex: number): number[] {
const groups = groupExcludeRuleIndexes(rules); const groups = groupExcludeRuleIndexes(rules);
const group = groups[groupIndex]; const group = groups[groupIndex];


+ 172
- 28
src/components/DeliveryOrderFloorSettings/ExcludeWarehouseRuleList.tsx Vedi File

@@ -8,6 +8,10 @@ import {
Box, Box,
Button, Button,
Checkbox, Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControlLabel, FormControlLabel,
IconButton, IconButton,
Stack, Stack,
@@ -18,8 +22,11 @@ import {
import type { WarehousePickRow } from "@/app/api/settings/deliveryOrderFloor/client"; import type { WarehousePickRow } from "@/app/api/settings/deliveryOrderFloor/client";
import { import {
EXCLUDE_ALL_AREAS, EXCLUDE_ALL_AREAS,
combineCandidate,
earlierOverlappingExcludeGroups, earlierOverlappingExcludeGroups,
emptyExcludeWarehouseRule, emptyExcludeWarehouseRule,
ensureExcludeCardIds,
excludeCombinePairKey,
excludeRuleHasTarget, excludeRuleHasTarget,
groupExcludeRuleIndexes, groupExcludeRuleIndexes,
type ExcludePrintMode, type ExcludePrintMode,
@@ -100,6 +107,69 @@ function withoutArea(group: ExcludeWarehouseRule[], area: string, areaNames: str
.filter((rule) => tokens(rule.areas).length > 0); .filter((rule) => tokens(rule.areas).length > 0);
} }


function unionSlotCsv(leftCsv: string, rightCsv: string): string {
const left = tokens(leftCsv);
const right = tokens(rightCsv);
if (left.includes("-")) return right.includes("-") ? "-" : rightCsv;
if (right.includes("-")) return leftCsv;
if (left.length === 0 || right.length === 0) return "";
const seen = new Map<string, string>();
for (const slot of [...left, ...right]) seen.set(slot.toUpperCase(), slot);
return [...seen.values()].join(",");
}

function mergeExcludeCards(
rules: ExcludeWarehouseRule[],
sourceId: string,
targetId: string,
warehouses: WarehousePickRow[],
): ExcludeWarehouseRule[] {
const source = rules.filter((rule) => rule.cardId === sourceId);
const target = rules.filter((rule) => rule.cardId === targetId);
const base = target[0] ?? source[0];
if (!base) return rules;
const template: ExcludeWarehouseRule = { ...base, cardId: targetId, areas: "", slots: "", enabled: true };
const matched = warehouses.filter(
(row) =>
row.storeId.toUpperCase() === template.floor.trim().toUpperCase() &&
row.warehouse.toUpperCase() === template.warehouse.trim().toUpperCase(),
);
const areaNames = uniqueSorted(matched.map((row) => row.area));
const slotsByArea = new Map<string, string[]>();
matched.forEach((row) => {
const slot = slotCode(row.code);
const list = slotsByArea.get(row.area) ?? [];
if (slot && !list.some((item) => item.toUpperCase() === slot.toUpperCase())) list.push(slot);
slotsByArea.set(row.area, list);
});
const slotMap = new Map<string, string>();
const absorb = (card: ExcludeWarehouseRule[]) => {
for (const rule of card) {
const names = tokens(rule.areas).some((area) => area.toUpperCase() === EXCLUDE_ALL_AREAS) ? areaNames : tokens(rule.areas);
for (const area of names) {
const key = area.toUpperCase();
const previous = slotMap.get(key);
slotMap.set(key, previous == null ? rule.slots : unionSlotCsv(previous, rule.slots));
}
}
};
absorb(target);
absorb(source);
let built: ExcludeWarehouseRule[] = [];
for (const [key, slots] of slotMap) {
const area = areaNames.find((name) => name.toUpperCase() === key) ?? key;
const known = uniqueSorted(slotsByArea.get(area) ?? []);
const picked = tokens(slots);
const stored = known.length === 0 ? slots : slotsToStore(picked.includes("-") ? [] : picked.length === 0 ? known : known.filter((slot) => picked.some((item) => item.toUpperCase() === slot.toUpperCase())), known);
built = withAreaSlots(built, area, stored, areaNames, template);
}
built = built.map((rule) => ({ ...rule, cardId: targetId, enabled: true, printMode: template.printMode }));
const firstTarget = rules.findIndex((rule) => rule.cardId === targetId);
const rest = rules.filter((rule) => rule.cardId !== sourceId && rule.cardId !== targetId);
const insertAt = rules.slice(0, Math.max(firstTarget, 0)).filter((rule) => rule.cardId !== sourceId && rule.cardId !== targetId).length;
return [...rest.slice(0, insertAt), ...built, ...rest.slice(insertAt)];
}

function withAreaSlots( function withAreaSlots(
group: ExcludeWarehouseRule[], group: ExcludeWarehouseRule[],
area: string, area: string,
@@ -203,24 +273,72 @@ export function ExcludeWarehouseRuleList({
}) { }) {
const { t } = useTranslation("deliveryOrderFloor"); const { t } = useTranslation("deliveryOrderFloor");
const [viewByGroup, setViewByGroup] = React.useState<Record<string, string>>({}); const [viewByGroup, setViewByGroup] = React.useState<Record<string, string>>({});
const declinedCombineRef = React.useRef(new Set<string>());
const [combineAsk, setCombineAsk] = React.useState<{
next: ExcludeWarehouseRule[];
editedIndexes: number[];
targetCardId: string;
targetNumber: number;
fromNumber: number;
floor: string;
warehouse: string;
} | null>(null);
const readyRules = React.useMemo(() => ensureExcludeCardIds(rules), [rules]);

const saveGroup = (indexes: number[], nextGroup: ExcludeWarehouseRule[], askCombine = false) => {
const next = replaceGroup(readyRules, indexes, nextGroup);
if (askCombine) {
const editedIndex = indexes[0] ?? 0;
const candidate = combineCandidate(next, editedIndex, declinedCombineRef.current);
const edited = next[editedIndex];
if (candidate && edited) {
setCombineAsk({
next,
editedIndexes: Array.from({ length: nextGroup.length }, (_, offset) => editedIndex + offset),
targetCardId: candidate.cardId,
targetNumber: candidate.groupNumber,
fromNumber: candidate.fromNumber,
floor: edited.floor,
warehouse: edited.warehouse,
});
return;
}
}
onSave(next);
};

const acceptCombine = () => {
if (!combineAsk) return;
const edited = combineAsk.next[combineAsk.editedIndexes[0] ?? -1];
const merged = edited
? mergeExcludeCards(combineAsk.next, edited.cardId, combineAsk.targetCardId, warehouses)
: combineAsk.next;
setCombineAsk(null);
onSave(merged);
};


const saveGroup = (indexes: number[], nextGroup: ExcludeWarehouseRule[]) => {
onSave(replaceGroup(rules, indexes, nextGroup));
const declineCombine = () => {
if (!combineAsk) return;
const edited = combineAsk.next[combineAsk.editedIndexes[0] ?? -1];
if (edited) declinedCombineRef.current.add(excludeCombinePairKey(edited.cardId, combineAsk.targetCardId, edited));
const next = combineAsk.next;
setCombineAsk(null);
onSave(next);
}; };


const floors = uniqueSorted(warehouses.map((row) => row.storeId)); const floors = uniqueSorted(warehouses.map((row) => row.storeId));
const groups = groupExcludeRuleIndexes(rules).map((indexes) => ({ key: indexes.join(","), indexes }));
const groups = groupExcludeRuleIndexes(readyRules).map((indexes) => ({ key: indexes.join(","), indexes }));


return ( return (
<Stack spacing={1.5}> <Stack spacing={1.5}>
{rules.length === 0 ? (
{readyRules.length === 0 ? (
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{t("Empty exclude rules")} {t("Empty exclude rules")}
</Typography> </Typography>
) : ( ) : (
groups.map((group, groupIndex) => { groups.map((group, groupIndex) => {
const overlappedBy = earlierOverlappingExcludeGroups(rules, groupIndex);
const groupRules = group.indexes.map((index) => rules[index]).filter((rule): rule is ExcludeWarehouseRule => rule != null);
const overlappedBy = earlierOverlappingExcludeGroups(readyRules, groupIndex);
const groupRules = group.indexes.map((index) => readyRules[index]).filter((rule): rule is ExcludeWarehouseRule => rule != null);
const rule = groupRules[0]; const rule = groupRules[0];
if (!rule) return null; if (!rule) return null;
const warehousesForFloor = warehouses.filter( const warehousesForFloor = warehouses.filter(
@@ -258,9 +376,9 @@ export function ExcludeWarehouseRuleList({
); );
const selectableRules = groupRules.filter((item) => excludeRuleHasTarget(item)); const selectableRules = groupRules.filter((item) => excludeRuleHasTarget(item));
const canEnable = selectableRules.length > 0; const canEnable = selectableRules.length > 0;
const applyGroup = (nextGroup: ExcludeWarehouseRule[]) => {
const applyGroup = (nextGroup: ExcludeWarehouseRule[], askCombine = false) => {
const kept = nextGroup.length > 0 ? nextGroup : [{ ...rule, areas: "", slots: "" }]; const kept = nextGroup.length > 0 ? nextGroup : [{ ...rule, areas: "", slots: "" }];
saveGroup(group.indexes, kept);
saveGroup(group.indexes, kept, askCombine);
}; };
const setAreaIncluded = (area: string, included: boolean) => { const setAreaIncluded = (area: string, included: boolean) => {
if (!included) { if (!included) {
@@ -295,6 +413,7 @@ export function ExcludeWarehouseRuleList({
...item, ...item,
enabled: event.target.checked && excludeRuleHasTarget(item), enabled: event.target.checked && excludeRuleHasTarget(item),
})), })),
event.target.checked,
) )
} }
/> />
@@ -306,14 +425,14 @@ export function ExcludeWarehouseRuleList({
aria-label={t("Delete row")} aria-label={t("Delete row")}
size="small" size="small"
disabled={saving} disabled={saving}
onClick={() => onSave(rules.filter((_, ruleIndex) => !group.indexes.includes(ruleIndex)))}
onClick={() => onSave(readyRules.filter((_, ruleIndex) => !group.indexes.includes(ruleIndex)))}
> >
<DeleteOutline fontSize="small" /> <DeleteOutline fontSize="small" />
</IconButton> </IconButton>
</Stack> </Stack>
{overlappedBy.length > 0 ? ( {overlappedBy.length > 0 ? (
<Alert severity="warning" sx={{ mb: 1.5 }}> <Alert severity="warning" sx={{ mb: 1.5 }}>
{t("Putaway rule overlap", { rules: overlappedBy.map((index) => index + 1).join(", ") })}
{t("Exclude rule overlap", { rules: overlappedBy.map((index) => index + 1).join(", ") })}
</Alert> </Alert>
) : null} ) : null}
<Stack spacing={1.5}> <Stack spacing={1.5}>
@@ -324,15 +443,17 @@ export function ExcludeWarehouseRuleList({
disabled={saving} disabled={saving}
value={rule.floor || null} value={rule.floor || null}
onChange={(next) => onChange={(next) =>
applyGroup([
{
...rule,
floor: typeof next === "string" ? next : "",
warehouse: "",
areas: "",
slots: "",
},
])
applyGroup(
[
{
...rule,
floor: typeof next === "string" ? next : "",
warehouse: "",
areas: "",
slots: "",
},
],
)
} }
/> />
<ToggleRows <ToggleRows
@@ -342,14 +463,16 @@ export function ExcludeWarehouseRuleList({
disabled={saving || !rule.floor} disabled={saving || !rule.floor}
value={rule.warehouse || null} value={rule.warehouse || null}
onChange={(next) => onChange={(next) =>
applyGroup([
{
...rule,
warehouse: typeof next === "string" ? next : "",
areas: "",
slots: "",
},
])
applyGroup(
[
{
...rule,
warehouse: typeof next === "string" ? next : "",
areas: "",
slots: "",
},
],
)
} }
/> />
<Box sx={{ display: "flex", alignItems: "flex-start", gap: 1.5, minWidth: 0 }}> <Box sx={{ display: "flex", alignItems: "flex-start", gap: 1.5, minWidth: 0 }}>
@@ -502,11 +625,32 @@ export function ExcludeWarehouseRuleList({
<Button <Button
variant="outlined" variant="outlined"
disabled={saving} disabled={saving}
onClick={() => onSave([...rules, emptyExcludeWarehouseRule()])}
onClick={() => onSave([...readyRules, emptyExcludeWarehouseRule()])}
sx={{ alignSelf: "flex-start" }} sx={{ alignSelf: "flex-start" }}
> >
{t("Add exclude rule")} {t("Add exclude rule")}
</Button> </Button>
<Dialog open={combineAsk != null} onClose={declineCombine} fullWidth maxWidth="xs">
<DialogTitle>{t("Combine exclude rules title")}</DialogTitle>
<DialogContent>
<Typography variant="body2">
{combineAsk
? t("Combine exclude rules", {
from: combineAsk.fromNumber,
to: combineAsk.targetNumber,
floor: combineAsk.floor,
warehouse: combineAsk.warehouse,
})
: ""}
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={declineCombine}>{t("Combine decline")}</Button>
<Button variant="contained" onClick={acceptCombine}>
{t("Combine accept")}
</Button>
</DialogActions>
</Dialog>
</Stack> </Stack>
); );
} }

+ 5
- 0
src/i18n/en/deliveryOrderFloor.json Vedi File

@@ -9,6 +9,11 @@
"Putaway rule n": "Rule {{n}}", "Putaway rule n": "Rule {{n}}",
"Rule enabled": "Enabled", "Rule enabled": "Enabled",
"Putaway rule overlap": "Overlaps rule {{rules}}. Within the same order type, the first match wins, so overlapping items will not use this rule.", "Putaway rule overlap": "Overlaps rule {{rules}}. Within the same order type, the first match wins, so overlapping items will not use this rule.",
"Exclude rule overlap": "Overlaps rule {{rules}}.",
"Combine exclude rules title": "Combine into one card?",
"Combine exclude rules": "Rule {{from}} and rule {{to}} are both {{floor}} / {{warehouse}}, and their slots do not overlap. Combine them before enabling? If not, this rule stays separate and is still enabled.",
"Combine accept": "Combine",
"Combine decline": "Keep separate",
"Empty putaway rules": "No automatic put-away rules.", "Empty putaway rules": "No automatic put-away rules.",
"Rule removed pending save": "This rule is removed. Save to keep the change.", "Rule removed pending save": "This rule is removed. Save to keep the change.",
"All items": "All items", "All items": "All items",


+ 5
- 0
src/i18n/zh/deliveryOrderFloor.json Vedi File

@@ -9,6 +9,11 @@
"Putaway rule n": "規則 {{n}}", "Putaway rule n": "規則 {{n}}",
"Rule enabled": "啟用", "Rule enabled": "啟用",
"Putaway rule overlap": "與規則 {{rules}} 條件重疊。", "Putaway rule overlap": "與規則 {{rules}} 條件重疊。",
"Exclude rule overlap": "與規則 {{rules}} 條件重疊。",
"Combine exclude rules title": "合併成同一張卡?",
"Combine exclude rules": "規則 {{from}} 和規則 {{to}} 都是 {{floor}} / {{warehouse}},儲位沒有重疊。要合併後再啟用嗎?不合併就分開保留,這條規則仍然啟用。",
"Combine accept": "合併",
"Combine decline": "分開保留",
"Empty putaway rules": "目前沒有自動上架規則。", "Empty putaway rules": "目前沒有自動上架規則。",
"Rule removed pending save": "這條規則已移除,按儲存變更後才會寫入。", "Rule removed pending save": "這條規則已移除,按儲存變更後才會寫入。",
"All items": "全部貨品", "All items": "全部貨品",


Caricamento…
Annulla
Salva