Pārlūkot izejas kodu

Merge branch 'develop' of http://132.232.92.186:3000/XinXiBu/OA2023 into develop

yuanrf 1 dienu atpakaļ
vecāks
revīzija
4aca8f1172

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 829 - 129
OASystem/OASystem.Api/Controllers/GroupsController.cs


+ 36 - 6
OASystem/OASystem.Api/OAMethodLib/GeneralMethod.cs

@@ -1086,6 +1086,17 @@ namespace OASystem.API.OAMethodLib
                 .First();
             if (groupInfo == null) return view;
 
+            var authUsers = _sqlSugar.Queryable<Grp_GroupsTaskAssignment>()
+                .LeftJoin<Sys_Users>((x, u) => x.UId == u.Id)
+                .Where((x, u) => x.DIId == groupId && x.IsDel == 0)
+                .Select((x,u) => new 
+                {
+                    GroupId = x.DIId,
+                    CType = x.CTId,
+                    UserId = u.Id,
+                    UserName = u.CnName
+                }).Distinct().ToList();
+
             //获取团组下的操作人
             //车/导游地接
             var opUers = _sqlSugar.Queryable<Grp_CarTouristGuideGroundReservations>()
@@ -1097,7 +1108,7 @@ namespace OASystem.API.OAMethodLib
                     UserId = u.Id,
                     UserName = u.CnName
                 }).Distinct().ToList();
-            view.Where(it => it.CTableId == 79).FirstOrDefault().OperationUsers = opUers;
+            view.Where(it => it.CTableId == 79).FirstOrDefault().OperationUsers = opUers.Distinct().ToList();
 
             //签证 
             var visaUsers = _sqlSugar.Queryable<Grp_VisaInfo>()
@@ -1109,7 +1120,7 @@ namespace OASystem.API.OAMethodLib
                     UserId = u.Id,
                     UserName = u.CnName
                 }).Distinct().ToList();
-            view.Where(it => it.CTableId == 80).FirstOrDefault().OperationUsers = visaUsers;
+            view.Where(it => it.CTableId == 80).FirstOrDefault().OperationUsers = visaUsers.Distinct().ToList();
 
             //保险 
             var insUsers = _sqlSugar.Queryable<Grp_Customers>()
@@ -1121,7 +1132,7 @@ namespace OASystem.API.OAMethodLib
                     UserId = u.Id,
                     UserName = u.CnName
                 }).Distinct().ToList();
-            view.Where(it => it.CTableId == 82).FirstOrDefault().OperationUsers = insUsers;
+            view.Where(it => it.CTableId == 82).FirstOrDefault().OperationUsers = insUsers.Distinct().ToList();
 
             //邀请/公务活动 
             var ioaUsers = _sqlSugar.Queryable<Grp_InvitationOfficialActivities>()
@@ -1144,7 +1155,21 @@ namespace OASystem.API.OAMethodLib
                         UserName = y.CnName
                     }).Distinct().ToList();
             }
-            view.Where(it => it.CTableId == 81).FirstOrDefault().OperationUsers = ioaUsers;
+
+            ////公务资料信息
+            //var ioaUsers1 = _sqlSugar.Queryable< Res_OfficialActivities >()
+            //    .LeftJoin<Sys_Users>((x,y) => x.CreateUserId == y.Id)
+            //    .Where((x,y) => x.DiId == groupId && x.IsDel == 0)
+            //    .Select((x, y) => new GroupOperationUserInfo
+            //    {
+            //        UserId = y.Id,
+            //        UserName = y.CnName
+            //    }).Distinct().ToList();
+            //if (!ioaUsers.Any())
+            //{
+            //    ioaUsers.AddRange(ioaUsers1);
+            //}
+            view.Where(it => it.CTableId == 81).FirstOrDefault().OperationUsers = ioaUsers.Distinct().ToList();
 
             //其他款项 98
             var otherUsers = _sqlSugar.Queryable<Grp_DecreasePayments>()
@@ -1156,7 +1181,8 @@ namespace OASystem.API.OAMethodLib
                     UserId = u.Id,
                     UserName = u.CnName
                 }).Distinct().ToList();
-            view.Where(it => it.CTableId == 98).FirstOrDefault().OperationUsers = otherUsers;
+
+            view.Where(it => it.CTableId == 98).FirstOrDefault().OperationUsers = otherUsers.Distinct().ToList();
 
             //添加分配人员
             // 79	车/导游地接
@@ -1166,7 +1192,8 @@ namespace OASystem.API.OAMethodLib
 
             // 27	商邀
             // 28	OP
-            var jobSetdataids = new List<int>() { 27, 28 };
+            // 22   王鸽(主管)
+            var jobSetdataids = new List<int>() { 22, 27, 28 };
 
             var q = _sqlSugar.Queryable<Grp_GroupsTaskAssignment, Sys_Users>(
             (ccta, su) => new JoinQueryInfos(
@@ -1194,6 +1221,9 @@ namespace OASystem.API.OAMethodLib
                     var qtbUsers = qtb.AsEnumerable().
                         Where(x => x.Field<int>("CTId") == groupModule.CTableId).
                         Select(x => x.Field<int>("userid")).ToList();
+
+                    //王鸽、王思雨、朱琳
+
                     groupModule.OperationUsers = qtbUsers.Select(x => new GeneralMethod.GroupOperationUserInfo()
                     {
                         UserId = x,

+ 218 - 0
OASystem/OASystem.Api/OAMethodLib/VisaFormExtractor.cs

@@ -0,0 +1,218 @@
+using Aspose.Words;
+using Aspose.Words.Tables;
+
+/// <summary>
+/// 签证文件识别服务类
+/// </summary>
+public class VisaFormExtractor
+{
+    public VisaApplicationData ExtractData(string filePath)
+    {
+        var data = new VisaApplicationData();
+        data.FileName = System.IO.Path.GetFileName(filePath);
+
+        // 加载文档
+        Document doc = new Document(filePath);
+
+        // 获取文档中的所有表格
+        Table[] tables = doc.GetChildNodes(NodeType.Table, true).ToArray().Cast<Table>().ToArray();
+
+        // 根据文件名或内容判断签证类型,并分发给不同的提取器
+        // 这里是一个简单的判断逻辑
+        if (data.FileName.Contains("澳新") || data.FileName.Contains("澳大利亚") || data.FileName.Contains("新西兰"))
+        {
+            data.VisaType = "ANZ";
+            ExtractANZData(tables, data.ExtractedFields);
+        }
+        else if (data.FileName.Contains("美国"))
+        {
+            data.VisaType = "USA";
+            ExtractUSAData(tables, data.ExtractedFields);
+        }
+        else if (data.FileName.Contains("申根"))
+        {
+            data.VisaType = "Schengen";
+            ExtractSchengenData(doc, data.ExtractedFields); // 申根表可能不全是表格
+        }
+        // ... 添加其他签证类型的判断和提取方法
+
+        return data;
+    }
+
+    /// <summary>
+    /// 提取澳新签证表数据
+    /// </summary>
+    private void ExtractANZData(Table[] tables, Dictionary<string, string> fields)
+    {
+        // 遍历所有表格,寻找关键字段
+        foreach (Table table in tables)
+        {
+            foreach (Row row in table.Rows)
+            {
+                for (int cellIndex = 0; cellIndex < row.Cells.Count; cellIndex++)
+                {
+                    Cell cell = row.Cells[cellIndex];
+                    string cellText = cell.GetText().Trim().Replace("\a", ""); // 获取单元格文本并清理
+
+                    // 1. 识别“姓名”字段
+                    if (cellText.Contains("姓名") && !cellText.Contains("曾用名") && !cellText.Contains("拼音"))
+                    {
+                        // 假设“姓名”的值在同行下一个单元格
+                        string value = GetAdjacentCellValue(row, cellIndex, isNext: true);
+                        if (!string.IsNullOrEmpty(value))
+                        {
+                            fields["姓名"] = value;
+                        }
+                    }
+
+                    // 2. 识别“出生日期”字段
+                    if (cellText.Contains("出生日期"))
+                    {
+                        // 假设“出生日期”的值在同行下一个单元格
+                        string value = GetAdjacentCellValue(row, cellIndex, isNext: true);
+                        if (!string.IsNullOrEmpty(value))
+                        {
+                            // 使用正则表达式验证并清理日期格式
+                            var dateMatch = Regex.Match(value, @"\d{4}年\d{1,2}月\d{1,2}日");
+                            if (dateMatch.Success)
+                            {
+                                fields["出生日期"] = dateMatch.Value;
+                            }
+                            else
+                            {
+                                fields["出生日期"] = value; // 如果不是标准格式,也先存下来
+                            }
+                        }
+                    }
+
+                    // 3. 识别“性别”字段 (处理带有选项的情况)
+                    if (cellText.Contains("性 别") || cellText.Contains("性别"))
+                    {
+                        // 这个单元格里可能直接是“男”或“女”,也可能在后面的Run里有带格式的文本
+                        string value = GetTextFromCellOrFollowingRuns(cell);
+                        // 清理文本,只取“男”或“女”
+                        if (value.Contains("男")) fields["性别"] = "男";
+                        else if (value.Contains("女")) fields["性别"] = "女";
+                    }
+
+                    // 4. 识别“婚姻状况”字段 (处理打勾的情况)
+                    if (cellText.Contains("婚姻状况"))
+                    {
+                        // 方法1: 在单元格内查找被勾选的项 (例如 R已婚 中的 R 可能代表被选中)
+                        string innerText = cell.GetText();
+                        if (innerText.Contains("R已婚")) fields["婚姻状况"] = "已婚";
+                        else if (innerText.Contains("R未婚")) fields["婚姻状况"] = "未婚";
+                        // ... 其他选项
+
+                        // 方法2: 如果勾选框是符号,可能需要检查特定的Run的字体
+                    }
+                    // ... 根据您的“澳新表”模板,继续添加更多字段的提取逻辑
+                    // 例如:现单位名称、月收入、家庭成员、教育经历等。
+                    // 这些字段可能需要更复杂的逻辑,比如跨行、跨表格读取。
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// 提取美国签证表数据 (逻辑类似,但映射规则不同)
+    /// </summary>
+    private void ExtractUSAData(Table[] tables, Dictionary<string, string> fields)
+    {
+        foreach (Table table in tables)
+        {
+            foreach (Row row in table.Rows)
+            {
+                for (int cellIndex = 0; cellIndex < row.Cells.Count; cellIndex++)
+                {
+                    Cell cell = row.Cells[cellIndex];
+                    string cellText = cell.GetText().Trim().Replace("\a", "");
+
+                    // 美国表的“姓名”字段可能直接就是“姓名:”
+                    if (cellText.StartsWith("姓名:"))
+                    {
+                        // 值可能在同一个单元格的“:”后面,也可能在下一个单元格
+                        string value = cellText.Split(':').Last().Trim();
+                        if (string.IsNullOrEmpty(value))
+                        {
+                            value = GetAdjacentCellValue(row, cellIndex, isNext: true);
+                        }
+                        fields["姓名"] = value;
+                    }
+
+                    if (cellText.StartsWith("出生日期:"))
+                    {
+                        string value = cellText.Split(':').Last().Trim();
+                        if (string.IsNullOrEmpty(value))
+                        {
+                            value = GetAdjacentCellValue(row, cellIndex, isNext: true);
+                        }
+                        fields["出生日期"] = value;
+                    }
+                    // ... 添加美国表特有的字段,如“护照号码”、“DS160确认号”、“美国联系人”等。
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// 提取申根表数据 (可能包含段落)
+    /// </summary>
+    private void ExtractSchengenData(Document doc, Dictionary<string, string> fields)
+    {
+        // 申根表可能不完全是表格,需要结合段落(Paragraph)来搜索
+        NodeCollection paragraphs = doc.GetChildNodes(NodeType.Paragraph, true);
+
+        foreach (Paragraph para in paragraphs)
+        {
+            string paraText = para.GetText().Trim();
+
+            if (paraText.StartsWith("1.姓名:"))
+            {
+                // 提取姓名,可能在冒号后面,也可能在下一行或下一个Run
+                fields["姓名"] = paraText.Replace("1.姓名:", "").Trim();
+            }
+            // ... 处理其他申根表字段
+        }
+
+        // 同时也检查表格
+        Table[] tables = doc.GetChildNodes(NodeType.Table, true).ToArray().Cast<Table>().ToArray();
+        // ... 类似ANZ和USA的方法,但使用申根表的映射规则
+    }
+
+    // --- 辅助方法 ---
+
+    /// <summary>
+    /// 获取同一行中相邻单元格的值
+    /// </summary>
+    /// <param name="row">当前行</param>
+    /// <param name="currentCellIndex">当前单元格索引</param>
+    /// <param name="isNext">true=下一个单元格,false=上一个单元格</param>
+    /// <returns>相邻单元格的文本</returns>
+    private string GetAdjacentCellValue(Row row, int currentCellIndex, bool isNext = true)
+    {
+        int targetIndex = isNext ? currentCellIndex + 1 : currentCellIndex - 1;
+        if (targetIndex >= 0 && targetIndex < row.Cells.Count)
+        {
+            return row.Cells[targetIndex].GetText().Trim().Replace("\a", "");
+        }
+        return null;
+    }
+
+    /// <summary>
+    /// 从一个单元格及其子Run中提取文本,有助于获取格式化的文本(如被勾选的项)
+    /// </summary>
+    private string GetTextFromCellOrFollowingRuns(Cell cell)
+    {
+        // 这是一个简化的版本,实际中可能需要递归遍历所有子节点
+        return cell.GetText().Trim().Replace("\a", "");
+    }
+
+    // 这是一个通用模型,您需要为每种签证类型定义更详细的模型
+    public class VisaApplicationData
+    {
+        public string FileName { get; set; }
+        public string VisaType { get; set; } // 可根据文件名判断
+        public Dictionary<string, string> ExtractedFields { get; set; } = new Dictionary<string, string>();
+    }
+}

+ 4 - 0
OASystem/OASystem.Api/appsettings.json

@@ -419,6 +419,10 @@
           "Id": 149,
           "Name": "王鸽"
         },
+        {
+          "Id": 327,
+          "Name": "江姗"
+        },
         //{
         //  "Id": 330,
         //  "Name": "刘琪"

+ 3 - 0
OASystem/OASystem.Domain/Entities/Customer/Crm_CustomerCompany.cs

@@ -27,16 +27,19 @@ namespace OASystem.Domain.Entities.Customer
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
         public string? Address { get; set; }
+
         /// <summary>
         /// 邮政编码
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "varchar(80)")]
         public string? PostCodes { get; set; }
+
         /// <summary>
         /// 最后操作人
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "int")]
         public int LastedOpUserId { get; set; }
+
         /// <summary>
         /// 最后操作时间
         /// </summary>

+ 224 - 5
OASystem/OASystem.Domain/Entities/Customer/Crm_DeleClient.cs

@@ -216,12 +216,12 @@ namespace OASystem.Domain.Entities.Customer
         /// 美国抵达日期
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "DateTime")]
-        public DateTime USADate { get; set; }
+        public DateTime? USADate { get; set; }
         /// <summary>
         /// 美国停留天数
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "int")]
-        public int USADays { get; set; } = 0;
+        public int? USADays { get; set; } = 0;
         /// <summary>
         /// 加拿大抵达日期
         /// </summary>
@@ -272,7 +272,7 @@ namespace OASystem.Domain.Entities.Customer
         /// 被拒时间
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "DateTime")]
-        public DateTime RejectedDate { get; set; }
+        public DateTime? RejectedDate { get; set; }
         /// <summary>
         /// 被拒地点
         /// </summary>
@@ -410,12 +410,12 @@ namespace OASystem.Domain.Entities.Customer
         /// 结婚日期
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "DateTime")]
-        public DateTime WeddingDate { get; set; }
+        public DateTime? WeddingDate { get; set; }
         /// <summary>
         /// 离婚日期
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "DateTime")]
-        public DateTime DivorceDate { get; set; }
+        public DateTime? DivorceDate { get; set; }
         /// <summary>
         /// 配偶姓名
         /// </summary>
@@ -476,6 +476,194 @@ namespace OASystem.Domain.Entities.Customer
         /// </summary>
         [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
         public string PhD { get; set; }
+
+
+        //----------增加字段------------
+        /// <summary>
+        /// 离婚原因
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
+        public string DivorceRsn { get; set; }
+
+        /// <summary>
+        /// 美国驾照No
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USA_DLNo { get; set; }
+        /// <summary>
+        /// 美国驾照签发地
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USA_DLPlace { get; set; }
+
+        /// <summary>
+        /// 美国社会安全号或者纳税ID号
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USA_SSNOrITIN { get; set; }
+
+        /// <summary>
+        /// 是否是其他国家的永久居民
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string Resident { get; set; }
+
+        /// <summary>
+        /// 近五年内是否使用过任何社交账号
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
+        public string HasSocialAcc‌ { get; set; }
+
+        /// <summary>
+        /// 请提供美国详细住址
+        ///   赴美目的
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
+        public string USAAddress { get; set; }
+
+        /// <summary>
+        /// 赴美目的
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
+        public string PurposeUSA { get; set; }
+
+        /// <summary>
+        /// 美国联系人或组织
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USAContact‌ { get; set; }
+
+        /// <summary>
+        /// 美国联系人电话
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAContact‌Tel‌ { get; set; }
+        /// <summary>
+        /// 美国联系人邮箱
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAContact‌Email { get; set; }
+        /// <summary>
+        /// 赴美的到达城市
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USAArrivalCity‌ { get; set; }
+        /// <summary>
+        /// 赴美的离开城市
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USADepartCity‌ { get; set; }
+        /// <summary>
+        /// 计划在美国访问的景点
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USAVisitSites‌ { get; set; }
+        /// <summary>
+        /// 赴美同行人姓名
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USACmpName‌ { get; set; }
+        /// <summary>
+        /// 赴美同行人关系
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string USACmpRelation‌ { get; set; }
+        /// <summary>
+        /// 过去5年内到过的国家
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string Cntry5Y‌ { get; set; }
+        /// <summary>
+        /// 签时能流利交流的语言
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
+        public string LangSkill‌ { get; set; }
+        /// <summary>
+        /// 曾被哪些国家拒签/拒签原因
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(300)")]
+        public string DenyNationRsn { get; set; }
+        /// <summary>
+        /// 曾抵达过美国时间
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAHadArrival‌ { get; set; }
+        /// <summary>
+        /// 曾停留美国天数
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")]
+        public string USAHadDays‌ { get; set; }
+        /// <summary>
+        /// 美国移民签证时间
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "DateTime")]
+        public DateTime? USAImmVisa‌Date { get; set; }
+        /// <summary>
+        /// 美国移民签证地点
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAImmVisa‌Place { get; set; }
+        /// <summary>
+        /// 美国移民签证种类
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAImmVisa‌ { get; set; }
+
+        /// <summary>
+        /// 直系亲属在美国 姓名
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAImmedFamName { get; set; }
+        /// <summary>
+        /// 直系亲属在美国 关系
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAImmedFamRel { get; set; }
+        /// <summary>
+        /// 直系亲属在美国 身份
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAImmedFamStat { get; set; }
+
+        /// <summary>
+        /// 旁系亲属在美国 姓名
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAUncleFamName { get; set; }
+        /// <summary>
+        /// 旁系亲属在美国 关系
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAUncleFamRel { get; set; }
+        /// <summary>
+        /// 旁系亲属在美国 身份
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string USAUncleFamStat { get; set; }
+
+        /// <summary>
+        /// 父亲姓名
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string FthName { get; set; }
+
+        /// <summary>
+        /// 父亲出生日期
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public DateTime? FthBirth { get; set; }
+
+        /// <summary>
+        /// 母亲姓名
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public string MthName { get; set; }
+
+        /// <summary>
+        /// 母亲出生日期
+        /// </summary>
+        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+        public DateTime? MthBirth { get; set; }
     }
 
 
@@ -491,4 +679,35 @@ namespace OASystem.Domain.Entities.Customer
         [Encrypted]
         public string CompanyFullName { get; set; }
     }
+
+    /// <summary>
+    /// 军事信息
+    /// </summary>
+    public class MilitaryInfo
+    {
+        /// <summary>
+        /// 军种(陆军/海军/空军等)
+        /// </summary>
+        public string MilBranch { get; set; }
+
+        /// <summary>
+        /// 军衔(列兵/上校等)
+        /// </summary>
+        public string MilRank { get; set; }
+
+        /// <summary>
+        /// 军事特长(狙击/医疗等)
+        /// </summary>
+        public string MilSkill { get; set; }
+
+        /// <summary>
+        /// 服役时间(单位:xxxx年xx月xx日) 起
+        /// </summary>
+        public DateTime? ServeTimeBegin { get; set; }
+        /// <summary>
+        /// 服役时间(单位:xxxx年xx月xx日)止
+        /// </summary>
+        public DateTime? ServeTimeEnd { get; set; }
+
+    }
 }

+ 1 - 1
OASystem/OASystem.Domain/Entities/Customer/Crm_VisaCustomerSchool.cs

@@ -18,7 +18,7 @@ namespace OASystem.Domain.Entities.Customer
         [SugarColumn(IsNullable =true,ColumnDataType ="int")]
         public int DcId { get; set; }
         /// <summary>
-        /// 学校名称和学历
+        /// 学校名称
         /// </summary>
         [SugarColumn(IsNullable =true,ColumnDataType = "varchar(100)")]
         public string School { get; set; }

+ 306 - 0
OASystem/OASystem.Domain/Entities/Groups/Grp_ProcessOverview.cs

@@ -0,0 +1,306 @@
+using OASystem.Domain.Enums;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OASystem.Domain.Entities.Groups
+{
+    /// <summary>
+    /// 团组流程总览表
+    /// </summary>
+    [SugarTable("Grp_ProcessOverview", "团组流程总览表")]
+    public class Grp_ProcessOverview : EntityBase
+    {
+        /// <summary>
+        /// 团组Id
+        /// </summary>
+        [SugarColumn(ColumnName = "GroupId", ColumnDescription = "团组Id", IsNullable = true, ColumnDataType = "int")]
+        public int GroupId { get; set; }
+
+        /// <summary>
+        /// 流程顺序
+        /// </summary>
+        [SugarColumn(ColumnName = "ProcessOrder", ColumnDescription = "流程顺序", IsNullable = true, ColumnDataType = "int")]
+        public int ProcessOrder { get; set; }
+
+        /// <summary>
+        /// 流程类型 
+        /// </summary>
+        [SugarColumn(ColumnName = "ProcessType", ColumnDescription = "流程类型", ColumnDataType = "int")]
+        public GroupProcessType ProcessType { get; set; }
+
+        /// <summary>
+        /// 整体状态 
+        /// </summary>
+        [SugarColumn(ColumnName = "OverallStatus", ColumnDescription = "流程整体状态",ColumnDataType = "int")]
+        public ProcessStatus OverallStatus { get; set; } = ProcessStatus.UnStarted;
+
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        [SugarColumn(ColumnName = "EndTime", ColumnDescription = "开始时间", IsNullable = true, ColumnDataType = "datetime")]
+        public DateTime? StartTime { get; set; }
+
+        /// <summary>
+        /// 结束时间
+        /// </summary>
+        [SugarColumn(ColumnName = "EndTime", ColumnDescription = "结束时间", IsNullable = true, ColumnDataType = "datetime")]
+        public DateTime? EndTime { get; set; }
+
+        /// <summary>
+        /// 更新人 
+        /// </summary>
+        [SugarColumn(ColumnName = "UpdatedUserId", ColumnDescription = "更新人", IsNullable = true, ColumnDataType = "int")]
+        public int UpdatedUserId { get; set; }
+
+        /// <summary>
+        /// 更新时间
+        /// </summary>
+        [SugarColumn(ColumnName = "UpdatedTime", ColumnDescription = "更新时间", ColumnDataType = "datetime")]
+        public DateTime UpdatedTime { get; set; } = DateTime.Now;
+
+        /// <summary>
+        /// 节点集合 - 流程包含的所有节点(导航属性)
+        /// </summary>
+        [Navigate(NavigateType.OneToMany, nameof(Grp_ProcessNode.ProcessId))]
+        public List<Grp_ProcessNode> Nodes { get; set; } = new List<Grp_ProcessNode>();
+
+        public Grp_ProcessOverview() { }
+
+        /// <summary>
+        /// 流程进度默认模板
+        /// </summary>
+        /// <param name="groupId"></param>
+        /// <param name="currUserId"></param>
+        /// <param name="visaCountries"></param>
+        /// <returns></returns>
+        public static List<Grp_ProcessOverview> ProcessInit(int groupId, int currUserId, List<string> visaCountries)
+        {
+            //单独处理签证流程节点
+            var visaDefualtNodes = new List<VisaNode>() {
+                new(){NodeOrder = 1, NodeName = "收集资料",IsHandle = false,HandleTime = "" },
+                new(){NodeOrder = 2, NodeName = "取护照资料",IsHandle = false,HandleTime = "" },
+                new(){NodeOrder = 3, NodeName = "填资料",IsHandle = false,HandleTime = "" },
+                new(){NodeOrder = 4, NodeName = "送签",IsHandle = false,HandleTime = "" },
+                new(){NodeOrder = 5, NodeName = "出签+取护照",IsHandle = false,HandleTime = "" },
+                new(){NodeOrder = 6, NodeName = "归还护照",IsHandle = false,HandleTime = "",Returner="" }
+            };
+
+            var visaNodes = new List<Grp_ProcessNode>();
+
+            if (visaCountries != null && visaCountries.Count > 0)
+            {
+                for (int i = 1; i < visaCountries.Count + 1; i++)
+                {
+                    visaNodes.Add(new Grp_ProcessNode()
+                    {
+                        NodeOrder = i,
+                        NodeName = visaCountries[i].ToString(),
+                        OverallStatus = ProcessStatus.UnStarted,
+                        IsCurrent = i == 1,
+                        CreateUserId = currUserId
+                    });
+                }
+            }
+
+            return new List<Grp_ProcessOverview>
+            {
+                //商邀报批流程
+                new()
+                {
+                    GroupId = groupId,
+                    ProcessOrder = 1,
+                    ProcessType = GroupProcessType.Invitation,
+                    OverallStatus = ProcessStatus.UnStarted,
+                    StartTime = DateTime.Now,
+                    UpdatedUserId = currUserId,
+                    CreateUserId = currUserId,
+                    Nodes = new List<Grp_ProcessNode>()
+                {
+                    new(){NodeOrder = 1, NodeName="报批资料准备", OverallStatus=ProcessStatus.UnStarted, IsCurrent=true,CreateUserId = currUserId },
+                    new(){NodeOrder = 2, NodeName="开始报批", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId },
+                    new(){NodeOrder = 3, NodeName="报批中", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId },
+                    new(){NodeOrder = 4, NodeName="报批已出", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId }
+                }
+                },
+
+                //签证流程
+                new()
+                {
+                    GroupId = groupId,
+                    ProcessOrder = 2,
+                    ProcessType = GroupProcessType.Visa,
+                    OverallStatus = ProcessStatus.UnStarted,
+                    StartTime = DateTime.Now,
+                    UpdatedUserId = currUserId,
+                    CreateUserId = currUserId,
+                    Nodes=visaNodes
+                },
+
+                //机票流程
+                new()
+                {
+                    GroupId = groupId,
+                    ProcessOrder = 3,
+                    ProcessType = GroupProcessType.AirTicket,
+                    OverallStatus = ProcessStatus.UnStarted,
+                    StartTime = DateTime.Now,
+                    UpdatedUserId = currUserId,
+                    CreateUserId = currUserId,
+                    Nodes = new List<Grp_ProcessNode>()
+                {
+                    new(){NodeOrder = 1, NodeName="机票占位中", OverallStatus=ProcessStatus.UnStarted, IsCurrent=true,CreateUserId = currUserId },
+                    new(){NodeOrder = 2, NodeName="机票已出", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId },
+                }
+                },
+
+                //酒店流程
+                new()
+                {
+                    GroupId = groupId,
+                    ProcessOrder = 4,
+                    ProcessType = GroupProcessType.Hotel,
+                    OverallStatus = ProcessStatus.UnStarted,
+                    StartTime = DateTime.Now,
+                    UpdatedUserId = currUserId,
+                    CreateUserId = currUserId,
+                    Nodes = new List<Grp_ProcessNode>()
+                {
+                    new(){NodeOrder = 1, NodeName="酒店占房中", OverallStatus=ProcessStatus.UnStarted, IsCurrent=true,CreateUserId = currUserId },
+                    new(){NodeOrder = 2, NodeName="酒店已订", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId },
+                }
+                },
+
+                //地接流程
+                new()
+                {
+                    GroupId = groupId,
+                    ProcessOrder = 5,
+                    ProcessType = GroupProcessType.LocalGuide,
+                    OverallStatus = ProcessStatus.UnStarted,
+                    StartTime = DateTime.Now,
+                    UpdatedUserId = currUserId,
+                    CreateUserId = currUserId,
+                    Nodes = new List<Grp_ProcessNode>()
+                {
+                    new(){NodeOrder = 1, NodeName="地接对接中", OverallStatus=ProcessStatus.UnStarted, IsCurrent=true,CreateUserId = currUserId },
+                    new(){NodeOrder = 2, NodeName="地接已安排好", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId },
+                    new(){NodeOrder = 2, NodeName="出行物资准备完毕", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId },
+                }
+                },
+
+                //费用结算流程
+                new()
+                {
+                    GroupId = groupId,
+                    ProcessOrder = 6,
+                    ProcessType = GroupProcessType.FeeSettle,
+                    OverallStatus = ProcessStatus.UnStarted,
+                    StartTime = DateTime.Now,
+                    UpdatedUserId = currUserId,
+                    CreateUserId = currUserId,
+                    Nodes = new List<Grp_ProcessNode>()
+                {
+                    new(){NodeOrder = 1, NodeName="费用结算中", OverallStatus=ProcessStatus.UnStarted, IsCurrent=true,CreateUserId = currUserId },
+                    new(){NodeOrder = 2, NodeName="费用结算完毕", OverallStatus=ProcessStatus.UnStarted, IsCurrent=false,CreateUserId = currUserId },
+                }
+                }
+            };
+        }
+    }
+
+    /// <summary>
+    /// 流程节点
+    /// </summary>
+    [SugarTable("Grp_ProcessNode", "流程节点")]
+    public class Grp_ProcessNode : EntityBase
+    {
+        /// <summary>
+        /// 流程ID
+        /// </summary>
+        [SugarColumn(ColumnName = "ProcessId ", ColumnDescription = "流程ID", IsNullable = true, ColumnDataType = "int")]
+        public int ProcessId { get; set; }
+
+        /// <summary>
+        /// 节点名称
+        /// </summary>
+        [SugarColumn(ColumnName = "NodeName", ColumnDescription = "节点名称", ColumnDataType = "varchar(100)")]
+        public string NodeName { get; set; }
+
+        /// <summary>
+        /// 节点顺序 
+        /// </summary>
+        [SugarColumn(ColumnName = "NodeOrder", ColumnDescription = "节点顺序", ColumnDataType = "int")]
+        public int NodeOrder { get; set; } 
+
+        /// <summary>
+        /// 节点状态 
+        /// </summary>
+        [SugarColumn(ColumnName = "OverallStatus", ColumnDescription = "节点状态", ColumnDataType = "int")]
+        public ProcessStatus OverallStatus { get; set; } = ProcessStatus.UnStarted;
+
+        /// <summary>
+        /// 操作人 
+        /// </summary>
+        [SugarColumn(ColumnName = "Operator ", ColumnDescription = "操作人", IsNullable = true, ColumnDataType = "int")]
+        public int Operator { get; set; }
+
+        /// <summary>
+        /// 操作时间 
+        /// </summary>
+        [SugarColumn(ColumnName = "OperationTime ", ColumnDescription = "操作时间", ColumnDataType = "datetime")]
+        public DateTime OperationTime { get; set; } = DateTime.Now;
+
+        ///// <summary>
+        ///// 国家
+        ///// </summary>
+        //[SugarColumn(ColumnName = "Country", ColumnDescription = "国家", ColumnDataType = "varchar(100)",IsNullable = true)]
+        //public string? Country { get; set; }
+
+        /// <summary>
+        /// 是否为当前节点
+        /// </summary>
+        [SugarColumn(ColumnName = "IsCurrent", ColumnDescription = "是否为当前节点", ColumnDataType = "bit")]
+        [DefaultValue(false)]
+        public bool IsCurrent { get; set; }
+
+        /// <summary>
+        /// 流程信息 - 关联的流程信息(导航属性)
+        /// </summary>
+        [Navigate(NavigateType.OneToOne, nameof(ProcessId))]
+        public Grp_ProcessOverview Process { get; set; }
+    }
+
+
+    public class VisaNode
+    {
+        /// <summary>
+        /// 节点顺序
+        /// </summary>
+        public int NodeOrder { get; set; }
+
+        /// <summary>
+        /// 节点名称
+        /// </summary>
+        public string NodeName { get; set; }
+
+        /// <summary>
+        /// 是否办理
+        /// </summary>
+        public bool IsHandle { get; set; }
+
+        /// <summary>
+        /// 办理时间
+        /// </summary>
+        public string HandleTime { get; set; }
+
+        /// <summary>
+        /// 归还人
+        /// NodeName = "归还护照" 时使用
+        /// </summary>
+        public string Returner { get; set; }
+    }
+}

+ 60 - 0
OASystem/OASystem.Domain/Enums/GroupProcessType.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OASystem.Domain.Enums
+{
+    /// <summary>
+    /// 团组流程类型
+    /// 商邀报批/签证/机票/酒店/地接/费用结算
+    /// </summary>
+    public enum GroupProcessType
+    {
+        /// <summary>
+        /// 商邀报批
+        /// </summary>
+        Invitation = 1,
+        /// <summary>
+        /// 签证
+        /// </summary>
+        Visa = 2,
+        /// <summary>
+        /// 机票
+        /// </summary>
+        AirTicket = 4,
+        /// <summary>
+        /// 酒店
+        /// </summary>
+        Hotel = 5,
+        /// <summary>
+        /// 地接
+        /// </summary>
+        LocalGuide = 6,
+        /// <summary>
+        /// 费用结算
+        /// </summary>
+        FeeSettle = 7
+    }
+
+    /// <summary>
+    /// 流程整体状态
+    /// 未开始/进行中/已完成
+    /// </summary>
+    public enum ProcessStatus
+    {
+        /// <summary>
+        /// 未开始
+        /// </summary>
+        UnStarted = 1,
+        /// <summary>
+        /// 进行中
+        /// </summary>
+        InProgress = 2,
+        /// <summary>
+        /// 已完成
+        /// </summary>
+        Completed = 3
+    }
+}

+ 3 - 0
OASystem/OASystem.Infrastructure/Repositories/BaseRepository.cs

@@ -1,5 +1,6 @@
 using OASystem.Domain.Entities;
 using OASystem.Domain.Enums;
+using System.Reflection;
 
 namespace OASystem.Infrastructure.Repositories
 {
@@ -322,5 +323,7 @@ namespace OASystem.Infrastructure.Repositories
             return result > 0;
         }
         #endregion
+
+        
     }
 }

+ 88 - 0
OASystem/OASystem.Infrastructure/Repositories/Groups/ProcessOverviewRepository.cs

@@ -0,0 +1,88 @@
+using AutoMapper;
+using OASystem.Domain;
+using OASystem.Domain.Entities.Groups;
+using OASystem.Domain.ViewModels.JuHeExchangeRate;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OASystem.Infrastructure.Repositories.Groups
+{
+    /// <summary>
+    /// 团组流程总览表仓储
+    /// </summary>
+    public class ProcessOverviewRepository : BaseRepository<Grp_ProcessOverview, Grp_ProcessOverview>
+    {
+        private readonly IMapper _mapper;
+        private readonly DelegationInfoRepository _groupRep;
+        public ProcessOverviewRepository(SqlSugarClient sqlSugar, IMapper mapper, DelegationInfoRepository groupRep) : base(sqlSugar)
+        {
+            _mapper = mapper;
+            _groupRep = groupRep;
+        }
+
+        /// <summary>
+        /// 团组流程初始化
+        /// </summary>
+        /// <param name="request">创建流程请求参数</param>
+        /// <returns>创建的流程信息</returns>
+        public async Task<Result> ProcessInitAsync(int groupId,int currUserId)
+        {
+            //团组验证
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().FirstAsync(g => g.Id == groupId);
+            if (groupInfo == null)
+            {
+                return new Result { Code = 400, Msg = "团组不存在" };
+            }
+
+            // 检查是否已存在流程
+            var existingProcesses = await _sqlSugar.Queryable<Grp_ProcessOverview>().Where(p => p.GroupId == groupId).ToListAsync();
+            if (existingProcesses.Any())
+            {
+                return new Result { Code = 400, Msg = "该团组的流程已存在" };
+            }
+
+            //处理签证国家
+            var visaCountries = _groupRep.GroupSplitCountry(groupInfo.VisitCountry);
+
+            // 定义默认的流程节点
+            var processs = Grp_ProcessOverview.ProcessInit(groupId,currUserId,visaCountries);
+
+
+            _sqlSugar.BeginTran();
+
+            foreach (var item in processs)
+            {
+                var processId = await _sqlSugar.Insertable(item).ExecuteReturnIdentityAsync();
+
+                if (processId < 1 )
+                {
+                    _sqlSugar.RollbackTran();
+                    return new Result { Code = 400, Msg = "团组流程进度总览表添加失败!" };
+                }
+
+                var nodes = item.Nodes.Select((nodeDto, index) => new Grp_ProcessNode
+                {
+                    ProcessId = processId,
+                    NodeName = nodeDto.NodeName,
+                    NodeOrder = nodeDto.NodeOrder,
+                    OverallStatus = ProcessStatus.UnStarted,
+                    //Country = nodeDto.Country,
+                    IsCurrent = nodeDto.IsCurrent,
+                }).ToList();
+
+                var nodeIds = await _sqlSugar.Insertable(nodes).ExecuteCommandAsync();
+                if (nodeIds < 1)
+                {
+                    _sqlSugar.RollbackTran();
+                    return new Result { Code = 400, Msg = "团组流程进度流程表添加失败!" };
+                }
+            }
+
+           return new Result { Code = 200, Msg = "添加成功!" }; ;
+        }
+
+    }
+}

+ 65 - 0
OASystem/OASystem.Infrastructure/Tools/CommonFun.cs

@@ -873,4 +873,69 @@ public static class CommonFun
         return index >= 0 && index < list.Count ? list[index] : null;
     }
 
+
+    #region 姓名 取姓和名
+    /// <summary>
+    /// 取姓
+    /// </summary>
+    public static string GetLastName(this string fullName)
+    {
+        if (string.IsNullOrWhiteSpace(fullName)) return string.Empty;
+
+        // 1. 英文/拼音:有逗号
+        var comma = fullName.IndexOf(',');
+        if (comma >= 0) return fullName[..comma].Trim();
+
+        // 2. 英文/拼音:无逗号,按最后一个空格切
+        var lastSpace = fullName.LastIndexOf(' ');
+        if (lastSpace >= 0) return fullName[..lastSpace].Trim();
+
+        // 3. 中文:默认首字为姓
+        return fullName.Length switch
+        {
+            0 => string.Empty,
+            1 => fullName,
+            _ => fullName[..1]   // 复姓可改成 ..2
+        };
+    }
+
+    /// <summary>
+    /// 取名(去掉姓)
+    /// </summary>
+    public static string GetFirstName(this string fullName)
+    {
+        if (string.IsNullOrWhiteSpace(fullName)) return string.Empty;
+
+        var comma = fullName.IndexOf(',');
+        if (comma >= 0) return fullName[(comma + 1)..].Trim();
+
+        var lastSpace = fullName.LastIndexOf(' ');
+        if (lastSpace >= 0) return fullName[(lastSpace + 1)..].Trim();
+
+        // 中文:去掉首字
+        return fullName.Length <= 1 ? string.Empty : fullName[1..];
+    }
+    #endregion
+
+    /// <summary>
+    /// 从地址字符串取出省份和城市
+    /// </summary>
+    public static (string province, string city) SplitProvinceCity(string src)
+    {
+        if (string.IsNullOrWhiteSpace(src)) return ("", "");
+
+        ReadOnlySpan<char> s = src.AsSpan();
+
+        // 1. 省
+        int pEnd = s.IndexOf("省");
+        if (pEnd < 0) pEnd = s.IndexOf("自治区");
+        string province = pEnd < 0 ? "" : s[..pEnd].ToString();
+
+        // 2. 市
+        int cStart = pEnd + (pEnd >= 0 ? (province.EndsWith("自治区") ? 3 : 1) : 0);
+        int cEnd = s[cStart..].IndexOf("市");
+        string city = cEnd < 0 ? "" : s.Slice(cStart, cEnd).ToString();
+
+        return (province, city);
+    }
 }