5-18 接口优化
本文档说明 2026-05-18 对美国版接口修复与约定,包括:
/api/app/rbac-role:accessPermissions读写(见下文 rbac-role 章节)。/api/app/team-member:成员 平台端无法登录(已修复);列表支持 Company / Region / Location 筛选(见 team-member-list)。/api/app/label-type:列表 No. of Labels / Region / Location;新增/编辑 Region·Location 多选(见 label-type)。/api/app/label-multiple-option:列表 Region / Location 筛选与出参;新增/编辑 Region·Location 多选(见 label-multiple-option)。/api/app/reports/print-log-list:Print Log Label ID 当日门店序号 + Expiry Date 从打印快照解析(见 reports-print-log)。/api/app/reports/label-report:按 Token 门店绑定统计(管理员全量;见 reports-label-report)。/api/app/us-app-auth/location-detail/{locationId}:出参增加 经营时间operatingHours(见 us-app-auth-location-detail)。- App
POST /api/app/us-app-labeling/get-print-log-list、get-label-report:管理员 / Partner 角色可查看当前门店全部打印记录与统计(见 us-app-print-log)。 GET /api/app/us-app-labeling/labeling-tree:修复产品分类SPECIFIED未配门店关联导致全店空树(见 us-app-labeling-tree)。GET /api/app/location:门店列表按 Token Region 数据范围过滤(见 location-list)。GET /api/app/reports/template-print-stat-list:按模板统计打印标签数量(见 reports-template-print-stat)。
reports-print-log 打印日志
应用服务:ReportsAppService
接口:GET /api/app/reports/print-log-list(及同源导出 export-print-log-pdf / export-print-log-excel)
辅助类:ReportsPrintLogDailyLabelIdHelper、ReportsPrintLogExpiryHelper
Label ID 变更说明
| 项 | 说明 |
|---|---|
| Label ID 含义 | 由 fl_label.LabelCode(如 079)改为 门店当日打印序号 |
| 展示格式 | {yyyyMMdd}-{n},例如 20260513-1、20260513-2 |
| 排序规则 | 按 LocationId(门店) + 自然日(PrintedAt,无则 CreationTime)分组;组内按打印时间 升序,同秒按任务 Id 升序,n 从 1 递增 |
| 跨页一致 | 分页列表与导出使用同一套序号(按门店全日任务计算,非仅当前页内排序) |
| API 字段名 | 仍为 labelCode(兼容前端列绑定),内容为当日序号,不是标签主数据编码 |
列表出参(节选)
| 字段 | 说明 |
|---|---|
| taskId | 打印任务 Id(fl_label_print_task.Id,重打用) |
| labelCode | Label ID 列:20260515-1 这种当日门店序号 |
| productName / categoryName / templateText | 不变 |
| printedAt | 打印时间 |
| locationText / locationId | 门店 |
| expiryDateText | Expiry Date 列:从 PrintInputJson 解析的保质期展示文案;无则「无」 |
示例
{
"taskId": "task-guid-001",
"labelCode": "20260515-3",
"productName": "Tuna & Bacon Sub",
"printedAt": "2026-05-15T15:00:40",
"locationText": "UNCC store (LOC001)",
"expiryDateText": "05/17"
}
Expiry Date(到期时间)变更说明
| 项 | 说明 |
|---|---|
| 数据来源 | fl_label_print_task.PrintInputJson(App 打印接口落库,多为整份模板快照 JSON,含 elements[]) |
| 解析方式 | ReportsPrintLogExpiryHelper.ExtractExpiryText:先读根级 expiryDate / expiry / expirationDate 等;若无,在 elements 中匹配 duration date / expiry 类元素,取 config.text(或 config.format) |
| 匹配规则 | 与 Web isDateTimeDataEntryField 对齐:elementName / inputKey 含 durationdate、expirydate 等;typeAdd 含 duration date;排除 currentdate、currenttime、prepped 等制备日期字段 |
| 展示格式 | 与标签出纸一致(如 05/17、2026-05-17),不做二次换算 |
| 适用范围 | 分页列表、export-print-log-pdf、export-print-log-excel 共用同一解析逻辑 |
库内样例(节选)
PrintInputJson.elements 中 elementName: "durationdate1" → config.text: "05/17",接口应返回 expiryDateText: "05/17"(此前仅查根级字段,列恒为「无」)。
Label ID 计算逻辑(与代码一致)
同一门店、同一天内:
- 查询
fl_label_print_task中LocationId相同且DATE(COALESCE(PrintedAt, CreationTime))相同的全部任务; - 按打印时间升序编号
1, 2, 3…; - 格式化为
yyyyMMdd-n。
注意:序号统计范围为该门店当日全部打印任务(不限于当前列表筛选关键字),以保证同一天内序号全局唯一、连续。列表/导出的日期、门店筛选只决定哪些行展示,不改变已展示行的序号。
库内核对 SQL
SELECT
t.Id AS task_id,
t.LocationId,
DATE(COALESCE(t.PrintedAt, t.CreationTime)) AS print_day,
ROW_NUMBER() OVER (
PARTITION BY t.LocationId, DATE(COALESCE(t.PrintedAt, t.CreationTime))
ORDER BY COALESCE(t.PrintedAt, t.CreationTime), t.Id
) AS daily_seq,
CONCAT(
DATE_FORMAT(COALESCE(t.PrintedAt, t.CreationTime), '%Y%m%d'),
'-',
ROW_NUMBER() OVER (
PARTITION BY t.LocationId, DATE(COALESCE(t.PrintedAt, t.CreationTime))
ORDER BY COALESCE(t.PrintedAt, t.CreationTime), t.Id
)
) AS label_id_display
FROM fl_label_print_task t
WHERE t.LocationId = :locationId
AND COALESCE(t.PrintedAt, t.CreationTime) >= :dayStart
AND COALESCE(t.PrintedAt, t.CreationTime) < :dayEnd
ORDER BY print_day, daily_seq;
联调注意
| 现象 | 可能原因 |
|---|---|
仍显示 079 等 |
后端未部署;或前端绑错字段(应绑 labelCode) |
| 同一天序号不连续 | 存在无 LocationId 的任务,显示「无」 |
| 与 App 端打印记录不一致 | App 接口为另一套(us-app-labeling 打印历史),本规则仅 Reports Print Log |
location 门店列表
应用服务:LocationAppService
接口:GET /api/app/location(分页;同源 Excel 导出 export-locations-excel 使用相同筛选)
辅助类:LocationRegionScopeHelper、ReportsRoleHelper
数据范围(按 Token)
| 角色 | 可见门店 |
|---|---|
| 管理员 | 全部未删除门店(ReportsRoleHelper.IsAdminRole) |
| 非管理员 | 仅 userlocation 绑定门店所属 Region 下的全部门店 |
Region 判定:与 fl_group / group 列表一致——取绑定门店的 location.Partner(公司名称)+ location.GroupName(Region 名称)去重后,列表返回 同一 Partner + GroupName 的所有 location 行(不限于本人绑定的那几家店)。
未绑定门店、或绑定门店缺少 Partner/GroupName 时,列表为空。
请求示例
GET /api/app/location?SkipCount=1&MaxResultCount=10
Authorization: Bearer {token}
可选 Query:Keyword、Partner、GroupName、State、Sorting(在数据范围之上再收窄)。
与 group 列表的关系
| 接口 | 非管理员范围 |
|---|---|
GET /api/app/group |
可见的 fl_group 记录 |
GET /api/app/location |
上述 Region 对应的 location 门店 |
库内核对(非管理员)
-- 当前用户绑定门店所属的 Region(Partner + GroupName)
SELECT DISTINCT loc.Partner, loc.GroupName
FROM userlocation ul
INNER JOIN location loc ON ul.LocationId = loc.Id AND loc.IsDeleted = 0
WHERE ul.IsDeleted = 0 AND ul.UserId = :currentUserId
AND loc.Partner IS NOT NULL AND loc.Partner != ''
AND loc.GroupName IS NOT NULL AND loc.GroupName != '';
-- 列表应返回的同 Region 全部门店
SELECT loc.*
FROM location loc
WHERE loc.IsDeleted = 0
AND (loc.Partner, loc.GroupName) IN (
SELECT DISTINCT l2.Partner, l2.GroupName
FROM userlocation ul
INNER JOIN location l2 ON ul.LocationId = l2.Id AND l2.IsDeleted = 0
WHERE ul.IsDeleted = 0 AND ul.UserId = :currentUserId
);
联调注意
| 现象 | 处理 |
|---|---|
| 非管理员列表为空 | 检查 userlocation 是否有绑定;门店 Partner、GroupName 是否已填 |
| 只能看到部分 Region | 正常:仅能看到绑定门店所在 Region;换绑门店可扩大范围 |
入参 GroupName 越权筛选 |
仅能筛 已有权限范围内 的数据,不会扩大范围 |
us-app-labeling-tree 四级列表
应用服务:UsAppLabelingAppService.GetLabelingTreeAsync
接口:GET /api/app/us-app-labeling/labeling-tree?locationId={guid}
问题与修复(2026-05-18)
| 现象 | 原因 | 处理 |
|---|---|---|
| App 各门店 Labeling 页 No products found | 产品已写入 fl_location_product,但 fl_product_category.AvailabilityType=SPECIFIED 且 未在 fl_product_category_location 配置当前门店时,旧逻辑在 Join 条件中整行过滤 |
已修复:四级树的产品范围仅由 fl_location_product + fl_label.LocationId 决定;产品分类、标签分类仅校验未删除且启用,不再用 SPECIFIED 子查询拦截 |
| 某门店仍为空 | 该门店 无 fl_label(LocationId 匹配且 State=1) |
属数据:需在 Web Labels 为该门店创建/复制标签,或把已有标签的 LocationId 指到该门店 |
数据范围(与代码一致)
- 门店产品:
fl_location_product.LocationId = locationId - 门店标签:
fl_label.LocationId = locationId且未删除、启用 - 关联:
fl_label_product连接标签与产品 - 登录:须绑定该门店(
userlocation),否则返回业务错误
请求示例
GET /api/app/us-app-labeling/labeling-tree?locationId=3a212211-3b01-d66f-a804-125c0cee3bf0
Authorization: Bearer {token}
出参
UsAppLabelCategoryTreeNodeDto[](L1 标签分类 → L2 产品分类 → L3 产品+模板卡片 → L4 标签类型)。
库内核对 SQL
-- 门店是否有可展示产品(绑定产品数)
SELECT COUNT(*) FROM fl_location_product WHERE LocationId = :locationId;
-- 门店是否有可用标签(决定树是否非空)
SELECT COUNT(*) FROM fl_label
WHERE LocationId = :locationId AND IsDeleted = 0 AND State = 1;
-- 修复后应能查到的树行数(与接口一致)
SELECT COUNT(*) AS tree_rows
FROM fl_label_product lp
INNER JOIN fl_label l ON lp.LabelId = l.Id
INNER JOIN fl_product p ON lp.ProductId = p.Id
INNER JOIN fl_label_category c ON l.LabelCategoryId = c.Id
INNER JOIN fl_label_type t ON l.LabelTypeId = t.Id
INNER JOIN fl_label_template tpl ON l.TemplateId = tpl.Id
LEFT JOIN fl_product_category pc ON p.CategoryId = pc.Id
WHERE l.LocationId = :locationId
AND p.Id IN (SELECT ProductId FROM fl_location_product WHERE LocationId = :locationId)
AND l.IsDeleted = 0 AND l.State = 1
AND p.IsDeleted = 0 AND p.State = 1
AND c.IsDeleted = 0 AND c.State = 1
AND t.IsDeleted = 0 AND t.State = 1
AND tpl.IsDeleted = 0
AND (pc.Id IS NULL OR (pc.IsDeleted = 0 AND pc.State = 1));
us-app-print-log App 打印日志与报表
应用服务:UsAppLabelingAppService
辅助类:UsAppPrintLogScopeHelper、ReportsRoleHelper
涉及接口
| 接口 | 说明 |
|---|---|
POST /api/app/us-app-labeling/get-print-log-list |
打印日志分页 |
POST /api/app/us-app-labeling/get-label-report |
当前门店 Label Report 统计(出参与 Web reports/label-report 同结构) |
POST /api/app/us-app-labeling/reprint |
重打;权限与日志查看一致 |
查看权限(按 Token + 当前门店)
须已登录,且 userlocation 绑定 locationId。
| 角色 | 打印日志 / Report 数据范围 |
|---|---|
| 管理员 | 当前门店 全部 用户的打印任务(ReportsRoleHelper.IsAdminRole:用户名为 admin、角色码 admin、权限 *:*:* 等) |
| Partner | 同上;判定为 UserRole → Role 的 RoleCode 或 RoleName 含 partner(忽略大小写,如 Partner Admin) |
| 其它(如 Staff、Store Manager) | 仅 CreatedBy == 当前用户 Id |
get-print-log-list
请求示例
POST /api/app/us-app-labeling/get-print-log-list
Authorization: Bearer {token}
Content-Type: application/json
{
"locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8",
"skipCount": 1,
"maxResultCount": 20
}
出参要点
| 字段 | 说明 |
|---|---|
items[].operatorName |
实际打印人姓名(全店可见时为对应 CreatedBy 用户,非固定当前登录人) |
| 其它 | 与既有 PrintLogItemDto 一致 |
get-label-report
请求示例
{
"locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8",
"startDate": "2026-04-07",
"endDate": "2026-05-18",
"keyword": ""
}
出参:ReportsLabelReportOutputDto(summary、labelsByCategory、printVolumeTrend、mostUsedProducts)。
与 Web Reports 的差异
| 模块 | 范围 |
|---|---|
App get-print-log-list / get-label-report |
单门店 + 上表角色规则 |
Web reports/print-log-list |
非管理员仍仅本人;见 reports-print-log 章节 |
Web reports/label-report |
非管理员为绑定门店全量;见 reports-label-report 章节 |
联调注意
| 现象 | 处理 |
|---|---|
| Partner 仍只看本人 | 确认角色 RoleCode/RoleName 含 partner,或是否为管理员 |
| 列表为空 | 确认 locationId 与绑定门店一致;Staff 仅能看到自己打印的记录 |
us-app-auth-location-detail 门店详情
应用服务:UsAppAuthAppService
接口:GET /api/app/us-app-auth/location-detail/{locationId}
鉴权:Bearer Token;仅可查当前用户在 userlocation 中绑定的门店。
变更说明
| 项 | 说明 |
|---|---|
| 新增出参 | operatingHours(经营时间) |
| 数据来源 | location.OperatingHours(varchar(512) 自由文本) |
| 空值展示 | 未维护或空白时返回 无(与 locationName、storePhone 等字段一致) |
| 维护入口 | Web 管理端 POST/PUT /api/app/location 的 operatingHours 字段 |
数据库(若列不存在须先执行)
脚本:美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_location_add_operating_hours_column.sql
ALTER TABLE `location`
ADD COLUMN `OperatingHours` varchar(512) DEFAULT NULL COMMENT '经营时间(自由文本)' AFTER `Longitude`;
请求示例
GET /api/app/us-app-auth/location-detail/3a21220f-db37-3e32-7390-d55f64cd62a8
Authorization: Bearer {token}
响应体(UsAppLocationDetailOutputDto)
| 字段(JSON) | 类型 | 说明 |
|---|---|---|
locationId |
string | 门店主键 Guid |
locationName |
string | 门店名称 |
fullAddress |
string | 街道/城市/州/邮编拼接 |
storePhone |
string | location.Phone |
operatingHours |
string | 经营时间;示例:Mon–Fri 9:00 AM – 6:00 PM;空为 无 |
managerName |
string | 本店绑定用户中角色含 manager 者姓名 |
managerPhone |
string | 同上用户电话 |
响应示例
{
"locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8",
"locationName": "Central Park Store",
"fullAddress": "123 Main St, New York, NY 10001",
"storePhone": "(212) 555-0100",
"operatingHours": "Mon–Fri 9:00 AM – 6:00 PM",
"managerName": "Jane Doe",
"managerPhone": "+1 (555) 123-4567"
}
联调注意
| 现象 | 处理 |
|---|---|
operatingHours 恒为 无 |
1)确认已执行 DDL;2)在 Web 门店编辑保存 operatingHours;3)重启 API 使实体映射生效 |
| 403 / 业务异常 | 当前 Token 用户未在 userlocation 绑定该 locationId |
reports-template-print-stat 模板打印统计
应用服务:ReportsAppService
接口:GET /api/app/reports/template-print-stat-list
辅助类:ReportsLocationScopeHelper、ReportsRoleHelper
功能
按 fl_label_template 汇总 fl_label_print_task 行数,返回 模板名称 + 打印标签数量 分页列表(默认按 printedCount 降序)。
数据范围(与 label-report 一致)
| 角色 | 统计范围 |
|---|---|
| 管理员 | 全部门店打印任务(可按 Company/Region/Location 入参收窄) |
| 非管理员 | 仅 userlocation 绑定门店内全部打印任务(不按 CreatedBy 过滤) |
入参
| 参数 | 说明 |
|---|---|
SkipCount / MaxResultCount |
分页(SkipCount 为 1-based 页码,第一页传 1) |
StartDate / EndDate |
统计区间(含起止日;未传默认近 30 天至今天) |
PartnerId |
Company(fl_partner.Id) |
GroupId |
Region(fl_group.Id) |
LocationId |
门店(location.Id) |
Keyword |
模板名称模糊匹配(fl_label_template.TemplateName) |
Sorting |
可选 PrintedCount asc;默认 PrintedCount desc |
请求示例
GET /api/app/reports/template-print-stat-list?SkipCount=1&MaxResultCount=20&StartDate=2026-04-07&EndDate=2026-05-18
Authorization: Bearer {token}
出参(items[])
| 字段 | 类型 | 说明 |
|---|---|---|
templateId |
string? | fl_label_template.Id |
templateName |
string | 模板名称;缺失时 「无」 |
printedCount |
int | 该模板下打印任务条数 |
分页包装:pageIndex、pageSize、totalCount、totalPages、items(与其它列表一致)。
响应示例
{
"pageIndex": 1,
"pageSize": 20,
"totalCount": 3,
"totalPages": 1,
"items": [
{ "templateId": "tpl-001", "templateName": "2x3 Price Label", "printedCount": 128 },
{ "templateId": "tpl-002", "templateName": "Deli Scale Label", "printedCount": 45 }
]
}
库内核对
SELECT t.TemplateId,
tpl.TemplateName,
COUNT(*) AS printed_count
FROM fl_label_print_task t
LEFT JOIN fl_label_template tpl ON t.TemplateId = tpl.Id
INNER JOIN location loc ON t.LocationId = CAST(loc.Id AS CHAR) AND loc.IsDeleted = 0
WHERE COALESCE(t.PrintedAt, t.CreationTime) >= :start
AND COALESCE(t.PrintedAt, t.CreationTime) < :endExcl
AND t.LocationId IN (:allowedLocationIds) -- 非管理员:userlocation 绑定门店
GROUP BY t.TemplateId, tpl.TemplateName
ORDER BY printed_count DESC;
联调注意
| 现象 | 处理 |
|---|---|
| 列表为空 | 检查日期区间、门店绑定、该区间是否有打印任务 |
| 与 print-log-list 数量不一致 | print-log 非管理员仅本人任务;本接口与 label-report 同范围 |
| 模板已删除仍有统计 | 任务仍保留 TemplateId;名称来自 Left Join,无名称时显示「无」 |
reports-label-report 标签报表
应用服务:ReportsAppService
接口:GET /api/app/reports/label-report(及同源 export-label-report-pdf)
辅助类:ReportsLocationScopeHelper、ReportsRoleHelper
数据范围(按 Token)
| 角色 | 统计范围 | 说明 |
|---|---|---|
| 管理员 | 全部门店打印任务 | 识别方式与 Print Log 一致:admin 角色 / 用户名为 admin / 权限 *:*:* |
| 非管理员 | 仅 userlocation 绑定门店 |
UserLocation.UserId = 当前用户 Id 的 LocationId;统计该门店下全部打印任务,不按 CreatedBy 过滤 |
入参筛选(与 Print Log 一致)
| 参数 | 说明 |
|---|---|
StartDate / EndDate |
统计区间(含起日、含止日;未传时默认近 30 天至今天) |
PartnerId |
Company(fl_partner.Id) |
GroupId |
Region(fl_group.Id) |
LocationId |
门店(location.Id) |
Keyword |
产品名 / 标签分类 / 产品分类模糊匹配 |
筛选叠加规则
- 管理员:未传 Company/Region/Location → 不限制门店;传入后与对应门店集合取交集。
- 非管理员:始终在绑定门店集合内统计;若再传
PartnerId/GroupId/LocationId,与绑定门店 取交集(传了未绑定门店 → 空数据)。 - 无绑定门店:返回空统计(各指标为 0 / 空列表)。
示例
GET /api/app/reports/label-report?StartDate=2026-04-07&EndDate=2026-05-18
Authorization: Bearer {token}
出参结构(不变)
| 块 | 字段 |
|---|---|
summary |
totalLabelsPrinted、totalLabelsPrintedPrevPeriod、mostPrintedCategoryName、topProductName、avgDailyPrints 等 |
labelsByCategory |
按标签分类汇总 |
printVolumeTrend |
近 7 日(在查询区间内)每日打印量 |
mostUsedProducts |
Top 20 产品 |
与 Print Log 的差异
| 模块 | 非管理员范围 |
|---|---|
| label-report | 绑定门店内所有打印记录 |
| print-log-list | 仍仅 CreatedBy = 当前用户(见 报表Reports接口对接说明.md) |
库内核对(非管理员)
-- 当前用户绑定的门店
SELECT ul.LocationId
FROM userlocation ul
WHERE ul.IsDeleted = 0 AND ul.UserId = :currentUserId;
-- 绑定门店在区间内的打印量(应与接口 summary.totalLabelsPrinted 一致)
SELECT COUNT(*) AS cnt
FROM fl_label_print_task t
WHERE t.LocationId IN (:boundLocationIds)
AND COALESCE(t.PrintedAt, t.CreationTime) >= :start
AND COALESCE(t.PrintedAt, t.CreationTime) < :endExclusive;
label-multiple-option 多选项
应用服务:LabelMultipleOptionAppService
命名约定:UI Region = 入参 regionIds / groupIds、列表筛选 groupId(均为 fl_group.Id);UI Location = 入参 locationIds、列表筛选 locationId(location.Id)。存储表 fl_label_multiple_option_location(与 label-type / label-category 一致)。
数据库变更(列表筛选前须执行)
脚本:美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_multiple_option_scope.sql
ALTER TABLE `fl_label_multiple_option`
ADD COLUMN `AvailabilityType` varchar(20) NOT NULL DEFAULT 'ALL' COMMENT '门店可用范围:ALL/SPECIFIED' AFTER `State`;
CREATE TABLE IF NOT EXISTS `fl_label_multiple_option_location` (
`Id` varchar(36) NOT NULL,
`MultipleOptionId` varchar(36) NOT NULL,
`LocationId` varchar(36) NOT NULL,
`CreationTime` datetime NOT NULL,
`CreatorId` varchar(36) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `idx_fl_lmol_option` (`MultipleOptionId`),
KEY `idx_fl_lmol_location` (`LocationId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签多选项适用门店';
执行 DDL 后,历史数据默认
AvailabilityType = ALL,任意 Region/门店筛选下仍可见。
新增 POST /api/app/label-multiple-option、编辑 PUT /api/app/label-multiple-option/{id}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| optionCode | string | 是 | 多选项编码 |
| optionName | string | 是 | 多选项名称 |
| optionValuesJson | string | 否 | 选项值 JSON 字符串 |
| state | bool | 否 | 默认 true |
| orderNum | int | 否 | 排序 |
| availabilityType | string | 否 | ALL / SPECIFIED;传 region/location 数组时自动 SPECIFIED |
| regionIds | string[] | 否 | Region 多选(fl_group.Id) |
| groupIds | string[] | 否 | 与 regionIds 合并去重 |
| locationIds | string[] | 否 | Location 多选(location.Id) |
合并规则:每个 regionIds 展开为该 Region 下全部门店,再与 locationIds 取并集 → 写入 fl_label_multiple_option_location;SPECIFIED 时至少 1 个有效门店。
请求示例
POST /api/app/label-multiple-option
{
"optionCode": "OPT_ALLERGENS",
"optionName": "Allergens",
"optionValuesJson": "[\"Peanuts\",\"Dairy\",\"Gluten\"]",
"state": true,
"orderNum": 1,
"availabilityType": "SPECIFIED",
"regionIds": ["fl_group_id_east"],
"locationIds": ["11111111-1111-1111-1111-111111111111"]
}
详情 GET /api/app/label-multiple-option/{id} 出参(范围字段)
| 字段 | 说明 |
|---|---|
| availabilityType | ALL / SPECIFIED |
| regionIds | Region Id(SPECIFIED 时由门店反推) |
| groupIds | 与 regionIds 相同 |
| locationIds | 已绑定门店 Id |
编辑:Body 与新增相同;会 先删后插 重建 fl_label_multiple_option_location。
列表 GET /api/app/label-multiple-option
示例:?SkipCount=1&MaxResultCount=10
列表 Query 筛选
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| skipCount | int | 是 | 跳过条数(第 1 页常为 1) |
| maxResultCount | int | 是 | 每页条数 |
| sorting | string | 否 | 排序 |
| keyword | string | 否 | 匹配 optionCode、optionName |
| state | bool | 否 | 启用状态 |
| groupId | string | 否 | Region 筛选(fl_group.Id) |
| locationId | string | 否 | Location 筛选;优先于 groupId |
筛选语义(与 label-category / label-type 一致):
- 未传
groupId、locationId:返回全部未删除多选项。 - 传入筛选:返回
availabilityType = ALL或fl_label_multiple_option_location命中该门店 的记录。
请求示例
GET /api/app/label-multiple-option?SkipCount=1&MaxResultCount=10
Authorization: {token}
GET /api/app/label-multiple-option?SkipCount=1&MaxResultCount=10&groupId=你的fl_group主键
Authorization: {token}
GET /api/app/label-multiple-option?SkipCount=1&MaxResultCount=10&locationId=11111111-1111-1111-1111-111111111111
Authorization: {token}
列表出参 items[]
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 多选项主键 |
| optionCode / optionName | string | 编码、名称 |
| optionValuesJson | string | 选项值 JSON |
| state / orderNum | bool / int | 状态、排序 |
| availabilityType | string | ALL / SPECIFIED |
| region | string | 列表列 Region:ALL → All Regions;SPECIFIED → 绑定门店 GroupName 拼接 |
| location | string | 列表列 Location:ALL → All Locations;SPECIFIED → 门店名拼接 |
| regionIds | string[] | Region Id(SPECIFIED 时由门店反推) |
| locationIds | string[] | 门店 Id |
| lastEdited | datetime | 最近编辑时间 |
响应示例
{
"id": "opt_allergens_001",
"optionCode": "OPT_ALLERGENS",
"optionName": "Allergens",
"optionValuesJson": "[\"Peanuts\",\"Dairy\"]",
"state": true,
"availabilityType": "SPECIFIED",
"orderNum": 1,
"region": "East Region",
"location": "UNCC store",
"regionIds": ["fl_group_id_east"],
"locationIds": ["11111111-1111-1111-1111-111111111111"],
"lastEdited": "2026-05-18T12:00:00"
}
库内核对 SQL
SELECT
o.Id,
o.OptionName,
o.AvailabilityType,
ol.LocationId,
loc.GroupName AS region_name,
COALESCE(NULLIF(TRIM(loc.LocationName), ''), loc.LocationCode) AS location_name
FROM fl_label_multiple_option o
LEFT JOIN fl_label_multiple_option_location ol ON ol.MultipleOptionId = o.Id
LEFT JOIN location loc ON loc.Id = ol.LocationId AND loc.IsDeleted = 0
WHERE o.IsDeleted = 0
ORDER BY o.OrderNum DESC;
联调注意
| 现象 | 可能原因 |
|---|---|
region / location 为 All Regions / All Locations |
availabilityType = ALL(历史数据默认) |
| region / location 为「无」 | SPECIFIED 但未写入 fl_label_multiple_option_location |
| 筛 Region 后列表为空 | 非 ALL 且关联表未覆盖该门店 |
| 接口报错列不存在 | 未执行 fl_label_multiple_option_scope.sql |
| 保存报「至少需要匹配到一个有效门店」 | Region 下无门店且 locationIds 为空 |
label-type 标签类型
应用服务:LabelTypeAppService
命名约定:UI Region = regionIds / groupIds(fl_group.Id);UI Location = locationIds(location.Id)。存储表 fl_label_type_location(与 label-category 一致)。
数据库变更(新增/编辑 Region·Location 前须执行)
脚本:美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_type_scope.sql
ALTER TABLE `fl_label_type`
ADD COLUMN `AvailabilityType` varchar(20) NOT NULL DEFAULT 'ALL' COMMENT '门店可用范围:ALL/SPECIFIED' AFTER `State`;
CREATE TABLE IF NOT EXISTS `fl_label_type_location` (
`Id` varchar(36) NOT NULL,
`LabelTypeId` varchar(36) NOT NULL,
`LocationId` varchar(36) NOT NULL,
`CreationTime` datetime NOT NULL,
`CreatorId` varchar(36) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `idx_fl_ltl_type` (`LabelTypeId`),
KEY `idx_fl_ltl_location` (`LocationId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签类型适用门店';
| 表/字段 | 说明 |
|---|---|
fl_label_type.AvailabilityType |
ALL = 全部门店;SPECIFIED = 仅关联表内门店 |
fl_label_type_location |
LabelTypeId + LocationId 多选落库 |
新增 POST /api/app/label-type、编辑 PUT /api/app/label-type/{id}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| typeCode | string | 是 | 类型编码 |
| typeName | string | 是 | 类型名称 |
| state | bool | 否 | 默认 true |
| orderNum | int | 否 | 排序 |
| availabilityType | string | 否 | ALL / SPECIFIED;传了 region/location 数组时自动 SPECIFIED |
| regionIds | string[] | 否 | Region 多选(fl_group.Id);推荐字段名 |
| groupIds | string[] | 否 | 与 regionIds 合并去重(兼容) |
| locationIds | string[] | 否 | Location 多选(location.Id) |
合并规则:每个 regionIds 展开为该 Region 下全部门店,再与 locationIds 取并集 → 写入 fl_label_type_location;SPECIFIED 时至少 1 个有效门店,否则报错:指定适用区域或门店时,至少需要匹配到一个有效门店。
请求示例
POST /api/app/label-type
{
"typeCode": "PRICE",
"typeName": "Price Label",
"state": true,
"orderNum": 10,
"availabilityType": "SPECIFIED",
"regionIds": ["fl_group_id_east"],
"locationIds": ["11111111-1111-1111-1111-111111111111"]
}
详情 GET /api/app/label-type/{id} 出参(范围字段)
| 字段 | 说明 |
|---|---|
| availabilityType | ALL / SPECIFIED |
| regionIds | Region Id 数组(SPECIFIED 时由门店反推) |
| groupIds | 与 regionIds 相同 |
| locationIds | 已绑定门店 Id |
编辑:Body 字段与新增相同;会 先删后插 重建 fl_label_type_location。
列表 GET /api/app/label-type
示例:?SkipCount=1&MaxResultCount=10
列表 UI 列与 API 字段对照
| 列表列(UI) | API 字段(camelCase) | 类型 | 说明 |
|---|---|---|---|
| No. of Labels | noOfLabels | long | 该 Label Type 下 未删除 标签条数(fl_label.LabelTypeId = fl_label_type.Id 且 IsDeleted = 0) |
| Region | region | string | ALL → All Regions;SPECIFIED → 绑定门店 GroupName 拼接;未配置为 无 |
| Location | location | string | ALL → All Locations;SPECIFIED → 门店名拼接 |
| (辅助) | regionIds | string[] | Region 主键(fl_group.Id,由门店反推) |
| (辅助) | locationIds | string[] | 门店主键(location.Id) |
| Last Edited | lastEdited | datetime | 类型与下属标签最近编辑时间的较大值 |
No. of Labels 是否需要库字段?
不需要 NoOfLabels 物理列,由 fl_label 实时 COUNT。
| 表 | 作用 |
|---|---|
fl_label_type |
类型主数据 + AvailabilityType |
fl_label_type_location |
新增/编辑时 Region·Location 多选落库 |
fl_label |
标签实例;noOfLabels 统计来源 |
location |
门店;Region 展示用 GroupName |
统计逻辑(与代码一致):
-- 某类型下标签数(No. of Labels)
SELECT COUNT(*) AS no_of_labels
FROM fl_label
WHERE IsDeleted = 0
AND LabelTypeId = :labelTypeId;
列表接口在分页查出类型后,对当前页 Id 批量 GROUP BY LabelTypeId 计数,避免 N+1;标签增删改后无需改类型表,下次列表即反映最新数量。
变更说明
| 项 | 说明 |
|---|---|
列表 GET /api/app/label-type |
出参增加 noOfLabels、region、location、regionIds、locationIds、lastEdited |
| 列表筛选 | Query 可选 groupId(Region)、locationId(Location);locationId 优先于 groupId |
| 统计一致性 | 传 Region/门店筛选时,noOfLabels、region/location 仅统计筛选范围内标签 |
| 筛选语义 | 传 groupId/locationId 时:返回 availabilityType=ALL 或 关联门店命中 的类型(与 label-category 一致) |
| 列表 region/location | 来自 配置(AvailabilityType + fl_label_type_location),非仅标签反推 |
命名约定:UI Region = Query groupId(fl_group.Id);UI Location = Query locationId(location.Id)。
列表 Query
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| skipCount | int | 是 | 跳过条数(项目约定:第 1 页常为 1) |
| maxResultCount | int | 是 | 每页条数 |
| sorting | string | 否 | 排序,如 orderNum desc |
| keyword | string | 否 | 匹配 typeCode、typeName |
| state | bool | 否 | 启用状态 |
| groupId | string | 否 | Region 筛选(fl_group.Id) |
| locationId | string | 否 | Location 筛选(location.Id);优先于 groupId |
请求示例(默认列表,含三列出参)
GET /api/app/label-type?SkipCount=1&MaxResultCount=10
Authorization: {token}
按 Region / Location 筛选
GET /api/app/label-type?SkipCount=1&MaxResultCount=10&groupId=你的fl_group主键
Authorization: {token}
GET /api/app/label-type?SkipCount=1&MaxResultCount=10&locationId=11111111-1111-1111-1111-111111111111
Authorization: {token}
列表出参 items[](节选)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 类型主键(fl_label_type.Id) |
| typeCode / typeName | string | 编码、名称 |
| state / orderNum | bool / int | 状态、排序 |
| availabilityType | string | ALL / SPECIFIED |
| noOfLabels | long | 列表列 No. of Labels |
| region | string | 列表列 Region |
| location | string | 列表列 Location |
| regionIds | string[] | Region Id(fl_group.Id) |
| locationIds | string[] | 门店 Id |
| lastEdited | datetime | 最近编辑时间 |
响应 items[] 示例
{
"id": "type_price_001",
"typeCode": "PRICE",
"typeName": "Price Label",
"state": true,
"availabilityType": "SPECIFIED",
"orderNum": 10,
"region": "East Region",
"location": "UNCC store, Central Park Store",
"regionIds": ["fl_group_id_east"],
"locationIds": ["11111111-1111-1111-1111-111111111111"],
"noOfLabels": 8,
"lastEdited": "2026-05-18T10:00:00"
}
库内核对 SQL(只读,无需执行 DDL)
1)确认 fl_label_type 无 NoOfLabels 列(不应加列)
SELECT COLUMN_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'fl_label_type'
ORDER BY ORDINAL_POSITION;
2)按类型统计标签数(与接口 noOfLabels 一致)
SELECT
t.Id,
t.TypeCode,
t.TypeName,
COUNT(l.Id) AS no_of_labels
FROM fl_label_type t
LEFT JOIN fl_label l
ON l.LabelTypeId = t.Id AND l.IsDeleted = 0
WHERE t.IsDeleted = 0
GROUP BY t.Id, t.TypeCode, t.TypeName
ORDER BY t.OrderNum DESC, t.CreationTime DESC;
3)某类型已配置的 Region / Location(与列表 region/location 一致)
SELECT
t.Id,
t.TypeName,
t.AvailabilityType,
tl.LocationId,
loc.GroupName AS region_name,
COALESCE(NULLIF(TRIM(loc.LocationName), ''), loc.LocationCode) AS location_name
FROM fl_label_type t
LEFT JOIN fl_label_type_location tl ON tl.LabelTypeId = t.Id
LEFT JOIN location loc ON loc.Id = tl.LocationId AND loc.IsDeleted = 0
WHERE t.IsDeleted = 0
AND t.Id = :labelTypeId;
联调注意(label-type)
| 现象 | 可能原因 |
|---|---|
| noOfLabels 为 0 | 该类型下尚无标签,或标签均已逻辑删除 |
| region / location 为「无」 | SPECIFIED 但未绑定门店,或门店无 GroupName/名称 |
| 保存报「至少需要匹配到一个有效门店」 | Region 下无门店且 locationIds 为空 |
| 筛 Region 后列表变空 | 非 ALL 且 fl_label_type_location 未覆盖该门店 |
| 未执行 DDL | 先跑 fl_label_type_scope.sql |
| noOfLabels 与标签列表不一致 | 对比时 label 列表需传与 label-type 相同的 groupId/locationId |
| 前端列空白 | 绑定 noOfLabels、region、location(勿用 creationTime 代替 lastEdited) |
与 label-category(见
5-17接口优化.md)区别:分类有availabilityType=ALL;标签类型无 ALL,三列均由fl_label实时汇总。不提供
ALTER TABLE fl_label_type ADD NoOfLabels ...:冗余字段易与fl_label不一致,且每次标签变更都要维护计数。
team-member 列表 Company·Region·Location 筛选
应用服务:TeamMemberAppService
接口:GET /api/app/team-member(分页;PDF 导出 export-team-members-pdf 使用相同 Query 筛选)
辅助类:LocationScopeBindingHelper.ResolveFilteredLocationIdsForListAsync
Query 参数(在原有 Keyword / RoleId / State 基础上)
| UI | Query 参数 | 说明 |
|---|---|---|
| Company | partnerId |
fl_partner.Id |
| Region | groupId |
fl_group.Id |
| Location | locationId |
location.Id(Guid 字符串) |
筛选优先级(与 product 列表一致):传 locationId 时仅按该门店;否则传 groupId 按 Region 下全部门店;否则传 partnerId 按 Company 下全部门店;均未传则不按组织范围过滤。
命中规则:返回在 userlocation 中至少绑定一家「落在上述门店集合内」的成员;列表行内 assignedLocations 在传入范围参数时仅展示该范围内的绑定门店。
请求示例
GET /api/app/team-member?SkipCount=1&MaxResultCount=10&partnerId={fl_partner.Id}
Authorization: Bearer {token}
GET /api/app/team-member?SkipCount=1&MaxResultCount=10&groupId={fl_group.Id}
GET /api/app/team-member?SkipCount=1&MaxResultCount=10&locationId={location.Id}
可与 Keyword、RoleId、State、Sorting 组合使用。
库内核对
-- 某 Region 下应出现在列表中的成员(userId 去重)
SELECT DISTINCT ul.UserId
FROM userlocation ul
INNER JOIN location loc ON ul.LocationId = CAST(loc.Id AS CHAR) AND loc.IsDeleted = 0
WHERE ul.IsDeleted = 0
AND loc.Partner = :partnerName
AND loc.GroupName = :groupName;
联调注意
| 现象 | 处理 |
|---|---|
传了 groupId 仍无数据 |
确认成员 userlocation 是否绑定该区域下门店 |
partnerId / groupId 无效 |
返回空列表(非 404) |
| 与 5-17 新增字段关系 | 列表 筛选 用 Query;新增/编辑 Body 仍用 partnerId / regionIds / locationIds(见 5-17接口优化.md) |
team-member 新增用户无法平台登录
问题现象
通过 POST /api/app/team-member 新增用户(如 email=123@qq.com、userName=1234)后,在平台登录页用 邮箱 + 创建时密码 登录失败(Sign-in failed: incorrect email or password)。
根因(两处)
1)新增时双重 BuildPassword(已修复)
new UserAggregateRoot(userName, password, …)构造函数内已BuildPassword()一次;- 若再调用
user.BuildPassword(),会对 已哈希值再哈希,明文登录必失败。
2)改密时 SqlSugar IsOwnsOne 只更新了 Salt、未更新 Password(本次主因)
PUT /api/app/team-member/{id}传入新明文 →BuildPassword()会生成 新 Salt + 新 Password 哈希;UpdateAsync整表更新时Password列可能未写入,库中仍保留旧哈希(例如与 admin 相同的ANg9hGZC…),但 Salt 已是新值;- 校验
SHA2Encode(明文, Salt)与库中Password永远对不上(查库可见123@qq.com与admin的Password相同、Salt不同)。
代码修复
- 新增:与
UserDataSeed一致,EncryPassword = new EncryPasswordValueObject(明文)后 只调用一次BuildPassword()。 - 改密 / 重置密码:改密后调用
UserPasswordHelper.EnsurePasswordColumnsPersistedAsync,显式 SETPassword与Salt(Yi.Framework.Rbac.Domain/Helpers/UserPasswordHelper.cs)。 - 登录校验统一走
UserPasswordHelper.VerifyPlainPassword/UserAggregateRoot.JudgePassword。
平台登录约定
| 项目 | 说明 |
|---|---|
| 接口 | POST /api/app/account/login(AccountService.PostLoginAsync) |
| 入参 | userName 填 邮箱(平台 UI 的 Email 框);password 为明文 |
| 匹配 | User.Email(忽略大小写)优先;否则 User.UserName 与邮箱相同也可 |
| 状态 | State = true 且未删除才可登录 |
登录示例
POST /api/app/account/login
{
"userName": "123@qq.com",
"password": "创建时设置的明文密码"
}
已受影响账号的处理
部署修复后,已受影响账号(如 123@qq.com,库内 Password/Salt 不一致)必须 再保存一次密码:
PUT /api/app/team-member/{id}
Content-Type: application/json
{
"fullName": "显示名",
"userName": "1234",
"email": "123@qq.com",
"password": "新的明文密码",
"phone": 1234567890,
"roleId": "角色Guid",
"locationIds": ["门店Id"],
"state": true
}
保存后用 邮箱 + 新密码 登录。
库内核对
SELECT Id, UserName, Email, State, Password, Salt
FROM User
WHERE Email = '123@qq.com' AND IsDeleted = 0;
-- 正常:同一用户的 Password 须与 SHA512(salt+明文) 一致;勿出现与 admin 相同 Password、不同 Salt。
新增成员入参提醒
| 字段 | 说明 |
|---|---|
| userName | 登录名(可与邮箱不同);平台登录仍建议用 email 字段 |
| 平台登录主键(须为合法邮箱格式) | |
| password | 明文,至少 6 位(UserManager 校验) |
| roleId | 须绑定角色,否则登录后可能无菜单 |
| locationIds / partnerId / groupIds | 门店范围(见 5-17 team-member 章节) |
rbac-role accessPermissions
以下为 /api/app/rbac-role 相关说明(原 5-18 内容)。
问题与根因
| 现象 | 根因 |
|---|---|
POST/PUT 传了 accessPermissions 后仍无菜单绑定 |
库表 Menu.PermissionCode 全为空,按 Code 解析不到 MenuId;或入参 menuIds: [] 抢先清空绑定 |
GET /api/app/rbac-role/{id} 的 accessPermissions 为 "" |
无 RoleMenu 记录,或已绑定菜单的 PermissionCode 为空 |
menuIds 详情为空(历史问题) |
曾错误查询 rolemenu 字符串表;已改为查询 RoleMenu(Guid) |
数据链路(正确)
accessPermissions(入参/出参字符串)
↔ Menu.PermissionCode(权限码,可为空时按 Router 推导 menu.xxx)
↔ RoleMenu(RoleId + MenuId)
↔ Role
变更摘要
| 项 | 说明 |
|---|---|
| 绑定写入 | accessPermissions 解析为 MenuId 后 覆盖写入 RoleMenu;解析不到任何菜单时 返回明确错误 |
| 出参汇总 | GET 列表/详情从 RoleMenu + Menu.PermissionCode 汇总;PermissionCode 为空时按 Router 推导(与回填 SQL 一致) |
| 详情 menuIds | 从 RoleMenu 读取,不再查错误的 rolemenu 映射 |
| RoleMenu 主键 | 插入时生成 Id(RoleMenu 表必填) |
| 数据库脚本 | scripts/menu_backfill_permission_code.sql 批量回填 PermissionCode |
公共约定
- 宿主:
Yi.Abp.Web;路由前缀api/app。 - 应用服务:
RbacRoleAppService(模块food-labeling-us)。 - 鉴权:
Authorization: {token}。
accessPermissions 规则
出参(只读)
- 查询
RoleMenu中该角色绑定的菜单,取Menu.PermissionCode(非空优先)。 - 若
PermissionCode为空,按Router推导:/labels→menu.labels。 - 去重后按字母序用
,拼接;无绑定为""。
入参(写入)
| 场景 | menuIds | accessPermissions | 行为 |
|---|---|---|---|
| 新增/编辑 | 非空数组 | 任意 | 以 menuIds 为准(覆盖 RoleMenu) |
| 新增/编辑 | 不传 / null | 非空字符串 | 按 PermissionCode 解析菜单并 覆盖绑定 |
| 新增/编辑 | 不传 / null | "" |
清空 RoleMenu |
| 新增/编辑 | [] |
不传 | 清空 RoleMenu |
| 编辑 | 不传 | 不传 / null | 不修改 已有菜单绑定 |
- 解析 忽略大小写;支持英文逗号、分号分隔。
- 若传了非空
accessPermissions但 0 条菜单匹配,接口返回业务错误(提示检查PermissionCode或执行回填脚本)。
1 角色详情
| 项目 | 说明 |
|---|---|
| HTTP | GET |
| 路径 | /api/app/rbac-role/{id} |
| 示例 | /api/app/rbac-role/3a1f077b-3665-63f2-5fea-0fd7e7044b88 |
响应 data 字段(节选)
| 字段 | 说明 |
|---|---|
| menuIds | 已绑定菜单 Guid 字符串数组(来自 RoleMenu) |
| accessPermissions | 已绑定菜单权限码汇总,如 menu.account-management, menu.labels |
2 新增角色
| 项目 | 说明 |
|---|---|
| HTTP | POST |
| 路径 | /api/app/rbac-role |
请求示例(accessPermissions)
{
"roleName": "Partner Admin",
"roleCode": "admin",
"state": true,
"accessPermissions": "menu.labels, menu.label-categories, menu.account-management"
}
须先保证
Menu表存在对应PermissionCode(或执行回填脚本后使用menu.{router-segment}形式)。
3 编辑角色
| 项目 | 说明 |
|---|---|
| HTTP | PUT |
| 路径 | /api/app/rbac-role/{id} |
| Body | 与新增相同(RbacRoleUpdateInputVo) |
数据库准备(必做)
执行(按环境选择库):
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/menu_backfill_permission_code.sql
回填后示例:
| MenuName | Router | PermissionCode |
|---|---|---|
| Labels | /labels | menu.labels |
| Account Management | /account-management | menu.account-management |
验证 SQL
SELECT r.RoleName, m.MenuName, m.PermissionCode
FROM Role r
INNER JOIN RoleMenu rm ON rm.RoleId = r.Id
INNER JOIN Menu m ON m.Id = rm.MenuId AND m.IsDeleted = 0
WHERE r.Id = '3a1f077b-3665-63f2-5fea-0fd7e7044b88';
关联接口
| 接口 | 说明 |
|---|---|
POST /api/app/rbac-role-menu/set |
仅维护 RoleMenu(menuIds 覆盖式),不直接写 accessPermissions 列 |
GET /api/app/rbac-role-menu/menu-ids/{roleId} |
查询已绑定菜单 Id |
保存菜单权限后,再调 GET /api/app/rbac-role/{id} 应能看到非空 accessPermissions(在 PermissionCode 已配置前提下)。
联调注意
| 现象 | 处理 |
|---|---|
| accessPermissions 仍为空 | 1)查 RoleMenu 是否有记录;2)查 Menu.PermissionCode 是否已回填 |
| 保存报未匹配到菜单 | accessPermissions 与库中 Code 不一致;先执行回填脚本或改用 menuIds |
同时传 menuIds: [] 与 accessPermissions |
menuIds 优先,空数组会清空绑定,accessPermissions 被忽略 |
修订记录
| 日期 | 说明 |
|---|---|
| 2026-05-18 | 修复 rbac-role accessPermissions 读写;RoleMenu 绑定;Menu.PermissionCode 回填脚本 |
| 2026-05-18 | 修复 team-member 登录:去掉双重 BuildPassword;改密显式落库 Password+Salt;已建账号需 PUT 重置密码 |
| 2026-05-18 | label-type 列表 noOfLabels/region/location;新增编辑 regionIds+locationIds 多选;DDL fl_label_type_scope.sql |
| 2026-05-18 | label-multiple-option 列表筛选与出参;新增/编辑 regionIds+locationIds 多选;DDL fl_label_multiple_option_scope.sql |
| 2026-05-18 | reports print-log-list:Label ID 改为门店当日序号 yyyyMMdd-n(labelCode 字段) |
| 2026-05-18 | reports print-log-list:Expiry Date 从 PrintInputJson 模板快照 elements 解析(expiryDateText) |
| 2026-05-18 | reports label-report:管理员全量;非管理员按 userlocation 绑定门店统计(不按 CreatedBy) |
| 2026-05-18 | us-app-auth location-detail:出参 operatingHours 读 location.OperatingHours |
| 2026-05-18 | us-app get-print-log-list / get-label-report:管理员与 Partner 可看当前门店全部打印;reprint 同权 |
| 2026-05-18 | us-app labeling-tree:产品已绑门店时不再因产品分类 SPECIFIED 无 location 关联而空树 |
| 2026-05-18 | location 列表:管理员全部门店;非管理员仅绑定门店所属 Region(Partner+GroupName) |
| 2026-05-18 | team-member 列表:Query 增加 partnerId / groupId / locationId(Company·Region·Location)筛选 |
| 2026-05-18 | reports template-print-stat-list:按模板汇总打印标签数量(templateName + printedCount) |