()
+ .Where(t => pageTaskIds.Contains(t.Id))
+ .Select(t => new { t.Id, t.CreatedBy })
+ .ToListAsync())
+ .ToDictionary(x => x.Id, x => x.CreatedBy, StringComparer.Ordinal);
+
var operatorMap = await UsAppPrintLogScopeHelper.LoadOperatorNameMapAsync(
_dbContext.SqlSugarClient,
- pageRows.Select(x => x.CreatedBy));
+ createdByByTaskId.Values.Concat(pageRows.Select(x => x.CreatedBy)));
var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync(
_dbContext.SqlSugarClient,
@@ -968,7 +977,9 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
LabelSizeText = FormatLabelSizeWithUnit(x.TemplateWidth, x.TemplateHeight, x.TemplateUnit),
PrintInputJson = x.PrintInputJson,
PrintedAt = x.PrintedAt ?? x.CreationTime,
- OperatorName = UsAppPrintLogScopeHelper.ResolveOperatorName(operatorMap, x.CreatedBy),
+ OperatorName = UsAppPrintLogScopeHelper.ResolveOperatorName(
+ operatorMap,
+ createdByByTaskId.TryGetValue(x.Id, out var createdBy) ? createdBy : x.CreatedBy),
LocationName = locationName
}).ToList();
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql
new file mode 100644
index 0000000..d5886b1
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql
@@ -0,0 +1,79 @@
+-- 标签类型 / 分类 / 多选项适用 Company 多选(ALL/SPECIFIED)
+-- 执行前请备份;可重复执行
+
+SET @db := DATABASE();
+
+-- fl_label_type.AppliedPartnerType
+SET @col_type_partner := (
+ SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'fl_label_type' AND COLUMN_NAME = 'AppliedPartnerType'
+);
+SET @ddl_type_partner := IF(
+ @col_type_partner = 0,
+ 'ALTER TABLE `fl_label_type` ADD COLUMN `AppliedPartnerType` varchar(20) NOT NULL DEFAULT ''ALL'' COMMENT ''适用Company:ALL/SPECIFIED'' AFTER `AvailabilityType`',
+ 'SELECT 1'
+);
+PREPARE stmt_type_partner FROM @ddl_type_partner;
+EXECUTE stmt_type_partner;
+DEALLOCATE PREPARE stmt_type_partner;
+
+-- fl_label_category.AppliedPartnerType
+SET @col_cat_partner := (
+ SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'fl_label_category' AND COLUMN_NAME = 'AppliedPartnerType'
+);
+SET @ddl_cat_partner := IF(
+ @col_cat_partner = 0,
+ 'ALTER TABLE `fl_label_category` ADD COLUMN `AppliedPartnerType` varchar(20) NOT NULL DEFAULT ''ALL'' COMMENT ''适用Company:ALL/SPECIFIED'' AFTER `AvailabilityType`',
+ 'SELECT 1'
+);
+PREPARE stmt_cat_partner FROM @ddl_cat_partner;
+EXECUTE stmt_cat_partner;
+DEALLOCATE PREPARE stmt_cat_partner;
+
+-- fl_label_multiple_option.AppliedPartnerType
+SET @col_opt_partner := (
+ SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'fl_label_multiple_option' AND COLUMN_NAME = 'AppliedPartnerType'
+);
+SET @ddl_opt_partner := IF(
+ @col_opt_partner = 0,
+ 'ALTER TABLE `fl_label_multiple_option` ADD COLUMN `AppliedPartnerType` varchar(20) NOT NULL DEFAULT ''ALL'' COMMENT ''适用Company:ALL/SPECIFIED'' AFTER `AvailabilityType`',
+ 'SELECT 1'
+);
+PREPARE stmt_opt_partner FROM @ddl_opt_partner;
+EXECUTE stmt_opt_partner;
+DEALLOCATE PREPARE stmt_opt_partner;
+
+CREATE TABLE IF NOT EXISTS `fl_label_type_partner` (
+ `Id` varchar(36) NOT NULL COMMENT '主键',
+ `LabelTypeId` varchar(36) NOT NULL COMMENT 'fl_label_type.Id',
+ `PartnerId` varchar(36) NOT NULL COMMENT 'fl_partner.Id',
+ `CreationTime` datetime NOT NULL COMMENT '创建时间',
+ `CreatorId` varchar(36) DEFAULT NULL COMMENT '创建者',
+ PRIMARY KEY (`Id`),
+ KEY `idx_fl_ltyp_template` (`LabelTypeId`),
+ KEY `idx_fl_ltyp_partner` (`PartnerId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签类型适用Company';
+
+CREATE TABLE IF NOT EXISTS `fl_label_category_partner` (
+ `Id` varchar(36) NOT NULL COMMENT '主键',
+ `CategoryId` varchar(36) NOT NULL COMMENT 'fl_label_category.Id',
+ `PartnerId` varchar(36) NOT NULL COMMENT 'fl_partner.Id',
+ `CreationTime` datetime NOT NULL COMMENT '创建时间',
+ `CreatorId` varchar(36) DEFAULT NULL COMMENT '创建者',
+ PRIMARY KEY (`Id`),
+ KEY `idx_fl_lcatp_category` (`CategoryId`),
+ KEY `idx_fl_lcatp_partner` (`PartnerId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签分类适用Company';
+
+CREATE TABLE IF NOT EXISTS `fl_label_multiple_option_partner` (
+ `Id` varchar(36) NOT NULL COMMENT '主键',
+ `MultipleOptionId` varchar(36) NOT NULL COMMENT 'fl_label_multiple_option.Id',
+ `PartnerId` varchar(36) NOT NULL COMMENT 'fl_partner.Id',
+ `CreationTime` datetime NOT NULL COMMENT '创建时间',
+ `CreatorId` varchar(36) DEFAULT NULL COMMENT '创建者',
+ PRIMARY KEY (`Id`),
+ KEY `idx_fl_lmop_option` (`MultipleOptionId`),
+ KEY `idx_fl_lmop_partner` (`PartnerId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签多选项适用Company';
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json b/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json
index 1078ea3..1a80493 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json
@@ -20,7 +20,7 @@
},
//应用启动:SelfUrl 供 Program.cs UseUrls 绑定;用 0.0.0.0 避免写成固定局域网 IP 在本机无该网卡时启动失败(WinError 10049)
"App": {
- "SelfUrl": "http://192.168.31.89:19001",
+ "SelfUrl": "http://192.168.31.88:19001",
"CorsOrigins": "http://localhost:19001;http://localhost:18000;http://localhost:5666;http://localhost:5173;http://localhost:3000"
},
//配置
diff --git a/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx b/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx
index 31cc22c..c34b89f 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx
@@ -60,6 +60,7 @@ import {
} from "../ui/pagination";
import {
getLabelCategories,
+ getLabelCategory,
createLabelCategory,
updateLabelCategory,
deleteLabelCategory,
@@ -68,9 +69,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildLabelingScopeSaveBody,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- hydrateLabelingScopeFromLocationIds,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -923,17 +924,17 @@ function CreateLabelCategoryDialog({
}
}
- const scopePayload = buildLabelingScopeSaveBody({
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
- fixedPartnerId,
selectedPartnerIds,
selectedRegionNames,
selectedRegionIds,
selectedLocationIds,
+ fixedPartnerId,
+ locations,
partners,
groups,
- locations,
});
if (!scopePayload.ok) {
toast.error("Validation failed", { description: scopePayload.message });
@@ -967,13 +968,7 @@ function CreateLabelCategoryDialog({
buttonImageUrl: apSel.image ? String(form.categoryPhotoUrl ?? "").trim() || null : null,
buttonAppearance: tokens,
buttonStyleJson,
- availabilityType: scopePayload.body.availabilityType,
- appliedPartnerType: scopePayload.body.appliedPartnerType,
- partnerIds: scopePayload.body.partnerIds,
- companyIds: scopePayload.body.companyIds,
- regionIds: scopePayload.body.regionIds,
- groupIds: scopePayload.body.groupIds,
- locationIds: scopePayload.body.locationIds,
+ ...scopePayload.body,
});
toast.success("Label category created.", {
description: "The label category has been created successfully.",
@@ -1028,6 +1023,10 @@ function CreateLabelCategoryDialog({
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -1234,6 +1233,7 @@ 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");
@@ -1257,117 +1257,145 @@ function EditLabelCategoryDialog({
}, [apSel.text, apSel.color]);
useEffect(() => {
- if (!open || !category) return;
+ if (!open || !category?.id) return;
- const hydrateAvailability = () => {
- const rawAt = String(category.availabilityType ?? "ALL").trim().toUpperCase();
- if (rawAt !== "SPECIFIED") {
+ 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 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;
- }
- const lids = (category.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
- if (requireCompanySelection) {
- const scope = hydrateLabelingScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerIds(scope.partnerIds);
- setSelectedRegionIds(scope.regionIds);
- setSelectedLocationIds(scope.locationIds);
- setSelectedPartnerId("");
- setSelectedRegionNames([]);
- return;
+ } finally {
+ if (!ac.signal.aborted) setLoadingDetail(false);
}
- const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerId(scope.partnerId);
- setSelectedRegionNames(scope.regionNames);
- setSelectedLocationIds(scope.locationIds);
- setSelectedPartnerIds([]);
- setSelectedRegionIds([]);
- };
-
- 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;
@@ -1412,17 +1440,17 @@ function EditLabelCategoryDialog({
}
}
- const scopePayload = buildLabelingScopeSaveBody({
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
- fixedPartnerId,
selectedPartnerIds,
selectedRegionNames,
selectedRegionIds,
selectedLocationIds,
+ fixedPartnerId,
+ locations,
partners,
groups,
- locations,
});
if (!scopePayload.ok) {
toast.error("Validation failed", { description: scopePayload.message });
@@ -1456,13 +1484,7 @@ function EditLabelCategoryDialog({
buttonImageUrl: apSel.image ? String(form.categoryPhotoUrl ?? "").trim() || null : null,
buttonAppearance: tokens,
buttonStyleJson,
- availabilityType: scopePayload.body.availabilityType,
- appliedPartnerType: scopePayload.body.appliedPartnerType,
- partnerIds: scopePayload.body.partnerIds,
- companyIds: scopePayload.body.companyIds,
- regionIds: scopePayload.body.regionIds,
- groupIds: scopePayload.body.groupIds,
- locationIds: scopePayload.body.locationIds,
+ ...scopePayload.body,
});
toast.success("Label category updated.", {
description: "The label category has been updated successfully.",
@@ -1500,24 +1522,28 @@ function EditLabelCategoryDialog({
/>
-
+ {loadingDetail ? (
+ Loading company, region and location…
+ ) : (
+
+ )}
Button Appearance
@@ -1689,7 +1715,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 002d828..d017e5e 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
@@ -51,9 +51,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildLabelingScopeSaveBody,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- hydrateLabelingScopeFromLocationIds,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -832,7 +832,9 @@ function CreateLabelTypeDialog({
const resetForm = () => {
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
typeName: "",
@@ -859,17 +861,17 @@ function CreateLabelTypeDialog({
return;
}
- const scopePayload = buildLabelingScopeSaveBody({
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
- fixedPartnerId,
selectedPartnerIds,
selectedRegionNames,
selectedRegionIds,
selectedLocationIds,
+ fixedPartnerId,
+ locations,
partners,
groups,
- locations,
});
if (!scopePayload.ok) {
toast.error("Validation failed", { description: scopePayload.message });
@@ -881,13 +883,7 @@ function CreateLabelTypeDialog({
await createLabelType({
...form,
typeCode: typeCodeFromName(form.typeName),
- availabilityType: scopePayload.body.availabilityType,
- appliedPartnerType: scopePayload.body.appliedPartnerType,
- partnerIds: scopePayload.body.partnerIds,
- companyIds: scopePayload.body.companyIds,
- groupIds: scopePayload.body.groupIds,
- regionIds: scopePayload.body.regionIds,
- locationIds: scopePayload.body.locationIds,
+ ...scopePayload.body,
});
toast.success("Label type created.", {
description: "The label type has been created successfully.",
@@ -956,6 +952,10 @@ function CreateLabelTypeDialog({
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -1023,23 +1023,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) {
- if (requireCompanySelection) {
- const scope = hydrateLabelingScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerIds(scope.partnerIds);
- setSelectedRegionIds(scope.regionIds);
- setSelectedLocationIds(scope.locationIds);
- setSelectedPartnerId("");
- setSelectedRegionNames([]);
- } else {
- const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerId(scope.partnerId);
- setSelectedRegionNames(scope.regionNames);
- setSelectedLocationIds(scope.locationIds);
- setSelectedPartnerIds([]);
- setSelectedRegionIds([]);
- }
+ const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
+ setSelectedPartnerId(scope.partnerId);
+ setSelectedRegionNames(scope.regionNames);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1048,26 +1049,15 @@ function EditLabelTypeDialog({
.filter(Boolean);
if (gids.length > 0) {
const matchedGroups = groups.filter((g) => gids.includes(g.id));
- if (requireCompanySelection) {
- const partnerIds = [
- ...new Set(matchedGroups.map((g) => String(g.partnerId ?? "").trim()).filter(Boolean)),
- ];
- setSelectedPartnerIds(partnerIds);
- setSelectedRegionIds(gids);
- setSelectedLocationIds([]);
- setSelectedPartnerId("");
- setSelectedRegionNames([]);
- } else {
- const pid = matchedGroups[0]?.partnerId ?? "";
- const names = [
- ...new Set(matchedGroups.map((g) => (g.groupName ?? "").trim()).filter(Boolean)),
- ];
- setSelectedPartnerId(pid);
- setSelectedRegionNames(names);
- setSelectedLocationIds([]);
- setSelectedPartnerIds([]);
- setSelectedRegionIds([]);
- }
+ const pid = matchedGroups[0]?.partnerId ?? "";
+ const names = [
+ ...new Set(matchedGroups.map((g) => (g.groupName ?? "").trim()).filter(Boolean)),
+ ];
+ setSelectedPartnerId(pid);
+ setSelectedRegionNames(names);
+ setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1096,6 +1086,8 @@ function EditLabelTypeDialog({
setSelectedRegionIds([]);
setSelectedLocationIds([]);
}
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
} catch {
if (ac.signal.aborted) return;
setForm({
@@ -1104,7 +1096,9 @@ function EditLabelTypeDialog({
orderNum: type.orderNum ?? null,
});
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
} finally {
if (!ac.signal.aborted) setLoadingDetail(false);
@@ -1127,17 +1121,17 @@ function EditLabelTypeDialog({
return;
}
- const scopePayload = buildLabelingScopeSaveBody({
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
- fixedPartnerId,
selectedPartnerIds,
selectedRegionNames,
selectedRegionIds,
selectedLocationIds,
+ fixedPartnerId,
+ locations,
partners,
groups,
- locations,
});
if (!scopePayload.ok) {
toast.error("Validation failed", { description: scopePayload.message });
@@ -1149,13 +1143,7 @@ function EditLabelTypeDialog({
await updateLabelType(type.id, {
...form,
typeCode: type.typeCode ?? "",
- availabilityType: scopePayload.body.availabilityType,
- appliedPartnerType: scopePayload.body.appliedPartnerType,
- partnerIds: scopePayload.body.partnerIds,
- companyIds: scopePayload.body.companyIds,
- groupIds: scopePayload.body.groupIds,
- regionIds: scopePayload.body.regionIds,
- locationIds: scopePayload.body.locationIds,
+ ...scopePayload.body,
});
toast.success("Label type updated.", {
description: "The label type has been updated successfully.",
@@ -1227,6 +1215,10 @@ function EditLabelTypeDialog({
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 5cfb498..7b2765d 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
@@ -51,9 +51,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildLabelingScopeSaveBody,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- hydrateLabelingScopeFromLocationIds,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -818,7 +818,9 @@ function CreateMultipleOptionDialog({
const resetForm = () => {
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
optionName: "",
@@ -876,17 +878,17 @@ function CreateMultipleOptionDialog({
return;
}
- const scopePayload = buildLabelingScopeSaveBody({
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
- fixedPartnerId,
selectedPartnerIds,
selectedRegionNames,
selectedRegionIds,
selectedLocationIds,
+ fixedPartnerId,
+ locations,
partners,
groups,
- locations,
});
if (!scopePayload.ok) {
toast.error("Validation failed", { description: scopePayload.message });
@@ -898,13 +900,7 @@ function CreateMultipleOptionDialog({
await createLabelMultipleOption({
...form,
optionCode: optionCodeFromName(form.optionName),
- availabilityType: scopePayload.body.availabilityType,
- appliedPartnerType: scopePayload.body.appliedPartnerType,
- partnerIds: scopePayload.body.partnerIds,
- companyIds: scopePayload.body.companyIds,
- groupIds: scopePayload.body.groupIds,
- regionIds: scopePayload.body.regionIds,
- locationIds: scopePayload.body.locationIds,
+ ...scopePayload.body,
});
toast.success("Multiple option created.", {
description: "The multiple option has been created successfully.",
@@ -1009,6 +1005,10 @@ function CreateMultipleOptionDialog({
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -1080,23 +1080,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) {
- if (requireCompanySelection) {
- const scope = hydrateLabelingScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerIds(scope.partnerIds);
- setSelectedRegionIds(scope.regionIds);
- setSelectedLocationIds(scope.locationIds);
- setSelectedPartnerId("");
- setSelectedRegionNames([]);
- } else {
- const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerId(scope.partnerId);
- setSelectedRegionNames(scope.regionNames);
- setSelectedLocationIds(scope.locationIds);
- setSelectedPartnerIds([]);
- setSelectedRegionIds([]);
- }
+ const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
+ setSelectedPartnerId(scope.partnerId);
+ setSelectedRegionNames(scope.regionNames);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1105,26 +1106,15 @@ function EditMultipleOptionDialog({
.filter(Boolean);
if (gids.length > 0) {
const matchedGroups = groups.filter((g) => gids.includes(g.id));
- if (requireCompanySelection) {
- const partnerIds = [
- ...new Set(matchedGroups.map((g) => String(g.partnerId ?? "").trim()).filter(Boolean)),
- ];
- setSelectedPartnerIds(partnerIds);
- setSelectedRegionIds(gids);
- setSelectedLocationIds([]);
- setSelectedPartnerId("");
- setSelectedRegionNames([]);
- } else {
- const pid = matchedGroups[0]?.partnerId ?? "";
- const names = [
- ...new Set(matchedGroups.map((g) => (g.groupName ?? "").trim()).filter(Boolean)),
- ];
- setSelectedPartnerId(pid);
- setSelectedRegionNames(names);
- setSelectedLocationIds([]);
- setSelectedPartnerIds([]);
- setSelectedRegionIds([]);
- }
+ const pid = matchedGroups[0]?.partnerId ?? "";
+ const names = [
+ ...new Set(matchedGroups.map((g) => (g.groupName ?? "").trim()).filter(Boolean)),
+ ];
+ setSelectedPartnerId(pid);
+ setSelectedRegionNames(names);
+ setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1153,6 +1143,8 @@ function EditMultipleOptionDialog({
setSelectedRegionIds([]);
setSelectedLocationIds([]);
}
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
} catch {
if (ac.signal.aborted) return;
setForm({
@@ -1162,7 +1154,9 @@ function EditMultipleOptionDialog({
orderNum: option.orderNum ?? null,
});
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setNewValue("");
} finally {
@@ -1215,17 +1209,17 @@ function EditMultipleOptionDialog({
return;
}
- const scopePayload = buildLabelingScopeSaveBody({
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
- fixedPartnerId,
selectedPartnerIds,
selectedRegionNames,
selectedRegionIds,
selectedLocationIds,
+ fixedPartnerId,
+ locations,
partners,
groups,
- locations,
});
if (!scopePayload.ok) {
toast.error("Validation failed", { description: scopePayload.message });
@@ -1237,13 +1231,7 @@ function EditMultipleOptionDialog({
await updateLabelMultipleOption(option.id, {
...form,
optionCode: option.optionCode ?? "",
- availabilityType: scopePayload.body.availabilityType,
- appliedPartnerType: scopePayload.body.appliedPartnerType,
- partnerIds: scopePayload.body.partnerIds,
- companyIds: scopePayload.body.companyIds,
- groupIds: scopePayload.body.groupIds,
- regionIds: scopePayload.body.regionIds,
- locationIds: scopePayload.body.locationIds,
+ ...scopePayload.body,
});
toast.success("Multiple option updated.", {
description: "The multiple option has been updated successfully.",
@@ -1351,6 +1339,10 @@ function EditMultipleOptionDialog({
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 40f19ee..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
@@ -411,7 +411,7 @@ export function CategoryScopeFields({
)}
{templateScopeMode
- ? "Required. Select one or more companies, or Select All."
+ ? "Required. Leave empty or select all for every company; narrows region and location lists."
: "Required. Select company before region and location."}
@@ -427,10 +427,18 @@ export function CategoryScopeFields({
placeholder={regionPlaceholder}
searchPlaceholder="Search regions…"
selectAllRowLabel="Select All"
- disabled={!companyReady}
+ disabled={!templateScopeMode && !companyReady}
/>
- {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."}
@@ -440,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 20fa7fe..d2e41f8 100644
--- a/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
+++ b/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
@@ -1,11 +1,8 @@
import type { GroupListItem } from "../types/group";
import type { LocationDto } from "../types/location";
import type { PartnerListItem } from "../types/partner";
-import {
- buildLabelTemplateScopePayload,
- type AppliedScopeType,
- parseAppliedScopeType,
-} from "../types/labelTemplate";
+import type { AppliedScopeType } from "../types/labelTemplate";
+import { parseAppliedScopeType } from "../types/labelTemplate";
/** 表单提交用的公司 Id(非管理员取账号绑定公司) */
export function effectiveScopePartnerId(
@@ -616,3 +613,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 213207a..ee64a7f 100644
--- a/美国版/Food Labeling Management Platform/src/types/label.ts
+++ b/美国版/Food Labeling Management Platform/src/types/label.ts
@@ -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 ff8f637..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,10 +65,11 @@ export type LabelCategoryCreateInput = {
buttonStyleJson?: string | null;
state?: boolean;
orderNum?: number | null;
- availabilityType?: "ALL" | "SPECIFIED" | string | 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 f34234b..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,10 +47,12 @@ export type LabelMultipleOptionCreateInput = {
optionValuesJson: string[];
state?: boolean;
orderNum?: number | null;
- availabilityType?: "ALL" | "SPECIFIED" | string | 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 d6db61b..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,10 +52,12 @@ export type LabelTypeCreateInput = {
typeName: string;
state?: boolean;
orderNum?: number | null;
- availabilityType?: "ALL" | "SPECIFIED" | string | 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;