From 2748a56b8b75a8271852871e9520bb56ae933472 Mon Sep 17 00:00:00 2001 From: 李曜臣 Date: Mon, 23 Mar 2026 13:45:10 +0800 Subject: [PATCH] 实现标签模块 --- label-template-template-1773988794039 (1).json | 218 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- label-template-template-1773999322132.json | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelPreviewResolveInputVo.cs | 24 ++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelTemplatePreviewDto.cs | 42 ++++++++++++++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductCreateInputVo.cs | 15 +++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListInputVo.cs | 21 +++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListOutputDto.cs | 22 ++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetOutputDto.cs | 24 ++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductUpdateInputVo.cs | 6 ++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationCreateInputVo.cs | 17 +++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListInputVo.cs | 21 +++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListOutputDto.cs | 18 ++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetOutputDto.cs | 11 +++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationUpdateInputVo.cs | 12 ++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductAppService.cs | 38 ++++++++++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductLocationAppService.cs | 38 ++++++++++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationProductDbEntity.cs | 15 +++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductAppService.cs | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductLocationAppService.cs | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 项目相关文档/标签模块接口对接说明.md | 584 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 20 files changed, 1789 insertions(+), 218 deletions(-) delete mode 100644 label-template-template-1773988794039 (1).json create mode 100644 label-template-template-1773999322132.json create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelPreviewResolveInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelTemplatePreviewDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductCreateInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListOutputDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetOutputDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductUpdateInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationCreateInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListOutputDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetOutputDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationUpdateInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductAppService.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductLocationAppService.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationProductDbEntity.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductAppService.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductLocationAppService.cs create mode 100644 项目相关文档/标签模块接口对接说明.md diff --git a/label-template-template-1773988794039 (1).json b/label-template-template-1773988794039 (1).json deleted file mode 100644 index bbbacba..0000000 --- a/label-template-template-1773988794039 (1).json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "id": "template-1773988794039", - "name": "未命名模板", - "labelType": "PRICE", - "unit": "inch", - "width": 4, - "height": 6, - "appliedLocation": "ALL", - "showRuler": true, - "showGrid": true, - "elements": [ - { - "id": "el-1773989351080-vqc03nr", - "type": "IMAGE", - "x": 32, - "y": 24, - "width": 60, - "height": 60, - "rotation": "horizontal", - "border": "none", - "config": { - "src": "", - "scaleMode": "contain" - } - }, - { - "id": "el-1773989452538-0ejrxoe", - "type": "TEXT_STATIC", - "x": 32, - "y": 104, - "width": 120, - "height": 24, - "rotation": "horizontal", - "border": "none", - "config": { - "text": "文本", - "fontFamily": "Arial", - "fontSize": 14, - "fontWeight": "normal", - "textAlign": "left" - } - }, - { - "id": "el-1773989466493-ibbroio", - "type": "QRCODE", - "x": 32, - "y": 136, - "width": 80, - "height": 80, - "rotation": "horizontal", - "border": "none", - "config": { - "data": "https://example.com", - "errorLevel": "M" - } - }, - { - "id": "el-1773989469008-f1l39qj", - "type": "BARCODE", - "x": 0, - "y": 224, - "width": 160, - "height": 48, - "rotation": "horizontal", - "border": "none", - "config": { - "barcodeType": "CODE128", - "data": "123456789", - "showText": true, - "orientation": "horizontal" - } - }, - { - "id": "el-1773989473436-j7fdeh2", - "type": "BLANK", - "x": 32, - "y": 288, - "width": 48, - "height": 32, - "rotation": "horizontal", - "border": "none", - "config": {} - }, - { - "id": "el-1773989483341-ifwcyjj", - "type": "TEXT_PRICE", - "x": 152, - "y": 24, - "width": 80, - "height": 24, - "rotation": "horizontal", - "border": "none", - "config": { - "text": "0.00", - "prefix": "¥", - "decimal": 2, - "fontFamily": "Arial", - "fontSize": 14, - "fontWeight": "bold", - "textAlign": "right" - } - }, - { - "id": "el-1773989498031-e4d61j8", - "type": "IMAGE", - "x": 192, - "y": 56, - "width": 60, - "height": 60, - "rotation": "horizontal", - "border": "none", - "config": { - "src": "", - "scaleMode": "contain" - } - }, - { - "id": "el-1773989505076-1lxccx7", - "type": "TEXT_PRODUCT", - "x": 200, - "y": 136, - "width": 120, - "height": 24, - "rotation": "horizontal", - "border": "none", - "config": { - "text": "商品名", - "fontFamily": "Arial", - "fontSize": 14, - "fontWeight": "normal", - "textAlign": "left" - } - }, - { - "id": "el-1773989509805-ax3392v", - "type": "TEXT_STATIC", - "x": 192, - "y": 160, - "width": 120, - "height": 24, - "rotation": "horizontal", - "border": "none", - "config": { - "text": "文本", - "fontFamily": "Arial", - "fontSize": 14, - "fontWeight": "normal", - "textAlign": "left" - } - }, - { - "id": "el-1773989512993-xt8bg7q", - "type": "QRCODE", - "x": 184, - "y": 184, - "width": 80, - "height": 80, - "rotation": "horizontal", - "border": "none", - "config": { - "data": "https://example.com", - "errorLevel": "M" - } - }, - { - "id": "el-1773989525383-eji8p2s", - "type": "BARCODE", - "x": 0, - "y": 288, - "width": 160, - "height": 48, - "rotation": "horizontal", - "border": "none", - "config": { - "barcodeType": "CODE128", - "data": "123456789", - "showText": true, - "orientation": "horizontal" - } - }, - { - "id": "el-1773989540159-dr2avdf", - "type": "NUTRITION", - "x": 184, - "y": 280, - "width": 200, - "height": 120, - "rotation": "horizontal", - "border": "none", - "config": { - "calories": 120, - "fat": "5g", - "protein": "3g", - "carbs": "10g", - "layout": "standard" - } - }, - { - "id": "el-1773989549679-mcxrdnw", - "type": "TEXT_PRICE", - "x": 24, - "y": 352, - "width": 80, - "height": 24, - "rotation": "horizontal", - "border": "none", - "config": { - "text": "0.00", - "prefix": "¥", - "decimal": 2, - "fontFamily": "Arial", - "fontSize": 14, - "fontWeight": "bold", - "textAlign": "right" - } - } - ] -} \ No newline at end of file diff --git a/label-template-template-1773999322132.json b/label-template-template-1773999322132.json new file mode 100644 index 0000000..ad57772 --- /dev/null +++ b/label-template-template-1773999322132.json @@ -0,0 +1,325 @@ +{ + "id": "template-1773999322132", + "name": "未命名模板", + "labelType": "PRICE", + "unit": "cm", + "width": 21, + "height": 29.7, + "appliedLocation": "ALL", + "showRuler": true, + "showGrid": true, + "elements": [ + { + "id": "el-1773999335969-ugvoy6v", + "type": "TEXT_STATIC", + "x": 392, + "y": 48, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "文本", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773999339450-cc9y2lv", + "type": "QRCODE", + "x": 200, + "y": 24, + "width": 80, + "height": 80, + "rotation": "horizontal", + "border": "none", + "config": { + "data": "https://example.com", + "errorLevel": "M" + } + }, + { + "id": "el-1773999341556-c795unj", + "type": "BARCODE", + "x": 8, + "y": 48, + "width": 160, + "height": 48, + "rotation": "horizontal", + "border": "none", + "config": { + "barcodeType": "CODE128", + "data": "123456789", + "showText": true, + "orientation": "horizontal" + } + }, + { + "id": "el-1773999346493-onxkjxn", + "type": "BLANK", + "x": 40, + "y": 120, + "width": 40, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": {} + }, + { + "id": "el-1773999351836-igrbrib", + "type": "TEXT_PRICE", + "x": 520, + "y": 24, + "width": 80, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "0.00", + "prefix": "¥", + "decimal": 2, + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "bold", + "textAlign": "right" + } + }, + { + "id": "el-1773999357328-0jof4kh", + "type": "IMAGE", + "x": 480, + "y": 96, + "width": 60, + "height": 60, + "rotation": "horizontal", + "border": "none", + "config": { + "src": "", + "scaleMode": "contain" + } + }, + { + "id": "el-1773999360132-bwle4nb", + "type": "IMAGE", + "x": 152, + "y": 120, + "width": 60, + "height": 60, + "rotation": "horizontal", + "border": "none", + "config": { + "src": "", + "scaleMode": "contain" + } + }, + { + "id": "el-1773999364492-yxs89ov", + "type": "TEXT_PRODUCT", + "x": 272, + "y": 120, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "商品名", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773999367121-3uesy24", + "type": "TEXT_STATIC", + "x": 48, + "y": 216, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "文本", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773999369332-fib58lw", + "type": "QRCODE", + "x": 160, + "y": 208, + "width": 80, + "height": 80, + "rotation": "horizontal", + "border": "none", + "config": { + "data": "https://example.com", + "errorLevel": "M" + } + }, + { + "id": "el-1773999371501-p1ot3id", + "type": "BARCODE", + "x": 280, + "y": 224, + "width": 160, + "height": 48, + "rotation": "horizontal", + "border": "none", + "config": { + "barcodeType": "CODE128", + "data": "123456789", + "showText": true, + "orientation": "horizontal" + } + }, + { + "id": "el-1773999374282-ux37cow", + "type": "NUTRITION", + "x": 480, + "y": 200, + "width": 200, + "height": 120, + "rotation": "horizontal", + "border": "none", + "config": { + "calories": 120, + "fat": "5g", + "protein": "3g", + "carbs": "10g", + "layout": "standard" + } + }, + { + "id": "el-1773999377353-n90ved8", + "type": "TEXT_PRICE", + "x": 664, + "y": 152, + "width": 80, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "0.00", + "prefix": "¥", + "decimal": 2, + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "bold", + "textAlign": "right" + } + }, + { + "id": "el-1773999389265-sukmurs", + "type": "DATE", + "x": 40, + "y": 320, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "format": "YYYY-MM-DD", + "offsetDays": 0 + } + }, + { + "id": "el-1773999392135-3ub707o", + "type": "TIME", + "x": 144, + "y": 320, + "width": 100, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "format": "HH:mm", + "offsetDays": 0 + } + }, + { + "id": "el-1773999394947-3y8li6m", + "type": "DURATION", + "x": 272, + "y": 320, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "format": "YYYY-MM-DD", + "offsetDays": 3 + } + }, + { + "id": "el-1773999399456-nbglmfa", + "type": "IMAGE", + "x": 464, + "y": 304, + "width": 60, + "height": 60, + "rotation": "horizontal", + "border": "none", + "config": { + "src": "", + "scaleMode": "contain" + } + }, + { + "id": "el-1773999401976-wzmsx8f", + "type": "TEXT_STATIC", + "x": 600, + "y": 320, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "文本", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773999407826-91if0px", + "type": "TEXT_STATIC", + "x": 40, + "y": 368, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "文本", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773999417908-lje3b3w", + "type": "TEXT_STATIC", + "x": 200, + "y": 368, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "文本", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + } + ] +} \ No newline at end of file diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelPreviewResolveInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelPreviewResolveInputVo.cs new file mode 100644 index 0000000..bc1128a --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelPreviewResolveInputVo.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace FoodLabeling.Application.Contracts.Dtos.Label; + +public class LabelPreviewResolveInputVo +{ + /// + /// 标签编码(fl_label.LabelCode) + /// + public string LabelCode { get; set; } = string.Empty; + + /// + /// 选择用于预览的产品Id(fl_product.Id) + /// 如果不传,默认取该标签绑定的第一个产品 + /// + public string? ProductId { get; set; } + + /// + /// 打印输入(前端传,用于 PRINT_INPUT 元素) + /// key 建议使用模板元素的 InputKey + /// + public Dictionary? PrintInputJson { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelTemplatePreviewDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelTemplatePreviewDto.cs new file mode 100644 index 0000000..52577cb --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelTemplatePreviewDto.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using FoodLabeling.Application.Contracts.Dtos.LabelTemplate; + +namespace FoodLabeling.Application.Contracts.Dtos.Label; + +/// +/// 预览输出:与前端 LabelCanvas/LabelPreviewOnly 的 LabelTemplate 结构尽量一致 +/// +public class LabelTemplatePreviewDto +{ + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("labelType")] + public string LabelType { get; set; } = string.Empty; + + [JsonPropertyName("unit")] + public string Unit { get; set; } = string.Empty; + + [JsonPropertyName("width")] + public decimal Width { get; set; } + + [JsonPropertyName("height")] + public decimal Height { get; set; } + + [JsonPropertyName("appliedLocation")] + public string AppliedLocation { get; set; } = "ALL"; + + [JsonPropertyName("showRuler")] + public bool ShowRuler { get; set; } + + [JsonPropertyName("showGrid")] + public bool ShowGrid { get; set; } + + [JsonPropertyName("elements")] + public List Elements { get; set; } = new(); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductCreateInputVo.cs new file mode 100644 index 0000000..2f5c40c --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductCreateInputVo.cs @@ -0,0 +1,15 @@ +namespace FoodLabeling.Application.Contracts.Dtos.Product; + +public class ProductCreateInputVo +{ + public string ProductCode { get; set; } = string.Empty; + + public string ProductName { get; set; } = string.Empty; + + public string? CategoryName { get; set; } + + public string? ProductImageUrl { get; set; } + + public bool State { get; set; } = true; +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListInputVo.cs new file mode 100644 index 0000000..b6e26fb --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListInputVo.cs @@ -0,0 +1,21 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using Volo.Abp.Application.Dtos; + +namespace FoodLabeling.Application.Contracts.Dtos.Product; + +/// +/// 产品分页查询入参 +/// +public class ProductGetListInputVo : PagedAndSortedResultRequestDto +{ + /// + /// 模糊搜索(ProductCode/ProductName/CategoryName) + /// + public string? Keyword { get; set; } + + /// + /// 启用状态 + /// + public bool? State { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListOutputDto.cs new file mode 100644 index 0000000..f172144 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetListOutputDto.cs @@ -0,0 +1,22 @@ +namespace FoodLabeling.Application.Contracts.Dtos.Product; + +public class ProductGetListOutputDto +{ + public string Id { get; set; } = string.Empty; + + public string ProductCode { get; set; } = string.Empty; + + public string ProductName { get; set; } = string.Empty; + + public string? CategoryName { get; set; } + + public string? ProductImageUrl { get; set; } + + public bool State { get; set; } + + /// + /// 该产品关联的标签数量(fl_label_product + fl_label) + /// + public long NoOfLabels { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetOutputDto.cs new file mode 100644 index 0000000..c307077 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductGetOutputDto.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace FoodLabeling.Application.Contracts.Dtos.Product; + +public class ProductGetOutputDto +{ + public string Id { get; set; } = string.Empty; + + public string ProductCode { get; set; } = string.Empty; + + public string ProductName { get; set; } = string.Empty; + + public string? CategoryName { get; set; } + + public string? ProductImageUrl { get; set; } + + public bool State { get; set; } + + /// + /// 该产品关联的门店Id列表(来自 fl_location_product) + /// + public List LocationIds { get; set; } = new(); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductUpdateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductUpdateInputVo.cs new file mode 100644 index 0000000..94b8a58 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Product/ProductUpdateInputVo.cs @@ -0,0 +1,6 @@ +namespace FoodLabeling.Application.Contracts.Dtos.Product; + +public class ProductUpdateInputVo : ProductCreateInputVo +{ +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationCreateInputVo.cs new file mode 100644 index 0000000..52b68b0 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationCreateInputVo.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; + +public class ProductLocationCreateInputVo +{ + /// + /// 门店Id + /// + public string LocationId { get; set; } = string.Empty; + + /// + /// 产品Id列表 + /// + public List ProductIds { get; set; } = new(); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListInputVo.cs new file mode 100644 index 0000000..f0864f1 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListInputVo.cs @@ -0,0 +1,21 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using Volo.Abp.Application.Dtos; + +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; + +/// +/// 产品-门店分页查询入参 +/// +public class ProductLocationGetListInputVo : PagedAndSortedResultRequestDto +{ + /// + /// 门店Id(location.Id,string 表示) + /// + public string? LocationId { get; set; } + + /// + /// 产品Id(fl_product.Id,string) + /// + public string? ProductId { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListOutputDto.cs new file mode 100644 index 0000000..4f9ad76 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetListOutputDto.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; + +public class ProductLocationGetListOutputDto +{ + public string Id { get; set; } = string.Empty; + + public string LocationId { get; set; } = string.Empty; + public string? LocationCode { get; set; } + public string? LocationName { get; set; } + + public string ProductId { get; set; } = string.Empty; + public string? ProductCode { get; set; } + public string? ProductName { get; set; } + public string? ProductImageUrl { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetOutputDto.cs new file mode 100644 index 0000000..c945a01 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationGetOutputDto.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; + +public class ProductLocationGetOutputDto +{ + public string LocationId { get; set; } = string.Empty; + + public List Products { get; set; } = new(); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationUpdateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationUpdateInputVo.cs new file mode 100644 index 0000000..416e356 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductLocation/ProductLocationUpdateInputVo.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace FoodLabeling.Application.Contracts.Dtos.ProductLocation; + +public class ProductLocationUpdateInputVo +{ + /// + /// 产品Id列表(将替换当前门店下的全部产品关联) + /// + public List ProductIds { get; set; } = new(); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductAppService.cs new file mode 100644 index 0000000..c0f0cbd --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductAppService.cs @@ -0,0 +1,38 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using FoodLabeling.Application.Contracts.Dtos.Product; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace FoodLabeling.Application.Contracts.IServices; + +/// +/// 产品管理接口(Products,fl_product) +/// +public interface IProductAppService : IApplicationService +{ + /// + /// 产品分页列表 + /// + Task> GetListAsync(ProductGetListInputVo input); + + /// + /// 产品详情 + /// + Task GetAsync(string id); + + /// + /// 新增产品 + /// + Task CreateAsync(ProductCreateInputVo input); + + /// + /// 编辑产品 + /// + Task UpdateAsync(string id, ProductUpdateInputVo input); + + /// + /// 删除产品(逻辑删除) + /// + Task DeleteAsync(string id); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductLocationAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductLocationAppService.cs new file mode 100644 index 0000000..cea35f1 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IProductLocationAppService.cs @@ -0,0 +1,38 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using FoodLabeling.Application.Contracts.Dtos.ProductLocation; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace FoodLabeling.Application.Contracts.IServices; + +/// +/// 产品-门店关联管理(fl_location_product) +/// +public interface IProductLocationAppService : IApplicationService +{ + /// + /// 关联分页列表(可按门店Id/产品Id过滤) + /// + Task> GetListAsync(ProductLocationGetListInputVo input); + + /// + /// 门店下的全部产品(id=LocationId) + /// + Task GetAsync(string id); + + /// + /// 新增/批量建立门店与产品的关联 + /// + Task CreateAsync(ProductLocationCreateInputVo input); + + /// + /// 编辑:替换该门店下的全部产品关联 + /// + Task UpdateAsync(string id, ProductLocationUpdateInputVo input); + + /// + /// 删除:删除该门店的全部产品关联 + /// + Task DeleteAsync(string id); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationProductDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationProductDbEntity.cs new file mode 100644 index 0000000..1b2d1ad --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLocationProductDbEntity.cs @@ -0,0 +1,15 @@ +using SqlSugar; + +namespace FoodLabeling.Application.Services.DbModels; + +[SugarTable("fl_location_product")] +public class FlLocationProductDbEntity +{ + [SugarColumn(IsPrimaryKey = true)] + public string Id { get; set; } = string.Empty; + + public string LocationId { get; set; } = string.Empty; + + public string ProductId { get; set; } = string.Empty; +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductAppService.cs new file mode 100644 index 0000000..3dd5abe --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductAppService.cs @@ -0,0 +1,222 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using FoodLabeling.Application.Contracts.Dtos.Product; +using FoodLabeling.Application.Contracts.IServices; +using FoodLabeling.Application.Services.DbModels; +using SqlSugar; +using Volo.Abp; +using Volo.Abp.Application.Services; +using Volo.Abp.Guids; +using Volo.Abp.Uow; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace FoodLabeling.Application.Services; + +/// +/// 产品管理(Products) +/// +public class ProductAppService : ApplicationService, IProductAppService +{ + private readonly ISqlSugarDbContext _dbContext; + private readonly IGuidGenerator _guidGenerator; + + public ProductAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator) + { + _dbContext = dbContext; + _guidGenerator = guidGenerator; + } + + public async Task> GetListAsync(ProductGetListInputVo input) + { + RefAsync total = 0; + var keyword = input.Keyword?.Trim(); + + var query = _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted) + .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => + x.ProductCode.Contains(keyword!) || + x.ProductName.Contains(keyword!) || + (x.CategoryName != null && x.CategoryName.Contains(keyword!))) + .WhereIF(input.State != null, x => x.State == input.State); + + if (!string.IsNullOrWhiteSpace(input.Sorting)) + { + query = query.OrderBy(input.Sorting); + } + else + { + query = query.OrderByDescending(x => x.ProductName); + } + + var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); + var ids = entities.Select(x => x.Id).ToList(); + + // Count labels for each product + var countMap = new Dictionary(); + if (ids.Count > 0) + { + var countRows = await _dbContext.SqlSugarClient.Queryable( + (lp, l) => lp.LabelId == l.Id) + .Where((lp, l) => !l.IsDeleted && ids.Contains(lp.ProductId)) + .GroupBy((lp, l) => lp.ProductId) + .Select((lp, l) => new { ProductId = lp.ProductId, Count = SqlFunc.AggregateCount(lp.Id) }) + .ToListAsync(); + + countMap = countRows.ToDictionary(x => x.ProductId, x => (long)x.Count); + } + + var items = entities.Select(x => new ProductGetListOutputDto + { + Id = x.Id, + ProductCode = x.ProductCode, + ProductName = x.ProductName, + CategoryName = x.CategoryName, + ProductImageUrl = x.ProductImageUrl, + State = x.State, + NoOfLabels = countMap.TryGetValue(x.Id, out var count) ? count : 0 + }).ToList(); + + return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); + } + + public async Task GetAsync(string id) + { + var productId = id?.Trim(); + if (string.IsNullOrWhiteSpace(productId)) + { + throw new UserFriendlyException("产品Id不能为空"); + } + + var entity = await _dbContext.SqlSugarClient.Queryable() + .FirstAsync(x => !x.IsDeleted && x.Id == productId); + + if (entity is null) + { + throw new UserFriendlyException("产品不存在"); + } + + var locationIds = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.ProductId == productId) + .Select(x => x.LocationId) + .Distinct() + .ToListAsync(); + + return new ProductGetOutputDto + { + Id = entity.Id, + ProductCode = entity.ProductCode, + ProductName = entity.ProductName, + CategoryName = entity.CategoryName, + ProductImageUrl = entity.ProductImageUrl, + State = entity.State, + LocationIds = locationIds + }; + } + + [UnitOfWork] + public async Task CreateAsync(ProductCreateInputVo input) + { + var code = input.ProductCode?.Trim(); + var name = input.ProductName?.Trim(); + if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name)) + { + throw new UserFriendlyException("产品编码和名称不能为空"); + } + + var duplicated = await _dbContext.SqlSugarClient.Queryable() + .AnyAsync(x => !x.IsDeleted && (x.ProductCode == code)); + if (duplicated) + { + throw new UserFriendlyException("产品编码已存在"); + } + + var entity = new FlProductDbEntity + { + Id = _guidGenerator.Create().ToString(), + IsDeleted = false, + ProductCode = code, + ProductName = name, + CategoryName = input.CategoryName?.Trim(), + ProductImageUrl = input.ProductImageUrl?.Trim(), + State = input.State + }; + + await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); + return await GetAsync(entity.Id); + } + + [UnitOfWork] + public async Task UpdateAsync(string id, ProductUpdateInputVo input) + { + var productId = id?.Trim(); + if (string.IsNullOrWhiteSpace(productId)) + { + throw new UserFriendlyException("产品Id不能为空"); + } + + var entity = await _dbContext.SqlSugarClient.Queryable() + .FirstAsync(x => !x.IsDeleted && x.Id == productId); + if (entity is null) + { + throw new UserFriendlyException("产品不存在"); + } + + var code = input.ProductCode?.Trim(); + var name = input.ProductName?.Trim(); + if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name)) + { + throw new UserFriendlyException("产品编码和名称不能为空"); + } + + var duplicated = await _dbContext.SqlSugarClient.Queryable() + .AnyAsync(x => !x.IsDeleted && x.Id != productId && x.ProductCode == code); + if (duplicated) + { + throw new UserFriendlyException("产品编码已存在"); + } + + entity.ProductCode = code; + entity.ProductName = name; + entity.CategoryName = input.CategoryName?.Trim(); + entity.ProductImageUrl = input.ProductImageUrl?.Trim(); + entity.State = input.State; + + await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); + return await GetAsync(productId); + } + + [UnitOfWork] + public async Task DeleteAsync(string id) + { + var productId = id?.Trim(); + if (string.IsNullOrWhiteSpace(productId)) + { + throw new UserFriendlyException("产品Id不能为空"); + } + + var entity = await _dbContext.SqlSugarClient.Queryable() + .FirstAsync(x => !x.IsDeleted && x.Id == productId); + if (entity is null) + { + throw new UserFriendlyException("产品不存在"); + } + + entity.IsDeleted = true; + await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); + } + + private static PagedResultWithPageDto BuildPagedResult(int skipCount, int maxResultCount, int total, List items) + { + var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; + var pageIndex = pageSize <= 0 ? 1 : (skipCount / pageSize) + 1; + var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize); + return new PagedResultWithPageDto + { + PageIndex = pageIndex, + PageSize = pageSize, + TotalCount = total, + TotalPages = totalPages, + Items = items + }; + } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductLocationAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductLocationAppService.cs new file mode 100644 index 0000000..786d5b1 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductLocationAppService.cs @@ -0,0 +1,334 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using FoodLabeling.Application.Contracts.Dtos.ProductLocation; +using FoodLabeling.Application.Contracts.IServices; +using FoodLabeling.Application.Services.DbModels; +using FoodLabeling.Domain.Entities; +using SqlSugar; +using Volo.Abp; +using Volo.Abp.Application.Services; +using Volo.Abp.Guids; +using Volo.Abp.Uow; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace FoodLabeling.Application.Services; + +/// +/// 产品-门店关联管理(fl_location_product) +/// +public class ProductLocationAppService : ApplicationService, IProductLocationAppService +{ + private readonly ISqlSugarDbContext _dbContext; + private readonly IGuidGenerator _guidGenerator; + + public ProductLocationAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator) + { + _dbContext = dbContext; + _guidGenerator = guidGenerator; + } + + public async Task> GetListAsync(ProductLocationGetListInputVo input) + { + RefAsync total = 0; + + var locationId = input.LocationId?.Trim(); + var productId = input.ProductId?.Trim(); + + var query = _dbContext.SqlSugarClient + .Queryable((lp, p) => lp.ProductId == p.Id) + .Where((lp, p) => p.IsDeleted == false); + + if (!string.IsNullOrWhiteSpace(locationId)) + { + query = query.Where((lp, p) => lp.LocationId == locationId); + } + + if (!string.IsNullOrWhiteSpace(productId)) + { + query = query.Where((lp, p) => lp.ProductId == productId); + } + + // 默认排序 + query = string.IsNullOrWhiteSpace(input.Sorting) + ? query.OrderBy((lp, p) => p.ProductName) + : query.OrderBy(input.Sorting); + + var entities = await query + .Select((lp, p) => new ProductLocationGetListOutputDto + { + Id = lp.Id, + LocationId = lp.LocationId, + ProductId = p.Id, + ProductCode = p.ProductCode, + ProductName = p.ProductName, + ProductImageUrl = p.ProductImageUrl, + LocationCode = null, + LocationName = null + }) + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); + + // 拉取门店信息用于输出 + var locationIdSet = entities + .Select(x => x.LocationId) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct() + .ToList(); + + var locationGuidList = locationIdSet + .Where(x => Guid.TryParse(x, out _)) + .Select(Guid.Parse) + .Distinct() + .ToList(); + + var locationMap = new Dictionary(); + if (locationGuidList.Count > 0) + { + var locations = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted) + .Where(x => locationGuidList.Contains(x.Id)) + .ToListAsync(); + + locationMap = locations.ToDictionary(x => x.Id, x => x); + } + + var items = entities.Select(x => + { + string? locationCode = null; + string? locationName = null; + if (Guid.TryParse(x.LocationId, out var gid) && locationMap.TryGetValue(gid, out var loc)) + { + locationCode = loc.LocationCode; + locationName = loc.LocationName ?? loc.LocationCode; + } + + return new ProductLocationGetListOutputDto + { + Id = x.Id, + LocationId = x.LocationId, + LocationCode = locationCode, + LocationName = locationName, + ProductId = x.ProductId, + ProductCode = x.ProductCode, + ProductName = x.ProductName, + ProductImageUrl = x.ProductImageUrl + }; + }).ToList(); + + return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); + } + + public async Task GetAsync(string id) + { + var locationId = id?.Trim(); + if (string.IsNullOrWhiteSpace(locationId)) + { + throw new UserFriendlyException("门店Id不能为空"); + } + + var rows = await _dbContext.SqlSugarClient + .Queryable((lp, p) => lp.ProductId == p.Id) + .Where((lp, p) => lp.LocationId == locationId && !p.IsDeleted) + .Select((lp, p) => new ProductLocationGetListOutputDto + { + Id = lp.Id, + LocationId = lp.LocationId, + ProductId = p.Id, + ProductCode = p.ProductCode, + ProductName = p.ProductName, + ProductImageUrl = p.ProductImageUrl + }) + .ToListAsync(); + + if (rows.Count == 0) + { + return new ProductLocationGetOutputDto + { + LocationId = locationId, + Products = new List() + }; + } + + // 门店信息 + string? locationCode = null; + string? locationName = null; + if (Guid.TryParse(locationId, out var gid)) + { + var loc = await _dbContext.SqlSugarClient.Queryable() + .FirstAsync(x => !x.IsDeleted && x.Id == gid); + if (loc != null) + { + locationCode = loc.LocationCode; + locationName = loc.LocationName ?? loc.LocationCode; + } + } + + var products = rows.Select(x => new ProductLocationGetListOutputDto + { + Id = x.Id, + LocationId = x.LocationId, + LocationCode = locationCode, + LocationName = locationName, + ProductId = x.ProductId, + ProductCode = x.ProductCode, + ProductName = x.ProductName, + ProductImageUrl = x.ProductImageUrl + }).ToList(); + + return new ProductLocationGetOutputDto + { + LocationId = locationId, + Products = products + }; + } + + [UnitOfWork] + public async Task CreateAsync(ProductLocationCreateInputVo input) + { + var locationId = input.LocationId?.Trim(); + if (string.IsNullOrWhiteSpace(locationId)) + { + throw new UserFriendlyException("门店Id不能为空"); + } + + var productIds = input.ProductIds? + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .Distinct() + .ToList() ?? new List(); + + if (productIds.Count == 0) + { + throw new UserFriendlyException("ProductIds至少1个"); + } + + // 校验门店存在 + if (!Guid.TryParse(locationId, out var gid)) + { + throw new UserFriendlyException("门店Id格式不正确"); + } + + var locExists = await _dbContext.SqlSugarClient.Queryable() + .AnyAsync(x => !x.IsDeleted && x.Id == gid); + if (!locExists) + { + throw new UserFriendlyException("门店不存在"); + } + + // 校验产品存在 + var productExistsCount = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted && productIds.Contains(x.Id)) + .CountAsync(); + if (productExistsCount != productIds.Count) + { + throw new UserFriendlyException("产品不存在"); + } + + // 去重:避免唯一键重复 + var existingProductIds = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.LocationId == locationId && productIds.Contains(x.ProductId)) + .Select(x => x.ProductId) + .Distinct() + .ToListAsync(); + + var newProductIds = productIds.Where(x => !existingProductIds.Contains(x)).ToList(); + if (newProductIds.Count == 0) + { + return await GetAsync(locationId); + } + + var rows = newProductIds.Select(pid => new FlLocationProductDbEntity + { + Id = _guidGenerator.Create().ToString(), + LocationId = locationId, + ProductId = pid + }).ToList(); + + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); + return await GetAsync(locationId); + } + + [UnitOfWork] + public async Task UpdateAsync(string id, ProductLocationUpdateInputVo input) + { + var locationId = id?.Trim(); + if (string.IsNullOrWhiteSpace(locationId)) + { + throw new UserFriendlyException("门店Id不能为空"); + } + + var productIds = input.ProductIds? + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .Distinct() + .ToList() ?? new List(); + + if (productIds.Count == 0) + { + throw new UserFriendlyException("ProductIds至少1个"); + } + + if (!Guid.TryParse(locationId, out var gid)) + { + throw new UserFriendlyException("门店Id格式不正确"); + } + + var locExists = await _dbContext.SqlSugarClient.Queryable() + .AnyAsync(x => !x.IsDeleted && x.Id == gid); + if (!locExists) + { + throw new UserFriendlyException("门店不存在"); + } + + var productExistsCount = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted && productIds.Contains(x.Id)) + .CountAsync(); + if (productExistsCount != productIds.Count) + { + throw new UserFriendlyException("产品不存在"); + } + + // 替换:先删后建 + await _dbContext.SqlSugarClient.Deleteable() + .Where(x => x.LocationId == locationId) + .ExecuteCommandAsync(); + + var rows = productIds.Select(pid => new FlLocationProductDbEntity + { + Id = _guidGenerator.Create().ToString(), + LocationId = locationId, + ProductId = pid + }).ToList(); + + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); + return await GetAsync(locationId); + } + + [UnitOfWork] + public async Task DeleteAsync(string id) + { + var locationId = id?.Trim(); + if (string.IsNullOrWhiteSpace(locationId)) + { + throw new UserFriendlyException("门店Id不能为空"); + } + + await _dbContext.SqlSugarClient.Deleteable() + .Where(x => x.LocationId == locationId) + .ExecuteCommandAsync(); + } + + private static PagedResultWithPageDto BuildPagedResult(int skipCount, int maxResultCount, int total, List items) + { + var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; + var pageIndex = pageSize <= 0 ? 1 : (skipCount / pageSize) + 1; + var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize); + return new PagedResultWithPageDto + { + PageIndex = pageIndex, + PageSize = pageSize, + TotalCount = total, + TotalPages = totalPages, + Items = items + }; + } +} + diff --git a/项目相关文档/标签模块接口对接说明.md b/项目相关文档/标签模块接口对接说明.md new file mode 100644 index 0000000..5707218 --- /dev/null +++ b/项目相关文档/标签模块接口对接说明.md @@ -0,0 +1,584 @@ +## 概述 + +美国版后端采用 ABP 动态接口(ConventionalControllers),宿主统一前缀为 `api/app`。 +Swagger 地址: + +- `http://localhost:19001/swagger` + +说明: +- 接口最终 URL 以 Swagger 展示为准(可在 Swagger 里搜索 `LabelCategory / LabelType / LabelMultipleOption / LabelTemplate / Label`)。 +- 本模块后端接口以各 AppService 的方法签名自动暴露。 +- 返回分页统一包含 `PageIndex / PageSize / TotalCount / TotalPages / Items`。 + +--- + +## Swagger 中如何找到 + +1. 启动后端宿主(`Yi.Abp.Web`),端口 `19001`。 +2. 打开 `http://localhost:19001/swagger`。 +3. 在接口分组里搜索以下关键词之一: + - `label-category` + - `label-type` + - `label-multiple-option` + - `label-template` + - `label` + +--- + +## 接口 1:Label Categories(标签分类) + +### 1.1 分页列表 + +方法:`GET /api/app/label-category` + +入参(`LabelCategoryGetListInputVo`,查询参数): + +- `skipCount`(int) +- `maxResultCount`(int) +- `sorting`(string,可选) +- `keyword`(string,可选) +- `state`(boolean,可选) + +示例(查询参数): + +```json +{ + "skipCount": 0, + "maxResultCount": 10, + "keyword": "Prep", + "state": true +} +``` + +### 1.2 详情 + +方法:`GET /api/app/label-category/{id}` + +入参: + +- `id`:分类 Id(字符串) + +### 1.3 新增 + +方法:`POST /api/app/label-category` + +入参(Body:`LabelCategoryCreateInputVo`): + +```json +{ + "categoryCode": "CAT_PREP", + "categoryName": "Prep", + "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png", + "state": true, + "orderNum": 1 +} +``` + +### 1.4 编辑 + +方法:`PUT /api/app/label-category/{id}` + +入参(Body:`LabelCategoryUpdateInputVo`,字段同创建): + +```json +{ + "categoryCode": "CAT_PREP", + "categoryName": "Prep", + "categoryPhotoUrl": null, + "state": true, + "orderNum": 2 +} +``` + +### 1.5 删除(逻辑删除) + +方法:`DELETE /api/app/label-category/{id}` + +入参: + +- `id`:分类 Id(字符串) + +删除校验: +- 若该分类已被 `fl_label` 引用,则抛出友好错误,禁止删除。 + +--- + +## 接口 2:Label Types(标签类型) + +### 2.1 分页列表 + +方法:`GET /api/app/label-type` + +入参(`LabelTypeGetListInputVo`,查询参数): + +```json +{ + "skipCount": 0, + "maxResultCount": 10, + "keyword": "Defrost", + "state": true +} +``` + +### 2.2 详情 + +方法:`GET /api/app/label-type/{id}` + +入参: + +- `id`:类型 Id(字符串) + +### 2.3 新增 + +方法:`POST /api/app/label-type` + +入参(Body:`LabelTypeCreateInputVo`): + +```json +{ + "typeCode": "TYPE_DEFROST", + "typeName": "Defrost", + "state": true, + "orderNum": 1 +} +``` + +### 2.4 编辑 + +方法:`PUT /api/app/label-type/{id}` + +入参(Body:`LabelTypeUpdateInputVo`,字段同创建): + +```json +{ + "typeCode": "TYPE_DEFROST", + "typeName": "Defrost", + "state": true, + "orderNum": 2 +} +``` + +### 2.5 删除(逻辑删除) + +方法:`DELETE /api/app/label-type/{id}` + +删除校验: +- 若该类型已被 `fl_label` 引用,则禁止删除。 + +--- + +## 接口 3:Multiple Options(多选项字典) + +### 3.1 分页列表 + +方法:`GET /api/app/label-multiple-option` + +入参(`LabelMultipleOptionGetListInputVo`,查询参数): + +```json +{ + "skipCount": 0, + "maxResultCount": 10, + "keyword": "Allergens", + "state": true +} +``` + +### 3.2 详情 + +方法:`GET /api/app/label-multiple-option/{id}` + +入参: + +- `id`:多选项 Id(字符串) + +### 3.3 新增 + +方法:`POST /api/app/label-multiple-option` + +入参(Body:`LabelMultipleOptionCreateInputVo`): + +```json +{ + "optionCode": "OPT_ALLERGENS", + "optionName": "Allergens", + "optionValuesJson": ["Peanuts", "Dairy", "Gluten", "Soy"], + "state": true, + "orderNum": 1 +} +``` + +### 3.4 编辑 + +方法:`PUT /api/app/label-multiple-option/{id}` + +入参(Body:`LabelMultipleOptionUpdateInputVo`,字段同创建): + +```json +{ + "optionCode": "OPT_ALLERGENS", + "optionName": "Allergens", + "optionValuesJson": ["Peanuts", "Dairy"], + "state": true, + "orderNum": 2 +} +``` + +### 3.5 删除(逻辑删除) + +方法:`DELETE /api/app/label-multiple-option/{id}` + +--- + +## 接口 4:Label Templates(标签模板) + +说明: +- 模板标识入参 `id` 使用 `fl_label_template.TemplateCode`。 +- 创建/编辑的 Body 字段名对齐你前端 editor JSON(`id/name/appliedLocation/elements/config`)。 + +### 4.1 分页列表 + +方法:`GET /api/app/label-template` + +入参(`LabelTemplateGetListInputVo`,查询参数): + +```json +{ + "skipCount": 0, + "maxResultCount": 10, + "keyword": "测试模板", + "locationId": "11111111-1111-1111-1111-111111111111", + "labelType": "PRICE", + "state": true +} +``` + +### 4.2 详情 + +方法:`GET /api/app/label-template/{id}` + +入参: + +- `id`:模板编码 `TemplateCode`(字符串) + +### 4.3 新增模板 + +方法:`POST /api/app/label-template` + +入参(Body:`LabelTemplateCreateInputVo`): + +```json +{ + "id": "TPL_TEST_001", + "name": "测试模板-价格签(4x6)", + "labelType": "PRICE", + "unit": "inch", + "width": 4, + "height": 6, + "appliedLocation": "ALL", + "showRuler": true, + "showGrid": true, + "state": true, + "elements": [ + { + "id": "el-fixed-title", + "type": "TEXT_STATIC", + "x": 32, + "y": 24, + "width": 160, + "height": 24, + "rotation": "horizontal", + "border": "none", + "zIndex": 1, + "orderNum": 1, + "valueSourceType": "FIXED", + "isRequiredInput": false, + "config": { + "text": "商品名", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "bold", + "textAlign": "left" + } + } + ], + "appliedLocationIds": [] +} +``` + +说明: +- 当 `appliedLocation=SPECIFIED` 时,`appliedLocationIds` 必须至少选择一个门店。 + +### 4.4 编辑模板 + +方法:`PUT /api/app/label-template/{id}` + +入参: +- Path:`id` 是当前模板编码(TemplateCode) +- Body:字段同新增(`id/name/elements/...`) + +示例(编辑:同样字段,appliedLocation 切到 SPECIFIED): + +```json +{ + "id": "TPL_TEST_001", + "name": "测试模板-价格签(4x6) v2", + "labelType": "PRICE", + "unit": "inch", + "width": 4, + "height": 6, + "appliedLocation": "SPECIFIED", + "showRuler": true, + "showGrid": true, + "state": true, + "elements": [], + "appliedLocationIds": ["11111111-1111-1111-1111-111111111111"] +} +``` + +版本: +- `VersionNo` 会在编辑时自动 `+1`。 +- `elements` 会按传入内容全量重建。 + +### 4.5 删除(逻辑删除) + +方法:`DELETE /api/app/label-template/{id}` + +入参: +- `id`:模板编码 `TemplateCode` + +删除校验: +- 若该模板已被 `fl_label` 引用,则禁止删除。 + +--- + +## 接口 5:Labels(按产品展示多个标签) + +说明: +- 列表接口按 `ProductId` 查询,一个产品会对应多条标签记录。 +- 标签详情/编辑/删除的 `id` 使用 `fl_label.LabelCode`。 + +### 5.1 分页列表(按产品) + +方法:`GET /api/app/label` + +入参(`LabelGetListInputVo`,查询参数): + +```json +{ + "skipCount": 0, + "maxResultCount": 10, + "sorting": "", + "keyword": "早餐", + "locationId": "11111111-1111-1111-1111-111111111111", + "productId": "22222222-2222-2222-2222-222222222222", + "labelCategoryId": "33333333-3333-3333-3333-333333333333", + "labelTypeId": "44444444-4444-4444-4444-444444444444", + "templateCode": "TPL_TEST_001", + "state": true +} +``` + +### 5.2 详情 + +方法:`GET /api/app/label/{id}` + +入参: +- `id`:标签编码 `LabelCode` + +返回: +- `productIds`:该标签绑定的产品Id 列表 + +### 5.3 新增标签 + +方法:`POST /api/app/label` + +入参(Body:`LabelCreateInputVo`): + +```json +{ + "labelCode": "LBL_TEST_001", + "labelName": "早餐标签", + "templateCode": "TPL_TEST_001", + "locationId": "11111111-1111-1111-1111-111111111111", + "labelCategoryId": "33333333-3333-3333-3333-333333333333", + "labelTypeId": "44444444-4444-4444-4444-444444444444", + "productIds": ["22222222-2222-2222-2222-222222222222"], + "labelInfoJson": { "note": "测试标签1" }, + "state": true +} +``` + +校验: +- `productIds` 至少 1 个 +- `templateCode/locationId/labelCategoryId/labelTypeId` 不能为空 + +### 5.4 编辑标签 + +方法:`PUT /api/app/label/{id}` + +入参: +- Path:`id` 为当前标签编码 `LabelCode` +- Body:字段同创建(`LabelUpdateInputVo`) + +```json +{ + "labelName": "早餐标签 v2", + "templateCode": "TPL_TEST_001", + "locationId": "11111111-1111-1111-1111-111111111111", + "labelCategoryId": "33333333-3333-3333-3333-333333333333", + "labelTypeId": "44444444-4444-4444-4444-444444444444", + "productIds": ["22222222-2222-2222-2222-222222222222"], + "labelInfoJson": { "note": "测试标签1 v2" }, + "state": true +} +``` + +关联维护: +- `fl_label_product` 会按新 `productIds` 重建。 + +### 5.5 删除标签(逻辑删除) + +方法:`DELETE /api/app/label/{id}` + +入参: +- `id`:标签编码 `LabelCode` + +删除行为: +- 逻辑删除 `fl_label` +- 删除该标签对应的 `fl_label_product` 关联 + +--- +## 接口 6:Products(产品) + +说明: +- 产品表:`fl_product` +- 删除为逻辑删除:`IsDeleted = true` + +### 6.1 分页列表 + +方法:`GET /api/app/product` + +入参(`ProductGetListInputVo`,查询参数): +```json +{ + "skipCount": 0, + "maxResultCount": 10, + "sorting": "", + "keyword": "Chicken", + "state": true +} +``` + +### 6.2 详情 + +方法:`GET /api/app/product/{id}` + +入参: +- `id`:产品Id(`fl_product.Id`) + +### 6.3 新增产品 + +方法:`POST /api/app/product` + +入参(Body:`ProductCreateInputVo`): +```json +{ + "productCode": "PRD_TEST_001", + "productName": "Chicken", + "categoryName": "Meat", + "productImageUrl": "https://example.com/img.png", + "state": true +} +``` + +校验: +- `productCode/productName` 不能为空 +- `productCode` 不能与未删除的数据重复 + +### 6.4 编辑产品 + +方法:`PUT /api/app/product/{id}` + +入参: +- Path:`id` 为当前产品Id(`fl_product.Id`) +- Body:字段同新增(`ProductUpdateInputVo`) + +### 6.5 删除(逻辑删除) + +方法:`DELETE /api/app/product/{id}` + +入参: +- `id`:产品Id + +--- +## 接口 7:Product-Location(门店-产品关联) + +说明: +- 关联表:`fl_location_product` +- 关联按门店进行批量替换: + - `Create`:在门店下新增未存在的 product 关联 + - `Update`:替换该门店下全部关联(先删后建) + - `Delete`:删除该门店下全部关联 + +### 7.1 分页列表 + +方法:`GET /api/app/product-location` + +入参(`ProductLocationGetListInputVo`,查询参数): +```json +{ + "skipCount": 0, + "maxResultCount": 10, + "sorting": "", + "locationId": "11111111-1111-1111-1111-111111111111", + "productId": "22222222-2222-2222-2222-222222222222" +} +``` + +### 7.2 获取门店下全部产品 + +方法:`GET /api/app/product-location/{id}` + +入参: +- `id`:门店Id(`location.Id`,string 表示) + +返回: +- 门店Id + 该门店关联的产品列表 + +### 7.3 新增/建立门店关联 + +方法:`POST /api/app/product-location` + +入参(Body:`ProductLocationCreateInputVo`): +```json +{ + "locationId": "11111111-1111-1111-1111-111111111111", + "productIds": ["22222222-2222-2222-2222-222222222222"] +} +``` + +校验: +- `locationId` 对应门店必须存在 +- `productIds` 必须都存在于 `fl_product` 且未删除 + +### 7.4 编辑/替换门店关联 + +方法:`PUT /api/app/product-location/{id}` + +入参: +- Path:`id` 为门店Id +- Body:`ProductLocationUpdateInputVo` +```json +{ + "productIds": ["22222222-2222-2222-2222-222222222222"] +} +``` + +### 7.5 删除门店关联(按门店删除全部) + +方法:`DELETE /api/app/product-location/{id}` + +入参: +- `id`:门店Id + -- libgit2 0.21.4