({
categoryName: "",
@@ -862,7 +864,9 @@ function CreateLabelCategoryDialog({
setDisplayTextForPhoto("");
setButtonBgColor("#3B82F6");
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
categoryName: "",
@@ -920,27 +924,20 @@ function CreateLabelCategoryDialog({
}
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
@@ -971,8 +968,7 @@ function CreateLabelCategoryDialog({
buttonImageUrl: apSel.image ? String(form.categoryPhotoUrl ?? "").trim() || null : null,
buttonAppearance: tokens,
buttonStyleJson,
- availabilityType: "SPECIFIED",
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label category created.", {
description: "The label category has been created successfully.",
@@ -1022,6 +1018,11 @@ function CreateLabelCategoryDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -1228,11 +1229,14 @@ function EditLabelCategoryDialog({
onUpdated: () => void;
}) {
const [submitting, setSubmitting] = useState(false);
+ const [loadingDetail, setLoadingDetail] = useState(false);
const [apSel, setApSel] = useState({ text: true, color: false, image: false });
const [displayTextForPhoto, setDisplayTextForPhoto] = useState("");
const [buttonBgColor, setButtonBgColor] = useState("#3B82F6");
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
categoryName: "",
@@ -1249,104 +1253,145 @@ function EditLabelCategoryDialog({
}, [apSel.text, apSel.color]);
useEffect(() => {
- if (!open || !category) return;
+ if (!open || !category?.id) return;
+
+ const ac = new AbortController();
+ setLoadingDetail(true);
+
+ (async () => {
+ try {
+ const detail = await getLabelCategory(category.id, ac.signal);
+ if (ac.signal.aborted) return;
+ const row = detail.id ? detail : category;
+
+ const hydrateAvailability = () => {
+ if (requireCompanySelection) {
+ const scope = hydrateEntityScopeFromDto(row, locations, partners, groups);
+ setSelectedPartnerIds(scope.partnerIds);
+ setSelectedRegionIds(scope.regionIds);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ return;
+ }
+ const rawAt = String(row.availabilityType ?? "ALL").trim().toUpperCase();
+ if (rawAt !== "SPECIFIED") {
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
+ return;
+ }
+ const lids = (row.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
+ const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
+ setSelectedPartnerId(scope.partnerId);
+ setSelectedRegionNames(scope.regionNames);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
+ };
+
+ const styleParsed = parseCategoryButtonStyleV1(
+ row.buttonStyleJson ?? (row as { ButtonStyleJson?: string | null }).ButtonStyleJson,
+ );
+ if (styleParsed) {
+ const sel = appearanceSelectionFromTokens(styleParsed.appearances);
+ setApSel(sel);
+ setDisplayTextForPhoto(String(styleParsed.displayText ?? ""));
+ setButtonBgColor(styleParsed.buttonBgColor || "#3B82F6");
+ setForm({
+ categoryName: row.categoryName ?? "",
+ categoryPhotoUrl: sel.image ? row.categoryPhotoUrl ?? null : null,
+ state: row.state ?? true,
+ orderNum: row.orderNum ?? null,
+ });
+ hydrateAvailability();
+ return;
+ }
- const hydrateAvailability = () => {
- const rawAt = String(category.availabilityType ?? "ALL").trim().toUpperCase();
- if (rawAt !== "SPECIFIED") {
+ const rawBa =
+ row.buttonAppearance ??
+ (row as { ButtonAppearance?: string | null }).ButtonAppearance ??
+ undefined;
+ const tokens = parseAppearanceTokens(rawBa);
+ const valArrZip = parseCategoryPhotoUrlValueArray(row.categoryPhotoUrl);
+ if (valArrZip && tokens.length > 0 && tokens.length === valArrZip.length) {
+ const merged = visualInputFromAppearanceAndValueArray(tokens, valArrZip, {
+ categoryName: row.categoryName,
+ name: undefined,
+ buttonTextColor: row.buttonTextColor ?? null,
+ });
+ const selZip = appearanceSelectionFromTokens(tokens);
+ setApSel(selZip);
+ setDisplayTextForPhoto(String(merged.displayText ?? ""));
+ setButtonBgColor(merged.buttonBgColor?.trim() ? merged.buttonBgColor : "#3B82F6");
+ setForm({
+ categoryName: row.categoryName ?? "",
+ categoryPhotoUrl: selZip.image ? (merged.buttonImageUrl ?? "").trim() || null : null,
+ state: row.state ?? true,
+ orderNum: row.orderNum ?? null,
+ });
+ hydrateAvailability();
+ return;
+ }
+
+ let sel = appearanceSelectionFromTokens(tokens);
+ if (tokens.length === 0) {
+ const v = resolveCategoryButtonVisual({
+ buttonAppearance: rawBa,
+ displayText: row.displayText,
+ buttonBgColor: row.buttonBgColor,
+ buttonImageUrl: row.buttonImageUrl,
+ categoryPhotoUrl: row.categoryPhotoUrl,
+ categoryName: row.categoryName,
+ });
+ if (v.mode === "image") sel = { text: false, color: false, image: true };
+ else if (v.mode === "colorText") sel = { text: true, color: true, image: false };
+ else if (v.mode === "color") sel = { text: false, color: true, image: false };
+ else if (v.mode === "text") sel = { text: true, color: false, image: false };
+ else sel = { text: true, color: false, image: false };
+ }
+ setApSel(sel);
+ const photo = String(row.categoryPhotoUrl ?? "").trim();
+ const disp = String(row.displayText ?? "").trim();
+ const bgField = normalizeHexColor(row.buttonBgColor);
+ const hexFromPhoto = normalizeHexColor(photo);
+ setDisplayTextForPhoto(
+ String(
+ disp ||
+ (!sel.image && sel.text && photo && !hexFromPhoto && !looksLikeImageUrl(photo) ? photo : "") ||
+ (!sel.image && sel.text && !sel.color ? (row.categoryName ?? "").trim() : ""),
+ ),
+ );
+ setButtonBgColor(bgField || (sel.color && hexFromPhoto ? hexFromPhoto : "") || "#3B82F6");
+ setForm({
+ categoryName: row.categoryName ?? "",
+ categoryPhotoUrl: sel.image ? row.categoryPhotoUrl ?? null : null,
+ state: row.state ?? true,
+ orderNum: row.orderNum ?? null,
+ });
+ hydrateAvailability();
+ } catch {
+ if (ac.signal.aborted) return;
+ setForm({
+ categoryName: category.categoryName ?? "",
+ categoryPhotoUrl: category.categoryPhotoUrl ?? null,
+ state: category.state ?? true,
+ orderNum: category.orderNum ?? null,
+ });
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
- return;
+ } finally {
+ if (!ac.signal.aborted) setLoadingDetail(false);
}
- const lids = (category.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
- const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerId(scope.partnerId);
- setSelectedRegionNames(scope.regionNames);
- setSelectedLocationIds(scope.locationIds);
- };
-
- const styleParsed = parseCategoryButtonStyleV1(
- category.buttonStyleJson ?? (category as { ButtonStyleJson?: string | null }).ButtonStyleJson,
- );
- if (styleParsed) {
- const sel = appearanceSelectionFromTokens(styleParsed.appearances);
- setApSel(sel);
- setDisplayTextForPhoto(String(styleParsed.displayText ?? ""));
- setButtonBgColor(styleParsed.buttonBgColor || "#3B82F6");
- setForm({
- categoryName: category.categoryName ?? "",
- categoryPhotoUrl: sel.image ? category.categoryPhotoUrl ?? null : null,
- state: category.state ?? true,
- orderNum: category.orderNum ?? null,
- });
- hydrateAvailability();
- return;
- }
-
- const rawBa =
- category.buttonAppearance ??
- (category as { ButtonAppearance?: string | null }).ButtonAppearance ??
- undefined;
- const tokens = parseAppearanceTokens(rawBa);
- const valArrZip = parseCategoryPhotoUrlValueArray(category.categoryPhotoUrl);
- if (valArrZip && tokens.length > 0 && tokens.length === valArrZip.length) {
- const merged = visualInputFromAppearanceAndValueArray(tokens, valArrZip, {
- categoryName: category.categoryName,
- name: undefined,
- buttonTextColor: category.buttonTextColor ?? null,
- });
- const selZip = appearanceSelectionFromTokens(tokens);
- setApSel(selZip);
- setDisplayTextForPhoto(String(merged.displayText ?? ""));
- setButtonBgColor(merged.buttonBgColor?.trim() ? merged.buttonBgColor : "#3B82F6");
- setForm({
- categoryName: category.categoryName ?? "",
- categoryPhotoUrl: selZip.image ? (merged.buttonImageUrl ?? "").trim() || null : null,
- state: category.state ?? true,
- orderNum: category.orderNum ?? null,
- });
- hydrateAvailability();
- return;
- }
+ })();
- let sel = appearanceSelectionFromTokens(tokens);
- if (tokens.length === 0) {
- const v = resolveCategoryButtonVisual({
- buttonAppearance: rawBa,
- displayText: category.displayText,
- buttonBgColor: category.buttonBgColor,
- buttonImageUrl: category.buttonImageUrl,
- categoryPhotoUrl: category.categoryPhotoUrl,
- categoryName: category.categoryName,
- });
- if (v.mode === "image") sel = { text: false, color: false, image: true };
- else if (v.mode === "colorText") sel = { text: true, color: true, image: false };
- else if (v.mode === "color") sel = { text: false, color: true, image: false };
- else if (v.mode === "text") sel = { text: true, color: false, image: false };
- else sel = { text: true, color: false, image: false };
- }
- setApSel(sel);
- const photo = String(category.categoryPhotoUrl ?? "").trim();
- const disp = String(category.displayText ?? "").trim();
- const bgField = normalizeHexColor(category.buttonBgColor);
- const hexFromPhoto = normalizeHexColor(photo);
- setDisplayTextForPhoto(
- String(
- disp ||
- (!sel.image && sel.text && photo && !hexFromPhoto && !looksLikeImageUrl(photo) ? photo : "") ||
- (!sel.image && sel.text && !sel.color ? (category.categoryName ?? "").trim() : ""),
- ),
- );
- setButtonBgColor(bgField || (sel.color && hexFromPhoto ? hexFromPhoto : "") || "#3B82F6");
- setForm({
- categoryName: category.categoryName ?? "",
- categoryPhotoUrl: sel.image ? category.categoryPhotoUrl ?? null : null,
- state: category.state ?? true,
- orderNum: category.orderNum ?? null,
- });
- hydrateAvailability();
- }, [open, category, locations, partners, groups]);
+ return () => ac.abort();
+ }, [open, category, locations, partners, groups, requireCompanySelection]);
const submit = async () => {
if (!category?.id) return;
@@ -1391,27 +1436,20 @@ function EditLabelCategoryDialog({
}
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
@@ -1442,8 +1480,7 @@ function EditLabelCategoryDialog({
buttonImageUrl: apSel.image ? String(form.categoryPhotoUrl ?? "").trim() || null : null,
buttonAppearance: tokens,
buttonStyleJson,
- availabilityType: "SPECIFIED",
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label category updated.", {
description: "The label category has been updated successfully.",
@@ -1481,19 +1518,28 @@ function EditLabelCategoryDialog({
/>
-
+ {loadingDetail ? (
+ Loading company, region and location…
+ ) : (
+
+ )}
Button Appearance
@@ -1665,7 +1711,7 @@ function EditLabelCategoryDialog({
diff --git a/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx b/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
index bf48672..6be29a6 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
@@ -51,11 +51,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildSpecifiedLocationPayload,
- effectiveScopePartnerId,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- regionNamesToGroupIds,
- scopePartnerValidationMessage,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -822,7 +820,9 @@ function CreateLabelTypeDialog({
}) {
const [submitting, setSubmitting] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
typeName: "",
@@ -832,7 +832,9 @@ function CreateLabelTypeDialog({
const resetForm = () => {
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
typeName: "",
@@ -859,40 +861,29 @@ function CreateLabelTypeDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await createLabelType({
...form,
typeCode: typeCodeFromName(form.typeName),
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label type created.", {
description: "The label type has been created successfully.",
@@ -956,6 +947,11 @@ function CreateLabelTypeDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -996,7 +992,9 @@ function EditLabelTypeDialog({
const [submitting, setSubmitting] = useState(false);
const [loadingDetail, setLoadingDetail] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
typeName: "",
@@ -1021,12 +1019,24 @@ function EditLabelTypeDialog({
orderNum: detail.orderNum ?? type.orderNum ?? null,
});
+ if (requireCompanySelection) {
+ const scope = hydrateEntityScopeFromDto(detail, locations, partners, groups);
+ setSelectedPartnerIds(scope.partnerIds);
+ setSelectedRegionIds(scope.regionIds);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ return;
+ }
+
const lids = (detail.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
if (lids.length > 0) {
const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
setSelectedPartnerId(scope.partnerId);
setSelectedRegionNames(scope.regionNames);
setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1042,6 +1052,8 @@ function EditLabelTypeDialog({
setSelectedPartnerId(pid);
setSelectedRegionNames(names);
setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1057,6 +1069,8 @@ function EditLabelTypeDialog({
setSelectedRegionNames([]);
setSelectedLocationIds([]);
}
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
} catch {
if (ac.signal.aborted) return;
setForm({
@@ -1065,7 +1079,9 @@ function EditLabelTypeDialog({
orderNum: type.orderNum ?? null,
});
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
} finally {
if (!ac.signal.aborted) setLoadingDetail(false);
@@ -1073,7 +1089,7 @@ function EditLabelTypeDialog({
})();
return () => ac.abort();
- }, [open, type, locations, partners, groups]);
+ }, [open, type, locations, partners, groups, requireCompanySelection]);
const submit = async () => {
if (!type?.id) return;
@@ -1088,40 +1104,29 @@ function EditLabelTypeDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await updateLabelType(type.id, {
...form,
typeCode: type.typeCode ?? "",
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label type updated.", {
description: "The label type has been updated successfully.",
@@ -1188,6 +1193,11 @@ function EditLabelTypeDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
)}
diff --git a/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx b/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
index 8c4f6bf..84be6ea 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
@@ -51,11 +51,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildSpecifiedLocationPayload,
- effectiveScopePartnerId,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- regionNamesToGroupIds,
- scopePartnerValidationMessage,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -806,7 +804,9 @@ function CreateMultipleOptionDialog({
}) {
const [submitting, setSubmitting] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
optionName: "",
@@ -818,7 +818,9 @@ function CreateMultipleOptionDialog({
const resetForm = () => {
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
optionName: "",
@@ -876,40 +878,29 @@ function CreateMultipleOptionDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await createLabelMultipleOption({
...form,
optionCode: optionCodeFromName(form.optionName),
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Multiple option created.", {
description: "The multiple option has been created successfully.",
@@ -1009,6 +1000,11 @@ function CreateMultipleOptionDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -1049,7 +1045,9 @@ function EditMultipleOptionDialog({
const [submitting, setSubmitting] = useState(false);
const [loadingDetail, setLoadingDetail] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
optionName: "",
@@ -1078,12 +1076,24 @@ function EditMultipleOptionDialog({
});
setNewValue("");
+ if (requireCompanySelection) {
+ const scope = hydrateEntityScopeFromDto(detail, locations, partners, groups);
+ setSelectedPartnerIds(scope.partnerIds);
+ setSelectedRegionIds(scope.regionIds);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ return;
+ }
+
const lids = (detail.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
if (lids.length > 0) {
const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
setSelectedPartnerId(scope.partnerId);
setSelectedRegionNames(scope.regionNames);
setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1099,6 +1109,8 @@ function EditMultipleOptionDialog({
setSelectedPartnerId(pid);
setSelectedRegionNames(names);
setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1114,6 +1126,8 @@ function EditMultipleOptionDialog({
setSelectedRegionNames([]);
setSelectedLocationIds([]);
}
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
} catch {
if (ac.signal.aborted) return;
setForm({
@@ -1123,7 +1137,9 @@ function EditMultipleOptionDialog({
orderNum: option.orderNum ?? null,
});
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setNewValue("");
} finally {
@@ -1132,7 +1148,7 @@ function EditMultipleOptionDialog({
})();
return () => ac.abort();
- }, [open, option, locations, partners, groups]);
+ }, [open, option, locations, partners, groups, requireCompanySelection]);
const addValue = () => {
const trimmed = newValue.trim();
@@ -1176,40 +1192,29 @@ function EditMultipleOptionDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await updateLabelMultipleOption(option.id, {
...form,
optionCode: option.optionCode ?? "",
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Multiple option updated.", {
description: "The multiple option has been updated successfully.",
@@ -1312,6 +1317,11 @@ function EditMultipleOptionDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
)}
diff --git a/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx b/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx
index eb986aa..8bf3835 100644
--- a/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx
@@ -390,15 +390,30 @@ export function CategoryScopeFields({
{requireCompanySelection ? (
Company *
-
-
Required. Select company before region and location.
+ {templateScopeMode ? (
+
+ ) : (
+
+ )}
+
+ {templateScopeMode
+ ? "Required. Leave empty or select all for every company; narrows region and location lists."
+ : "Required. Select company before region and location."}
+
) : null}
@@ -406,16 +421,24 @@ export function CategoryScopeFields({
Region *
- {companyReady ? "Required. Narrows the location list." : requireCompanySelection ? "Select a company first." : "Your account company is loading or not assigned."}
+ {templateScopeMode
+ ? companyReady
+ ? "Required. Leave empty or select all for every region in selected companies."
+ : "Select company first."
+ : companyReady
+ ? "Required. Narrows the location list."
+ : requireCompanySelection
+ ? "Select a company first."
+ : "Your account company is loading or not assigned."}
@@ -425,22 +448,34 @@ export function CategoryScopeFields({
onValuesChange={onLocationChange}
options={locationOptionsForPicker}
placeholder={
- companyReady
- ? "Select location(s)…"
- : requireCompanySelection
- ? "Select company first"
- : "Loading company scope…"
+ templateScopeMode
+ ? companyReady
+ ? "All Locations"
+ : requireCompanySelection
+ ? "Select company first"
+ : "Loading company scope…"
+ : companyReady
+ ? "Select location(s)…"
+ : requireCompanySelection
+ ? "Select company first"
+ : "Loading company scope…"
}
searchPlaceholder="Search locations…"
selectAllRowLabel="ALL"
- disabled={!companyReady}
+ disabled={!templateScopeMode && !companyReady}
/>
- {companyReady
- ? "Required. ALL selects every location in the filtered list (within selected regions)."
- : requireCompanySelection
- ? "Select a company first."
- : "Your account company is loading or not assigned."}
+ {templateScopeMode
+ ? companyReady
+ ? "Required. ALL selects every location in the filtered list (within selected regions)."
+ : requireCompanySelection
+ ? "Select a company first."
+ : "Your account company is loading or not assigned."
+ : companyReady
+ ? "Required. ALL selects every location in the filtered list (within selected regions)."
+ : requireCompanySelection
+ ? "Select a company first."
+ : "Your account company is loading or not assigned."}
diff --git a/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts b/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
index e9ff5f4..43b3601 100644
--- a/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
+++ b/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
@@ -1,6 +1,7 @@
import type { GroupListItem } from "../types/group";
import type { LocationDto } from "../types/location";
import type { PartnerListItem } from "../types/partner";
+import type { AppliedScopeType } from "../types/labelTemplate";
import { parseAppliedScopeType } from "../types/labelTemplate";
/** 表单提交用的公司 Id(非管理员取账号绑定公司) */
@@ -424,3 +425,223 @@ export function buildProductScopeSavePayload(
}
return { locationIds: locIds };
}
+
+function resolveScopeDimensionForSave(
+ selected: string[],
+ available: string[],
+): { type: AppliedScopeType; ids: string[] } {
+ const avail = dedupeIds(available);
+ const sel = dedupeIds(selected).filter((id) => !avail.length || avail.includes(id));
+ if (!avail.length || !sel.length) {
+ return { type: "ALL", ids: [] };
+ }
+ if (sel.length >= avail.length && avail.every((id) => sel.includes(id))) {
+ return { type: "ALL", ids: [] };
+ }
+ return { type: "SPECIFIED", ids: sel };
+}
+
+/** 模板/实体表单:按当前选中 Id 计算可选 Company / Region / Location 全集 */
+export function computeTemplateScopeAvailableIds(
+ partners: PartnerListItem[],
+ groups: GroupListItem[],
+ locations: LocationDto[],
+ selectedPartnerIds: string[],
+ selectedRegionIds: string[],
+): {
+ availablePartnerIds: string[];
+ availableRegionIds: string[];
+ availableLocationIds: string[];
+} {
+ const availablePartnerIds = partners.map((p) => String(p.id ?? "").trim()).filter(Boolean);
+ const effectivePartnerIds =
+ selectedPartnerIds.length > 0 ? selectedPartnerIds : availablePartnerIds;
+ const availableRegionIds = regionOptionsForPartners(groups, effectivePartnerIds).map(
+ (o) => o.value,
+ );
+ const availableLocationIds = locationsScopedForTemplateScope(
+ locations,
+ partners,
+ groups,
+ effectivePartnerIds,
+ selectedRegionIds,
+ ).map((l) => l.id);
+ return { availablePartnerIds, availableRegionIds, availableLocationIds };
+}
+
+export type EntityScopePayloadBody = {
+ appliedPartnerType: AppliedScopeType;
+ partnerIds: string[];
+ companyIds: string[];
+ regionIds: string[];
+ groupIds: string[];
+ locationIds: string[];
+ availabilityType: "ALL" | "SPECIFIED";
+};
+
+/**
+ * 标签类型 / 分类 / 多选项保存:Company + Region + Location 三维适用范围。
+ * 空选或全选当前可选项 → 该维度 ALL;部分选中 → SPECIFIED + Id 数组。
+ */
+export function buildEntityPartnerScopePayload(input: {
+ selectedPartnerIds: string[];
+ selectedRegionIds: string[];
+ selectedLocationIds: string[];
+ availablePartnerIds: string[];
+ availableRegionIds: string[];
+ availableLocationIds: string[];
+}): { ok: true; body: EntityScopePayloadBody } | { ok: false; message: string } {
+ const partnerDim = resolveScopeDimensionForSave(
+ input.selectedPartnerIds,
+ input.availablePartnerIds,
+ );
+ const regionDim = resolveScopeDimensionForSave(
+ input.selectedRegionIds,
+ input.availableRegionIds,
+ );
+ const locationDim = resolveScopeDimensionForSave(
+ input.selectedLocationIds,
+ input.availableLocationIds,
+ );
+
+ const locationSpecified =
+ regionDim.type === "SPECIFIED" || locationDim.type === "SPECIFIED";
+ const availabilityType: "ALL" | "SPECIFIED" = locationSpecified ? "SPECIFIED" : "ALL";
+
+ const anySpecified =
+ partnerDim.type === "SPECIFIED" || locationSpecified;
+ const effectiveLocationIds =
+ locationDim.type === "SPECIFIED" ? locationDim.ids : input.availableLocationIds;
+
+ if (anySpecified && effectiveLocationIds.length === 0) {
+ return {
+ ok: false,
+ message: "Please adjust company, region, or location so at least one store matches.",
+ };
+ }
+
+ const partnerIds = partnerDim.type === "SPECIFIED" ? partnerDim.ids : [];
+ const regionIds = regionDim.type === "SPECIFIED" ? regionDim.ids : [];
+ const locationIds = locationDim.type === "SPECIFIED" ? locationDim.ids : [];
+
+ return {
+ ok: true,
+ body: {
+ appliedPartnerType: partnerDim.type,
+ partnerIds,
+ companyIds: partnerIds,
+ regionIds,
+ groupIds: regionIds,
+ locationIds,
+ availabilityType,
+ },
+ };
+}
+
+/** 创建/编辑弹窗:按管理员多选或单公司模式构建保存 Body */
+export function buildEntityScopeSaveFromForm(input: {
+ requireCompanySelection: boolean;
+ selectedPartnerId: string;
+ selectedPartnerIds: string[];
+ selectedRegionNames: string[];
+ selectedRegionIds: string[];
+ selectedLocationIds: string[];
+ fixedPartnerId: string;
+ locations: LocationDto[];
+ partners: PartnerListItem[];
+ groups: GroupListItem[];
+}): { ok: true; body: EntityScopePayloadBody } | { ok: false; message: string } {
+ if (input.requireCompanySelection) {
+ const { availablePartnerIds, availableRegionIds, availableLocationIds } =
+ computeTemplateScopeAvailableIds(
+ input.partners,
+ input.groups,
+ input.locations,
+ input.selectedPartnerIds,
+ input.selectedRegionIds,
+ );
+ return buildEntityPartnerScopePayload({
+ selectedPartnerIds: input.selectedPartnerIds,
+ selectedRegionIds: input.selectedRegionIds,
+ selectedLocationIds: input.selectedLocationIds,
+ availablePartnerIds,
+ availableRegionIds,
+ availableLocationIds,
+ });
+ }
+
+ const scopePartnerId = effectiveScopePartnerId(
+ false,
+ input.selectedPartnerId,
+ input.fixedPartnerId,
+ );
+ const partnerErr = scopePartnerValidationMessage(false, scopePartnerId);
+ if (partnerErr) {
+ return { ok: false, message: partnerErr };
+ }
+
+ const locPayload = buildSpecifiedLocationPayload(
+ input.locations,
+ input.partners,
+ input.groups,
+ scopePartnerId,
+ input.selectedRegionNames,
+ input.selectedLocationIds,
+ );
+ if (!locPayload.ok) {
+ return { ok: false, message: locPayload.message };
+ }
+
+ const groupIds = regionNamesToGroupIds(
+ input.selectedRegionNames,
+ input.groups,
+ scopePartnerId,
+ );
+ const partnerIds = scopePartnerId ? [scopePartnerId] : [];
+ return {
+ ok: true,
+ body: {
+ appliedPartnerType: partnerIds.length ? "SPECIFIED" : "ALL",
+ partnerIds,
+ companyIds: partnerIds,
+ regionIds: groupIds,
+ groupIds,
+ locationIds: locPayload.locationIds,
+ availabilityType: "SPECIFIED",
+ },
+ };
+}
+
+/** 编辑弹窗:从详情 DTO 回填三维 Id 数组(管理员多选模式) */
+export function hydrateEntityScopeFromDto(
+ dto: {
+ appliedPartnerType?: string | null;
+ appliedRegionType?: string | null;
+ appliedLocation?: string | null;
+ appliedLocationType?: string | null;
+ availabilityType?: string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ regionIds?: string[] | null;
+ groupIds?: string[] | null;
+ locationIds?: string[] | null;
+ appliedLocationIds?: string[] | null;
+ },
+ locations: LocationDto[],
+ partners: PartnerListItem[],
+ groups: GroupListItem[],
+): { partnerIds: string[]; regionIds: string[]; locationIds: string[] } {
+ return hydrateLabelTemplateScopeFromDto(
+ {
+ ...dto,
+ appliedLocation:
+ dto.appliedLocation ??
+ (String(dto.availabilityType ?? "").trim().toUpperCase() === "SPECIFIED"
+ ? "SPECIFIED"
+ : dto.appliedLocation),
+ },
+ locations,
+ partners,
+ groups,
+ );
+}
diff --git a/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts b/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts
index 6b18ab5..6c6dbfd 100644
--- a/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts
+++ b/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts
@@ -22,6 +22,13 @@ const PATH = "/label-category";
function normalizeLabelCategoryDto(raw: unknown): LabelCategoryDto {
const r = (raw && typeof raw === "object" ? raw : {}) as Record;
+ const parseIds = (v: unknown): string[] | undefined => {
+ if (!Array.isArray(v)) return undefined;
+ return [...new Set(v.map((x) => String(x).trim()).filter(Boolean))];
+ };
+ const groupIds = parseIds(r.groupIds ?? r.GroupIds ?? r.regionIds ?? r.RegionIds);
+ const locationIds = parseIds(r.locationIds ?? r.LocationIds);
+ const partnerIds = parseIds(r.partnerIds ?? r.PartnerIds ?? r.companyIds ?? r.CompanyIds);
const noRaw = r.noOfLabels ?? r.NoOfLabels;
const noOfLabels =
typeof noRaw === "number" && Number.isFinite(noRaw)
@@ -36,7 +43,60 @@ function normalizeLabelCategoryDto(raw: unknown): LabelCategoryDto {
: typeof orderRaw === "string" && orderRaw.trim() !== "" && Number.isFinite(Number(orderRaw))
? Number(orderRaw)
: null;
- return { ...(r as object), id: String(r.id ?? r.Id ?? ""), noOfLabels, orderNum } as LabelCategoryDto;
+ const companyRaw = r.company ?? r.Company;
+ return {
+ ...(r as object),
+ id: String(r.id ?? r.Id ?? ""),
+ appliedPartnerType: String(r.appliedPartnerType ?? r.AppliedPartnerType ?? "").trim() || undefined,
+ partnerIds,
+ companyIds: partnerIds,
+ availabilityType: String(r.availabilityType ?? r.AvailabilityType ?? "").trim() || undefined,
+ groupIds,
+ regionIds: groupIds,
+ locationIds,
+ company: typeof companyRaw === "string" ? companyRaw : null,
+ noOfLabels,
+ orderNum,
+ } as LabelCategoryDto;
+}
+
+function scopeBodyFromInput(input: LabelCategoryCreateInput): Record {
+ const groupIds = [
+ ...new Set(
+ [...(input.groupIds ?? []), ...(input.regionIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const locationIds = [
+ ...new Set((input.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean)),
+ ];
+ const partnerIds = [
+ ...new Set(
+ [...(input.partnerIds ?? []), ...(input.companyIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const body: Record = {};
+ const appliedPartnerType = String(input.appliedPartnerType ?? "").trim().toUpperCase();
+ if (appliedPartnerType === "ALL" || appliedPartnerType === "SPECIFIED") {
+ body.appliedPartnerType = appliedPartnerType;
+ }
+ if (partnerIds.length) {
+ body.partnerIds = partnerIds;
+ body.companyIds = partnerIds;
+ }
+ const availabilityType = String(input.availabilityType ?? "ALL").trim().toUpperCase() || "ALL";
+ body.availabilityType = availabilityType;
+ if (groupIds.length) {
+ body.groupIds = groupIds;
+ body.regionIds = groupIds;
+ }
+ if (locationIds.length) {
+ body.locationIds = locationIds;
+ }
+ return body;
}
export async function getLabelCategories(input: LabelCategoryGetListInput, signal?: AbortSignal): Promise> {
@@ -60,11 +120,12 @@ export async function getLabelCategories(input: LabelCategoryGetListInput, signa
}
export async function getLabelCategory(id: string, signal?: AbortSignal): Promise {
- return api.requestJson({
+ const raw = await api.requestJson({
path: `${PATH}/${encodeURIComponent(id)}`,
method: "GET",
signal,
});
+ return normalizeLabelCategoryDto(raw);
}
export async function createLabelCategory(input: LabelCategoryCreateInput): Promise {
@@ -82,17 +143,13 @@ export async function createLabelCategory(input: LabelCategoryCreateInput): Prom
buttonStyleJson: (input.buttonStyleJson ?? "").trim() || null,
state: input.state ?? true,
orderNum: input.orderNum,
- availabilityType: String(input.availabilityType ?? "ALL").trim().toUpperCase() || "ALL",
- locationIds:
- Array.isArray(input.locationIds) && input.locationIds.length
- ? [...new Set(input.locationIds.map((x) => String(x).trim()).filter(Boolean))]
- : null,
+ ...scopeBodyFromInput(input),
},
});
}
export async function updateLabelCategory(id: string, input: LabelCategoryUpdateInput): Promise {
- return api.requestJson({
+ const raw = await api.requestJson({
path: `${PATH}/${encodeURIComponent(id)}`,
method: "PUT",
body: {
@@ -106,13 +163,10 @@ export async function updateLabelCategory(id: string, input: LabelCategoryUpdate
buttonStyleJson: (input.buttonStyleJson ?? "").trim() || null,
state: input.state ?? true,
orderNum: input.orderNum,
- availabilityType: String(input.availabilityType ?? "ALL").trim().toUpperCase() || "ALL",
- locationIds:
- Array.isArray(input.locationIds) && input.locationIds.length
- ? [...new Set(input.locationIds.map((x) => String(x).trim()).filter(Boolean))]
- : null,
+ ...scopeBodyFromInput(input),
},
});
+ return normalizeLabelCategoryDto(raw);
}
export async function deleteLabelCategory(id: string): Promise {
diff --git a/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts b/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts
index 88d3a70..15da9ad 100644
--- a/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts
+++ b/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts
@@ -63,13 +63,35 @@ function normalizeLabelMultipleOptionDto(item: LabelMultipleOptionDto): LabelMul
const locationIds = parseIds(
(raw as Record).locationIds ?? (raw as Record).LocationIds,
);
+ const partnerIds = parseIds(
+ (raw as Record).partnerIds ??
+ (raw as Record).PartnerIds ??
+ (raw as Record).companyIds ??
+ (raw as Record).CompanyIds,
+ );
+ const companyRaw = (raw as Record).company ?? (raw as Record).Company;
return {
...item,
optionValuesJson: parseOptionValuesJsonField(raw.optionValuesJson),
lastEdited,
+ appliedPartnerType:
+ String(
+ (raw as Record).appliedPartnerType ??
+ (raw as Record).AppliedPartnerType ??
+ "",
+ ).trim() || undefined,
+ partnerIds,
+ companyIds: partnerIds,
+ availabilityType:
+ String(
+ (raw as Record).availabilityType ??
+ (raw as Record).AvailabilityType ??
+ "",
+ ).trim() || undefined,
groupIds,
regionIds: groupIds,
locationIds,
+ company: typeof companyRaw === "string" ? companyRaw : null,
};
}
@@ -96,11 +118,34 @@ function scopeBodyFromInput(input: LabelMultipleOptionCreateInput): Record String(x).trim()).filter(Boolean)),
];
- return {
- groupIds: groupIds.length ? groupIds : undefined,
- regionIds: groupIds.length ? groupIds : undefined,
- locationIds: locationIds.length ? locationIds : undefined,
- };
+ const partnerIds = [
+ ...new Set(
+ [...(input.partnerIds ?? []), ...(input.companyIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const body: Record = {};
+ const appliedPartnerType = String(input.appliedPartnerType ?? "").trim().toUpperCase();
+ if (appliedPartnerType === "ALL" || appliedPartnerType === "SPECIFIED") {
+ body.appliedPartnerType = appliedPartnerType;
+ }
+ if (partnerIds.length) {
+ body.partnerIds = partnerIds;
+ body.companyIds = partnerIds;
+ }
+ const availabilityType = String(input.availabilityType ?? "").trim().toUpperCase();
+ if (availabilityType === "ALL" || availabilityType === "SPECIFIED") {
+ body.availabilityType = availabilityType;
+ }
+ if (groupIds.length) {
+ body.groupIds = groupIds;
+ body.regionIds = groupIds;
+ }
+ if (locationIds.length) {
+ body.locationIds = locationIds;
+ }
+ return body;
}
export async function getLabelMultipleOptions(input: LabelMultipleOptionGetListInput, signal?: AbortSignal): Promise> {
diff --git a/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts b/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts
index 7566872..75b1682 100644
--- a/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts
+++ b/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts
@@ -27,6 +27,10 @@ function normalizeLabelTypeDto(raw: unknown): LabelTypeDto {
};
const groupIds = parseIds(r.groupIds ?? r.GroupIds ?? r.regionIds ?? r.RegionIds);
const locationIds = parseIds(r.locationIds ?? r.LocationIds);
+ const partnerIds = parseIds(r.partnerIds ?? r.PartnerIds ?? r.companyIds ?? r.CompanyIds);
+ const appliedPartnerType = String(r.appliedPartnerType ?? r.AppliedPartnerType ?? "").trim() || undefined;
+ const availabilityType = String(r.availabilityType ?? r.AvailabilityType ?? "").trim() || undefined;
+ const companyRaw = r.company ?? r.Company;
const noRaw = r.noOfLabels ?? r.NoOfLabels;
const noOfLabels =
typeof noRaw === "number" && Number.isFinite(noRaw)
@@ -46,9 +50,14 @@ function normalizeLabelTypeDto(raw: unknown): LabelTypeDto {
return {
...(r as object),
id: String(r.id ?? r.Id ?? ""),
+ appliedPartnerType,
+ partnerIds,
+ companyIds: partnerIds,
+ availabilityType,
groupIds,
regionIds: groupIds,
locationIds,
+ company: typeof companyRaw === "string" ? companyRaw : null,
noOfLabels,
lastEdited,
region: typeof regionRaw === "string" ? regionRaw : null,
@@ -67,11 +76,34 @@ function scopeBodyFromInput(input: LabelTypeCreateInput): Record String(x).trim()).filter(Boolean)),
];
- return {
- groupIds: groupIds.length ? groupIds : undefined,
- regionIds: groupIds.length ? groupIds : undefined,
- locationIds: locationIds.length ? locationIds : undefined,
- };
+ const partnerIds = [
+ ...new Set(
+ [...(input.partnerIds ?? []), ...(input.companyIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const body: Record = {};
+ const appliedPartnerType = String(input.appliedPartnerType ?? "").trim().toUpperCase();
+ if (appliedPartnerType === "ALL" || appliedPartnerType === "SPECIFIED") {
+ body.appliedPartnerType = appliedPartnerType;
+ }
+ if (partnerIds.length) {
+ body.partnerIds = partnerIds;
+ body.companyIds = partnerIds;
+ }
+ const availabilityType = String(input.availabilityType ?? "").trim().toUpperCase();
+ if (availabilityType === "ALL" || availabilityType === "SPECIFIED") {
+ body.availabilityType = availabilityType;
+ }
+ if (groupIds.length) {
+ body.groupIds = groupIds;
+ body.regionIds = groupIds;
+ }
+ if (locationIds.length) {
+ body.locationIds = locationIds;
+ }
+ return body;
}
export async function getLabelTypes(input: LabelTypeGetListInput, signal?: AbortSignal): Promise> {
diff --git a/美国版/Food Labeling Management Platform/src/types/label.ts b/美国版/Food Labeling Management Platform/src/types/label.ts
index 4176626..ee64a7f 100644
--- a/美国版/Food Labeling Management Platform/src/types/label.ts
+++ b/美国版/Food Labeling Management Platform/src/types/label.ts
@@ -66,7 +66,7 @@ export type LabelCreateInput = {
/** 适用门店(至少 1 个;Select All 时为当前 Region 下全部门店 Id) */
locationIds: string[];
labelCategoryId: string;
- labelTypeId: string;
+ labelTypeId?: string | null;
productIds: string[]; // 至少 1 个
labelInfoJson?: Record | null;
state?: boolean;
@@ -81,7 +81,7 @@ export type LabelUpdateInput = {
/** 适用门店(至少 1 个) */
locationIds: string[];
labelCategoryId: string;
- labelTypeId: string;
+ labelTypeId?: string | null;
productIds: string[]; // 至少 1 个
labelInfoJson?: Record | null;
state?: boolean;
diff --git a/美国版/Food Labeling Management Platform/src/types/labelCategory.ts b/美国版/Food Labeling Management Platform/src/types/labelCategory.ts
index eef20ba..60d57a3 100644
--- a/美国版/Food Labeling Management Platform/src/types/labelCategory.ts
+++ b/美国版/Food Labeling Management Platform/src/types/labelCategory.ts
@@ -18,10 +18,19 @@ export type LabelCategoryDto = {
/** 该分类下标签数量(列表列 No. of Label) */
noOfLabels?: number | null;
creationTime?: string | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
/** ALL / SPECIFIED */
availabilityType?: "ALL" | "SPECIFIED" | string | null;
+ /** SPECIFIED 时绑定的 Region Id */
+ regionIds?: string[] | null;
+ groupIds?: string[] | null;
/** SPECIFIED 时绑定的门店 Id */
locationIds?: string[] | null;
+ /** 列表:Company 展示 */
+ company?: string | null;
/** 最近编辑时间(列表接口映射 LastModificationTime ?? CreationTime) */
lastEdited?: string | null;
};
@@ -56,7 +65,13 @@ export type LabelCategoryCreateInput = {
buttonStyleJson?: string | null;
state?: boolean;
orderNum?: number | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
availabilityType?: "ALL" | "SPECIFIED" | string | null;
+ regionIds?: string[] | null;
+ groupIds?: string[] | null;
locationIds?: string[] | null;
};
diff --git a/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts b/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts
index 2defb00..927028d 100644
--- a/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts
+++ b/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts
@@ -7,6 +7,12 @@ export type LabelMultipleOptionDto = {
state?: boolean | null;
orderNum?: number | null;
creationTime?: string | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
/** 列表:最近编辑时间 */
lastEdited?: string | null;
/** 适用范围:Region Id(`fl_group.Id`) */
@@ -14,6 +20,8 @@ export type LabelMultipleOptionDto = {
regionIds?: string[] | null;
/** 适用范围:门店 Id */
locationIds?: string[] | null;
+ /** 列表:Company 展示 */
+ company?: string | null;
};
export type PagedResultDto = {
@@ -39,6 +47,12 @@ export type LabelMultipleOptionCreateInput = {
optionValuesJson: string[];
state?: boolean;
orderNum?: number | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
groupIds?: string[] | null;
regionIds?: string[] | null;
locationIds?: string[] | null;
diff --git a/美国版/Food Labeling Management Platform/src/types/labelType.ts b/美国版/Food Labeling Management Platform/src/types/labelType.ts
index 0e14b33..4dc914c 100644
--- a/美国版/Food Labeling Management Platform/src/types/labelType.ts
+++ b/美国版/Food Labeling Management Platform/src/types/labelType.ts
@@ -5,11 +5,19 @@ export type LabelTypeDto = {
state?: boolean | null;
orderNum?: number | null;
creationTime?: string | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
/** 详情:Region Id 列表(`fl_group.Id`) */
groupIds?: string[] | null;
regionIds?: string[] | null;
/** 详情:门店 Id 列表 */
locationIds?: string[] | null;
+ /** 列表:Company 展示 */
+ company?: string | null;
/** 列表:该类型下标签数量 */
noOfLabels?: number | null;
/** 列表:最近编辑时间 */
@@ -44,6 +52,12 @@ export type LabelTypeCreateInput = {
typeName: string;
state?: boolean;
orderNum?: number | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
/** Region 多选(`fl_group.Id`) */
groupIds?: string[] | null;
regionIds?: string[] | null;