Commit 2748a56b8b75a8271852871e9520bb56ae933472
1 parent
9a5ffaaa
实现标签模块
Showing
19 changed files
with
1626 additions
and
55 deletions
label-template-template-1773988794039 (1).json renamed to label-template-template-1773999322132.json
| 1 | 1 | { |
| 2 | - "id": "template-1773988794039", | |
| 2 | + "id": "template-1773999322132", | |
| 3 | 3 | "name": "未命名模板", |
| 4 | 4 | "labelType": "PRICE", |
| 5 | - "unit": "inch", | |
| 6 | - "width": 4, | |
| 7 | - "height": 6, | |
| 5 | + "unit": "cm", | |
| 6 | + "width": 21, | |
| 7 | + "height": 29.7, | |
| 8 | 8 | "appliedLocation": "ALL", |
| 9 | 9 | "showRuler": true, |
| 10 | 10 | "showGrid": true, |
| 11 | 11 | "elements": [ |
| 12 | 12 | { |
| 13 | - "id": "el-1773989351080-vqc03nr", | |
| 14 | - "type": "IMAGE", | |
| 15 | - "x": 32, | |
| 16 | - "y": 24, | |
| 17 | - "width": 60, | |
| 18 | - "height": 60, | |
| 19 | - "rotation": "horizontal", | |
| 20 | - "border": "none", | |
| 21 | - "config": { | |
| 22 | - "src": "", | |
| 23 | - "scaleMode": "contain" | |
| 24 | - } | |
| 25 | - }, | |
| 26 | - { | |
| 27 | - "id": "el-1773989452538-0ejrxoe", | |
| 13 | + "id": "el-1773999335969-ugvoy6v", | |
| 28 | 14 | "type": "TEXT_STATIC", |
| 29 | - "x": 32, | |
| 30 | - "y": 104, | |
| 15 | + "x": 392, | |
| 16 | + "y": 48, | |
| 31 | 17 | "width": 120, |
| 32 | 18 | "height": 24, |
| 33 | 19 | "rotation": "horizontal", |
| ... | ... | @@ -41,10 +27,10 @@ |
| 41 | 27 | } |
| 42 | 28 | }, |
| 43 | 29 | { |
| 44 | - "id": "el-1773989466493-ibbroio", | |
| 30 | + "id": "el-1773999339450-cc9y2lv", | |
| 45 | 31 | "type": "QRCODE", |
| 46 | - "x": 32, | |
| 47 | - "y": 136, | |
| 32 | + "x": 200, | |
| 33 | + "y": 24, | |
| 48 | 34 | "width": 80, |
| 49 | 35 | "height": 80, |
| 50 | 36 | "rotation": "horizontal", |
| ... | ... | @@ -55,10 +41,10 @@ |
| 55 | 41 | } |
| 56 | 42 | }, |
| 57 | 43 | { |
| 58 | - "id": "el-1773989469008-f1l39qj", | |
| 44 | + "id": "el-1773999341556-c795unj", | |
| 59 | 45 | "type": "BARCODE", |
| 60 | - "x": 0, | |
| 61 | - "y": 224, | |
| 46 | + "x": 8, | |
| 47 | + "y": 48, | |
| 62 | 48 | "width": 160, |
| 63 | 49 | "height": 48, |
| 64 | 50 | "rotation": "horizontal", |
| ... | ... | @@ -71,20 +57,20 @@ |
| 71 | 57 | } |
| 72 | 58 | }, |
| 73 | 59 | { |
| 74 | - "id": "el-1773989473436-j7fdeh2", | |
| 60 | + "id": "el-1773999346493-onxkjxn", | |
| 75 | 61 | "type": "BLANK", |
| 76 | - "x": 32, | |
| 77 | - "y": 288, | |
| 78 | - "width": 48, | |
| 79 | - "height": 32, | |
| 62 | + "x": 40, | |
| 63 | + "y": 120, | |
| 64 | + "width": 40, | |
| 65 | + "height": 24, | |
| 80 | 66 | "rotation": "horizontal", |
| 81 | 67 | "border": "none", |
| 82 | 68 | "config": {} |
| 83 | 69 | }, |
| 84 | 70 | { |
| 85 | - "id": "el-1773989483341-ifwcyjj", | |
| 71 | + "id": "el-1773999351836-igrbrib", | |
| 86 | 72 | "type": "TEXT_PRICE", |
| 87 | - "x": 152, | |
| 73 | + "x": 520, | |
| 88 | 74 | "y": 24, |
| 89 | 75 | "width": 80, |
| 90 | 76 | "height": 24, |
| ... | ... | @@ -101,10 +87,24 @@ |
| 101 | 87 | } |
| 102 | 88 | }, |
| 103 | 89 | { |
| 104 | - "id": "el-1773989498031-e4d61j8", | |
| 90 | + "id": "el-1773999357328-0jof4kh", | |
| 91 | + "type": "IMAGE", | |
| 92 | + "x": 480, | |
| 93 | + "y": 96, | |
| 94 | + "width": 60, | |
| 95 | + "height": 60, | |
| 96 | + "rotation": "horizontal", | |
| 97 | + "border": "none", | |
| 98 | + "config": { | |
| 99 | + "src": "", | |
| 100 | + "scaleMode": "contain" | |
| 101 | + } | |
| 102 | + }, | |
| 103 | + { | |
| 104 | + "id": "el-1773999360132-bwle4nb", | |
| 105 | 105 | "type": "IMAGE", |
| 106 | - "x": 192, | |
| 107 | - "y": 56, | |
| 106 | + "x": 152, | |
| 107 | + "y": 120, | |
| 108 | 108 | "width": 60, |
| 109 | 109 | "height": 60, |
| 110 | 110 | "rotation": "horizontal", |
| ... | ... | @@ -115,10 +115,10 @@ |
| 115 | 115 | } |
| 116 | 116 | }, |
| 117 | 117 | { |
| 118 | - "id": "el-1773989505076-1lxccx7", | |
| 118 | + "id": "el-1773999364492-yxs89ov", | |
| 119 | 119 | "type": "TEXT_PRODUCT", |
| 120 | - "x": 200, | |
| 121 | - "y": 136, | |
| 120 | + "x": 272, | |
| 121 | + "y": 120, | |
| 122 | 122 | "width": 120, |
| 123 | 123 | "height": 24, |
| 124 | 124 | "rotation": "horizontal", |
| ... | ... | @@ -132,10 +132,10 @@ |
| 132 | 132 | } |
| 133 | 133 | }, |
| 134 | 134 | { |
| 135 | - "id": "el-1773989509805-ax3392v", | |
| 135 | + "id": "el-1773999367121-3uesy24", | |
| 136 | 136 | "type": "TEXT_STATIC", |
| 137 | - "x": 192, | |
| 138 | - "y": 160, | |
| 137 | + "x": 48, | |
| 138 | + "y": 216, | |
| 139 | 139 | "width": 120, |
| 140 | 140 | "height": 24, |
| 141 | 141 | "rotation": "horizontal", |
| ... | ... | @@ -149,10 +149,10 @@ |
| 149 | 149 | } |
| 150 | 150 | }, |
| 151 | 151 | { |
| 152 | - "id": "el-1773989512993-xt8bg7q", | |
| 152 | + "id": "el-1773999369332-fib58lw", | |
| 153 | 153 | "type": "QRCODE", |
| 154 | - "x": 184, | |
| 155 | - "y": 184, | |
| 154 | + "x": 160, | |
| 155 | + "y": 208, | |
| 156 | 156 | "width": 80, |
| 157 | 157 | "height": 80, |
| 158 | 158 | "rotation": "horizontal", |
| ... | ... | @@ -163,10 +163,10 @@ |
| 163 | 163 | } |
| 164 | 164 | }, |
| 165 | 165 | { |
| 166 | - "id": "el-1773989525383-eji8p2s", | |
| 166 | + "id": "el-1773999371501-p1ot3id", | |
| 167 | 167 | "type": "BARCODE", |
| 168 | - "x": 0, | |
| 169 | - "y": 288, | |
| 168 | + "x": 280, | |
| 169 | + "y": 224, | |
| 170 | 170 | "width": 160, |
| 171 | 171 | "height": 48, |
| 172 | 172 | "rotation": "horizontal", |
| ... | ... | @@ -179,10 +179,10 @@ |
| 179 | 179 | } |
| 180 | 180 | }, |
| 181 | 181 | { |
| 182 | - "id": "el-1773989540159-dr2avdf", | |
| 182 | + "id": "el-1773999374282-ux37cow", | |
| 183 | 183 | "type": "NUTRITION", |
| 184 | - "x": 184, | |
| 185 | - "y": 280, | |
| 184 | + "x": 480, | |
| 185 | + "y": 200, | |
| 186 | 186 | "width": 200, |
| 187 | 187 | "height": 120, |
| 188 | 188 | "rotation": "horizontal", |
| ... | ... | @@ -196,10 +196,10 @@ |
| 196 | 196 | } |
| 197 | 197 | }, |
| 198 | 198 | { |
| 199 | - "id": "el-1773989549679-mcxrdnw", | |
| 199 | + "id": "el-1773999377353-n90ved8", | |
| 200 | 200 | "type": "TEXT_PRICE", |
| 201 | - "x": 24, | |
| 202 | - "y": 352, | |
| 201 | + "x": 664, | |
| 202 | + "y": 152, | |
| 203 | 203 | "width": 80, |
| 204 | 204 | "height": 24, |
| 205 | 205 | "rotation": "horizontal", |
| ... | ... | @@ -213,6 +213,113 @@ |
| 213 | 213 | "fontWeight": "bold", |
| 214 | 214 | "textAlign": "right" |
| 215 | 215 | } |
| 216 | + }, | |
| 217 | + { | |
| 218 | + "id": "el-1773999389265-sukmurs", | |
| 219 | + "type": "DATE", | |
| 220 | + "x": 40, | |
| 221 | + "y": 320, | |
| 222 | + "width": 120, | |
| 223 | + "height": 24, | |
| 224 | + "rotation": "horizontal", | |
| 225 | + "border": "none", | |
| 226 | + "config": { | |
| 227 | + "format": "YYYY-MM-DD", | |
| 228 | + "offsetDays": 0 | |
| 229 | + } | |
| 230 | + }, | |
| 231 | + { | |
| 232 | + "id": "el-1773999392135-3ub707o", | |
| 233 | + "type": "TIME", | |
| 234 | + "x": 144, | |
| 235 | + "y": 320, | |
| 236 | + "width": 100, | |
| 237 | + "height": 24, | |
| 238 | + "rotation": "horizontal", | |
| 239 | + "border": "none", | |
| 240 | + "config": { | |
| 241 | + "format": "HH:mm", | |
| 242 | + "offsetDays": 0 | |
| 243 | + } | |
| 244 | + }, | |
| 245 | + { | |
| 246 | + "id": "el-1773999394947-3y8li6m", | |
| 247 | + "type": "DURATION", | |
| 248 | + "x": 272, | |
| 249 | + "y": 320, | |
| 250 | + "width": 120, | |
| 251 | + "height": 24, | |
| 252 | + "rotation": "horizontal", | |
| 253 | + "border": "none", | |
| 254 | + "config": { | |
| 255 | + "format": "YYYY-MM-DD", | |
| 256 | + "offsetDays": 3 | |
| 257 | + } | |
| 258 | + }, | |
| 259 | + { | |
| 260 | + "id": "el-1773999399456-nbglmfa", | |
| 261 | + "type": "IMAGE", | |
| 262 | + "x": 464, | |
| 263 | + "y": 304, | |
| 264 | + "width": 60, | |
| 265 | + "height": 60, | |
| 266 | + "rotation": "horizontal", | |
| 267 | + "border": "none", | |
| 268 | + "config": { | |
| 269 | + "src": "", | |
| 270 | + "scaleMode": "contain" | |
| 271 | + } | |
| 272 | + }, | |
| 273 | + { | |
| 274 | + "id": "el-1773999401976-wzmsx8f", | |
| 275 | + "type": "TEXT_STATIC", | |
| 276 | + "x": 600, | |
| 277 | + "y": 320, | |
| 278 | + "width": 120, | |
| 279 | + "height": 24, | |
| 280 | + "rotation": "horizontal", | |
| 281 | + "border": "none", | |
| 282 | + "config": { | |
| 283 | + "text": "文本", | |
| 284 | + "fontFamily": "Arial", | |
| 285 | + "fontSize": 14, | |
| 286 | + "fontWeight": "normal", | |
| 287 | + "textAlign": "left" | |
| 288 | + } | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "id": "el-1773999407826-91if0px", | |
| 292 | + "type": "TEXT_STATIC", | |
| 293 | + "x": 40, | |
| 294 | + "y": 368, | |
| 295 | + "width": 120, | |
| 296 | + "height": 24, | |
| 297 | + "rotation": "horizontal", | |
| 298 | + "border": "none", | |
| 299 | + "config": { | |
| 300 | + "text": "文本", | |
| 301 | + "fontFamily": "Arial", | |
| 302 | + "fontSize": 14, | |
| 303 | + "fontWeight": "normal", | |
| 304 | + "textAlign": "left" | |
| 305 | + } | |
| 306 | + }, | |
| 307 | + { | |
| 308 | + "id": "el-1773999417908-lje3b3w", | |
| 309 | + "type": "TEXT_STATIC", | |
| 310 | + "x": 200, | |
| 311 | + "y": 368, | |
| 312 | + "width": 120, | |
| 313 | + "height": 24, | |
| 314 | + "rotation": "horizontal", | |
| 315 | + "border": "none", | |
| 316 | + "config": { | |
| 317 | + "text": "文本", | |
| 318 | + "fontFamily": "Arial", | |
| 319 | + "fontSize": 14, | |
| 320 | + "fontWeight": "normal", | |
| 321 | + "textAlign": "left" | |
| 322 | + } | |
| 216 | 323 | } |
| 217 | 324 | ] |
| 218 | 325 | } |
| 219 | 326 | \ No newline at end of file | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelPreviewResolveInputVo.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Contracts.Dtos.Label; | |
| 4 | + | |
| 5 | +public class LabelPreviewResolveInputVo | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 标签编码(fl_label.LabelCode) | |
| 9 | + /// </summary> | |
| 10 | + public string LabelCode { get; set; } = string.Empty; | |
| 11 | + | |
| 12 | + /// <summary> | |
| 13 | + /// 选择用于预览的产品Id(fl_product.Id) | |
| 14 | + /// 如果不传,默认取该标签绑定的第一个产品 | |
| 15 | + /// </summary> | |
| 16 | + public string? ProductId { get; set; } | |
| 17 | + | |
| 18 | + /// <summary> | |
| 19 | + /// 打印输入(前端传,用于 PRINT_INPUT 元素) | |
| 20 | + /// key 建议使用模板元素的 InputKey | |
| 21 | + /// </summary> | |
| 22 | + public Dictionary<string, object>? PrintInputJson { get; set; } | |
| 23 | +} | |
| 24 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelTemplatePreviewDto.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | +using System.Text.Json.Serialization; | |
| 3 | +using FoodLabeling.Application.Contracts.Dtos.LabelTemplate; | |
| 4 | + | |
| 5 | +namespace FoodLabeling.Application.Contracts.Dtos.Label; | |
| 6 | + | |
| 7 | +/// <summary> | |
| 8 | +/// 预览输出:与前端 LabelCanvas/LabelPreviewOnly 的 LabelTemplate 结构尽量一致 | |
| 9 | +/// </summary> | |
| 10 | +public class LabelTemplatePreviewDto | |
| 11 | +{ | |
| 12 | + [JsonPropertyName("id")] | |
| 13 | + public string Id { get; set; } = string.Empty; | |
| 14 | + | |
| 15 | + [JsonPropertyName("name")] | |
| 16 | + public string Name { get; set; } = string.Empty; | |
| 17 | + | |
| 18 | + [JsonPropertyName("labelType")] | |
| 19 | + public string LabelType { get; set; } = string.Empty; | |
| 20 | + | |
| 21 | + [JsonPropertyName("unit")] | |
| 22 | + public string Unit { get; set; } = string.Empty; | |
| 23 | + | |
| 24 | + [JsonPropertyName("width")] | |
| 25 | + public decimal Width { get; set; } | |
| 26 | + | |
| 27 | + [JsonPropertyName("height")] | |
| 28 | + public decimal Height { get; set; } | |
| 29 | + | |
| 30 | + [JsonPropertyName("appliedLocation")] | |
| 31 | + public string AppliedLocation { get; set; } = "ALL"; | |
| 32 | + | |
| 33 | + [JsonPropertyName("showRuler")] | |
| 34 | + public bool ShowRuler { get; set; } | |
| 35 | + | |
| 36 | + [JsonPropertyName("showGrid")] | |
| 37 | + public bool ShowGrid { get; set; } | |
| 38 | + | |
| 39 | + [JsonPropertyName("elements")] | |
| 40 | + public List<LabelTemplateElementDto> Elements { get; set; } = new(); | |
| 41 | +} | |
| 42 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductCreateInputVo.cs
0 → 100644
| 1 | +namespace FoodLabeling.Application.Contracts.Dtos.Product; | |
| 2 | + | |
| 3 | +public class ProductCreateInputVo | |
| 4 | +{ | |
| 5 | + public string ProductCode { get; set; } = string.Empty; | |
| 6 | + | |
| 7 | + public string ProductName { get; set; } = string.Empty; | |
| 8 | + | |
| 9 | + public string? CategoryName { get; set; } | |
| 10 | + | |
| 11 | + public string? ProductImageUrl { get; set; } | |
| 12 | + | |
| 13 | + public bool State { get; set; } = true; | |
| 14 | +} | |
| 15 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListInputVo.cs
0 → 100644
| 1 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 2 | +using Volo.Abp.Application.Dtos; | |
| 3 | + | |
| 4 | +namespace FoodLabeling.Application.Contracts.Dtos.Product; | |
| 5 | + | |
| 6 | +/// <summary> | |
| 7 | +/// 产品分页查询入参 | |
| 8 | +/// </summary> | |
| 9 | +public class ProductGetListInputVo : PagedAndSortedResultRequestDto | |
| 10 | +{ | |
| 11 | + /// <summary> | |
| 12 | + /// 模糊搜索(ProductCode/ProductName/CategoryName) | |
| 13 | + /// </summary> | |
| 14 | + public string? Keyword { get; set; } | |
| 15 | + | |
| 16 | + /// <summary> | |
| 17 | + /// 启用状态 | |
| 18 | + /// </summary> | |
| 19 | + public bool? State { get; set; } | |
| 20 | +} | |
| 21 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListOutputDto.cs
0 → 100644
| 1 | +namespace FoodLabeling.Application.Contracts.Dtos.Product; | |
| 2 | + | |
| 3 | +public class ProductGetListOutputDto | |
| 4 | +{ | |
| 5 | + public string Id { get; set; } = string.Empty; | |
| 6 | + | |
| 7 | + public string ProductCode { get; set; } = string.Empty; | |
| 8 | + | |
| 9 | + public string ProductName { get; set; } = string.Empty; | |
| 10 | + | |
| 11 | + public string? CategoryName { get; set; } | |
| 12 | + | |
| 13 | + public string? ProductImageUrl { get; set; } | |
| 14 | + | |
| 15 | + public bool State { get; set; } | |
| 16 | + | |
| 17 | + /// <summary> | |
| 18 | + /// 该产品关联的标签数量(fl_label_product + fl_label) | |
| 19 | + /// </summary> | |
| 20 | + public long NoOfLabels { get; set; } | |
| 21 | +} | |
| 22 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetOutputDto.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Contracts.Dtos.Product; | |
| 4 | + | |
| 5 | +public class ProductGetOutputDto | |
| 6 | +{ | |
| 7 | + public string Id { get; set; } = string.Empty; | |
| 8 | + | |
| 9 | + public string ProductCode { get; set; } = string.Empty; | |
| 10 | + | |
| 11 | + public string ProductName { get; set; } = string.Empty; | |
| 12 | + | |
| 13 | + public string? CategoryName { get; set; } | |
| 14 | + | |
| 15 | + public string? ProductImageUrl { get; set; } | |
| 16 | + | |
| 17 | + public bool State { get; set; } | |
| 18 | + | |
| 19 | + /// <summary> | |
| 20 | + /// 该产品关联的门店Id列表(来自 fl_location_product) | |
| 21 | + /// </summary> | |
| 22 | + public List<string> LocationIds { get; set; } = new(); | |
| 23 | +} | |
| 24 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductUpdateInputVo.cs
0 → 100644
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationCreateInputVo.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; | |
| 4 | + | |
| 5 | +public class ProductLocationCreateInputVo | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 门店Id | |
| 9 | + /// </summary> | |
| 10 | + public string LocationId { get; set; } = string.Empty; | |
| 11 | + | |
| 12 | + /// <summary> | |
| 13 | + /// 产品Id列表 | |
| 14 | + /// </summary> | |
| 15 | + public List<string> ProductIds { get; set; } = new(); | |
| 16 | +} | |
| 17 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListInputVo.cs
0 → 100644
| 1 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 2 | +using Volo.Abp.Application.Dtos; | |
| 3 | + | |
| 4 | +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; | |
| 5 | + | |
| 6 | +/// <summary> | |
| 7 | +/// 产品-门店分页查询入参 | |
| 8 | +/// </summary> | |
| 9 | +public class ProductLocationGetListInputVo : PagedAndSortedResultRequestDto | |
| 10 | +{ | |
| 11 | + /// <summary> | |
| 12 | + /// 门店Id(location.Id,string 表示) | |
| 13 | + /// </summary> | |
| 14 | + public string? LocationId { get; set; } | |
| 15 | + | |
| 16 | + /// <summary> | |
| 17 | + /// 产品Id(fl_product.Id,string) | |
| 18 | + /// </summary> | |
| 19 | + public string? ProductId { get; set; } | |
| 20 | +} | |
| 21 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListOutputDto.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; | |
| 4 | + | |
| 5 | +public class ProductLocationGetListOutputDto | |
| 6 | +{ | |
| 7 | + public string Id { get; set; } = string.Empty; | |
| 8 | + | |
| 9 | + public string LocationId { get; set; } = string.Empty; | |
| 10 | + public string? LocationCode { get; set; } | |
| 11 | + public string? LocationName { get; set; } | |
| 12 | + | |
| 13 | + public string ProductId { get; set; } = string.Empty; | |
| 14 | + public string? ProductCode { get; set; } | |
| 15 | + public string? ProductName { get; set; } | |
| 16 | + public string? ProductImageUrl { get; set; } | |
| 17 | +} | |
| 18 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetOutputDto.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; | |
| 4 | + | |
| 5 | +public class ProductLocationGetOutputDto | |
| 6 | +{ | |
| 7 | + public string LocationId { get; set; } = string.Empty; | |
| 8 | + | |
| 9 | + public List<ProductLocationGetListOutputDto> Products { get; set; } = new(); | |
| 10 | +} | |
| 11 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationUpdateInputVo.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; | |
| 4 | + | |
| 5 | +public class ProductLocationUpdateInputVo | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 产品Id列表(将替换当前门店下的全部产品关联) | |
| 9 | + /// </summary> | |
| 10 | + public List<string> ProductIds { get; set; } = new(); | |
| 11 | +} | |
| 12 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductAppService.cs
0 → 100644
| 1 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 2 | +using FoodLabeling.Application.Contracts.Dtos.Product; | |
| 3 | +using Volo.Abp.Application.Dtos; | |
| 4 | +using Volo.Abp.Application.Services; | |
| 5 | + | |
| 6 | +namespace FoodLabeling.Application.Contracts.IServices; | |
| 7 | + | |
| 8 | +/// <summary> | |
| 9 | +/// 产品管理接口(Products,fl_product) | |
| 10 | +/// </summary> | |
| 11 | +public interface IProductAppService : IApplicationService | |
| 12 | +{ | |
| 13 | + /// <summary> | |
| 14 | + /// 产品分页列表 | |
| 15 | + /// </summary> | |
| 16 | + Task<PagedResultWithPageDto<ProductGetListOutputDto>> GetListAsync(ProductGetListInputVo input); | |
| 17 | + | |
| 18 | + /// <summary> | |
| 19 | + /// 产品详情 | |
| 20 | + /// </summary> | |
| 21 | + Task<ProductGetOutputDto> GetAsync(string id); | |
| 22 | + | |
| 23 | + /// <summary> | |
| 24 | + /// 新增产品 | |
| 25 | + /// </summary> | |
| 26 | + Task<ProductGetOutputDto> CreateAsync(ProductCreateInputVo input); | |
| 27 | + | |
| 28 | + /// <summary> | |
| 29 | + /// 编辑产品 | |
| 30 | + /// </summary> | |
| 31 | + Task<ProductGetOutputDto> UpdateAsync(string id, ProductUpdateInputVo input); | |
| 32 | + | |
| 33 | + /// <summary> | |
| 34 | + /// 删除产品(逻辑删除) | |
| 35 | + /// </summary> | |
| 36 | + Task DeleteAsync(string id); | |
| 37 | +} | |
| 38 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductLocationAppService.cs
0 → 100644
| 1 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 2 | +using FoodLabeling.Application.Contracts.Dtos.ProductLocation; | |
| 3 | +using Volo.Abp.Application.Dtos; | |
| 4 | +using Volo.Abp.Application.Services; | |
| 5 | + | |
| 6 | +namespace FoodLabeling.Application.Contracts.IServices; | |
| 7 | + | |
| 8 | +/// <summary> | |
| 9 | +/// 产品-门店关联管理(fl_location_product) | |
| 10 | +/// </summary> | |
| 11 | +public interface IProductLocationAppService : IApplicationService | |
| 12 | +{ | |
| 13 | + /// <summary> | |
| 14 | + /// 关联分页列表(可按门店Id/产品Id过滤) | |
| 15 | + /// </summary> | |
| 16 | + Task<PagedResultWithPageDto<ProductLocationGetListOutputDto>> GetListAsync(ProductLocationGetListInputVo input); | |
| 17 | + | |
| 18 | + /// <summary> | |
| 19 | + /// 门店下的全部产品(id=LocationId) | |
| 20 | + /// </summary> | |
| 21 | + Task<ProductLocationGetOutputDto> GetAsync(string id); | |
| 22 | + | |
| 23 | + /// <summary> | |
| 24 | + /// 新增/批量建立门店与产品的关联 | |
| 25 | + /// </summary> | |
| 26 | + Task<ProductLocationGetOutputDto> CreateAsync(ProductLocationCreateInputVo input); | |
| 27 | + | |
| 28 | + /// <summary> | |
| 29 | + /// 编辑:替换该门店下的全部产品关联 | |
| 30 | + /// </summary> | |
| 31 | + Task<ProductLocationGetOutputDto> UpdateAsync(string id, ProductLocationUpdateInputVo input); | |
| 32 | + | |
| 33 | + /// <summary> | |
| 34 | + /// 删除:删除该门店的全部产品关联 | |
| 35 | + /// </summary> | |
| 36 | + Task DeleteAsync(string id); | |
| 37 | +} | |
| 38 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationProductDbEntity.cs
0 → 100644
| 1 | +using SqlSugar; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Services.DbModels; | |
| 4 | + | |
| 5 | +[SugarTable("fl_location_product")] | |
| 6 | +public class FlLocationProductDbEntity | |
| 7 | +{ | |
| 8 | + [SugarColumn(IsPrimaryKey = true)] | |
| 9 | + public string Id { get; set; } = string.Empty; | |
| 10 | + | |
| 11 | + public string LocationId { get; set; } = string.Empty; | |
| 12 | + | |
| 13 | + public string ProductId { get; set; } = string.Empty; | |
| 14 | +} | |
| 15 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductAppService.cs
0 → 100644
| 1 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 2 | +using FoodLabeling.Application.Contracts.Dtos.Product; | |
| 3 | +using FoodLabeling.Application.Contracts.IServices; | |
| 4 | +using FoodLabeling.Application.Services.DbModels; | |
| 5 | +using SqlSugar; | |
| 6 | +using Volo.Abp; | |
| 7 | +using Volo.Abp.Application.Services; | |
| 8 | +using Volo.Abp.Guids; | |
| 9 | +using Volo.Abp.Uow; | |
| 10 | +using Yi.Framework.SqlSugarCore.Abstractions; | |
| 11 | + | |
| 12 | +namespace FoodLabeling.Application.Services; | |
| 13 | + | |
| 14 | +/// <summary> | |
| 15 | +/// 产品管理(Products) | |
| 16 | +/// </summary> | |
| 17 | +public class ProductAppService : ApplicationService, IProductAppService | |
| 18 | +{ | |
| 19 | + private readonly ISqlSugarDbContext _dbContext; | |
| 20 | + private readonly IGuidGenerator _guidGenerator; | |
| 21 | + | |
| 22 | + public ProductAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator) | |
| 23 | + { | |
| 24 | + _dbContext = dbContext; | |
| 25 | + _guidGenerator = guidGenerator; | |
| 26 | + } | |
| 27 | + | |
| 28 | + public async Task<PagedResultWithPageDto<ProductGetListOutputDto>> GetListAsync(ProductGetListInputVo input) | |
| 29 | + { | |
| 30 | + RefAsync<int> total = 0; | |
| 31 | + var keyword = input.Keyword?.Trim(); | |
| 32 | + | |
| 33 | + var query = _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 34 | + .Where(x => !x.IsDeleted) | |
| 35 | + .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => | |
| 36 | + x.ProductCode.Contains(keyword!) || | |
| 37 | + x.ProductName.Contains(keyword!) || | |
| 38 | + (x.CategoryName != null && x.CategoryName.Contains(keyword!))) | |
| 39 | + .WhereIF(input.State != null, x => x.State == input.State); | |
| 40 | + | |
| 41 | + if (!string.IsNullOrWhiteSpace(input.Sorting)) | |
| 42 | + { | |
| 43 | + query = query.OrderBy(input.Sorting); | |
| 44 | + } | |
| 45 | + else | |
| 46 | + { | |
| 47 | + query = query.OrderByDescending(x => x.ProductName); | |
| 48 | + } | |
| 49 | + | |
| 50 | + var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); | |
| 51 | + var ids = entities.Select(x => x.Id).ToList(); | |
| 52 | + | |
| 53 | + // Count labels for each product | |
| 54 | + var countMap = new Dictionary<string, long>(); | |
| 55 | + if (ids.Count > 0) | |
| 56 | + { | |
| 57 | + var countRows = await _dbContext.SqlSugarClient.Queryable<FlLabelProductDbEntity, FlLabelDbEntity>( | |
| 58 | + (lp, l) => lp.LabelId == l.Id) | |
| 59 | + .Where((lp, l) => !l.IsDeleted && ids.Contains(lp.ProductId)) | |
| 60 | + .GroupBy((lp, l) => lp.ProductId) | |
| 61 | + .Select((lp, l) => new { ProductId = lp.ProductId, Count = SqlFunc.AggregateCount(lp.Id) }) | |
| 62 | + .ToListAsync(); | |
| 63 | + | |
| 64 | + countMap = countRows.ToDictionary(x => x.ProductId, x => (long)x.Count); | |
| 65 | + } | |
| 66 | + | |
| 67 | + var items = entities.Select(x => new ProductGetListOutputDto | |
| 68 | + { | |
| 69 | + Id = x.Id, | |
| 70 | + ProductCode = x.ProductCode, | |
| 71 | + ProductName = x.ProductName, | |
| 72 | + CategoryName = x.CategoryName, | |
| 73 | + ProductImageUrl = x.ProductImageUrl, | |
| 74 | + State = x.State, | |
| 75 | + NoOfLabels = countMap.TryGetValue(x.Id, out var count) ? count : 0 | |
| 76 | + }).ToList(); | |
| 77 | + | |
| 78 | + return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); | |
| 79 | + } | |
| 80 | + | |
| 81 | + public async Task<ProductGetOutputDto> GetAsync(string id) | |
| 82 | + { | |
| 83 | + var productId = id?.Trim(); | |
| 84 | + if (string.IsNullOrWhiteSpace(productId)) | |
| 85 | + { | |
| 86 | + throw new UserFriendlyException("产品Id不能为空"); | |
| 87 | + } | |
| 88 | + | |
| 89 | + var entity = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 90 | + .FirstAsync(x => !x.IsDeleted && x.Id == productId); | |
| 91 | + | |
| 92 | + if (entity is null) | |
| 93 | + { | |
| 94 | + throw new UserFriendlyException("产品不存在"); | |
| 95 | + } | |
| 96 | + | |
| 97 | + var locationIds = await _dbContext.SqlSugarClient.Queryable<FlLocationProductDbEntity>() | |
| 98 | + .Where(x => x.ProductId == productId) | |
| 99 | + .Select(x => x.LocationId) | |
| 100 | + .Distinct() | |
| 101 | + .ToListAsync(); | |
| 102 | + | |
| 103 | + return new ProductGetOutputDto | |
| 104 | + { | |
| 105 | + Id = entity.Id, | |
| 106 | + ProductCode = entity.ProductCode, | |
| 107 | + ProductName = entity.ProductName, | |
| 108 | + CategoryName = entity.CategoryName, | |
| 109 | + ProductImageUrl = entity.ProductImageUrl, | |
| 110 | + State = entity.State, | |
| 111 | + LocationIds = locationIds | |
| 112 | + }; | |
| 113 | + } | |
| 114 | + | |
| 115 | + [UnitOfWork] | |
| 116 | + public async Task<ProductGetOutputDto> CreateAsync(ProductCreateInputVo input) | |
| 117 | + { | |
| 118 | + var code = input.ProductCode?.Trim(); | |
| 119 | + var name = input.ProductName?.Trim(); | |
| 120 | + if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name)) | |
| 121 | + { | |
| 122 | + throw new UserFriendlyException("产品编码和名称不能为空"); | |
| 123 | + } | |
| 124 | + | |
| 125 | + var duplicated = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 126 | + .AnyAsync(x => !x.IsDeleted && (x.ProductCode == code)); | |
| 127 | + if (duplicated) | |
| 128 | + { | |
| 129 | + throw new UserFriendlyException("产品编码已存在"); | |
| 130 | + } | |
| 131 | + | |
| 132 | + var entity = new FlProductDbEntity | |
| 133 | + { | |
| 134 | + Id = _guidGenerator.Create().ToString(), | |
| 135 | + IsDeleted = false, | |
| 136 | + ProductCode = code, | |
| 137 | + ProductName = name, | |
| 138 | + CategoryName = input.CategoryName?.Trim(), | |
| 139 | + ProductImageUrl = input.ProductImageUrl?.Trim(), | |
| 140 | + State = input.State | |
| 141 | + }; | |
| 142 | + | |
| 143 | + await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); | |
| 144 | + return await GetAsync(entity.Id); | |
| 145 | + } | |
| 146 | + | |
| 147 | + [UnitOfWork] | |
| 148 | + public async Task<ProductGetOutputDto> UpdateAsync(string id, ProductUpdateInputVo input) | |
| 149 | + { | |
| 150 | + var productId = id?.Trim(); | |
| 151 | + if (string.IsNullOrWhiteSpace(productId)) | |
| 152 | + { | |
| 153 | + throw new UserFriendlyException("产品Id不能为空"); | |
| 154 | + } | |
| 155 | + | |
| 156 | + var entity = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 157 | + .FirstAsync(x => !x.IsDeleted && x.Id == productId); | |
| 158 | + if (entity is null) | |
| 159 | + { | |
| 160 | + throw new UserFriendlyException("产品不存在"); | |
| 161 | + } | |
| 162 | + | |
| 163 | + var code = input.ProductCode?.Trim(); | |
| 164 | + var name = input.ProductName?.Trim(); | |
| 165 | + if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name)) | |
| 166 | + { | |
| 167 | + throw new UserFriendlyException("产品编码和名称不能为空"); | |
| 168 | + } | |
| 169 | + | |
| 170 | + var duplicated = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 171 | + .AnyAsync(x => !x.IsDeleted && x.Id != productId && x.ProductCode == code); | |
| 172 | + if (duplicated) | |
| 173 | + { | |
| 174 | + throw new UserFriendlyException("产品编码已存在"); | |
| 175 | + } | |
| 176 | + | |
| 177 | + entity.ProductCode = code; | |
| 178 | + entity.ProductName = name; | |
| 179 | + entity.CategoryName = input.CategoryName?.Trim(); | |
| 180 | + entity.ProductImageUrl = input.ProductImageUrl?.Trim(); | |
| 181 | + entity.State = input.State; | |
| 182 | + | |
| 183 | + await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); | |
| 184 | + return await GetAsync(productId); | |
| 185 | + } | |
| 186 | + | |
| 187 | + [UnitOfWork] | |
| 188 | + public async Task DeleteAsync(string id) | |
| 189 | + { | |
| 190 | + var productId = id?.Trim(); | |
| 191 | + if (string.IsNullOrWhiteSpace(productId)) | |
| 192 | + { | |
| 193 | + throw new UserFriendlyException("产品Id不能为空"); | |
| 194 | + } | |
| 195 | + | |
| 196 | + var entity = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 197 | + .FirstAsync(x => !x.IsDeleted && x.Id == productId); | |
| 198 | + if (entity is null) | |
| 199 | + { | |
| 200 | + throw new UserFriendlyException("产品不存在"); | |
| 201 | + } | |
| 202 | + | |
| 203 | + entity.IsDeleted = true; | |
| 204 | + await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); | |
| 205 | + } | |
| 206 | + | |
| 207 | + private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, List<T> items) | |
| 208 | + { | |
| 209 | + var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; | |
| 210 | + var pageIndex = pageSize <= 0 ? 1 : (skipCount / pageSize) + 1; | |
| 211 | + var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize); | |
| 212 | + return new PagedResultWithPageDto<T> | |
| 213 | + { | |
| 214 | + PageIndex = pageIndex, | |
| 215 | + PageSize = pageSize, | |
| 216 | + TotalCount = total, | |
| 217 | + TotalPages = totalPages, | |
| 218 | + Items = items | |
| 219 | + }; | |
| 220 | + } | |
| 221 | +} | |
| 222 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductLocationAppService.cs
0 → 100644
| 1 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 2 | +using FoodLabeling.Application.Contracts.Dtos.ProductLocation; | |
| 3 | +using FoodLabeling.Application.Contracts.IServices; | |
| 4 | +using FoodLabeling.Application.Services.DbModels; | |
| 5 | +using FoodLabeling.Domain.Entities; | |
| 6 | +using SqlSugar; | |
| 7 | +using Volo.Abp; | |
| 8 | +using Volo.Abp.Application.Services; | |
| 9 | +using Volo.Abp.Guids; | |
| 10 | +using Volo.Abp.Uow; | |
| 11 | +using Yi.Framework.SqlSugarCore.Abstractions; | |
| 12 | + | |
| 13 | +namespace FoodLabeling.Application.Services; | |
| 14 | + | |
| 15 | +/// <summary> | |
| 16 | +/// 产品-门店关联管理(fl_location_product) | |
| 17 | +/// </summary> | |
| 18 | +public class ProductLocationAppService : ApplicationService, IProductLocationAppService | |
| 19 | +{ | |
| 20 | + private readonly ISqlSugarDbContext _dbContext; | |
| 21 | + private readonly IGuidGenerator _guidGenerator; | |
| 22 | + | |
| 23 | + public ProductLocationAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator) | |
| 24 | + { | |
| 25 | + _dbContext = dbContext; | |
| 26 | + _guidGenerator = guidGenerator; | |
| 27 | + } | |
| 28 | + | |
| 29 | + public async Task<PagedResultWithPageDto<ProductLocationGetListOutputDto>> GetListAsync(ProductLocationGetListInputVo input) | |
| 30 | + { | |
| 31 | + RefAsync<int> total = 0; | |
| 32 | + | |
| 33 | + var locationId = input.LocationId?.Trim(); | |
| 34 | + var productId = input.ProductId?.Trim(); | |
| 35 | + | |
| 36 | + var query = _dbContext.SqlSugarClient | |
| 37 | + .Queryable<FlLocationProductDbEntity, FlProductDbEntity>((lp, p) => lp.ProductId == p.Id) | |
| 38 | + .Where((lp, p) => p.IsDeleted == false); | |
| 39 | + | |
| 40 | + if (!string.IsNullOrWhiteSpace(locationId)) | |
| 41 | + { | |
| 42 | + query = query.Where((lp, p) => lp.LocationId == locationId); | |
| 43 | + } | |
| 44 | + | |
| 45 | + if (!string.IsNullOrWhiteSpace(productId)) | |
| 46 | + { | |
| 47 | + query = query.Where((lp, p) => lp.ProductId == productId); | |
| 48 | + } | |
| 49 | + | |
| 50 | + // 默认排序 | |
| 51 | + query = string.IsNullOrWhiteSpace(input.Sorting) | |
| 52 | + ? query.OrderBy((lp, p) => p.ProductName) | |
| 53 | + : query.OrderBy(input.Sorting); | |
| 54 | + | |
| 55 | + var entities = await query | |
| 56 | + .Select((lp, p) => new ProductLocationGetListOutputDto | |
| 57 | + { | |
| 58 | + Id = lp.Id, | |
| 59 | + LocationId = lp.LocationId, | |
| 60 | + ProductId = p.Id, | |
| 61 | + ProductCode = p.ProductCode, | |
| 62 | + ProductName = p.ProductName, | |
| 63 | + ProductImageUrl = p.ProductImageUrl, | |
| 64 | + LocationCode = null, | |
| 65 | + LocationName = null | |
| 66 | + }) | |
| 67 | + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); | |
| 68 | + | |
| 69 | + // 拉取门店信息用于输出 | |
| 70 | + var locationIdSet = entities | |
| 71 | + .Select(x => x.LocationId) | |
| 72 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 73 | + .Distinct() | |
| 74 | + .ToList(); | |
| 75 | + | |
| 76 | + var locationGuidList = locationIdSet | |
| 77 | + .Where(x => Guid.TryParse(x, out _)) | |
| 78 | + .Select(Guid.Parse) | |
| 79 | + .Distinct() | |
| 80 | + .ToList(); | |
| 81 | + | |
| 82 | + var locationMap = new Dictionary<Guid, LocationAggregateRoot>(); | |
| 83 | + if (locationGuidList.Count > 0) | |
| 84 | + { | |
| 85 | + var locations = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | |
| 86 | + .Where(x => !x.IsDeleted) | |
| 87 | + .Where(x => locationGuidList.Contains(x.Id)) | |
| 88 | + .ToListAsync(); | |
| 89 | + | |
| 90 | + locationMap = locations.ToDictionary(x => x.Id, x => x); | |
| 91 | + } | |
| 92 | + | |
| 93 | + var items = entities.Select(x => | |
| 94 | + { | |
| 95 | + string? locationCode = null; | |
| 96 | + string? locationName = null; | |
| 97 | + if (Guid.TryParse(x.LocationId, out var gid) && locationMap.TryGetValue(gid, out var loc)) | |
| 98 | + { | |
| 99 | + locationCode = loc.LocationCode; | |
| 100 | + locationName = loc.LocationName ?? loc.LocationCode; | |
| 101 | + } | |
| 102 | + | |
| 103 | + return new ProductLocationGetListOutputDto | |
| 104 | + { | |
| 105 | + Id = x.Id, | |
| 106 | + LocationId = x.LocationId, | |
| 107 | + LocationCode = locationCode, | |
| 108 | + LocationName = locationName, | |
| 109 | + ProductId = x.ProductId, | |
| 110 | + ProductCode = x.ProductCode, | |
| 111 | + ProductName = x.ProductName, | |
| 112 | + ProductImageUrl = x.ProductImageUrl | |
| 113 | + }; | |
| 114 | + }).ToList(); | |
| 115 | + | |
| 116 | + return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); | |
| 117 | + } | |
| 118 | + | |
| 119 | + public async Task<ProductLocationGetOutputDto> GetAsync(string id) | |
| 120 | + { | |
| 121 | + var locationId = id?.Trim(); | |
| 122 | + if (string.IsNullOrWhiteSpace(locationId)) | |
| 123 | + { | |
| 124 | + throw new UserFriendlyException("门店Id不能为空"); | |
| 125 | + } | |
| 126 | + | |
| 127 | + var rows = await _dbContext.SqlSugarClient | |
| 128 | + .Queryable<FlLocationProductDbEntity, FlProductDbEntity>((lp, p) => lp.ProductId == p.Id) | |
| 129 | + .Where((lp, p) => lp.LocationId == locationId && !p.IsDeleted) | |
| 130 | + .Select((lp, p) => new ProductLocationGetListOutputDto | |
| 131 | + { | |
| 132 | + Id = lp.Id, | |
| 133 | + LocationId = lp.LocationId, | |
| 134 | + ProductId = p.Id, | |
| 135 | + ProductCode = p.ProductCode, | |
| 136 | + ProductName = p.ProductName, | |
| 137 | + ProductImageUrl = p.ProductImageUrl | |
| 138 | + }) | |
| 139 | + .ToListAsync(); | |
| 140 | + | |
| 141 | + if (rows.Count == 0) | |
| 142 | + { | |
| 143 | + return new ProductLocationGetOutputDto | |
| 144 | + { | |
| 145 | + LocationId = locationId, | |
| 146 | + Products = new List<ProductLocationGetListOutputDto>() | |
| 147 | + }; | |
| 148 | + } | |
| 149 | + | |
| 150 | + // 门店信息 | |
| 151 | + string? locationCode = null; | |
| 152 | + string? locationName = null; | |
| 153 | + if (Guid.TryParse(locationId, out var gid)) | |
| 154 | + { | |
| 155 | + var loc = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | |
| 156 | + .FirstAsync(x => !x.IsDeleted && x.Id == gid); | |
| 157 | + if (loc != null) | |
| 158 | + { | |
| 159 | + locationCode = loc.LocationCode; | |
| 160 | + locationName = loc.LocationName ?? loc.LocationCode; | |
| 161 | + } | |
| 162 | + } | |
| 163 | + | |
| 164 | + var products = rows.Select(x => new ProductLocationGetListOutputDto | |
| 165 | + { | |
| 166 | + Id = x.Id, | |
| 167 | + LocationId = x.LocationId, | |
| 168 | + LocationCode = locationCode, | |
| 169 | + LocationName = locationName, | |
| 170 | + ProductId = x.ProductId, | |
| 171 | + ProductCode = x.ProductCode, | |
| 172 | + ProductName = x.ProductName, | |
| 173 | + ProductImageUrl = x.ProductImageUrl | |
| 174 | + }).ToList(); | |
| 175 | + | |
| 176 | + return new ProductLocationGetOutputDto | |
| 177 | + { | |
| 178 | + LocationId = locationId, | |
| 179 | + Products = products | |
| 180 | + }; | |
| 181 | + } | |
| 182 | + | |
| 183 | + [UnitOfWork] | |
| 184 | + public async Task<ProductLocationGetOutputDto> CreateAsync(ProductLocationCreateInputVo input) | |
| 185 | + { | |
| 186 | + var locationId = input.LocationId?.Trim(); | |
| 187 | + if (string.IsNullOrWhiteSpace(locationId)) | |
| 188 | + { | |
| 189 | + throw new UserFriendlyException("门店Id不能为空"); | |
| 190 | + } | |
| 191 | + | |
| 192 | + var productIds = input.ProductIds? | |
| 193 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 194 | + .Select(x => x.Trim()) | |
| 195 | + .Distinct() | |
| 196 | + .ToList() ?? new List<string>(); | |
| 197 | + | |
| 198 | + if (productIds.Count == 0) | |
| 199 | + { | |
| 200 | + throw new UserFriendlyException("ProductIds至少1个"); | |
| 201 | + } | |
| 202 | + | |
| 203 | + // 校验门店存在 | |
| 204 | + if (!Guid.TryParse(locationId, out var gid)) | |
| 205 | + { | |
| 206 | + throw new UserFriendlyException("门店Id格式不正确"); | |
| 207 | + } | |
| 208 | + | |
| 209 | + var locExists = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | |
| 210 | + .AnyAsync(x => !x.IsDeleted && x.Id == gid); | |
| 211 | + if (!locExists) | |
| 212 | + { | |
| 213 | + throw new UserFriendlyException("门店不存在"); | |
| 214 | + } | |
| 215 | + | |
| 216 | + // 校验产品存在 | |
| 217 | + var productExistsCount = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 218 | + .Where(x => !x.IsDeleted && productIds.Contains(x.Id)) | |
| 219 | + .CountAsync(); | |
| 220 | + if (productExistsCount != productIds.Count) | |
| 221 | + { | |
| 222 | + throw new UserFriendlyException("产品不存在"); | |
| 223 | + } | |
| 224 | + | |
| 225 | + // 去重:避免唯一键重复 | |
| 226 | + var existingProductIds = await _dbContext.SqlSugarClient.Queryable<FlLocationProductDbEntity>() | |
| 227 | + .Where(x => x.LocationId == locationId && productIds.Contains(x.ProductId)) | |
| 228 | + .Select(x => x.ProductId) | |
| 229 | + .Distinct() | |
| 230 | + .ToListAsync(); | |
| 231 | + | |
| 232 | + var newProductIds = productIds.Where(x => !existingProductIds.Contains(x)).ToList(); | |
| 233 | + if (newProductIds.Count == 0) | |
| 234 | + { | |
| 235 | + return await GetAsync(locationId); | |
| 236 | + } | |
| 237 | + | |
| 238 | + var rows = newProductIds.Select(pid => new FlLocationProductDbEntity | |
| 239 | + { | |
| 240 | + Id = _guidGenerator.Create().ToString(), | |
| 241 | + LocationId = locationId, | |
| 242 | + ProductId = pid | |
| 243 | + }).ToList(); | |
| 244 | + | |
| 245 | + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); | |
| 246 | + return await GetAsync(locationId); | |
| 247 | + } | |
| 248 | + | |
| 249 | + [UnitOfWork] | |
| 250 | + public async Task<ProductLocationGetOutputDto> UpdateAsync(string id, ProductLocationUpdateInputVo input) | |
| 251 | + { | |
| 252 | + var locationId = id?.Trim(); | |
| 253 | + if (string.IsNullOrWhiteSpace(locationId)) | |
| 254 | + { | |
| 255 | + throw new UserFriendlyException("门店Id不能为空"); | |
| 256 | + } | |
| 257 | + | |
| 258 | + var productIds = input.ProductIds? | |
| 259 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 260 | + .Select(x => x.Trim()) | |
| 261 | + .Distinct() | |
| 262 | + .ToList() ?? new List<string>(); | |
| 263 | + | |
| 264 | + if (productIds.Count == 0) | |
| 265 | + { | |
| 266 | + throw new UserFriendlyException("ProductIds至少1个"); | |
| 267 | + } | |
| 268 | + | |
| 269 | + if (!Guid.TryParse(locationId, out var gid)) | |
| 270 | + { | |
| 271 | + throw new UserFriendlyException("门店Id格式不正确"); | |
| 272 | + } | |
| 273 | + | |
| 274 | + var locExists = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | |
| 275 | + .AnyAsync(x => !x.IsDeleted && x.Id == gid); | |
| 276 | + if (!locExists) | |
| 277 | + { | |
| 278 | + throw new UserFriendlyException("门店不存在"); | |
| 279 | + } | |
| 280 | + | |
| 281 | + var productExistsCount = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>() | |
| 282 | + .Where(x => !x.IsDeleted && productIds.Contains(x.Id)) | |
| 283 | + .CountAsync(); | |
| 284 | + if (productExistsCount != productIds.Count) | |
| 285 | + { | |
| 286 | + throw new UserFriendlyException("产品不存在"); | |
| 287 | + } | |
| 288 | + | |
| 289 | + // 替换:先删后建 | |
| 290 | + await _dbContext.SqlSugarClient.Deleteable<FlLocationProductDbEntity>() | |
| 291 | + .Where(x => x.LocationId == locationId) | |
| 292 | + .ExecuteCommandAsync(); | |
| 293 | + | |
| 294 | + var rows = productIds.Select(pid => new FlLocationProductDbEntity | |
| 295 | + { | |
| 296 | + Id = _guidGenerator.Create().ToString(), | |
| 297 | + LocationId = locationId, | |
| 298 | + ProductId = pid | |
| 299 | + }).ToList(); | |
| 300 | + | |
| 301 | + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); | |
| 302 | + return await GetAsync(locationId); | |
| 303 | + } | |
| 304 | + | |
| 305 | + [UnitOfWork] | |
| 306 | + public async Task DeleteAsync(string id) | |
| 307 | + { | |
| 308 | + var locationId = id?.Trim(); | |
| 309 | + if (string.IsNullOrWhiteSpace(locationId)) | |
| 310 | + { | |
| 311 | + throw new UserFriendlyException("门店Id不能为空"); | |
| 312 | + } | |
| 313 | + | |
| 314 | + await _dbContext.SqlSugarClient.Deleteable<FlLocationProductDbEntity>() | |
| 315 | + .Where(x => x.LocationId == locationId) | |
| 316 | + .ExecuteCommandAsync(); | |
| 317 | + } | |
| 318 | + | |
| 319 | + private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, List<T> items) | |
| 320 | + { | |
| 321 | + var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; | |
| 322 | + var pageIndex = pageSize <= 0 ? 1 : (skipCount / pageSize) + 1; | |
| 323 | + var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize); | |
| 324 | + return new PagedResultWithPageDto<T> | |
| 325 | + { | |
| 326 | + PageIndex = pageIndex, | |
| 327 | + PageSize = pageSize, | |
| 328 | + TotalCount = total, | |
| 329 | + TotalPages = totalPages, | |
| 330 | + Items = items | |
| 331 | + }; | |
| 332 | + } | |
| 333 | +} | |
| 334 | + | ... | ... |
项目相关文档/标签模块接口对接说明.md
0 → 100644
| 1 | +## 概述 | |
| 2 | + | |
| 3 | +美国版后端采用 ABP 动态接口(ConventionalControllers),宿主统一前缀为 `api/app`。 | |
| 4 | +Swagger 地址: | |
| 5 | + | |
| 6 | +- `http://localhost:19001/swagger` | |
| 7 | + | |
| 8 | +说明: | |
| 9 | +- 接口最终 URL 以 Swagger 展示为准(可在 Swagger 里搜索 `LabelCategory / LabelType / LabelMultipleOption / LabelTemplate / Label`)。 | |
| 10 | +- 本模块后端接口以各 AppService 的方法签名自动暴露。 | |
| 11 | +- 返回分页统一包含 `PageIndex / PageSize / TotalCount / TotalPages / Items`。 | |
| 12 | + | |
| 13 | +--- | |
| 14 | + | |
| 15 | +## Swagger 中如何找到 | |
| 16 | + | |
| 17 | +1. 启动后端宿主(`Yi.Abp.Web`),端口 `19001`。 | |
| 18 | +2. 打开 `http://localhost:19001/swagger`。 | |
| 19 | +3. 在接口分组里搜索以下关键词之一: | |
| 20 | + - `label-category` | |
| 21 | + - `label-type` | |
| 22 | + - `label-multiple-option` | |
| 23 | + - `label-template` | |
| 24 | + - `label` | |
| 25 | + | |
| 26 | +--- | |
| 27 | + | |
| 28 | +## 接口 1:Label Categories(标签分类) | |
| 29 | + | |
| 30 | +### 1.1 分页列表 | |
| 31 | + | |
| 32 | +方法:`GET /api/app/label-category` | |
| 33 | + | |
| 34 | +入参(`LabelCategoryGetListInputVo`,查询参数): | |
| 35 | + | |
| 36 | +- `skipCount`(int) | |
| 37 | +- `maxResultCount`(int) | |
| 38 | +- `sorting`(string,可选) | |
| 39 | +- `keyword`(string,可选) | |
| 40 | +- `state`(boolean,可选) | |
| 41 | + | |
| 42 | +示例(查询参数): | |
| 43 | + | |
| 44 | +```json | |
| 45 | +{ | |
| 46 | + "skipCount": 0, | |
| 47 | + "maxResultCount": 10, | |
| 48 | + "keyword": "Prep", | |
| 49 | + "state": true | |
| 50 | +} | |
| 51 | +``` | |
| 52 | + | |
| 53 | +### 1.2 详情 | |
| 54 | + | |
| 55 | +方法:`GET /api/app/label-category/{id}` | |
| 56 | + | |
| 57 | +入参: | |
| 58 | + | |
| 59 | +- `id`:分类 Id(字符串) | |
| 60 | + | |
| 61 | +### 1.3 新增 | |
| 62 | + | |
| 63 | +方法:`POST /api/app/label-category` | |
| 64 | + | |
| 65 | +入参(Body:`LabelCategoryCreateInputVo`): | |
| 66 | + | |
| 67 | +```json | |
| 68 | +{ | |
| 69 | + "categoryCode": "CAT_PREP", | |
| 70 | + "categoryName": "Prep", | |
| 71 | + "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png", | |
| 72 | + "state": true, | |
| 73 | + "orderNum": 1 | |
| 74 | +} | |
| 75 | +``` | |
| 76 | + | |
| 77 | +### 1.4 编辑 | |
| 78 | + | |
| 79 | +方法:`PUT /api/app/label-category/{id}` | |
| 80 | + | |
| 81 | +入参(Body:`LabelCategoryUpdateInputVo`,字段同创建): | |
| 82 | + | |
| 83 | +```json | |
| 84 | +{ | |
| 85 | + "categoryCode": "CAT_PREP", | |
| 86 | + "categoryName": "Prep", | |
| 87 | + "categoryPhotoUrl": null, | |
| 88 | + "state": true, | |
| 89 | + "orderNum": 2 | |
| 90 | +} | |
| 91 | +``` | |
| 92 | + | |
| 93 | +### 1.5 删除(逻辑删除) | |
| 94 | + | |
| 95 | +方法:`DELETE /api/app/label-category/{id}` | |
| 96 | + | |
| 97 | +入参: | |
| 98 | + | |
| 99 | +- `id`:分类 Id(字符串) | |
| 100 | + | |
| 101 | +删除校验: | |
| 102 | +- 若该分类已被 `fl_label` 引用,则抛出友好错误,禁止删除。 | |
| 103 | + | |
| 104 | +--- | |
| 105 | + | |
| 106 | +## 接口 2:Label Types(标签类型) | |
| 107 | + | |
| 108 | +### 2.1 分页列表 | |
| 109 | + | |
| 110 | +方法:`GET /api/app/label-type` | |
| 111 | + | |
| 112 | +入参(`LabelTypeGetListInputVo`,查询参数): | |
| 113 | + | |
| 114 | +```json | |
| 115 | +{ | |
| 116 | + "skipCount": 0, | |
| 117 | + "maxResultCount": 10, | |
| 118 | + "keyword": "Defrost", | |
| 119 | + "state": true | |
| 120 | +} | |
| 121 | +``` | |
| 122 | + | |
| 123 | +### 2.2 详情 | |
| 124 | + | |
| 125 | +方法:`GET /api/app/label-type/{id}` | |
| 126 | + | |
| 127 | +入参: | |
| 128 | + | |
| 129 | +- `id`:类型 Id(字符串) | |
| 130 | + | |
| 131 | +### 2.3 新增 | |
| 132 | + | |
| 133 | +方法:`POST /api/app/label-type` | |
| 134 | + | |
| 135 | +入参(Body:`LabelTypeCreateInputVo`): | |
| 136 | + | |
| 137 | +```json | |
| 138 | +{ | |
| 139 | + "typeCode": "TYPE_DEFROST", | |
| 140 | + "typeName": "Defrost", | |
| 141 | + "state": true, | |
| 142 | + "orderNum": 1 | |
| 143 | +} | |
| 144 | +``` | |
| 145 | + | |
| 146 | +### 2.4 编辑 | |
| 147 | + | |
| 148 | +方法:`PUT /api/app/label-type/{id}` | |
| 149 | + | |
| 150 | +入参(Body:`LabelTypeUpdateInputVo`,字段同创建): | |
| 151 | + | |
| 152 | +```json | |
| 153 | +{ | |
| 154 | + "typeCode": "TYPE_DEFROST", | |
| 155 | + "typeName": "Defrost", | |
| 156 | + "state": true, | |
| 157 | + "orderNum": 2 | |
| 158 | +} | |
| 159 | +``` | |
| 160 | + | |
| 161 | +### 2.5 删除(逻辑删除) | |
| 162 | + | |
| 163 | +方法:`DELETE /api/app/label-type/{id}` | |
| 164 | + | |
| 165 | +删除校验: | |
| 166 | +- 若该类型已被 `fl_label` 引用,则禁止删除。 | |
| 167 | + | |
| 168 | +--- | |
| 169 | + | |
| 170 | +## 接口 3:Multiple Options(多选项字典) | |
| 171 | + | |
| 172 | +### 3.1 分页列表 | |
| 173 | + | |
| 174 | +方法:`GET /api/app/label-multiple-option` | |
| 175 | + | |
| 176 | +入参(`LabelMultipleOptionGetListInputVo`,查询参数): | |
| 177 | + | |
| 178 | +```json | |
| 179 | +{ | |
| 180 | + "skipCount": 0, | |
| 181 | + "maxResultCount": 10, | |
| 182 | + "keyword": "Allergens", | |
| 183 | + "state": true | |
| 184 | +} | |
| 185 | +``` | |
| 186 | + | |
| 187 | +### 3.2 详情 | |
| 188 | + | |
| 189 | +方法:`GET /api/app/label-multiple-option/{id}` | |
| 190 | + | |
| 191 | +入参: | |
| 192 | + | |
| 193 | +- `id`:多选项 Id(字符串) | |
| 194 | + | |
| 195 | +### 3.3 新增 | |
| 196 | + | |
| 197 | +方法:`POST /api/app/label-multiple-option` | |
| 198 | + | |
| 199 | +入参(Body:`LabelMultipleOptionCreateInputVo`): | |
| 200 | + | |
| 201 | +```json | |
| 202 | +{ | |
| 203 | + "optionCode": "OPT_ALLERGENS", | |
| 204 | + "optionName": "Allergens", | |
| 205 | + "optionValuesJson": ["Peanuts", "Dairy", "Gluten", "Soy"], | |
| 206 | + "state": true, | |
| 207 | + "orderNum": 1 | |
| 208 | +} | |
| 209 | +``` | |
| 210 | + | |
| 211 | +### 3.4 编辑 | |
| 212 | + | |
| 213 | +方法:`PUT /api/app/label-multiple-option/{id}` | |
| 214 | + | |
| 215 | +入参(Body:`LabelMultipleOptionUpdateInputVo`,字段同创建): | |
| 216 | + | |
| 217 | +```json | |
| 218 | +{ | |
| 219 | + "optionCode": "OPT_ALLERGENS", | |
| 220 | + "optionName": "Allergens", | |
| 221 | + "optionValuesJson": ["Peanuts", "Dairy"], | |
| 222 | + "state": true, | |
| 223 | + "orderNum": 2 | |
| 224 | +} | |
| 225 | +``` | |
| 226 | + | |
| 227 | +### 3.5 删除(逻辑删除) | |
| 228 | + | |
| 229 | +方法:`DELETE /api/app/label-multiple-option/{id}` | |
| 230 | + | |
| 231 | +--- | |
| 232 | + | |
| 233 | +## 接口 4:Label Templates(标签模板) | |
| 234 | + | |
| 235 | +说明: | |
| 236 | +- 模板标识入参 `id` 使用 `fl_label_template.TemplateCode`。 | |
| 237 | +- 创建/编辑的 Body 字段名对齐你前端 editor JSON(`id/name/appliedLocation/elements/config`)。 | |
| 238 | + | |
| 239 | +### 4.1 分页列表 | |
| 240 | + | |
| 241 | +方法:`GET /api/app/label-template` | |
| 242 | + | |
| 243 | +入参(`LabelTemplateGetListInputVo`,查询参数): | |
| 244 | + | |
| 245 | +```json | |
| 246 | +{ | |
| 247 | + "skipCount": 0, | |
| 248 | + "maxResultCount": 10, | |
| 249 | + "keyword": "测试模板", | |
| 250 | + "locationId": "11111111-1111-1111-1111-111111111111", | |
| 251 | + "labelType": "PRICE", | |
| 252 | + "state": true | |
| 253 | +} | |
| 254 | +``` | |
| 255 | + | |
| 256 | +### 4.2 详情 | |
| 257 | + | |
| 258 | +方法:`GET /api/app/label-template/{id}` | |
| 259 | + | |
| 260 | +入参: | |
| 261 | + | |
| 262 | +- `id`:模板编码 `TemplateCode`(字符串) | |
| 263 | + | |
| 264 | +### 4.3 新增模板 | |
| 265 | + | |
| 266 | +方法:`POST /api/app/label-template` | |
| 267 | + | |
| 268 | +入参(Body:`LabelTemplateCreateInputVo`): | |
| 269 | + | |
| 270 | +```json | |
| 271 | +{ | |
| 272 | + "id": "TPL_TEST_001", | |
| 273 | + "name": "测试模板-价格签(4x6)", | |
| 274 | + "labelType": "PRICE", | |
| 275 | + "unit": "inch", | |
| 276 | + "width": 4, | |
| 277 | + "height": 6, | |
| 278 | + "appliedLocation": "ALL", | |
| 279 | + "showRuler": true, | |
| 280 | + "showGrid": true, | |
| 281 | + "state": true, | |
| 282 | + "elements": [ | |
| 283 | + { | |
| 284 | + "id": "el-fixed-title", | |
| 285 | + "type": "TEXT_STATIC", | |
| 286 | + "x": 32, | |
| 287 | + "y": 24, | |
| 288 | + "width": 160, | |
| 289 | + "height": 24, | |
| 290 | + "rotation": "horizontal", | |
| 291 | + "border": "none", | |
| 292 | + "zIndex": 1, | |
| 293 | + "orderNum": 1, | |
| 294 | + "valueSourceType": "FIXED", | |
| 295 | + "isRequiredInput": false, | |
| 296 | + "config": { | |
| 297 | + "text": "商品名", | |
| 298 | + "fontFamily": "Arial", | |
| 299 | + "fontSize": 14, | |
| 300 | + "fontWeight": "bold", | |
| 301 | + "textAlign": "left" | |
| 302 | + } | |
| 303 | + } | |
| 304 | + ], | |
| 305 | + "appliedLocationIds": [] | |
| 306 | +} | |
| 307 | +``` | |
| 308 | + | |
| 309 | +说明: | |
| 310 | +- 当 `appliedLocation=SPECIFIED` 时,`appliedLocationIds` 必须至少选择一个门店。 | |
| 311 | + | |
| 312 | +### 4.4 编辑模板 | |
| 313 | + | |
| 314 | +方法:`PUT /api/app/label-template/{id}` | |
| 315 | + | |
| 316 | +入参: | |
| 317 | +- Path:`id` 是当前模板编码(TemplateCode) | |
| 318 | +- Body:字段同新增(`id/name/elements/...`) | |
| 319 | + | |
| 320 | +示例(编辑:同样字段,appliedLocation 切到 SPECIFIED): | |
| 321 | + | |
| 322 | +```json | |
| 323 | +{ | |
| 324 | + "id": "TPL_TEST_001", | |
| 325 | + "name": "测试模板-价格签(4x6) v2", | |
| 326 | + "labelType": "PRICE", | |
| 327 | + "unit": "inch", | |
| 328 | + "width": 4, | |
| 329 | + "height": 6, | |
| 330 | + "appliedLocation": "SPECIFIED", | |
| 331 | + "showRuler": true, | |
| 332 | + "showGrid": true, | |
| 333 | + "state": true, | |
| 334 | + "elements": [], | |
| 335 | + "appliedLocationIds": ["11111111-1111-1111-1111-111111111111"] | |
| 336 | +} | |
| 337 | +``` | |
| 338 | + | |
| 339 | +版本: | |
| 340 | +- `VersionNo` 会在编辑时自动 `+1`。 | |
| 341 | +- `elements` 会按传入内容全量重建。 | |
| 342 | + | |
| 343 | +### 4.5 删除(逻辑删除) | |
| 344 | + | |
| 345 | +方法:`DELETE /api/app/label-template/{id}` | |
| 346 | + | |
| 347 | +入参: | |
| 348 | +- `id`:模板编码 `TemplateCode` | |
| 349 | + | |
| 350 | +删除校验: | |
| 351 | +- 若该模板已被 `fl_label` 引用,则禁止删除。 | |
| 352 | + | |
| 353 | +--- | |
| 354 | + | |
| 355 | +## 接口 5:Labels(按产品展示多个标签) | |
| 356 | + | |
| 357 | +说明: | |
| 358 | +- 列表接口按 `ProductId` 查询,一个产品会对应多条标签记录。 | |
| 359 | +- 标签详情/编辑/删除的 `id` 使用 `fl_label.LabelCode`。 | |
| 360 | + | |
| 361 | +### 5.1 分页列表(按产品) | |
| 362 | + | |
| 363 | +方法:`GET /api/app/label` | |
| 364 | + | |
| 365 | +入参(`LabelGetListInputVo`,查询参数): | |
| 366 | + | |
| 367 | +```json | |
| 368 | +{ | |
| 369 | + "skipCount": 0, | |
| 370 | + "maxResultCount": 10, | |
| 371 | + "sorting": "", | |
| 372 | + "keyword": "早餐", | |
| 373 | + "locationId": "11111111-1111-1111-1111-111111111111", | |
| 374 | + "productId": "22222222-2222-2222-2222-222222222222", | |
| 375 | + "labelCategoryId": "33333333-3333-3333-3333-333333333333", | |
| 376 | + "labelTypeId": "44444444-4444-4444-4444-444444444444", | |
| 377 | + "templateCode": "TPL_TEST_001", | |
| 378 | + "state": true | |
| 379 | +} | |
| 380 | +``` | |
| 381 | + | |
| 382 | +### 5.2 详情 | |
| 383 | + | |
| 384 | +方法:`GET /api/app/label/{id}` | |
| 385 | + | |
| 386 | +入参: | |
| 387 | +- `id`:标签编码 `LabelCode` | |
| 388 | + | |
| 389 | +返回: | |
| 390 | +- `productIds`:该标签绑定的产品Id 列表 | |
| 391 | + | |
| 392 | +### 5.3 新增标签 | |
| 393 | + | |
| 394 | +方法:`POST /api/app/label` | |
| 395 | + | |
| 396 | +入参(Body:`LabelCreateInputVo`): | |
| 397 | + | |
| 398 | +```json | |
| 399 | +{ | |
| 400 | + "labelCode": "LBL_TEST_001", | |
| 401 | + "labelName": "早餐标签", | |
| 402 | + "templateCode": "TPL_TEST_001", | |
| 403 | + "locationId": "11111111-1111-1111-1111-111111111111", | |
| 404 | + "labelCategoryId": "33333333-3333-3333-3333-333333333333", | |
| 405 | + "labelTypeId": "44444444-4444-4444-4444-444444444444", | |
| 406 | + "productIds": ["22222222-2222-2222-2222-222222222222"], | |
| 407 | + "labelInfoJson": { "note": "测试标签1" }, | |
| 408 | + "state": true | |
| 409 | +} | |
| 410 | +``` | |
| 411 | + | |
| 412 | +校验: | |
| 413 | +- `productIds` 至少 1 个 | |
| 414 | +- `templateCode/locationId/labelCategoryId/labelTypeId` 不能为空 | |
| 415 | + | |
| 416 | +### 5.4 编辑标签 | |
| 417 | + | |
| 418 | +方法:`PUT /api/app/label/{id}` | |
| 419 | + | |
| 420 | +入参: | |
| 421 | +- Path:`id` 为当前标签编码 `LabelCode` | |
| 422 | +- Body:字段同创建(`LabelUpdateInputVo`) | |
| 423 | + | |
| 424 | +```json | |
| 425 | +{ | |
| 426 | + "labelName": "早餐标签 v2", | |
| 427 | + "templateCode": "TPL_TEST_001", | |
| 428 | + "locationId": "11111111-1111-1111-1111-111111111111", | |
| 429 | + "labelCategoryId": "33333333-3333-3333-3333-333333333333", | |
| 430 | + "labelTypeId": "44444444-4444-4444-4444-444444444444", | |
| 431 | + "productIds": ["22222222-2222-2222-2222-222222222222"], | |
| 432 | + "labelInfoJson": { "note": "测试标签1 v2" }, | |
| 433 | + "state": true | |
| 434 | +} | |
| 435 | +``` | |
| 436 | + | |
| 437 | +关联维护: | |
| 438 | +- `fl_label_product` 会按新 `productIds` 重建。 | |
| 439 | + | |
| 440 | +### 5.5 删除标签(逻辑删除) | |
| 441 | + | |
| 442 | +方法:`DELETE /api/app/label/{id}` | |
| 443 | + | |
| 444 | +入参: | |
| 445 | +- `id`:标签编码 `LabelCode` | |
| 446 | + | |
| 447 | +删除行为: | |
| 448 | +- 逻辑删除 `fl_label` | |
| 449 | +- 删除该标签对应的 `fl_label_product` 关联 | |
| 450 | + | |
| 451 | +--- | |
| 452 | +## 接口 6:Products(产品) | |
| 453 | + | |
| 454 | +说明: | |
| 455 | +- 产品表:`fl_product` | |
| 456 | +- 删除为逻辑删除:`IsDeleted = true` | |
| 457 | + | |
| 458 | +### 6.1 分页列表 | |
| 459 | + | |
| 460 | +方法:`GET /api/app/product` | |
| 461 | + | |
| 462 | +入参(`ProductGetListInputVo`,查询参数): | |
| 463 | +```json | |
| 464 | +{ | |
| 465 | + "skipCount": 0, | |
| 466 | + "maxResultCount": 10, | |
| 467 | + "sorting": "", | |
| 468 | + "keyword": "Chicken", | |
| 469 | + "state": true | |
| 470 | +} | |
| 471 | +``` | |
| 472 | + | |
| 473 | +### 6.2 详情 | |
| 474 | + | |
| 475 | +方法:`GET /api/app/product/{id}` | |
| 476 | + | |
| 477 | +入参: | |
| 478 | +- `id`:产品Id(`fl_product.Id`) | |
| 479 | + | |
| 480 | +### 6.3 新增产品 | |
| 481 | + | |
| 482 | +方法:`POST /api/app/product` | |
| 483 | + | |
| 484 | +入参(Body:`ProductCreateInputVo`): | |
| 485 | +```json | |
| 486 | +{ | |
| 487 | + "productCode": "PRD_TEST_001", | |
| 488 | + "productName": "Chicken", | |
| 489 | + "categoryName": "Meat", | |
| 490 | + "productImageUrl": "https://example.com/img.png", | |
| 491 | + "state": true | |
| 492 | +} | |
| 493 | +``` | |
| 494 | + | |
| 495 | +校验: | |
| 496 | +- `productCode/productName` 不能为空 | |
| 497 | +- `productCode` 不能与未删除的数据重复 | |
| 498 | + | |
| 499 | +### 6.4 编辑产品 | |
| 500 | + | |
| 501 | +方法:`PUT /api/app/product/{id}` | |
| 502 | + | |
| 503 | +入参: | |
| 504 | +- Path:`id` 为当前产品Id(`fl_product.Id`) | |
| 505 | +- Body:字段同新增(`ProductUpdateInputVo`) | |
| 506 | + | |
| 507 | +### 6.5 删除(逻辑删除) | |
| 508 | + | |
| 509 | +方法:`DELETE /api/app/product/{id}` | |
| 510 | + | |
| 511 | +入参: | |
| 512 | +- `id`:产品Id | |
| 513 | + | |
| 514 | +--- | |
| 515 | +## 接口 7:Product-Location(门店-产品关联) | |
| 516 | + | |
| 517 | +说明: | |
| 518 | +- 关联表:`fl_location_product` | |
| 519 | +- 关联按门店进行批量替换: | |
| 520 | + - `Create`:在门店下新增未存在的 product 关联 | |
| 521 | + - `Update`:替换该门店下全部关联(先删后建) | |
| 522 | + - `Delete`:删除该门店下全部关联 | |
| 523 | + | |
| 524 | +### 7.1 分页列表 | |
| 525 | + | |
| 526 | +方法:`GET /api/app/product-location` | |
| 527 | + | |
| 528 | +入参(`ProductLocationGetListInputVo`,查询参数): | |
| 529 | +```json | |
| 530 | +{ | |
| 531 | + "skipCount": 0, | |
| 532 | + "maxResultCount": 10, | |
| 533 | + "sorting": "", | |
| 534 | + "locationId": "11111111-1111-1111-1111-111111111111", | |
| 535 | + "productId": "22222222-2222-2222-2222-222222222222" | |
| 536 | +} | |
| 537 | +``` | |
| 538 | + | |
| 539 | +### 7.2 获取门店下全部产品 | |
| 540 | + | |
| 541 | +方法:`GET /api/app/product-location/{id}` | |
| 542 | + | |
| 543 | +入参: | |
| 544 | +- `id`:门店Id(`location.Id`,string 表示) | |
| 545 | + | |
| 546 | +返回: | |
| 547 | +- 门店Id + 该门店关联的产品列表 | |
| 548 | + | |
| 549 | +### 7.3 新增/建立门店关联 | |
| 550 | + | |
| 551 | +方法:`POST /api/app/product-location` | |
| 552 | + | |
| 553 | +入参(Body:`ProductLocationCreateInputVo`): | |
| 554 | +```json | |
| 555 | +{ | |
| 556 | + "locationId": "11111111-1111-1111-1111-111111111111", | |
| 557 | + "productIds": ["22222222-2222-2222-2222-222222222222"] | |
| 558 | +} | |
| 559 | +``` | |
| 560 | + | |
| 561 | +校验: | |
| 562 | +- `locationId` 对应门店必须存在 | |
| 563 | +- `productIds` 必须都存在于 `fl_product` 且未删除 | |
| 564 | + | |
| 565 | +### 7.4 编辑/替换门店关联 | |
| 566 | + | |
| 567 | +方法:`PUT /api/app/product-location/{id}` | |
| 568 | + | |
| 569 | +入参: | |
| 570 | +- Path:`id` 为门店Id | |
| 571 | +- Body:`ProductLocationUpdateInputVo` | |
| 572 | +```json | |
| 573 | +{ | |
| 574 | + "productIds": ["22222222-2222-2222-2222-222222222222"] | |
| 575 | +} | |
| 576 | +``` | |
| 577 | + | |
| 578 | +### 7.5 删除门店关联(按门店删除全部) | |
| 579 | + | |
| 580 | +方法:`DELETE /api/app/product-location/{id}` | |
| 581 | + | |
| 582 | +入参: | |
| 583 | +- `id`:门店Id | |
| 584 | + | ... | ... |