6-11 代码优化
本文档说明 2026-06-11 对美国版 App 标签预览/打印相关的两项纯后端改造(不改 Web / App 前端):
POST /api/app/us-app-labeling/preview出参labelId改为门店当日序号yyyyMMdd-n- 模板 Company 自动生成元素:预览/打印时按门店从
fl_partner填充公司名及可选地址字段
测试环境:http://flus-test.3ffoodsafety.com
一、Preview 出参 labelId 格式
背景
预览页「Label ID」原先返回 fl_label.Id(GUID,如 3a2192be-7b8f-e3e8-db9c-3a5e627b9222),与业务要求不符。
业务规则:Label ID = 某门店当日每次打印任务的递增序号,与 Print Log、管理端报表一致:
| 示例 | 含义 |
|---|---|
20260513-1 |
该门店 2026-05-13 当日第 1 次打印 |
20260513-2 |
同日第 2 次 |
20260514-1 |
次日重新从 1 计数 |
格式:{yyyyMMdd}-{n}(n 从 1 递增,按 PrintedAt ?? CreationTime 所在自然日、同一 locationId 统计)。
接口说明
| 项目 | 内容 |
|---|---|
| 方法 | POST |
| 路径 | /api/app/us-app-labeling/preview |
| 鉴权 | Bearer Token |
入参(Body:UsAppLabelPreviewInputVo)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
locationId |
string | 是 | 门店 Id(location.Id) |
labelCode |
string | 是 | 标签编码(fl_label.LabelCode) |
productId |
string | 否 | 预览产品 Id |
baseTime |
DateTime | 否 | 基准时间;影响模板内日期/时间控件,也用于确定「哪一天的序号」;未传则用服务端当前时间 |
printInputJson |
object | 否 | 打印时输入项 |
出参(UsAppLabelPreviewDto)变更
| 字段 | 变更前 | 变更后 |
|---|---|---|
labelId |
fl_label.Id(GUID) |
门店当日下一个打印序号 yyyyMMdd-n |
其余字段(locationId、labelCode、template、labelLastEdited 等)不变。
labelId 计算规则
- 取
baseTime ?? 当前服务器时间的日期部分yyyyMMdd。 - 统计该
locationId在当日内已有打印任务数(fl_label_print_task,时间取PrintedAt ?? CreationTime)。 labelId = {yyyyMMdd}-{已有任务数 + 1}(预览不落库,表示「若此刻点击 Print 将获得的序号」)。- 与
POST /api/app/us-app-labeling/get-print-log-list、管理端GET /api/app/reports/print-log-list使用同一 Helper:ReportsPrintLogDailyLabelIdHelper。
注意:
labelId不是fl_label.LabelCode,也不是fl_label.Id。标签主键如需内部关联,请使用打印任务创建后的taskId或 print-log 中的labelEntityId。
请求示例(labelId)
curl -X POST "http://flus-test.3ffoodsafety.com/api/app/us-app-labeling/preview" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"locationId": "550e8400-e29b-41d4-a716-446655440000",
"labelCode": "LBL0001",
"productId": "PROD001",
"baseTime": "2026-05-13T09:00:00"
}'
响应片段(示例)
{
"labelId": "20260513-3",
"locationId": "550e8400-e29b-41d4-a716-446655440000",
"labelCode": "LBL0001",
"labelLastEdited": "2026-06-01T08:30:09",
"template": { }
}
若该门店在 2026-05-13 已有 2 条打印记录,预览返回 20260513-3;当日首条预览为 20260513-1。
涉及代码(labelId)
| 文件 | 说明 |
|---|---|
Helpers/ReportsPrintLogDailyLabelIdHelper.cs |
新增 ResolveNextDailyLabelIdAsync(预览取下一序号) |
Services/UsAppLabelingAppService.cs |
PreviewAsync 出参 LabelId 改用当日序号 |
Dtos/UsAppLabeling/UsAppLabelPreviewDto.cs |
labelId XML 注释 |
IServices/IUsAppLabelingAppService.cs |
接口注释 |
验证步骤(labelId)
- 选定门店,确认当日已有 N 条
fl_label_print_task(可用 print-log 列表核对)。 - 调用 preview,
labelId应为{今日yyyyMMdd}-{N+1}。 - 执行 print 创建新任务后,print-log 中该任务
labelId与预览时一致(同一时刻连续预览+打印)。 - 修改
baseTime为历史日期,序号应按该日任务数计算,而非「今天」。
SQL 抽查
SELECT Id, LocationId, PrintedAt, CreationTime
FROM fl_label_print_task
WHERE LocationId = '{locationId}'
AND IFNULL(PrintedAt, CreationTime) >= CURDATE()
AND IFNULL(PrintedAt, CreationTime) < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
ORDER BY IFNULL(PrintedAt, CreationTime), Id;
当日行数 + 1 应与 preview 返回的 -n 部分一致。
二、Company 自动生成元素(预览/打印填充)
背景
模板编辑器中的 Company 控件属于「按门店自动取数」类型。用户点击打印时,后端应:
- 始终展示当前门店所属 Company 名称(
fl_partner.PartnerName) - 仅当模板元素 config 中勾选 Address / City / State / Zip / Email 时,才追加对应行;未勾选或库中无值则不展示该字段
勾选状态不落独立表,保存在 fl_label_template_element.ConfigJson(通过 POST/PUT /api/app/label-template 的 elements[].config 写入)。公司主数据来自 fl_partner。
本次不提供 Web 属性面板 UI;需通过 label-template 保存接口 或 SQL 维护
config。
元素识别规则
满足以下任一条件即视为 Company 自动生成元素(PartnerCompanyDisplayHelper.IsCompanyAutoElement):
| 条件 | 说明 |
|---|---|
typeAdd = "auto_Company" |
标准 Company 控件 |
valueSourceType = "AUTO_DB" 且 typeAdd 以 auto_ 开头且含 Company |
兼容命名 |
常见组合:elementType = "TEXT_STATIC",valueSourceType = "AUTO_DB",typeAdd = "auto_Company"。
模板 config(勾选字段)
写入 elements[].config(JSON),支持两种等价写法(可混用):
方式 A:数组 companyIncludeFields
| 数组值 | 含义 | 数据来源(fl_partner) |
|---|---|---|
address |
街道地址 | Street |
city |
城市 | City |
state |
州/省 | StateCode |
zip |
邮编 | ZipCode |
email |
邮箱 | ContactEmail |
方式 B:布尔开关
includeAddress / includeCity / includeState / includeZip / includeEmail(true 表示勾选)
示例(保存模板时)
{
"elementType": "TEXT_STATIC",
"valueSourceType": "AUTO_DB",
"typeAdd": "auto_Company",
"name": "Company",
"config": {
"companyIncludeFields": ["address", "city", "state", "zip", "email"]
}
}
或:
"config": {
"includeAddress": true,
"includeCity": true,
"includeState": true,
"includeZip": true,
"includeEmail": false
}
展示文本格式
后端将解析结果写入 template.elements[].config.text(多行,\n 分隔):
| 行序 | 内容 | 规则 |
|---|---|---|
| 第 1 行 | 公司名 | 固定输出(有 PartnerName 时) |
| 第 2 行 | 街道 | 仅勾选 address 且 Street 非空 |
| 第 3 行 | 城市, 州, 邮编 | 勾选 city/state/zip 中任一项时,按「City, StateCode, ZipCode」逗号拼接(空段跳过) |
| 第 4 行 | 邮箱 | 仅勾选 email 且 ContactEmail 非空 |
渲染示例
Acme Foods Inc
123 Main Street
New York, NY, 10001
sales@acme.com
若仅勾选公司名(无任何 include),则 text 仅一行公司名。
门店 → Company 解析
- 入参
locationId(location.Id,Guid 字符串) - 查
location表取Partner字段 - 在
fl_partner中按Id = Partner或PartnerName = Partner匹配(未删除) - 匹配失败时不抛错,
config.text保持原样(不填充)
入参要求
| 场景 | locationId |
|---|---|
| 模板不含 Company 自动生成元素 | 可选(App preview 仍必填门店,与原有逻辑一致) |
| 模板含 Company 自动生成元素 | LabelPreviewResolveInputVo.locationId 必填;缺失返回 400:预览/打印需要 locationId 以填充 Company 信息 |
App 调用链:UsAppLabelPreviewInputVo.locationId → 内部 LabelAppService.PreviewAsync(LabelPreviewResolveInputVo) 时须原样传入 locationId。
受影响接口(均通过 LabelAppService.PreviewAsync 渲染模板):
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/app/us-app-labeling/preview |
App 预览 |
| POST | /api/app/us-app-labeling/print |
App 打印(落库前解析模板) |
| POST | /api/app/label/preview(或管理端等价预览) |
Web 预览;含 Company 元素时 Body 须带 locationId |
重打 reprint 使用历史任务 RenderTemplateJson 快照,不再重新解析 Company。
出参变更
无新增顶层字段;变更在 template.elements[] 内:
| 字段 | 变更 |
|---|---|
elements[].config.text |
Company 元素由后端按上文规则写入多行文本 |
数据库与脚本
| 项 | 说明 |
|---|---|
fl_partner.PartnerName |
公司名(必有) |
fl_partner.Street / City / StateCode / ZipCode / ContactEmail |
可选展示字段 |
location.Partner |
门店归属 Company 键(Id 或名称) |
| DDL | scripts/fl_partner_add_address_columns.sql(缺地址列时执行) |
模板适用范围(Company/Region/Location 三维 scope)见 项目相关文档/6-4代码优化.md,与本节「元素内 Company 自动填值」相互独立。
请求示例(含 Company 的 preview)
curl -X POST "http://flus-test.3ffoodsafety.com/api/app/us-app-labeling/preview" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"locationId": "550e8400-e29b-41d4-a716-446655440000",
"labelCode": "LBL0001",
"productId": "PROD001"
}'
响应 template.elements 中 Company 元素片段示例:
{
"elementType": "TEXT_STATIC",
"typeAdd": "auto_Company",
"valueSourceType": "AUTO_DB",
"config": {
"companyIncludeFields": ["address", "city", "state", "zip"],
"text": "Acme Foods Inc\n123 Main Street\nNew York, NY, 10001"
}
}
涉及代码(Company)
| 文件 | 说明 |
|---|---|
Helpers/PartnerCompanyDisplayHelper.cs |
识别元素、解析 config、格式化文本、门店→Partner |
Services/LabelAppService.cs |
PreviewAsync 填充 Company 的 config.text |
Dtos/Label/LabelPreviewResolveInputVo.cs |
新增 locationId |
Services/UsAppLabelingAppService.cs |
preview/print 调用预览时传入 locationId |
验证步骤(Company)
- 确认门店
location.Partner能关联到有效fl_partner记录(地址列已执行 DDL)。 - 模板含
auto_Company元素,config勾选若干 include 字段;保存后查fl_label_template_element.ConfigJson。 - 调用 preview,Body 带正确
locationId:template.elements中 Company 的config.text行数与勾选一致。 - 去掉
locationId或传空 → 返回 400(仅当模板含 Company 元素时)。 - 执行 print,检查
fl_label_print_task.RenderTemplateJson内 Companytext与预览一致。
SQL 抽查
-- 门店归属 Company
SELECT l.Id, l.Partner, p.PartnerName, p.Street, p.City, p.StateCode, p.ZipCode, p.ContactEmail
FROM location l
LEFT JOIN fl_partner p ON (p.Id = l.Partner OR p.PartnerName = l.Partner) AND p.IsDeleted = 0
WHERE l.Id = '{locationId}' AND l.IsDeleted = 0;
-- 模板 Company 元素 config
SELECT e.Id, e.TypeAdd, e.ValueSourceType, e.ConfigJson
FROM fl_label_template_element e
JOIN fl_label_template t ON t.Id = e.TemplateId
WHERE t.TemplateCode = '{templateCode}' AND e.IsDeleted = 0
AND e.TypeAdd = 'auto_Company';
关联文档
- 同序号规则:
项目相关文档/6-2代码优化.md→ Appget-print-log-list的 Label ID - 模板三维 scope:
项目相关文档/6-4代码优化.md→/api/app/label-template - App 预览页读取字段:
labelId/LabelId(preview.vue)