服务端的TigerApi 框架,基于.NET6 2024 版本
Rodney Chen
2024-05-30 1ae7b5a517aaa0f3a45f0b31b0c5173c35558318
按模块项目拆分Business
已删除3个文件
已修改4个文件
已重命名2个文件
已添加4个文件
860 ■■■■ 文件已修改
Tiger.Api.sln 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Api/Autofac/AutoFacContianer.cs 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business.MES/BIZ_MES_WO.cs 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business.MES/MES_WORKSHOP.cs 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business.MES/SMT/SmtTool.cs 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business.MES/Tiger.Business.MES.csproj 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business.MES/Transaction/LoadingMaterial.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business.MES/Transaction/MESTransactionBase.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business/MES/Biz.BIZ_MES_WO.cs 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business/MES/Biz.MES_WORKSHOP.cs 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business/MES/SMT/Biz.SmtTool.cs 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business/Model/TransactionBase.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Business/Tiger.Business.csproj 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Tiger.Api.sln
@@ -24,6 +24,8 @@
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "05-单元测试", "05-单元测试", "{CF29B377-FE5A-488A-AF99-DF9D9C6FCA95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tiger.Business.MES", "Tiger.Business.MES\Tiger.Business.MES.csproj", "{3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}"
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
@@ -64,6 +66,14 @@
        {60FA322B-9B03-4380-803C-63B1F240E453}.Release|Any CPU.Build.0 = Release|Any CPU
        {60FA322B-9B03-4380-803C-63B1F240E453}.Release|x86.ActiveCfg = Release|x86
        {60FA322B-9B03-4380-803C-63B1F240E453}.Release|x86.Build.0 = Release|x86
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Debug|x86.ActiveCfg = Debug|Any CPU
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Debug|x86.Build.0 = Debug|Any CPU
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Release|Any CPU.Build.0 = Release|Any CPU
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Release|x86.ActiveCfg = Release|Any CPU
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4}.Release|x86.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
@@ -73,6 +83,7 @@
        {2689560A-10E8-4E28-BCA4-37F697628F77} = {A994D516-643A-4543-B781-540E81F1DE55}
        {E7540FE4-F284-4F66-A11C-24D846C4B558} = {FF44BF62-08C5-4B52-B24F-54CD79E96848}
        {60FA322B-9B03-4380-803C-63B1F240E453} = {70881CAB-17ED-4C46-895A-62F2CE39A607}
        {3848BCAB-4E77-4D5E-8BF0-2A747981C1B4} = {70881CAB-17ED-4C46-895A-62F2CE39A607}
    EndGlobalSection
    GlobalSection(ExtensibilityGlobals) = postSolution
        SolutionGuid = {797A1D83-9F3C-4AEC-8A83-E3468102DBD1}
Tiger.Api/Autofac/AutoFacContianer.cs
@@ -39,28 +39,25 @@
        /// <param name="builder"></param>
        public static void Build(ContainerBuilder builder)
        {
            var assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "Tiger.Business.dll");
            //注入Tiger.Business.dll
            builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
            ////注入Tiger.Business.dll
            //var assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "Tiger.Business.dll");
            //builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
            #region åŠ¨æ€åŠ è½½å¤šä¸ªdll
            //string[] assemblyScanerPattern = new[] { @"MyModule.*.dll"};
            string[] assemblyPattern = new[] { "Tiger.Business([.].*)*.dll" };
            //// Make sure process paths are same...
            //Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
            // 1. Scan for assemblies containing autofac modules in the bin folder
            List<Assembly> assemblies = new List<Assembly>();
            assemblies.AddRange(
                Directory.EnumerateFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories)
                              .Where(filename => assemblyPattern.Any(pattern => Regex.IsMatch(filename, pattern)))
                              .Select(Assembly.LoadFrom)
            );
            //// 1. Scan for assemblies containing autofac modules in the bin folder
            //List<Assembly> assemblies = new List<Assembly>();
            //assemblies.AddRange(
            //    Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.dll", SearchOption.AllDirectories)
            //             .Where(filename => assemblyScanerPattern.Any(pattern => Regex.IsMatch(filename, pattern)))
            //             .Select(Assembly.LoadFrom)
            //);
            //foreach (var assembly in assemblies)
            //{
            //    builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
            //}
            foreach (var assembly in assemblies)
            {
                builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
            }
            #endregion
            //读取配置文件,把配置关系装载到ContainerBuilder
Tiger.Business.MES/BIZ_MES_WO.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
using Tiger.Model;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Rhea.Common;
using System.Net;
using System.Linq;
using Newtonsoft.Json;
using Tiger.IBusiness;
using static Tiger.Business.Biz;
using Microsoft.AspNetCore.Http;
namespace Tiger.Business.MES
{
    public partial class BizMesWo : IBIZ_MES_WO
    {
        /// <summary>
        /// ä¿å­˜
        /// </summary>
        /// <param name="wo"></param>
        /// <returns></returns>
        public async Task<ApiAction> SaveMesWo(BIZ_MES_WO wo)
        {
            var result = new ApiAction();
            try
            {
                var _wo = Db.Queryable<BIZ_MES_WO>().Where(x => x.ORDER_NO == wo.ORDER_NO).First();
                if (_wo != null && _wo.STATUS != (int)BIZ_MES_WO.STATUSs.Init)
                {
                    result.IsSuccessed = false;
                    result.LocaleMsg = new($"工单已经存在且不是初始化状态,不能保存修改!");
                    return result;
                }
                var db = Db;
                var dbTran = db.UseTran(() =>
                {
                    var y = db.Storageable(wo)
                       .WhereColumns(t => new { t.ORDER_NO, t.GHOST_ROW })
                       .ToStorage();
                    y.AsInsertable.ExecuteCommand();
                    y.AsUpdateable.IgnoreColumns(x => x.ID).ExecuteCommand();
                });
                if (!dbTran.IsSuccess)
                {
                    result.IsSuccessed = false;
                    result.Message = $"保存工单异常";
                }
            }
            catch (Exception ex)
            {
                result.CatchExceptionWithLog(ex, "保存工单异常");
            }
            return await Task.FromResult(result);
        }
        /// <summary>
        /// åˆ é™¤å·¥å•
        /// </summary>
        /// <param name="woId"></param>
        /// <returns></returns>
        public async Task<ApiAction> DeleteMesWo(string woId)
        {
            var result = new ApiAction();
            try
            {
                //查询是否已经有工单在用
                var _wo = Db.Queryable<BIZ_MES_WO>().Where(x => x.ID == woId).First();
                if (_wo != null && _wo.STATUS != (int)BIZ_MES_WO.STATUSs.Init)
                {
                    result.IsSuccessed = false;
                    result.LocaleMsg = new($"工单不是初始化状态,不能删除!");
                    return result;
                }
                var db = Db;
                var dbTran = db.UseTran(() =>
                {
                    db.Deleteable<BIZ_MES_WO>().Where(x => x.ID == woId).ExecuteCommand();
                });
                if (!dbTran.IsSuccess)
                {
                    result.IsSuccessed = false;
                    result.LocaleMsg = new($"删除工单异常");
                }
            }
            catch (Exception ex)
            {
                result.CatchExceptionWithLog(ex, "删除工单异常");
            }
            return await Task.FromResult(result);
        }
    }
}
Tiger.Business.MES/MES_WORKSHOP.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
using Tiger.Model;
using Rhea.Common;
using Tiger.IBusiness;
using static Tiger.Business.Biz;
using Microsoft.AspNetCore.Http;
namespace Tiger.Business.MES
{
    /// <summary>
    /// è½¦é—´
    /// </summary>
    public partial class BizMesWs : IMES_WORKSHOP
    {
        /// <summary>
        /// ä¿å­˜
        /// </summary>
        /// <param name="ws"></param>
        /// <returns></returns>
        public async Task<ApiAction> SaveMesWs(MES_WORKSHOP ws)
        {
            var result = new ApiAction();
            try
            {
                if (Db.Queryable<MES_WORKSHOP>().Where(x => x.WS_CODE == ws.WS_CODE && x.ID != ws.ID).Any())
                {
                    result.IsSuccessed = false;
                    result.LocaleMsg = new($"车间已经存在,不能新增!");
                    return result;
                }
                var db = Db;
                var dbTran = db.UseTran(() =>
                {
                    var y = db.Storageable(ws)
                       .WhereColumns(t => new { t.WS_CODE, t.GHOST_ROW })
                       .ToStorage();
                    y.AsInsertable.ExecuteCommand();
                    y.AsUpdateable.IgnoreColumns(x => x.ID).ExecuteCommand();
                });
                if (!dbTran.IsSuccess)
                {
                    result.IsSuccessed = false;
                    result.Message = $"保存车间异常";
                }
            }
            catch (Exception ex)
            {
                result.CatchExceptionWithLog(ex, "保存车间异常");
            }
            return await Task.FromResult(result);
        }
        /// <summary>
        /// åˆ é™¤è½¦é—´
        /// </summary>
        /// <param name="wsId"></param>
        /// <returns></returns>
        public async Task<ApiAction> DeleteMesWs(string wsId)
        {
            var result = new ApiAction();
            try
            {
                //查询是否已经有车间在用
                var db = Db;
                var dbTran = db.UseTran(() =>
                {
                    db.Deleteable<MES_WORKSHOP>().Where(x => x.ID == wsId).ExecuteCommand();
                });
                if (!dbTran.IsSuccess)
                {
                    result.IsSuccessed = false;
                    result.LocaleMsg = new($"删除车间异常");
                }
            }
            catch (Exception ex)
            {
                result.CatchExceptionWithLog(ex, "删除车间异常");
            }
            return await Task.FromResult(result);
        }
    }
}
Tiger.Business.MES/SMT/SmtTool.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
using Tiger.Model;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Rhea.Common;
using System.Net;
using System.Linq;
using Newtonsoft.Json;
using Tiger.IBusiness;
using static Tiger.Business.Biz;
using Microsoft.AspNetCore.Http;
using System.Collections;
using System.Data;
using Sundial;
namespace Tiger.Business.MES
{
    /// <summary>
    /// å·¥å…·ç®¡ç†
    /// </summary>
    public partial class SmtTool : ISmtTool
    {
        /// <summary>
        /// èŽ·å–æœªä¸Šæ–™åˆ—è¡¨
        /// </summary>
        /// <param name="paras"></param>
        /// <returns></returns>
        public async Task<ApiAction<List<SMT_WO_TABLE>>> GetNotLoadingMaterial(SmtLoadingInput paras)
        {
            var res = new ApiAction<List<SMT_WO_TABLE>>();
            List<SMT_WO_TABLE> sList = new List<SMT_WO_TABLE>();
            try
            {
                sList = Db.Queryable<SMT_WO_TABLE, SMT_LOADING>((t, l) =>
                                        new JoinQueryInfos(
                                            JoinType.Left,
                                            t.WORK_ORDER == l.WORK_ORDER
                                            && t.PROD_CODE == l.PROD_CODE
                                            && t.PCB_SURFACE == l.PCB_SURFACE
                                            && t.LINE_CODE == l.LINE_CODE
                                            && t.SLOT_NO == l.SLOT_NO
                                        ))
                             .Where((t, l) => SqlFunc.IsNullOrEmpty(l.WORK_ORDER) && t.WORK_ORDER == paras.moCode && t.PROD_CODE == paras.prodCode && t.LINE_CODE == paras.lineCode && t.PCB_SURFACE == paras.pcbSurface)
                             .WhereIF(SqlFunc.IsNullOrEmpty(paras.machineCode), (t, l) => t.SMT_CODE == paras.machineCode)
                             .Select((t, l) => t)
                             .ToList();
            }
            catch (Exception ex)
            {
                res.CatchExceptionWithLog(ex, "查询异常");
            }
            res.Data = sList;
            return await Task.FromResult(res);
        }
        /// <summary>
        /// å¯¼å…¥å·¥å•料站表
        /// </summary>
        /// <param name="paras"></param>
        /// <returns></returns>
        public async Task<ApiAction<List<smtWoTableIn>>> ValidateTableImport(List<smtWoTableIn> paras)
        {
            var result = new ApiAction<List<smtWoTableIn>>();
            try
            {
                //DataTable dt = JsonConvert.DeserializeObject<DataTable>(paras.ToString());
                List<smtWoTableIn> currentList = new List<smtWoTableIn>();
                if (paras?.Count > 0)
                {
                    //var duplicateWo = paras.GroupBy(p => p.关联工单号)
                    //                 .Where(g => g.Count() > 1)
                    //                 .Select(g => g.Key)
                    //                 .ToList();
                    //if (duplicateWo.Count() > 0)//存在重复数据
                    //{
                    //    string WORK_ORDER = string.Empty;
                    //    foreach (var item in duplicateWo)
                    //    {
                    //        WORK_ORDER += item;
                    //    }
                    //    WORK_ORDER = WORK_ORDER.TrimEnd(';');
                    //    result.IsSuccessed = false;
                    //    result.Message = $"导入的物料中有重复数据:关联工单:{WORK_ORDER}";
                    //}
                    //else
                    {
                        foreach (var item in paras)
                        {
                            item.处理方式 = "新增";
                            item.原因 = null;
                            if (item.关联工单号 == null || item.关联工单号 == " ")
                            {
                                item.处理方式 = "数据异常";
                                item.原因 += "工单号空或不存在!";
                            }
                            if (item.产品编码 == null || item.产品编码 == " ")
                            {
                                item.处理方式 = "数据异常";
                                item.原因 += $"产品编码为空!";
                            }
                            if (item.物料编码 == null || item.物料编码 == " ")
                            {
                                item.处理方式 = "数据异常";
                                item.原因 += $"物料编码为空!";
                            }
                            if (item.站位号 == null || item.站位号 == " ")
                            {
                                item.处理方式 = "数据异常";
                                item.原因 += $"站位号为空!";
                            }
                            currentList.Add(item);
                            if (await Db.Queryable<SMT_WO_TABLE>().AnyAsync(x => x.WORK_ORDER == item.关联工单号 && x.ITEM_CODE == item.物料编码 && x.SLOT_NO == item.站位号 && x.SMT_CODE == item.贴片机编码))
                            {
                                item.处理方式 = "修改";
                                item.原因 += $"工单{item.关联工单号},物料编码{item.物料编码},站位号{item.站位号},贴片机编码{item.贴片机编码}与数据库重复!";
                            }
                        }
                        //for (int i = 0; i < item..Count; i++)
                        {
                        }
                    }
                }
                else
                {
                    result.IsSuccessed = false;
                    result.Message = $"导入的物料不能为空";
                }
                result.Data = currentList;
            }
            catch (Exception ex)
            {
                result.CatchException(ex, $"验证导入工单料站表异常");
            }
            return result;
        }
        /// <summary>
        /// å¯¼å…¥å·¥å•料站表
        /// </summary>
        /// <param name="paras"></param>
        /// <returns></returns>
        public async Task<ApiAction> SaveValidateTableImport(List<smtWoTableIn> paras)
        {
            var result = new ApiAction();
            try
            {
                //DataTable dt = JsonConvert.DeserializeObject<DataTable>(paras.ToString());
                List<SMT_WO_TABLE> currentList = new List<SMT_WO_TABLE>();
                if (paras.Count > 0)
                {
                    var db = Biz.Db;
                    foreach (var item in paras)
                    {
                        var workorder = item.关联工单号.ToString();
                        var id = db.Queryable<SMT_WO_TABLE>().Where(s => s.WORK_ORDER == workorder).Select(q => q.ID).First();
                        SMT_WO_TABLE table = new SMT_WO_TABLE();
                        table.ID = item.处理方式?.ToString() == "新增" ? Guid.NewGuid().ToString() : id;
                        table.CREATE_TIME = DateTime.Now;
                        table.UPDATE_TIME = DateTime.Now;
                        table.WORK_ORDER = item.关联工单号.ToString();
                        table.PROD_CODE = item.产品编码?.ToString();
                        table.ITEM_CODE = item.物料编码?.ToString();
                        table.SUBITEM_CODE = item.替代料?.ToString();
                        table.UNIT = item.单位?.ToString();
                        table.UNIT_QTY = item.单位用量.ToInt32();
                        table.LINE_CODE = item.产线编码?.ToString();
                        table.SMT_CODE = item.贴片机编码?.ToString();
                        table.SMT_STENCIL = item.钢网编码?.ToString();
                        table.SLOT_NO = item.站位号?.ToString();
                        table.LOCATION = item.贴片位置?.ToString();
                        table.FEEDER_CODE = item.飞达编码?.ToString();
                        table.FEEDER_TYPE = item.飞达类型?.ToString();
                        table.PCB_SURFACE = item.加工面?.ToString();
                        table.LOAD_SEQ = item.上料顺序.ToInt32();
                        table.REMARK = item.备注?.ToString();
                        table.VALIDATION_TYPE = item.处理方式?.ToString();
                        table.VALIDATION_RESULT = item.原因?.ToString();
                        currentList.Add(table);
                    }
                    var add = currentList.Where(q => q.VALIDATION_TYPE == "新增").ToList();
                    var upd = currentList.Where(q => q.VALIDATION_TYPE == "修改").ToList();
                    if (add.Count > 0)
                    {
                        Biz.Db.Insertable(add).ExecuteCommand();
                    }
                    if (upd.Count > 0)
                    {
                        Biz.Db.Updateable(upd).ExecuteCommand();
                    }
                }
            }
            catch (Exception ex)
            {
                result.CatchException(ex, $"验证导入工单料站表异常");
            }
            return result;
        }
    }
}
Tiger.Business.MES/Tiger.Business.MES.csproj
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Tiger.Business\Tiger.Business.csproj" />
    <ProjectReference Include="..\Tiger.IBusiness\Tiger.IBusiness.csproj" />
  </ItemGroup>
  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="xcopy /r/y/i $(ProjectDir)$(OutDir)$(AssemblyName).dll $(SolutionDir)Tiger.Api\$(OutDir)&#xD;&#xA;xcopy /r/y/i $(ProjectDir)$(OutDir)$(AssemblyName).pdb $(SolutionDir)Tiger.Api\$(OutDir)&#xD;&#xA;xcopy /r/y/i $(ProjectDir)$(OutDir)BizSettings.json $(SolutionDir)Tiger.Api\$(OutDir)" />
  </Target>
</Project>
Tiger.Business.MES/Transaction/LoadingMaterial.cs
ÎļþÃû´Ó Tiger.Business/MES/Transaction/LoadingMaterial.cs ÐÞ¸Ä
@@ -142,7 +142,7 @@
                //02保存上料数据
                //03查看未上料列表
                Biz.SmtTool smtTool = new();
                SmtTool smtTool = new();
                var actionSmtTool = await smtTool.GetNotLoadingMaterial(input);
                if (actionSmtTool.IsSuccessed)
                {
Tiger.Business.MES/Transaction/MESTransactionBase.cs
ÎļþÃû´Ó Tiger.Business/MES/Transaction/MESTransactionBase.cs ÐÞ¸Ä
@@ -3,7 +3,6 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tiger.Business.WMS.Transaction;
using Tiger.IBusiness;
namespace Tiger.Business.MES.Transaction
Tiger.Business/MES/Biz.BIZ_MES_WO.cs
ÎļþÒÑɾ³ý
Tiger.Business/MES/Biz.MES_WORKSHOP.cs
ÎļþÒÑɾ³ý
Tiger.Business/MES/SMT/Biz.SmtTool.cs
ÎļþÒÑɾ³ý
Tiger.Business/Model/TransactionBase.cs
@@ -10,7 +10,7 @@
using Newtonsoft.Json;
using Tiger.IBusiness;
namespace Tiger.Business.WMS.Transaction
namespace Tiger.Business
{
    /// <summary>
    /// äº‹åŠ¡åŸºç±»
Tiger.Business/Tiger.Business.csproj
@@ -101,17 +101,7 @@
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="MailKit" Version="4.5.0" />
    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
    <PackageReference Include="NLog" Version="5.2.8" />
    <PackageReference Include="NLog.Database" Version="5.2.8" />
    <PackageReference Include="Rhea.Common" Version="6.1.7.1356" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\Tiger.IBusiness\Tiger.IBusiness.csproj" />
    <ProjectReference Include="..\Tiger.Model.Net\Tiger.Model.Net.csproj" />
  </ItemGroup>
  <ItemGroup>