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 | "name": "未命名模板", | 3 | "name": "未命名模板", |
| 4 | "labelType": "PRICE", | 4 | "labelType": "PRICE", |
| 5 | - "unit": "inch", | ||
| 6 | - "width": 4, | ||
| 7 | - "height": 6, | 5 | + "unit": "cm", |
| 6 | + "width": 21, | ||
| 7 | + "height": 29.7, | ||
| 8 | "appliedLocation": "ALL", | 8 | "appliedLocation": "ALL", |
| 9 | "showRuler": true, | 9 | "showRuler": true, |
| 10 | "showGrid": true, | 10 | "showGrid": true, |
| 11 | "elements": [ | 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 | "type": "TEXT_STATIC", | 14 | "type": "TEXT_STATIC", |
| 29 | - "x": 32, | ||
| 30 | - "y": 104, | 15 | + "x": 392, |
| 16 | + "y": 48, | ||
| 31 | "width": 120, | 17 | "width": 120, |
| 32 | "height": 24, | 18 | "height": 24, |
| 33 | "rotation": "horizontal", | 19 | "rotation": "horizontal", |
| @@ -41,10 +27,10 @@ | @@ -41,10 +27,10 @@ | ||
| 41 | } | 27 | } |
| 42 | }, | 28 | }, |
| 43 | { | 29 | { |
| 44 | - "id": "el-1773989466493-ibbroio", | 30 | + "id": "el-1773999339450-cc9y2lv", |
| 45 | "type": "QRCODE", | 31 | "type": "QRCODE", |
| 46 | - "x": 32, | ||
| 47 | - "y": 136, | 32 | + "x": 200, |
| 33 | + "y": 24, | ||
| 48 | "width": 80, | 34 | "width": 80, |
| 49 | "height": 80, | 35 | "height": 80, |
| 50 | "rotation": "horizontal", | 36 | "rotation": "horizontal", |
| @@ -55,10 +41,10 @@ | @@ -55,10 +41,10 @@ | ||
| 55 | } | 41 | } |
| 56 | }, | 42 | }, |
| 57 | { | 43 | { |
| 58 | - "id": "el-1773989469008-f1l39qj", | 44 | + "id": "el-1773999341556-c795unj", |
| 59 | "type": "BARCODE", | 45 | "type": "BARCODE", |
| 60 | - "x": 0, | ||
| 61 | - "y": 224, | 46 | + "x": 8, |
| 47 | + "y": 48, | ||
| 62 | "width": 160, | 48 | "width": 160, |
| 63 | "height": 48, | 49 | "height": 48, |
| 64 | "rotation": "horizontal", | 50 | "rotation": "horizontal", |
| @@ -71,20 +57,20 @@ | @@ -71,20 +57,20 @@ | ||
| 71 | } | 57 | } |
| 72 | }, | 58 | }, |
| 73 | { | 59 | { |
| 74 | - "id": "el-1773989473436-j7fdeh2", | 60 | + "id": "el-1773999346493-onxkjxn", |
| 75 | "type": "BLANK", | 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 | "rotation": "horizontal", | 66 | "rotation": "horizontal", |
| 81 | "border": "none", | 67 | "border": "none", |
| 82 | "config": {} | 68 | "config": {} |
| 83 | }, | 69 | }, |
| 84 | { | 70 | { |
| 85 | - "id": "el-1773989483341-ifwcyjj", | 71 | + "id": "el-1773999351836-igrbrib", |
| 86 | "type": "TEXT_PRICE", | 72 | "type": "TEXT_PRICE", |
| 87 | - "x": 152, | 73 | + "x": 520, |
| 88 | "y": 24, | 74 | "y": 24, |
| 89 | "width": 80, | 75 | "width": 80, |
| 90 | "height": 24, | 76 | "height": 24, |
| @@ -101,10 +87,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 | "type": "IMAGE", | 105 | "type": "IMAGE", |
| 106 | - "x": 192, | ||
| 107 | - "y": 56, | 106 | + "x": 152, |
| 107 | + "y": 120, | ||
| 108 | "width": 60, | 108 | "width": 60, |
| 109 | "height": 60, | 109 | "height": 60, |
| 110 | "rotation": "horizontal", | 110 | "rotation": "horizontal", |
| @@ -115,10 +115,10 @@ | @@ -115,10 +115,10 @@ | ||
| 115 | } | 115 | } |
| 116 | }, | 116 | }, |
| 117 | { | 117 | { |
| 118 | - "id": "el-1773989505076-1lxccx7", | 118 | + "id": "el-1773999364492-yxs89ov", |
| 119 | "type": "TEXT_PRODUCT", | 119 | "type": "TEXT_PRODUCT", |
| 120 | - "x": 200, | ||
| 121 | - "y": 136, | 120 | + "x": 272, |
| 121 | + "y": 120, | ||
| 122 | "width": 120, | 122 | "width": 120, |
| 123 | "height": 24, | 123 | "height": 24, |
| 124 | "rotation": "horizontal", | 124 | "rotation": "horizontal", |
| @@ -132,10 +132,10 @@ | @@ -132,10 +132,10 @@ | ||
| 132 | } | 132 | } |
| 133 | }, | 133 | }, |
| 134 | { | 134 | { |
| 135 | - "id": "el-1773989509805-ax3392v", | 135 | + "id": "el-1773999367121-3uesy24", |
| 136 | "type": "TEXT_STATIC", | 136 | "type": "TEXT_STATIC", |
| 137 | - "x": 192, | ||
| 138 | - "y": 160, | 137 | + "x": 48, |
| 138 | + "y": 216, | ||
| 139 | "width": 120, | 139 | "width": 120, |
| 140 | "height": 24, | 140 | "height": 24, |
| 141 | "rotation": "horizontal", | 141 | "rotation": "horizontal", |
| @@ -149,10 +149,10 @@ | @@ -149,10 +149,10 @@ | ||
| 149 | } | 149 | } |
| 150 | }, | 150 | }, |
| 151 | { | 151 | { |
| 152 | - "id": "el-1773989512993-xt8bg7q", | 152 | + "id": "el-1773999369332-fib58lw", |
| 153 | "type": "QRCODE", | 153 | "type": "QRCODE", |
| 154 | - "x": 184, | ||
| 155 | - "y": 184, | 154 | + "x": 160, |
| 155 | + "y": 208, | ||
| 156 | "width": 80, | 156 | "width": 80, |
| 157 | "height": 80, | 157 | "height": 80, |
| 158 | "rotation": "horizontal", | 158 | "rotation": "horizontal", |
| @@ -163,10 +163,10 @@ | @@ -163,10 +163,10 @@ | ||
| 163 | } | 163 | } |
| 164 | }, | 164 | }, |
| 165 | { | 165 | { |
| 166 | - "id": "el-1773989525383-eji8p2s", | 166 | + "id": "el-1773999371501-p1ot3id", |
| 167 | "type": "BARCODE", | 167 | "type": "BARCODE", |
| 168 | - "x": 0, | ||
| 169 | - "y": 288, | 168 | + "x": 280, |
| 169 | + "y": 224, | ||
| 170 | "width": 160, | 170 | "width": 160, |
| 171 | "height": 48, | 171 | "height": 48, |
| 172 | "rotation": "horizontal", | 172 | "rotation": "horizontal", |
| @@ -179,10 +179,10 @@ | @@ -179,10 +179,10 @@ | ||
| 179 | } | 179 | } |
| 180 | }, | 180 | }, |
| 181 | { | 181 | { |
| 182 | - "id": "el-1773989540159-dr2avdf", | 182 | + "id": "el-1773999374282-ux37cow", |
| 183 | "type": "NUTRITION", | 183 | "type": "NUTRITION", |
| 184 | - "x": 184, | ||
| 185 | - "y": 280, | 184 | + "x": 480, |
| 185 | + "y": 200, | ||
| 186 | "width": 200, | 186 | "width": 200, |
| 187 | "height": 120, | 187 | "height": 120, |
| 188 | "rotation": "horizontal", | 188 | "rotation": "horizontal", |
| @@ -196,10 +196,10 @@ | @@ -196,10 +196,10 @@ | ||
| 196 | } | 196 | } |
| 197 | }, | 197 | }, |
| 198 | { | 198 | { |
| 199 | - "id": "el-1773989549679-mcxrdnw", | 199 | + "id": "el-1773999377353-n90ved8", |
| 200 | "type": "TEXT_PRICE", | 200 | "type": "TEXT_PRICE", |
| 201 | - "x": 24, | ||
| 202 | - "y": 352, | 201 | + "x": 664, |
| 202 | + "y": 152, | ||
| 203 | "width": 80, | 203 | "width": 80, |
| 204 | "height": 24, | 204 | "height": 24, |
| 205 | "rotation": "horizontal", | 205 | "rotation": "horizontal", |
| @@ -213,6 +213,113 @@ | @@ -213,6 +213,113 @@ | ||
| 213 | "fontWeight": "bold", | 213 | "fontWeight": "bold", |
| 214 | "textAlign": "right" | 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 | \ No newline at end of file | 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 | + |