diff --git a/FactorySystem.sln b/FactorySystem.sln
new file mode 100644
index 0000000..08c5882
--- /dev/null
+++ b/FactorySystem.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32929.385
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FactorySystemApi", "FactorySystemApi\FactorySystemApi.csproj", "{7107DC39-2E93-425F-A4D8-C50768DE573C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FactorySystemBll", "FactorySystemBll\FactorySystemBll.csproj", "{DE5CA953-C5F7-4DF4-81B2-9C3D89849DC5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FactorySystemCommon", "FactorySystemCommon\FactorySystemCommon.csproj", "{EA8E8FC2-B255-4931-BBAE-7862F0173CD3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FactorySystemModel", "FactorySystemModel\FactorySystemModel.csproj", "{2C1A44D9-D4BA-464E-832B-6108595892D6}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{7107DC39-2E93-425F-A4D8-C50768DE573C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7107DC39-2E93-425F-A4D8-C50768DE573C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7107DC39-2E93-425F-A4D8-C50768DE573C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7107DC39-2E93-425F-A4D8-C50768DE573C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DE5CA953-C5F7-4DF4-81B2-9C3D89849DC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DE5CA953-C5F7-4DF4-81B2-9C3D89849DC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DE5CA953-C5F7-4DF4-81B2-9C3D89849DC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DE5CA953-C5F7-4DF4-81B2-9C3D89849DC5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EA8E8FC2-B255-4931-BBAE-7862F0173CD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EA8E8FC2-B255-4931-BBAE-7862F0173CD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EA8E8FC2-B255-4931-BBAE-7862F0173CD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EA8E8FC2-B255-4931-BBAE-7862F0173CD3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2C1A44D9-D4BA-464E-832B-6108595892D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2C1A44D9-D4BA-464E-832B-6108595892D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2C1A44D9-D4BA-464E-832B-6108595892D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2C1A44D9-D4BA-464E-832B-6108595892D6}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {B08F9A5A-D49A-4071-B561-D9A1AC7A1B54}
+	EndGlobalSection
+EndGlobal
diff --git a/FactorySystemApi/App_Start/ApiAuthorizeAttribute.cs b/FactorySystemApi/App_Start/ApiAuthorizeAttribute.cs
new file mode 100644
index 0000000..21ddeee
--- /dev/null
+++ b/FactorySystemApi/App_Start/ApiAuthorizeAttribute.cs
@@ -0,0 +1,153 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.EnumModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Web.Http.Controllers;
+using System.Web.Http.Filters;
+using System.Xml;
+
+namespace FactorySystemApi
+{
+    /// 
+    /// 授权
+    /// 
+    public class UserLoginFilter : AuthorizationFilterAttribute
+    {
+        /// 
+        /// 指示指定的控件是否已获得授权
+        /// 
+        public override void OnAuthorization(HttpActionContext actionContext)
+        {
+            try
+            {
+                //验证Referrer
+                string ApiRealNameUrl = AppSettingsHelper.GetAppSettingVal("ApiRealNameUrl");
+                if (!string.IsNullOrEmpty(ApiRealNameUrl))
+                {
+                    string[] ApiRealNameUrls = ApiRealNameUrl.Split(',');
+                    string requestHost = actionContext.Request.Headers.Referrer.Host;
+                    bool isRealUrl = false;
+                    for (int i = 0; i < ApiRealNameUrls.Length && !isRealUrl; i++)
+                    {
+                        Uri apiUrl = new Uri(ApiRealNameUrls[i]);
+                        if (apiUrl.Host == requestHost) isRealUrl = true;
+                    }
+                    if (!isRealUrl) throw new Exception("请求失败,不在授权访问内");
+                }
+                //验证接口配置
+                if (actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes().Count > 0)
+                {
+                    if (actionContext.ActionDescriptor.GetCustomAttributes().Count > 0) { return; }
+                }
+                else { return; }
+
+                string token = "";
+                //前端请求api时会将token存放在名为"auth"的请求头中
+                var authHeader = from t in actionContext.Request.Headers where t.Key == "token" select t.Value.FirstOrDefault();
+                if (authHeader == null || string.IsNullOrEmpty(token = authHeader.FirstOrDefault()))
+                {
+                    authHeader = from t in actionContext.Request.Headers where t.Key == "ticket" select t.Value.FirstOrDefault();
+                    if (authHeader != null && !string.IsNullOrEmpty(token = authHeader.FirstOrDefault()))
+                    {
+                        token = CasLogin(token, AppSettingsHelper.GetAppSettingVal("Cas_ReUrl"));
+                        actionContext.Request.Properties.Add("reticket", token);
+                    }
+                    else
+                    {
+                        throw new Exception("登录超时,请重新登录");
+                    }
+                }
+
+                //解密
+                ApiAuthInfo authInfo = JWTHelper.Decrypt(token);
+                if (authInfo == null || !DateTime.TryParse(authInfo.FExpireTime, out DateTime expireTime) || DateTime.Compare(expireTime, DateTime.Now) < 0)
+                {
+                   throw new Exception("登录超时,请重新登录");
+                }
+                actionContext.Request.Properties.Add("token", authInfo);
+            }
+            catch (Exception ex)
+            {
+                string errorMsg = string.Format("{1}", Environment.NewLine, ex.Message.ToString());
+                ExceptionHelper.WriteMessage(errorMsg, 2);
+                ApiResult errResult = new ApiResult().CustomError((int)Constant.ApiResultCode.异常, errorMsg);
+                if (ex.Message == "登录超时,请重新登录")
+                {
+                    errResult.Ticket = AppSettingsHelper.GetAppSettingVal("Cas_OAUrl") + "login?appid=testsso&service=" + AppSettingsHelper.GetAppSettingVal("Cas_ReUrl");
+                }
+                string tempStr = JsonConvert.SerializeObject(errResult);
+                actionContext.Response = new HttpResponseMessage() { Content = new StringContent(tempStr, Encoding.GetEncoding("UTF-8"), "application/json") };
+            }
+        }
+
+
+        /// 
+        /// OA登录信息对接
+        /// 
+        private string CasLogin(string ticket, string refUrl)
+        {
+            string valUrl = AppSettingsHelper.GetAppSettingVal("Cas_OAUrl");
+            valUrl += string.Format(@"serviceValidate?ticket={0}&service={1}", ticket, refUrl);
+            try
+            {
+                StreamReader Reader = new StreamReader(new WebClient().OpenRead(valUrl));
+                string resp = Reader.ReadToEnd();
+                ExceptionHelper.AddSystemJournal(null, valUrl, resp, -1, "CasLoginLog");
+                XmlDocument xmlDoc = new XmlDocument();
+                xmlDoc.LoadXml(resp);
+
+                string userid = "";
+                XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
+                nsmgr.AddNamespace("cas", "http://www.yale.edu/tp/cas");
+                if (xmlDoc.SelectSingleNode("/cas:serviceResponse/cas:authenticationSuccess/cas:attributes", nsmgr) != null)
+                {
+                    XmlNode root = xmlDoc.SelectSingleNode("/cas:serviceResponse/cas:authenticationSuccess/cas:attributes", nsmgr);
+                    userid = root.SelectSingleNode("cas:workcode", nsmgr) == null ? "" : root.SelectSingleNode("cas:workcode", nsmgr).InnerText;
+                }
+                if (!string.IsNullOrEmpty(userid))
+                {
+                    TUser user = BaseBll.GetTempModel(string.Format("FDockID='{0}'", userid.Replace("'", "\"")));
+                    if (user != null)
+                    {
+                        ApiAuthInfo apiAuthInfo = new ApiAuthInfo()
+                        {
+                            FID = user.FID,
+                            FName = user.FName,
+                            FUser = user.FUser,
+                            FExpireTime = DateTime.Now.AddDays(30).ToString("yyyy-MM-dd HH:mm:ss")
+                        };
+
+                        Dictionary updateModel = new Dictionary();
+                        updateModel.Add("FID", user.FID);
+                        updateModel.Add("FLoginDate", DateTime.Now);
+                        BaseBll.UpdateDataModel(updateModel, "TUser");
+
+                        return JWTHelper.Encryption(apiAuthInfo);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                ExceptionHelper.AddSystemJournal(null, valUrl, ex.Message, -1, "CasLoginLog");
+                throw new Exception("OA获取用户信息报错,请重新登录");
+            }
+            throw new Exception("登录超时,请重新登录");
+        }
+    }
+
+    /// 
+    /// 不需要授权
+    /// 
+    public class NoCheckUserLogin : Attribute
+    { }
+}
\ No newline at end of file
diff --git a/FactorySystemApi/App_Start/FilterConfig.cs b/FactorySystemApi/App_Start/FilterConfig.cs
new file mode 100644
index 0000000..a7a5b1d
--- /dev/null
+++ b/FactorySystemApi/App_Start/FilterConfig.cs
@@ -0,0 +1,13 @@
+using System.Web;
+using System.Web.Mvc;
+
+namespace FactorySystemApi
+{
+    public class FilterConfig
+    {
+        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
+        {
+            filters.Add(new HandleErrorAttribute());
+        }
+    }
+}
diff --git a/FactorySystemApi/App_Start/RouteConfig.cs b/FactorySystemApi/App_Start/RouteConfig.cs
new file mode 100644
index 0000000..783da01
--- /dev/null
+++ b/FactorySystemApi/App_Start/RouteConfig.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+
+namespace FactorySystemApi
+{
+    public class RouteConfig
+    {
+        public static void RegisterRoutes(RouteCollection routes)
+        {
+            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
+
+            routes.MapRoute(
+                name: "Default",
+                url: "{controller}/{action}/{id}",
+                defaults: new { controller = "Swagger", action = "Index", id = UrlParameter.Optional }
+            );
+        }
+    }
+}
diff --git a/FactorySystemApi/App_Start/SwaggerConfig.cs b/FactorySystemApi/App_Start/SwaggerConfig.cs
new file mode 100644
index 0000000..8cc673c
--- /dev/null
+++ b/FactorySystemApi/App_Start/SwaggerConfig.cs
@@ -0,0 +1,39 @@
+using System.Web.Http;
+using WebActivatorEx;
+using Swashbuckle.Application;
+using System.Linq;
+using System.Reflection;
+using FactorySystemApi;
+using FactorySystemApi.Controllers;
+
+[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
+namespace FactorySystemApi
+{
+    public class SwaggerConfig
+    {
+        public static void Register()
+        {
+            var thisAssembly = typeof(SwaggerConfig).Assembly;
+
+            GlobalConfiguration.Configuration
+                .EnableSwagger(c =>
+                {
+                    c.SingleApiVersion("v1", "䷽Эͬӿĵ");
+
+
+                    c.OperationFilter();
+
+                    c.IncludeXmlComments(string.Format("{0}/bin/FactorySystemModel.xml", System.AppDomain.CurrentDomain.BaseDirectory));
+                    c.IncludeXmlComments(string.Format("{0}/bin/FactorySystemApi.xml", System.AppDomain.CurrentDomain.BaseDirectory));
+                    c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
+                    c.CustomProvider((defaultProvider) => new SwaggerCacheProvider(defaultProvider, string.Format("{0}/bin/FactorySystemApi.XML",System.AppDomain.CurrentDomain.BaseDirectory)));
+                    c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
+                })
+                .EnableSwaggerUi(c =>
+                {
+                    c.InjectJavaScript(Assembly.GetExecutingAssembly(), "FactorySystemApi.Scripts.swagger.js");
+                });
+        }
+    }
+    
+}
diff --git a/FactorySystemApi/App_Start/WebApiConfig.cs b/FactorySystemApi/App_Start/WebApiConfig.cs
new file mode 100644
index 0000000..13e0f07
--- /dev/null
+++ b/FactorySystemApi/App_Start/WebApiConfig.cs
@@ -0,0 +1,18 @@
+using System.Web.Http;
+
+namespace FactorySystemApi
+{
+    public static class WebApiConfig
+    {
+        public static void Register(HttpConfiguration config)
+        {
+            // Web API 路由
+            config.MapHttpAttributeRoutes();
+            config.Routes.MapHttpRoute(
+                name: "DefaultApi",
+                routeTemplate: "api/{controller}/{action}",
+                defaults: new { id = RouteParameter.Optional }
+            );
+        }
+    }
+}
diff --git a/FactorySystemApi/Controllers/BaseController.cs b/FactorySystemApi/Controllers/BaseController.cs
new file mode 100644
index 0000000..1bc0951
--- /dev/null
+++ b/FactorySystemApi/Controllers/BaseController.cs
@@ -0,0 +1,404 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.ResponseModel;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Web;
+using System.Web.Http;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 基础接口
+    /// 
+    [UserLoginFilter]
+    public class BaseController : ApiController
+    {
+        /// 
+        /// 数据处理层
+        /// 
+        public readonly BaseBll BaseBll = new BaseBll();
+
+        #region 公共接口-开始
+
+        /// 
+        /// (通用)获取信息
+        /// 
+        [HttpPost]
+        public virtual ApiResult GetDataModel(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult
+            {
+                Data = -1
+            };
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                inParam.TryGetValue("FID", out object dataId);
+                apiResult.Data = BaseBll.GetTempDict(dataId == null ? -1 : int.Parse(dataId.ToString()), inParam.ContainsKey("FKey") ? inParam["FKey"].ToString() : "");
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// (通用)新增信息
+        /// 
+        [HttpPost]
+        public virtual ApiResult InsertDataModel(Dictionary insertData)
+        {
+            ApiResult apiResult = new ApiResult
+            {
+                Data = -1
+            };
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                //表可新增字段
+                if (!string.IsNullOrEmpty(InsertField))
+                {
+                    insertData = GetDictByField(insertData, InsertField);
+                }
+                //新增数据
+                if (insertData.Count > 1)
+                {
+                    //设置用户字段
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    if (insertData.ContainsKey("FAddUser")) { insertData["FAddUser"] = user.FID; }
+                    if (insertData.ContainsKey("FEditUser")) { insertData["FEditUser"] = user.FID; }
+                    else { insertData.Add("FEditUser", user.FID); }
+                    if (insertData.ContainsKey("FEditDate")) { insertData["FEditDate"] = DateTime.Now; }
+                    else { insertData.Add("FEditDate", DateTime.Now); }
+                    apiResult.Data = BaseBll.InsertDataModel(insertData, typeof(T).Name);
+                }
+            }, apiResult, Request, insertData);
+        }
+
+        /// 
+        /// (通用)修改信息
+        /// 
+        [HttpPost]
+        public virtual ApiResult UpdateDataModel(Dictionary updateModel)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                updateModel.Remove("FEditUser");
+                updateModel.Remove("FEditDate");
+                updateModel.Add("FEditUser", user.FID);
+                updateModel.Add("FEditDate", DateTime.Now);
+                apiResult.Data = UpdateData(updateModel);
+            }, apiResult, Request, updateModel);
+        }
+
+        /// 
+        /// (通用)删除信息
+        /// 
+        [HttpPost]
+        public ApiResult DeleteDataById(Dictionary updateModel)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = BaseBll.DeleteDataById(updateModel, typeof(T).Name);
+            }, apiResult, Request, updateModel);
+        }
+
+        /// 
+        /// (通用)列表分页
+        /// 
+        [HttpPost]
+        public ApiResult GetPageList(Dictionary pageParam)
+        {
+            return GetTPageList(pageParam);
+        }
+
+        /// 
+        ///(通用)模板上传
+        /// 
+        [HttpPost]
+        public ApiResult UploadTempFile(int FType, int FFuncType)
+        {
+            ApiResult apiResult = new ApiResult();
+            List fileList = new List();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                var files = HttpContext.Current.Request.Files;
+                if (files != null && files.Count > 0)
+                {
+                    string typeName = "";
+                    switch (FType)
+                    {
+                        case 2://物料
+                            typeName = "Material";
+                            break;
+                        case 1://配方
+                        default:
+                            typeName = "Formula";
+                            break;
+                    }
+
+                    ApiAuthInfo authInfo = Request.Properties["token"] as ApiAuthInfo;
+                    string[] exts = ".xlsx,.xls".Split(',');
+                    string saveBase = string.Format("/Upload/{0}/" + authInfo.FID + "/", typeName);
+                    saveBase = System.Web.Hosting.HostingEnvironment.MapPath(saveBase);
+                    if (!Directory.Exists(saveBase)) Directory.CreateDirectory(saveBase);
+
+                    for (int i = 0; i < files.Count; i++)
+                    {
+                        string ext = Path.GetExtension(files[i].FileName).ToLower();
+                        if (Array.IndexOf(exts, ext) != -1)
+                        {
+                            string filePath = saveBase + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "_" + i + ext;
+                            files[i].SaveAs(filePath);
+                            fileList.Add(filePath);
+                        }
+                    }
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    apiResult.Data = CheckUploadFile(fileList, FFuncType, user.FID);
+                }
+            }, apiResult, Request, new { FType, FFuncType, FileList = fileList });
+        }
+
+        /// 
+        /// (临时)创建表
+        /// 
+        [HttpPost]
+        [NoCheckUserLogin]
+        [NonAction]
+        public ApiResult CreateSqlSugarModel(Dictionary updateModel)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                updateModel.TryGetValue("FAuth", out object apiAuth);
+                if (apiAuth == null || apiAuth.ToString() != (DateTime.Now.ToString("yyyyMMdd-") + DateTime.Now.DayOfYear))
+                {
+                    apiResult.Message = "认证信息错误";
+                }
+                else
+                {
+                    updateModel.TryGetValue("FName", out object tableName);
+                    BaseBll.CreateSqlSugarModel(tableName == null ? "TFS_" : tableName.ToString());
+                }
+            }, apiResult, Request, updateModel);
+        }
+
+        #endregion 公共接口-结束
+
+        #region 内部方法-开始
+
+        /// 
+        /// 可修改字段,非空则只有这些字段是可以修改的
+        /// 
+        internal string UpdateField = string.Empty;
+        /// 
+        /// 插入字段,非空则这些字段是必须的,默认值自己数据库设置
+        /// 
+        internal string InsertField = string.Empty;
+
+        /// 
+        /// (通用)修改信息
+        /// 
+        /// 修改数据
+        /// 表名
+        /// 权限→ null不限制,反之只能改这几个值
+        protected int UpdateData(Dictionary updateModel, string canOptField = null, string tableName = null)
+        {
+            //表可修改字段
+            if (!string.IsNullOrEmpty(UpdateField))
+            {
+                updateModel = GetDictByField(updateModel, UpdateField);
+            }
+            //权限可修改字段
+            if (canOptField != null)
+            {
+                updateModel = GetDictByField(updateModel, canOptField);
+            }
+            //修改数据
+            if (updateModel.ContainsKey("FID") && updateModel.Count > 1)
+            {
+                return BaseBll.UpdateDataModel(updateModel, string.IsNullOrEmpty(tableName) ? typeof(T).Name : tableName) > 0 ? 1 : 0;
+            }
+            return 0;
+        }
+
+        /// 
+        /// 模板文件处理
+        /// 
+        protected virtual object CheckUploadFile(List fileList, int funcType, int userId)
+        {
+            return fileList;
+        }
+
+        /// 
+        /// 筛选字段
+        /// 
+        protected Dictionary GetDictByField(Dictionary updateModel, string fieldKey)
+        {
+            Dictionary tempDict = new Dictionary();
+            if (updateModel.Count > 0)
+            {
+                string[] fieldKeys = (fieldKey.Trim() + ",FID").Split(',');
+                foreach (string fieldName in fieldKeys)
+                {
+                    if (!string.IsNullOrEmpty(fieldName) && updateModel.TryGetValue(fieldName, out object fieldVal))
+                    {
+                        tempDict.Add(fieldName, fieldVal);
+                    }
+                }
+            }
+            return tempDict;
+        }
+
+        /// 
+        /// (通用)获取列表分页
+        /// 
+        protected ApiResult GetTPageList(Dictionary pageParam, string searchKey = null, string orderBy = "FID desc")
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = new
+                {
+                    List = BaseBll.GetPageList(pageParam, out int totalCount, searchKey, orderBy),
+                    Total = totalCount
+                };
+            }, apiResult, Request, pageParam);
+        }
+
+        /// 
+        /// 根据名称查找值
+        /// 
+        protected string GetValueByName(Dictionary inParam, string name, string nullVal = "")
+        {
+            List names = name.Split(',').ToList();
+            for (int i = 0; i < names.Count(); i++)
+            {
+                if (inParam.TryGetValue(names[i], out object result))
+                {
+                    return result == null ? "" : result.ToString().Trim();
+                }
+            }
+            return nullVal;
+        }
+
+        /// 
+        /// 根据名称查找值
+        /// 
+        protected string GetValueByName(DataRow dr, List items, string name, string nullVal = "")
+        {
+            List names = name.Split(',').ToList();
+            for (int i = 0; i < names.Count(); i++)
+            {
+                if (items.IndexOf(names[i]) != -1)
+                {
+                    try { return dr[names[i]].ToString().Trim(); }
+                    catch (Exception) { }
+                }
+            }
+            return nullVal;
+        }
+
+        /// 
+        /// 获取MDM代码
+        /// 
+        public string GetMdmCode(Dictionary inParam)
+        {
+            string logMessage = "";
+            string jObject = "";
+            try
+            {
+                JObject jo = new JObject
+                {
+                    { "systemCode", "mdm" },
+                    { "gdCode", "sap_material" },
+                    { "masterData",
+                        new JArray
+                        {
+                            new JObject
+                                {
+                                    { "id", Guid.NewGuid().ToString() },//随机生成值,不可重复
+                                    { "sap_flag_system", "香精" },//固定
+                                    { "sap_material_basicunit#code",  GetValueByName(inParam, "FWeightUnit") },//必填
+                                    { "sap_material_busvol", "" },
+                                    { "sap_material_code", "" },
+                                    { "sap_material_code_2", "" },
+                                    { "sap_material_code_flag", "Y" },//固定
+                                    { "sap_material_ex_code", GetValueByName(inParam, "FTestCode") },//试验号,可不填
+                                    { "sap_material_fame", "" },
+                                    { "sap_material_grossweight", "" },
+                                    { "sap_material_grouptype#code",  GetValueByName(inParam, "FMaterialGroup,FGroup")}, //必填
+                                    { "sap_material_lable#code", "" },
+                                    { "sap_material_models", "" },
+                                    { "sap_material_models_text", "" },
+                                    { "sap_material_name", GetValueByName(inParam, "FSaleCode,FName")}, //必填
+                                    { "sap_material_netweight", "" },
+                                    { "sap_material_type#code", GetValueByName(inParam, "FType,FMaterialType") },//固定,必填
+                                    { "sap_material_volunit#code", "" },
+                                    { "sap_material_weightunit#code", "" }
+                                }
+                        }
+                    }
+                };
+                jObject = jo.ToString();
+                WebRequest webRequest = WebRequest.Create(AppSettingsHelper.GetAppSettingVal("Mdm_Url"));
+                HttpWebRequest httpRequest = webRequest as HttpWebRequest;
+                httpRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";
+                httpRequest.ContentType = "application/json";
+                httpRequest.Method = "post";
+                httpRequest.Headers.Add("mdmtoken", "ac9b7db8-9661-461d-b5b2-ed66f33a1d69");
+                httpRequest.Headers.Add("tenantid", "tenant");
+                Encoding encoding = Encoding.GetEncoding("utf-8");
+                byte[] bytesToPost = encoding.GetBytes(jObject);
+                httpRequest.ContentLength = bytesToPost.Length;
+                Stream requestStream = httpRequest.GetRequestStream();
+                requestStream.Write(bytesToPost, 0, bytesToPost.Length);
+                requestStream.Close();
+                Stream responseStream = httpRequest.GetResponse().GetResponseStream();
+                using (StreamReader responseReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8")))
+                {
+                    logMessage = responseReader.ReadToEnd();
+                }
+                responseStream.Close();
+                ExceptionHelper.AddSystemJournal(Request, new
+                {
+                    inParam,
+                    jObject
+                }, logMessage, -1, "GetMdmCode");
+            }
+            catch (Exception ex)
+            {
+                ExceptionHelper.AddSystemJournal(Request, new
+                {
+                    inParam,
+                    jObject
+                }, new
+                {
+                    Resule = logMessage,
+                    Error = ex.Message
+                }, -1, "GetMdmCode");
+            }
+            try
+            {
+                if (logMessage.IndexOf("success") != -1)
+                {
+                    MDMResponseModel resModel = JsonConvert.DeserializeObject(logMessage);
+                    if (resModel.success)
+                    {
+                        List datas = JsonConvert.DeserializeObject>(resModel.data);
+                        return datas.Count > 0 ? datas.First().mdm_code : "";
+                    }
+                }
+            }
+            catch (Exception) { }
+            return "";
+        }
+        #endregion 内部方法-结束
+    }
+}
diff --git a/FactorySystemApi/Controllers/CommonController.cs b/FactorySystemApi/Controllers/CommonController.cs
new file mode 100644
index 0000000..387b4b3
--- /dev/null
+++ b/FactorySystemApi/Controllers/CommonController.cs
@@ -0,0 +1,208 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 公共接口
+    /// 
+    [UserLoginFilter]
+    public class CommonController : BaseController
+    {
+        private readonly CommonBll CommonBll = new CommonBll();
+
+        /// 
+        /// 获取配置集合
+        /// 
+        [HttpPost]
+        public ApiResult GetBasicList(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam.TryGetValue("FType", out object objType) && int.TryParse(objType.ToString(), out int intType))
+                {
+                    apiResult.Data = CommonBll.GetBasicList(intType);
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 获取模板信息
+        /// 
+        [HttpPost]
+        public ApiResult GetTempFile(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                inParam.TryGetValue("FType", out object objType);
+                objType = objType ?? "";
+                string filePath = "/File/{0}/{1}.xlsx";
+                switch (objType.ToString())
+                {
+                    case "2":
+                        filePath = string.Format(filePath, "Material", "SAP物料导入模板");
+                        break;
+                    case "3":
+                        filePath = string.Format(filePath, "Material", "替代料导入模板");
+                        break;
+                    case "4":
+                        filePath = string.Format(filePath, "Material", "副产物导入模板");
+                        break;
+                    case "5":
+                        filePath = string.Format(filePath, "Material", "物料信息补全导入模板");
+                        break;
+                    case "1"://配方导入模板
+                    default:
+                        filePath = string.Format(filePath, "Formula", "配方导入模板");
+                        break;
+                }
+                apiResult.Data = Request.RequestUri.AbsoluteUri.Replace(Request.RequestUri.AbsolutePath, "").Trim('/') + filePath;
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 验证用户权限
+        /// 
+        [HttpPost]
+        public ApiResult CheckIsHasPower(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                Dictionary outParam = new Dictionary();
+                List funcList = new List();
+                inParam.TryGetValue("FType", out object oType);
+                if (null != inParam)
+                {
+                    inParam.Remove("FType");
+                    int.TryParse(oType.ToString(), out int iType);
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    funcList = CommonBll.CheckIsHasPower(iType, user.FID);
+                }
+                foreach (var item in inParam)
+                {
+                    bool has = funcList.Contains(int.Parse(item.Key.Replace("P", "")));
+                    outParam.Add(item.Key, has);
+                }
+                apiResult.Data = outParam;
+            }, apiResult, Request, inParam);
+        }
+
+
+        /// 
+        /// 获取数据选择值
+        /// 
+        [HttpPost]
+        public ApiResult GetDataCodeList(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam.TryGetValue("FType", out object oType))
+                {
+                    string[] typeIds = oType.ToString().Split(',');
+                    List codeList = CommonBll.GetDataCodeList(typeIds);
+                    Dictionary outParam = new Dictionary();
+                    foreach (var item in codeList.GroupBy(s => s.FType))
+                    {
+                        outParam.Add("FType" + item.Key, item.Select(s => new { s.FID, s.FName, s.FValue }));
+                    }
+                    apiResult.Data = outParam;
+                }
+                else
+                {
+                    apiResult.Data = CommonBll.GetDataCodeList(null);
+                }
+            }, apiResult, Request, inParam);
+        }
+
+
+        /// 
+        /// 修改数据值状态
+        /// 
+        [HttpPost]
+        public ApiResult StateDataCode(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                inParam.TryGetValue("FID", out object dataId);
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                apiResult.Data = CommonBll.StateDataCode(int.Parse(dataId.ToString()), user.FID);
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 删除系统配置值
+        /// 
+        [HttpPost]
+        public ApiResult DeleteDataCode(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = BaseBll.DeleteDataById(inParam, "TDataCode", true);
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 设置系统配置值
+        /// 
+        [HttpPost]
+        public ApiResult ChangeDataCode(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                inParam.Add("FDateOpt", DateTime.Now);
+                inParam.Add("FUserOpt", user.FID);
+                if (inParam.ContainsKey("FID"))
+                {
+                    apiResult.Data = BaseBll.UpdateDataModel(inParam, "TDataCode");
+                }
+                else
+                {
+                    apiResult.Data = BaseBll.InsertDataModel(inParam, "TDataCode");
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 获取配置集合
+        /// 
+        [HttpPost]
+        public ApiResult GetConfigList(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = CommonBll.GetConfigList();
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 获取系统配置值
+        /// 
+        [HttpPost]
+        public ApiResult GetConfigValue(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                inParam.TryGetValue("FID", out object dataId);
+                apiResult.Data = CommonBll.GetConfigValue(int.Parse(dataId.ToString()));
+            }, apiResult, Request, inParam);
+        }
+
+    }
+}
diff --git a/FactorySystemApi/Controllers/FactoryController.cs b/FactorySystemApi/Controllers/FactoryController.cs
new file mode 100644
index 0000000..adf475c
--- /dev/null
+++ b/FactorySystemApi/Controllers/FactoryController.cs
@@ -0,0 +1,52 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using System.Collections.Generic;
+using System.Web.Http;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 工厂接口
+    /// 
+    [UserLoginFilter]
+    public class FactoryController : BaseController
+    {
+        private readonly FactoryBll FactoryBll = new FactoryBll();
+        /// 
+        /// 初始化
+        /// 
+        public FactoryController()
+        {
+            //设置可新增、修改字段
+            InsertField = UpdateField = "FName,FCode,FType,FFactoryID,FChargePerson,FPhone,FState,FEditUser,FEditDate";
+        }
+
+        /// 
+        /// 获取工厂集合
+        /// 
+        [HttpPost]
+        public ApiResult GetFactoryList()
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = FactoryBll.GetFactoryList();
+            }, apiResult, Request);
+        }
+
+        /// 
+        /// 验证工厂代码是否重复
+        /// 
+        [HttpPost]
+        public ApiResult CheckHasCode(Dictionary insertData)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = FactoryBll.CheckHasCode(insertData);
+            }, apiResult, Request);
+        }
+    }
+}
\ No newline at end of file
diff --git a/FactorySystemApi/Controllers/FieldController.cs b/FactorySystemApi/Controllers/FieldController.cs
new file mode 100644
index 0000000..0e2e0a0
--- /dev/null
+++ b/FactorySystemApi/Controllers/FieldController.cs
@@ -0,0 +1,20 @@
+using FactorySystemModel.SqlSugarModel;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 视图字段关系接口(入库)
+    /// 
+    [UserLoginFilter]
+    public class FieldController : BaseController
+    {
+        /// 
+        /// 初始化
+        /// 
+        public FieldController()
+        {
+            //设置可修改字段
+            UpdateField = "";
+        }
+    }
+}
diff --git a/FactorySystemApi/Controllers/FormulaController.cs b/FactorySystemApi/Controllers/FormulaController.cs
new file mode 100644
index 0000000..712fe99
--- /dev/null
+++ b/FactorySystemApi/Controllers/FormulaController.cs
@@ -0,0 +1,234 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Web.Http;
+using FactorySystemModel.EnumModel;
+using FactorySystemModel.RequestModel;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 配方接口
+    /// 
+    [UserLoginFilter]
+    public class FormulaController : BaseController
+    {
+        /// 
+        /// 数据处理层
+        /// 
+        public readonly FormulaBll FormulaBll = new FormulaBll();
+        /// 
+        /// 初始化
+        /// 
+        public FormulaController()
+        {
+            //设置可修改字段
+            UpdateField = "";
+        }
+
+
+        /// 
+        /// 分业请求
+        /// 
+        [HttpPost]
+        public ApiResult GetFormulaPageList(FormulaQuery fq)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = new
+                {
+                    List = FormulaBll.GetList(fq, out var totalNumber),
+                    Total = totalNumber
+                };
+            }, apiResult, Request);
+        }
+
+        /// 
+        /// 对接配方数据(对方写入)
+        /// 
+        [HttpPost]
+        public ApiResult DockingRecipeData(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            apiResult.Data = 0;
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam == null)
+                {
+                    apiResult.Error("未接收到参数");
+                }
+                else
+                {
+                    List fieldList = BaseBll.GetFileInfoList((int)Constant.FieldInfoType.配方模板导入);
+                    TFS_Formula formula = new TFS_Formula();
+                    List propertys = formula.GetType().GetProperties().ToList();
+                    foreach (var field in fieldList)
+                    {
+                        PropertyInfo temp = propertys.Find(s => s.Name == field.FColumnFIeld);
+                        if (temp != null) temp.SetValue(formula, GetValueByName(inParam, field.FFieldName, field.FDefault));
+                    }
+                    if (string.IsNullOrEmpty(formula.FPlmCode))
+                    {
+                        apiResult.Error("FPlmCode不能为空");
+                    }
+                    else if (string.IsNullOrEmpty(formula.FVersionCode))
+                    {
+                        apiResult.Error("FVersionCode不能为空");
+                    }
+                    else if (string.IsNullOrEmpty(formula.FName))
+                    {
+                        apiResult.Error("FName不能为空");
+                    }
+                    else if (string.IsNullOrEmpty(formula.FType))
+                    {
+                        apiResult.Error("FType不能为空");
+                    }
+                    /**
+                     * 20230401 新增
+                     * 接口新增:
+                     * 转规格人员 FConversionPersonnel
+                     * BOM版本号 FBomVersionCode
+                     * **/
+                    else if (string.IsNullOrEmpty(formula.FConversionPersonnel))
+                    {
+                        apiResult.Error("FConversionPersonnel不能为空");
+                    }
+                    else if (string.IsNullOrEmpty(formula.FBomVersionCode))
+                    {
+                        apiResult.Error("FBomVersionCode不能为空");
+                    }
+                    else
+                    {
+                        int userId = -1;
+                        if (Request.Properties.ContainsKey("token"))
+                        {
+                            userId = Request.Properties["token"] is ApiAuthInfo user ? user.FID : userId;
+                            apiResult.Data = FormulaBll.DockingRecipeData(new List() { formula }, userId);
+                        }
+                        else
+                        {
+                            apiResult.Error("token信息不存在");
+                        }
+                    }
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// (对接)SAP配方同步
+        /// 
+        [HttpPost]
+        public ApiResult DockSapFormula()
+        {
+            ApiResult apiResult = new ApiResult
+            {
+                Data = 0
+            };
+            int result = 0;
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                Sap_Formula.dt_pp062_reqDATA[] reqDatas = new Sap_Formula.dt_pp062_reqDATA[1] {
+                    new Sap_Formula.dt_pp062_reqDATA()
+                    {
+                        SOURCESYS = "MCS",
+                        TARGETSYS = "SAP"
+                    }
+                };
+                List factoryList = FactoryBll.GetFactoryList();
+                if (factoryList.Count > 0)
+                {
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    Sap_Formula.si_pp062_bc_senderService sapService = new Sap_Formula.si_pp062_bc_senderService()
+                    {
+                        Credentials = new System.Net.NetworkCredential()
+                        {
+                            UserName = AppSettingsHelper.GetAppSettingVal("Sap_UserName"),
+                            Password = AppSettingsHelper.GetAppSettingVal("Sap_Password")
+                        }
+                    };
+                    foreach (TFS_Factory factory in factoryList)
+                    {
+                        reqDatas[0].UPDATETIME = DateTime.Now.ToString("yyyyMMddHHmmss");
+                        reqDatas[0].WERKS = factory.FCode;
+                        try
+                        {
+                            Sap_Formula.dt_pp062_res resData = sapService.si_pp062_bc_sender(reqDatas);
+                            ExceptionHelper.AddSystemJournal(Request, reqDatas, resData);
+                            List formulaList = new List();
+                            if (resData != null && resData.DATA != null && resData.DATA.Length > 0)
+                            {
+                                foreach (var item in resData.DATA)
+                                {
+                                    formulaList.Add(new TFS_Formula()
+                                    {
+                                        FFactoryID = factory.FID,
+                                        FFactoryCode = factory.FCode,
+                                        FName = string.IsNullOrEmpty(item.MAKTX) ? "" : item.MAKTX,
+                                        FTestCode = string.IsNullOrEmpty(item.MATNR) ? "" : item.MATNR,
+                                        FAddDate = DateTime.Now,
+                                        FEditUser = user.FID
+                                    });
+                                }
+                            }
+                            if (formulaList.Count > 0) result += FormulaBll.DockSapFormula(formulaList);
+                        }
+                        catch (Exception ex)
+                        {
+                            ExceptionHelper.AddSystemJournal(Request, reqDatas, ex.Message);
+                        }
+                    }
+                }
+                apiResult.Data = result;
+            }, apiResult, Request, null);
+        }
+
+        #region 内部方法
+
+        /// 
+        /// 模板上传处理(重构父方法)
+        /// 
+        protected override object CheckUploadFile(List fileList, int funcType, int userId)
+        {
+            if (funcType <= 1)
+            {
+                List formulas = new List();
+                List fieldList = BaseBll.GetFileInfoList((int)Constant.FieldInfoType.配方模板导入);
+                List formulaProp = typeof(TFS_Formula).GetTypeInfo().GetProperties().ToList();
+                PropertyInfo temp = null;
+                foreach (string filePath in fileList)
+                {
+                    DataTable dt = NPOIHelper.ImportExceltoDt(filePath);
+                    List items = new List();
+                    for (int i = 0; i < dt.Columns.Count; i++)
+                    {
+                        items.Add(dt.Columns[i].ColumnName);
+                    }
+                    foreach (DataRow dr in dt.Rows)
+                    {
+                        TFS_Formula formula = new TFS_Formula();
+                        foreach (var field in fieldList)
+                        {
+                            if ((temp = formulaProp.Find(s => s.Name.Equals(field.FColumnFIeld))) != null)
+                            {
+                                temp.SetValue(formula, GetValueByName(dr, items, field.FFieldName, field.FDefault));
+                            }
+                        }
+                        formulas.Add(formula);
+                    }
+                }
+                return FormulaBll.DockingRecipeData(formulas, userId);
+            }
+            return fileList;
+        }
+
+        #endregion
+    }
+}
diff --git a/FactorySystemApi/Controllers/MaterialController.cs b/FactorySystemApi/Controllers/MaterialController.cs
new file mode 100644
index 0000000..5821d9e
--- /dev/null
+++ b/FactorySystemApi/Controllers/MaterialController.cs
@@ -0,0 +1,490 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Web.Http;
+using FactorySystemModel.EnumModel;
+using System.IO;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 物料接口
+    /// 
+    [UserLoginFilter]
+    public class MaterialController : BaseController
+    {
+        /// 
+        /// 数据处理层
+        /// 
+        public readonly MaterialBll MaterialBll = new MaterialBll();
+        /// 
+        /// 初始化
+        /// 
+        public MaterialController()
+        {
+            //设置可修改字段
+            UpdateField = "FSuccedaneumID,FSuccedaneumInfo,FSuccedaneumType,FSuccedaneumCode,FFuProductsID,FFuProductsInfo,FFuProductsType,FFuProductsCode,FFuProductsCount";
+        }
+
+        /// 
+        /// (对接)SAP物料同步
+        /// 
+        [HttpPost]
+        public ApiResult DockSapMaterial()
+        {
+            ApiResult apiResult = new ApiResult
+            {
+                Data = 0
+            };
+            int result = 0;
+            return ExceptionHelper.TryReturnException(() =>
+            {
+
+                List factoryList = FactoryBll.GetFactoryList();
+                if (factoryList.Count > 0)
+                {
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    Sap_Material1.si_mm100_mcs_senderService sapService = new Sap_Material1.si_mm100_mcs_senderService()
+                    {
+                        Credentials = new System.Net.NetworkCredential()
+                        {
+                            UserName = AppSettingsHelper.GetAppSettingVal("Sap_UserName"),
+                            Password = AppSettingsHelper.GetAppSettingVal("Sap_Password")
+                        }
+                    };
+                    Sap_Material1.dt_mm100_req reqDatas = new Sap_Material1.dt_mm100_req();
+                    //委外在前面
+                    factoryList = factoryList.OrderBy(s => s.FType).ToList();
+                    foreach (TFS_Factory factory in factoryList)
+                    {
+                        reqDatas.WERKS = factory.FCode;
+                        try
+                        {
+                            Sap_Material1.dt_mm100_res resData = sapService.si_mm100_mcs_sender(reqDatas);
+                            ExceptionHelper.AddSystemJournal(Request, reqDatas, resData);
+                            List materialList = new List();
+                            if (resData != null && resData.DATA != null && resData.DATA.Length > 0)
+                            {
+                                foreach (var item in resData.DATA)
+                                {
+                                    string name = string.IsNullOrEmpty(item.EXTWG) ? item.MAKTX : item.EXTWG;
+                                    materialList.Add(new TFS_Material()
+                                    {
+                                        FFactoryID = factory.FID,
+                                        FFactoryCode = factory.FCode,
+                                        FName = string.IsNullOrEmpty(name) ? "" : item.MAKTX,
+                                        FCode = string.IsNullOrEmpty(item.MATNR) ? "" : item.MATNR,
+                                        FTestCode = string.IsNullOrEmpty(item.BISMT) ? "" : item.BISMT,
+                                        FBaseUnit = string.IsNullOrEmpty(item.MEINS) ? "" : item.MEINS,
+                                        FMaterialGroup = string.IsNullOrEmpty(item.MATKL) ? "" : item.MATKL,
+                                        FAddDate = DateTime.Now,
+                                        FEditUser = user.FID,
+                                        FType = string.IsNullOrEmpty(item.ZCATEG) ? "" : item.ZCATEG,//产品分类
+                                        FMaterialType = string.IsNullOrEmpty(item.MTART) ? "" : item.MTART,//物料类型
+                                    });
+                                }
+                            }
+                            if (materialList.Count > 0) result += MaterialBll.DockSapMaterial(materialList, user.FID);
+                        }
+                        catch (Exception ex)
+                        {
+                            ExceptionHelper.AddSystemJournal(Request, reqDatas, ex.Message);
+                        }
+                    }
+                    apiResult.Data = result;
+                }
+            }, apiResult, Request, null);
+        }
+
+        /// 
+        /// 对接物料数据(对方写入)
+        /// 
+        [HttpPost]
+        public ApiResult DockingMaterialData(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam == null)
+                {
+                    apiResult.Error("未接收到参数");
+                }
+                else
+                {
+                    List fieldList = BaseBll.GetFileInfoList((int)Constant.FieldInfoType.物料模板导入);
+                    TFS_Material material = new TFS_Material();
+                    List propertys = material.GetType().GetProperties().ToList();
+                    foreach (var field in fieldList)
+                    {
+                        PropertyInfo temp = propertys.Find(s => s.Name == field.FColumnFIeld);
+                        if (temp != null) temp.SetValue(material, GetValueByName(inParam, field.FFieldName, field.FDefault));
+                    }
+                    if (string.IsNullOrEmpty(material.FCode))
+                    {
+                        apiResult.Error("FCode不能为空");
+                    }
+                    else if (string.IsNullOrEmpty(material.FName))
+                    {
+                        apiResult.Error("FName不能为空");
+                    }
+                    else if (string.IsNullOrEmpty(material.FMaterialGroup))
+                    {
+                        apiResult.Error("FMaterialGroup不能为空");
+                    }
+                    else
+                    {
+                        int userId = -1;
+                        if (Request.Properties.ContainsKey("token"))
+                        {
+                            userId = Request.Properties["token"] is ApiAuthInfo user ? user.FID : userId;
+                            List mainProp = typeof(TFS_Material).GetTypeInfo().GetProperties().ToList();
+                            apiResult.Data = MaterialBll.DockingRecipeData(new List() { material }, mainProp, userId);
+                        }
+                        else
+                        {
+                            apiResult.Error("token信息不存在");
+                        }
+                    }
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// (对接)MDM获取物料PLM码
+        /// 
+        [HttpPost]
+        public ApiResult DockMDMGetPlmCode(Dictionary inParam)
+        {
+
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = GetMdmCode(inParam);
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 物料查询接口(对方查询)
+        /// 
+        [HttpPost]
+        public ApiResult SearchMaterialData(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam == null || inParam.Count < 0)
+                {
+                    apiResult.Error("未接收到参数");
+                }
+                else if (inParam.ContainsKey("FType"))
+                {
+                    apiResult.Data = MaterialBll.SearchMaterialData2(inParam);
+                }
+                else
+                {
+                    apiResult.Error("缺少FType参数");
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 导出视图
+        /// 
+        /// 
+        public ApiResult DownMateialView(Dictionary inParam) 
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam.ContainsKey("FID"))
+                {
+                    inParam.TryGetValue("FViewType", out object objType);
+                    //string FType = null == objType ? Constant.TeamViewType.物料视图.ToString() : objType.ToString();
+                    //string selectSql = "", joinSql = "", whereSql = string.Format("TFS_FTeamwork.FID={0} ", inParam["FTeamID"]);
+                    string basePath = AppDomain.CurrentDomain.BaseDirectory.Trim('\\');
+                    string savePath = basePath + string.Format("\\File\\Temp\\{0}_{1}\\", inParam["FID"], "View");
+                    if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
+                    string tempPath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                    if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath);
+                    savePath += ".xlsx";
+
+                    bool hasFinish = inParam.ContainsKey("FFinish");
+                    if (hasFinish) savePath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                    if (!File.Exists(savePath) || !hasFinish)
+                    {
+                        MaterialBll.CreateExeclFile(inParam["FName"].ToString(), savePath, "", "", "", inParam["FID"].ToString(), "");
+                    }
+                    else
+                    {
+                        File.Delete(basePath + string.Format("\\File\\Temp\\{0}_{1}\\", inParam["FID"], "View"));
+                        MaterialBll.CreateExeclFile(inParam["FName"].ToString(), savePath, "", "", "", inParam["FID"].ToString(), "");
+                    }
+                    string url = Request.RequestUri.AbsoluteUri.Replace(Request.RequestUri.AbsolutePath, "") + savePath.Replace(basePath, "").Replace("\\", "/").Replace(".xlsx", inParam["FName"].ToString() + ".xlsx");
+                    apiResult.Data = url;
+
+                }
+                else
+                {
+                    apiResult.Error("视图信息获取失败");
+                }
+            }, apiResult, Request, inParam);
+        }
+
+
+
+
+        /// 
+        /// 导出SAP视图
+        /// 
+        /// 
+        public ApiResult DownSAP(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam.ContainsKey("FID"))
+                {
+                    inParam.TryGetValue("FViewType", out object objType);
+                    //string FType = null == objType ? Constant.TeamViewType.物料视图.ToString() : objType.ToString();
+                    //string selectSql = "", joinSql = "", whereSql = string.Format("TFS_FTeamwork.FID={0} ", inParam["FTeamID"]);
+                    string basePath = AppDomain.CurrentDomain.BaseDirectory.Trim('\\');
+                    string savePath = basePath + string.Format("\\File\\Temp\\{0}_{1}\\", inParam["FID"], "SAP");
+                    if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
+                    string tempPath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                    if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath);
+                    savePath += ".xlsx";
+
+                    bool hasFinish = inParam.ContainsKey("FFinish");
+                    if (hasFinish) savePath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                    if (!File.Exists(savePath) || !hasFinish)
+                    {
+                        MaterialBll.CreateExeclFileSAP(inParam["FName"].ToString(), savePath, "", "", "", inParam["FID"].ToString(), "");
+                    }
+                    else
+                    {
+                        File.Delete(basePath + string.Format("\\File\\Temp\\{0}_{1}\\", inParam["FID"], "SAP"));
+                        MaterialBll.CreateExeclFileSAP(inParam["FName"].ToString(), savePath, "", "", "", inParam["FID"].ToString(), "");
+                    }
+                    string url = Request.RequestUri.AbsoluteUri.Replace(Request.RequestUri.AbsolutePath, "") + savePath.Replace(basePath, "").Replace("\\", "/").Replace(".xlsx", inParam["FName"].ToString() + ".xlsx");
+                    apiResult.Data = url;
+
+                }
+                else
+                {
+                    apiResult.Error("物料信息获取失败");
+                }
+            }, apiResult, Request, inParam);
+        }
+
+
+        /// 
+        /// 
+        /// 
+        /// 
+        /// 
+        public ApiResult DownViewAll(Dictionary inParam) 
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                string str = inParam["FType"].ToString() == "1" ? "视图" : "物料视图";
+                inParam.TryGetValue("FViewType", out object objType);
+                //string FType = null == objType ? Constant.TeamViewType.物料视图.ToString() : objType.ToString();
+                //string selectSql = "", joinSql = "", whereSql = string.Format("TFS_FTeamwork.FID={0} ", inParam["FTeamID"]);
+                string basePath = AppDomain.CurrentDomain.BaseDirectory.Trim('\\');
+                string savePath = basePath + string.Format("\\File\\Temp\\{0}\\", str);
+                if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
+                string tempPath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath);
+                savePath += ".xlsx";
+
+                bool hasFinish = inParam.ContainsKey("FFinish");
+                if (hasFinish) savePath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                if (!File.Exists(savePath) || !hasFinish)
+                {
+                    MaterialBll.CreateExeclFileALL(inParam["FType"].ToString(), savePath);
+                }
+                else
+                {
+                    File.Delete(basePath + string.Format("\\File\\Temp\\{0}\\", "", str));
+                    MaterialBll.CreateExeclFileALL(inParam["FType"].ToString(), savePath);
+                }
+               
+                string url = Request.RequestUri.AbsoluteUri.Replace(Request.RequestUri.AbsolutePath, "") + savePath.Replace(basePath, "").Replace("\\", "/").Replace(".xlsx", str+".xlsx");
+                apiResult.Data = url;
+            }, apiResult, Request, inParam);
+        }
+
+
+        #region 内部方法
+
+        /// 
+        /// 模板上传处理(重构父方法)
+        /// 
+        protected override object CheckUploadFile(List fileList, int funcType, int userId)
+        {
+            //替代料导入、副产物导入
+            List mainList;
+            List mainProp;
+            if (funcType == 2 || funcType == 3)
+            {
+                GetFuMaterialFromFile(fileList, funcType, out mainList, out mainProp, out List fieldList);
+                if (funcType == 3) return MaterialBll.InsertBatchFuMaterialData(mainList, mainProp, fieldList, userId);
+                else return MaterialBll.InsertBatchTiMaterialData(mainList, mainProp, fieldList, userId);
+            }
+            else if (funcType == 4)
+            {
+                //物料信息补全导入
+                GetMaterialInfoFromFile(fileList, out mainList, out List infoList, out mainProp, out List childProp, out List fieldList);
+                return MaterialBll.InsertBatchInfoMaterialData(mainList, infoList, mainProp, childProp, fieldList, userId);
+            }
+            else if (funcType <= 1)
+            {
+                //物料导入
+                GetMaterialFromFile(fileList, out mainList, out List childList, out mainProp, out List childProp);
+                return MaterialBll.DockingRecipeData(mainList, mainProp, userId) + MaterialBll.DockingRecipeData(childList, childProp, userId);
+            }
+            return fileList;
+        }
+
+        /// 
+        /// 信息补全模板
+        /// 
+        private void GetMaterialInfoFromFile(List fileList, out List mainList, out List infoList,
+            out List mainProp, out List childProp, out List fieldList)
+        {
+            mainList = new List();
+            infoList = new List();
+            fieldList = BaseBll.GetFileInfoList((int)Constant.FieldInfoType.物料信息补充模板导入);
+            mainProp = typeof(TFS_Material).GetTypeInfo().GetProperties().ToList();
+            childProp = typeof(TFS_MaterialInfo).GetTypeInfo().GetProperties().ToList();
+
+            PropertyInfo temp = null;
+            foreach (string filePath in fileList)
+            {
+                DataTable dt = NPOIHelper.ImportExceltoDt(filePath);
+                List items = new List();
+                for (int i = 0; i < dt.Columns.Count; i++)
+                {
+                    items.Add(dt.Columns[i].ColumnName);
+                }
+                foreach (DataRow dr in dt.Rows)
+                {
+                    TFS_Material main = new TFS_Material();
+                    TFS_MaterialInfo child = new TFS_MaterialInfo();
+                    foreach (var field in fieldList)
+                    {
+                        string value = GetValueByName(dr, items, field.FFieldName, field.FDefault);
+                        if ((temp = mainProp.Find(s => s.Name.Equals(field.FColumnFIeld))) != null) temp.SetValue(main, value);
+                        if ((temp = childProp.Find(s => s.Name.Equals(field.FColumnFIeld))) != null) temp.SetValue(child, value);
+                    }
+                    if (!string.IsNullOrEmpty(main.FCode))
+                    {
+                        mainList.Add(main);
+                        infoList.Add(child);
+                    }
+                }
+            }
+        }
+
+        /// 
+        /// 根据导入文件生成两个表集合
+        /// 
+        private void GetMaterialFromFile(List fileList, out List mainList, out List childList, out List mainProp, out List childProp)
+        {
+            mainList = new List();
+            childList = new List();
+            List fieldList = BaseBll.GetFileInfoList((int)Constant.FieldInfoType.物料模板导入);
+            mainProp = typeof(TFS_Material).GetTypeInfo().GetProperties().ToList();
+            childProp = typeof(TFS_ViewMaterial).GetTypeInfo().GetProperties().ToList();
+            PropertyInfo temp = null;
+            foreach (string filePath in fileList)
+            {
+                DataTable dt = NPOIHelper.ImportExceltoDt(filePath);
+                List items = new List();
+                for (int i = 0; i < dt.Columns.Count; i++)
+                {
+                    items.Add(dt.Columns[i].ColumnName);
+                }
+                foreach (DataRow dr in dt.Rows)
+                {
+                    //物料表
+                    TFS_Material main = new TFS_Material();
+                    //视图表
+                    TFS_ViewMaterial view = new TFS_ViewMaterial()
+                    {
+                        FViewType = (int)Constant.ViewType.成品视图
+                    };
+                    foreach (var field in fieldList)
+                    {
+                        string value = GetValueByName(dr, items, field.FFieldName, field.FDefault);
+                        if ((temp = mainProp.Find(s => s.Name.Equals(field.FColumnFIeld))) != null)
+                        {
+                            temp.SetValue(main, value);
+                        }
+                        if ((temp = childProp.Find(s => s.Name.Equals(field.FColumnFIeld))) != null)
+                        {
+                            temp.SetValue(view, value);
+                        }
+                    }
+                    if (!string.IsNullOrEmpty(main.FCode))
+                    {
+                        mainList.Add(main);
+                        childList.Add(view);
+                    }
+                }
+            }
+        }
+
+        /// 
+        /// 根据导入文件生成副产物关系表
+        /// 
+        private void GetFuMaterialFromFile(List fileList, int funcType, out List mainList, out List mainProp,
+            out List fieldList)
+        {
+            mainList = new List();
+            fieldList = BaseBll.GetFileInfoList(funcType == 2 ? (int)Constant.FieldInfoType.替代料模板导入 : (int)Constant.FieldInfoType.副产物模板导入);
+            mainProp = typeof(TFS_Material).GetTypeInfo().GetProperties().ToList();
+            PropertyInfo temp = null;
+            foreach (string filePath in fileList)
+            {
+                DataTable dt = NPOIHelper.ImportExceltoDt(filePath);
+                List items = new List();
+                for (int i = 0; i < dt.Columns.Count; i++)
+                {
+                    items.Add(dt.Columns[i].ColumnName);
+                }
+                foreach (DataRow dr in dt.Rows)
+                {
+                    TFS_Material main = new TFS_Material();
+                    foreach (var field in fieldList)
+                    {
+                        string value = GetValueByName(dr, items, field.FFieldName, field.FDefault);
+                        if ((temp = mainProp.Find(s => s.Name.Equals(field.FColumnFIeld))) != null) temp.SetValue(main, value);
+                    }
+                    if (!string.IsNullOrEmpty(main.FCode))
+                    {
+                        if (funcType != 2 && !string.IsNullOrEmpty(main.FFuProductsCode))
+                        {
+                            mainList.Add(main);
+                        }
+                        else if (funcType == 2 && !string.IsNullOrEmpty(main.FSuccedaneumCode))
+                        {
+                            mainList.Add(main);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        #endregion
+    }
+}
\ No newline at end of file
diff --git a/FactorySystemApi/Controllers/MaterialTypeController.cs b/FactorySystemApi/Controllers/MaterialTypeController.cs
new file mode 100644
index 0000000..b24a249
--- /dev/null
+++ b/FactorySystemApi/Controllers/MaterialTypeController.cs
@@ -0,0 +1,136 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Web.Http;
+using FactorySystemModel.EnumModel;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 物料分类接口
+    /// 
+    [UserLoginFilter]
+    public class MaterialTypeController : BaseController
+    {
+        /// 
+        /// 数据处理层
+        /// 
+        public readonly MaterialTypeBll MaterialTypeBll = new MaterialTypeBll();
+        /// 
+        /// 初始化
+        /// 
+        public MaterialTypeController()
+        {
+
+        }
+
+        /// 
+        /// 获取信息数据
+        /// 
+        [HttpPost]
+        public ApiResult GetInfoData(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                int dataId = int.Parse(inParam["FDataID"].ToString());
+                int dataType = int.Parse(inParam["FType"].ToString());
+                string sqlWhere = string.Format("FDataID={0} and FType={1}", dataId, dataType);
+                apiResult.Data = BaseBll.GetTempModel(sqlWhere);
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 修改信息数据
+        /// 
+        [HttpPost]
+        public ApiResult ChangeInfoData(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                inParam.Remove("FAddUser");
+                inParam.Remove("FAddDate");
+                inParam.Remove("FEditUser");
+                inParam.Remove("FEditDate");
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                if (inParam.ContainsKey("FID"))
+                {
+                    inParam.Add("FEditUser", user.FID);
+                    inParam.Add("FEditDate", DateTime.Now);
+                    apiResult.Data = BaseBll.UpdateDataModel(inParam, "TFS_MaterialInfo");
+                }
+                else
+                {
+                    inParam.Add("FEditUser", user.FID);
+                    inParam.Add("FAddDate", DateTime.Now);
+                    apiResult.Data = BaseBll.InsertDataModel(inParam, "TFS_MaterialInfo");
+                }
+            }, apiResult, Request, inParam);
+        }
+
+
+        /// 
+        /// 模板上传处理(重构父方法)
+        /// 
+        protected override object CheckUploadFile(List fileList, int funcType, int userId)
+        {
+            if (funcType == 1)
+            {
+                GetMaterialTypeFromFile(fileList, out List mainList, out List mainProp);
+                return MaterialTypeBll.InsertBatchMaterialTypeData(mainList, mainProp, userId);
+            }
+            return fileList;
+        }
+
+        /// 
+        /// 获取分类信息
+        /// 
+        private void GetMaterialTypeFromFile(List fileList, out List mainList, out List mainProp)
+        {
+            mainList = new List();
+            List fieldList = BaseBll.GetFileInfoList((int)Constant.FieldInfoType.物料信息补充模板导入);
+            mainProp = typeof(TFS_MaterialInfo).GetTypeInfo().GetProperties().ToList();
+            PropertyInfo temp = null;
+            foreach (string filePath in fileList)
+            {
+                DataTable dt = NPOIHelper.ImportExceltoDt(filePath);
+                List items = new List();
+                for (int i = 0; i < dt.Columns.Count; i++)
+                {
+                    items.Add(dt.Columns[i].ColumnName);
+                }
+                foreach (DataRow dr in dt.Rows)
+                {
+                    TFS_MaterialInfo main = new TFS_MaterialInfo();
+                    foreach (TFS_FieldInfo field in fieldList)
+                    {
+                        string value = GetValueByName(dr, items, field.FFieldName, field.FDefault);
+                        if ((temp = mainProp.Find(s => s.Name.Equals(field.FColumnFIeld))) != null) temp.SetValue(main, value);
+                    }
+                    {
+                        //一二级分类处理
+                        if (string.IsNullOrEmpty(main.FQualityTest2)) main.FQualityTest2 = "";
+                        TFS_FieldInfo type1 = fieldList.Find(s => s.FColumnFIeld == "FTypeName1");
+                        string name = type1 != null ? GetValueByName(dr, items, type1.FFieldName, type1.FDefault) : "";
+                        if (string.IsNullOrEmpty(name)) continue;
+                        main.FQualityTest2 += "&$&||&$&" + name;
+
+                        type1 = fieldList.Find(s => s.FColumnFIeld == "FTypeName2");
+                        name = type1 != null ? GetValueByName(dr, items, type1.FFieldName, type1.FDefault) : "";
+                        if (string.IsNullOrEmpty(name)) continue;
+                        main.FQualityTest2 += "&$&||&$&" + name;
+                    }
+                    mainList.Add(main);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/FactorySystemApi/Controllers/OperateLogController.cs b/FactorySystemApi/Controllers/OperateLogController.cs
new file mode 100644
index 0000000..2f6b88b
--- /dev/null
+++ b/FactorySystemApi/Controllers/OperateLogController.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Web.Http;
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.ResponseModel;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 操作日志
+    /// 
+    [UserLoginFilter]
+    public class OperateLogController : ApiController
+    {
+        private readonly OperateLogBll _operateLogBll = new OperateLogBll();
+
+        /// 
+        /// 获取分页
+        /// 
+        [HttpPost]
+        public ApiResult GetPageList(Dictionary inParam)
+        {
+            var apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = new
+                {
+                    List = _operateLogBll.GetPageList(inParam, out int totalCount),
+                    Total = totalCount
+                };
+            }, apiResult, Request);
+        }
+
+        /// 
+        /// 保存操作日志
+        /// 
+        [HttpPost]
+        public ApiResult Save(Dictionary inParams)
+        {
+            var teamId = int.Parse(inParams["teamId"].ToString());
+            var type = int.Parse(inParams["type"].ToString());
+            var desc = inParams["desc"].ToString();
+            var apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (Request.Properties["token"] is ApiAuthInfo user)
+                {
+                    OperateLogBll.Add(teamId, type, desc, user.FID);
+                }
+            }, apiResult, Request);
+        }
+
+    }
+}
diff --git a/FactorySystemApi/Controllers/PackageController.cs b/FactorySystemApi/Controllers/PackageController.cs
new file mode 100644
index 0000000..156ecc5
--- /dev/null
+++ b/FactorySystemApi/Controllers/PackageController.cs
@@ -0,0 +1,298 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.EnumModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using Newtonsoft.Json;
+using SqlSugar.Extensions;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Web.Http;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 包材接口
+    /// 
+    [UserLoginFilter]
+    public class PackageController : BaseController
+    {
+        private readonly PackageBll PackageBll = new PackageBll();
+        /// 
+        /// 初始化
+        /// 
+        public PackageController()
+        {
+            //设置可新增、修改字段
+            InsertField = UpdateField = "FFactory,FCode,FSpecs,FSize,FNetWeight,FGrossWeight,FEditUser,FEditDate";
+        }
+
+        /// 
+        /// 获取子项集合
+        /// 
+        [HttpPost]
+        public ApiResult GetPackageChildList(Dictionary pageParam)
+        {
+            return GetTPageList(pageParam);
+        }
+
+        /// 
+        /// 删除子项信息
+        /// 
+        [HttpPost]
+        public ApiResult DeletePackageChild(Dictionary deleteParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = BaseBll.DeleteDataById(deleteParam, "TFS_PackageChild");
+            }, apiResult, Request, deleteParam);
+        }
+
+        /// 
+        /// 不补充包材信息
+        /// 
+        [HttpPost]
+        public ApiResult NoSupplyPackageChild(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = PackageBll.NoSupplyPackageChild(inParam);
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 获取包材信息
+        /// 
+        [HttpPost]
+        public ApiResult GetPackageInfo(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                inParam.TryGetValue("FTeamID", out object objTeamId);
+                apiResult.Data = PackageBll.GetPackageInfo(int.Parse(objTeamId.ToString()));
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 操作包材信息(包含主子)
+        /// 
+        [HttpPost]
+        public ApiResult UpdatePackageData(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            int mainId = -1;
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                if (inParam.ContainsKey("FEditUser")) inParam.Remove("FEditUser");
+                inParam.Add("FEditUser", user.FID);
+
+                // 20230404 需求变更:增加包材新增、修改入口(自包材清单)
+                if (inParam.ContainsKey("FOperateType"))
+                {
+                    mainId = UpdatePackage(inParam);
+                }
+                else
+                {
+                    mainId = PackageBll.UpdatePackageData(inParam);
+                    if (mainId > 0 && inParam.TryGetValue("FChild", out object childStr))
+                    {
+                        List childList = JsonConvert.DeserializeObject>(childStr.ToString());
+                        if (childList.Count > 0)
+                        {
+                            if (childList[0].FID <= 0)
+                            {
+                                inParam.Remove("FID");
+                                inParam.Add("FID", mainId);
+                                PackageBll.InsertChildData(inParam, childList);
+                            }
+                            //当都有子项代码的时候则完成
+                            if (childList.Find(s => string.IsNullOrEmpty(s.FCode)) == null)
+                            {
+                                PackageBll.TaskCompleted(inParam);
+                            }
+                        }
+                    }
+                }
+                
+                apiResult.Data = mainId;
+            }, apiResult, Request, inParam);
+        }
+
+        /**
+         * 20230404 需求变更
+         * 增加包材新增、修改入口(自包材清单)
+         * 此部分功能和协同无关
+         * 从包材清单进入时,会带入FOperateType参数,且值为1
+         * **/
+        private int UpdatePackage(Dictionary inParam)
+        {
+            object oOperateType;
+            string sOperateType;
+            int mainId = -1;
+
+            inParam.TryGetValue("FOperateType", out oOperateType);
+            sOperateType = oOperateType.ToString();
+
+            if ("1".Equals(sOperateType))
+            {
+                mainId = PackageBll.UpdatePackage(inParam);
+                inParam.Remove("FID");
+                inParam.Add("FID", mainId);
+
+                if (mainId > 0 && inParam.TryGetValue("FChild", out object childStr))
+                {
+                    List childList = JsonConvert.DeserializeObject>(childStr.ToString());
+                    List oldChildren = null;
+                    List newChildren = null;
+
+                    if (childList.Count > 0)
+                    {
+                        // 将子项列表拆分成新旧两个列表
+                        // 当FID > 0时,判断为已存在的子项
+                        oldChildren = childList.Where(o => o.FID > 0).ToList();
+                        // 当FID <= 0时,判断为新的子项
+                        newChildren = childList.Where(n => n.FID <= 0).ToList();
+
+                        if (oldChildren != null && oldChildren.Count > 0)
+                        {
+                            PackageBll.UpdatePackageChild(inParam, oldChildren);
+                        }
+
+                        if (newChildren != null && newChildren.Count > 0)
+                        {
+                            PackageBll.InsertPackageChild(inParam, newChildren);
+                        }
+                    }
+                }
+            }
+
+            return mainId;
+        }
+
+        /// 
+        /// 对接子项代码
+        /// 
+        [HttpPost]
+        public ApiResult DockMDMCode(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = -1;
+                List childList = JsonConvert.DeserializeObject>(inParam["FList"].ToString());
+                if (childList != null && childList.Count > 0)
+                {
+                    var tempList2 = childList.Where(s => s.FMaterialID > 0).ToList();
+                    childList = childList.Where(s => s.FMaterialID < 0).ToList();
+                    foreach (var item in childList)
+                    {
+                        Dictionary temp = new Dictionary();
+                        temp.Add("FName", item.FName);
+                        temp.Add("FGroup", item.FGroup);
+                        temp.Add("FWeightUnit", item.FUnit);
+                        temp.Add("FType", "ZMAT");
+                        item.FCode = GetMdmCode(temp);
+                    }
+                    foreach (var item in tempList2)
+                    {
+                        Dictionary temp = new Dictionary();
+                        temp.Add("FName", item.FName);
+                        temp.Add("FGroup", item.FGroup);
+                        temp.Add("FWeightUnit", item.FUnit);
+                        temp.Add("FType", "ZMAT");
+                        item.FCode = item.FCode;
+                    }
+                    childList = childList.Where(s => s.FMaterialID < 0 && !string.IsNullOrEmpty(s.FCode)).ToList();
+                    tempList2 = tempList2.Where(s => s.FMaterialID > 0 && !string.IsNullOrEmpty(s.FCode)).ToList();
+                    if (childList != null && childList.Count > 0)
+                    {
+                        ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                        apiResult.Data = PackageBll.DockChildCode(inParam, childList, user.FID);
+                    }
+                    else if (tempList2 != null && tempList2.Count > 0)
+                    {
+                        ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                        apiResult.Data = PackageBll.DockChildCode(inParam, tempList2, user.FID);
+                    }
+                    else
+                    {
+                        apiResult.CustomError((int)Constant.ApiResultCode.失败, "对接失败,请稍后再试");
+                    }
+                }
+            }, apiResult, Request, inParam);
+        }
+
+
+        /// 
+        /// 模板上传处理(重构父方法)
+        /// 
+        protected override object CheckUploadFile(List fileList, int funcType, int userId)
+        {
+            if (funcType == 1)
+            {
+                GetPackageFromFile(fileList, out List mainList, out List childList);
+                return PackageBll.InsertBatchPackageData(mainList, childList, userId);
+            }
+            return fileList;
+        }
+
+        /// 
+        /// 梳理包材导入文件
+        /// 
+        private void GetPackageFromFile(List fileList, out List mainList, out List childList)
+        {
+            mainList = new List();
+            childList = new List();
+            List mainProp = typeof(TFS_PackageMain).GetTypeInfo().GetProperties().ToList();
+            List childProp = typeof(TFS_PackageChild).GetTypeInfo().GetProperties().ToList();
+            List fieldList = BaseBll.GetFileInfoList((int)Constant.FieldInfoType.包材信息模板导入);
+
+            PropertyInfo temp = null;
+            foreach (string filePath in fileList)
+            {
+                DataTable dt = NPOIHelper.ImportExceltoDt(filePath);
+                List items = new List();
+                for (int i = 0; i < dt.Columns.Count; i++)
+                {
+                    items.Add(dt.Columns[i].ColumnName);
+                }
+                foreach (DataRow dr in dt.Rows)
+                {
+                    TFS_PackageMain main = new TFS_PackageMain();
+                    TFS_PackageChild child = new TFS_PackageChild();
+                    foreach (TFS_FieldInfo field in fieldList)
+                    {
+                        string value = GetValueByName(dr, items, field.FFieldName, field.FDefault);
+                        string[] temps = field.FColumnFIeld.Split('.');
+                        if ((temp = mainProp.Find(s => s.Name.Equals(temps.Last()))) != null)
+                        {
+                            if (temps.Length == 1 || temps[0] == "TFS_PackageMain")
+                            {
+                                temp.SetValue(main, value);
+                            }
+                        }
+                        if ((temp = childProp.Find(s => s.Name.Equals(temps.Last()))) != null)
+                        {
+                            if (temps.Length == 1 || temps[0] == "TFS_PackageChild")
+                            {
+                                temp.SetValue(child, value);
+                            }
+                        }
+                    }
+                    if (!string.IsNullOrEmpty(main.FCode))
+                    {
+                        mainList.Add(main);
+                        childList.Add(child);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/FactorySystemApi/Controllers/SwaggerHelper/HttpAuthHeaderFilter.cs b/FactorySystemApi/Controllers/SwaggerHelper/HttpAuthHeaderFilter.cs
new file mode 100644
index 0000000..2c0ef9f
--- /dev/null
+++ b/FactorySystemApi/Controllers/SwaggerHelper/HttpAuthHeaderFilter.cs
@@ -0,0 +1,37 @@
+using Swashbuckle.Swagger;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Web.Http;
+using System.Web.Http.Description;
+using System.Web.Http.Filters;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// swagger 增加 AUTH 选项
+    /// 
+    public class HttpAuthHeaderFilter : IOperationFilter
+    {
+        /// 
+        /// 应用
+        /// 
+        /// 
+        /// 
+        /// 
+        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
+
+        {
+            if (operation.parameters == null)
+                operation.parameters = new List();
+            var filterPipeline = apiDescription.ActionDescriptor.GetFilterPipeline(); //判断是否添加权限过滤器
+            var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Instance).Any(filter => filter is IAuthorizationFilter); //判断是否允许匿名方法 
+            var allowAnonymous = apiDescription.ActionDescriptor.GetCustomAttributes().Any();
+            if (isAuthorized && !allowAnonymous)
+            {
+                operation.parameters.Add(new Parameter { name = "token", @in = "header", description = "用户登录token", required = false, type = "string" });
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/FactorySystemApi/Controllers/SwaggerHelper/SwaggerCacheProvider.cs b/FactorySystemApi/Controllers/SwaggerHelper/SwaggerCacheProvider.cs
new file mode 100644
index 0000000..62e3396
--- /dev/null
+++ b/FactorySystemApi/Controllers/SwaggerHelper/SwaggerCacheProvider.cs
@@ -0,0 +1,89 @@
+using Swashbuckle.Swagger;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Web;
+using System.Xml;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// swagger显示控制器的描述
+    /// 
+    public class SwaggerCacheProvider : ISwaggerProvider
+    {
+        private readonly ISwaggerProvider _swaggerProvider;
+        private static ConcurrentDictionary _cache = new ConcurrentDictionary();
+        private readonly string _xml;
+        /// 
+        /// 
+        /// 
+        /// 
+        /// xml文档路径
+        public SwaggerCacheProvider(ISwaggerProvider swaggerProvider, string xml)
+        {
+            _swaggerProvider = swaggerProvider;
+            _xml = xml;
+        }
+
+        public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
+        {
+
+            var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion);
+            SwaggerDocument srcDoc = null;
+            //只读取一次
+            if (!_cache.TryGetValue(cacheKey, out srcDoc))
+            {
+                srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion);
+
+                srcDoc.vendorExtensions = new Dictionary { { "ControllerDesc", GetControllerDesc() } };
+                _cache.TryAdd(cacheKey, srcDoc);
+            }
+            return srcDoc;
+        }
+
+        /// 
+        /// 从API文档中读取控制器描述
+        /// 
+        /// 所有控制器描述
+        public ConcurrentDictionary GetControllerDesc()
+        {
+            string xmlpath = _xml;
+            ConcurrentDictionary controllerDescDict = new ConcurrentDictionary();
+            if (File.Exists(xmlpath))
+            {
+                XmlDocument xmldoc = new XmlDocument();
+                xmldoc.Load(xmlpath);
+                string type = string.Empty, path = string.Empty, controllerName = string.Empty;
+
+                string[] arrPath;
+                int length = -1, cCount = "Controller".Length;
+                XmlNode summaryNode = null;
+                foreach (XmlNode node in xmldoc.SelectNodes("//member"))
+                {
+                    type = node.Attributes["name"].Value;
+                    if (type.StartsWith("T:"))
+                    {
+                        //控制器
+                        arrPath = type.Split('.');
+                        length = arrPath.Length;
+                        controllerName = arrPath[length - 1];
+                        if (controllerName.EndsWith("Controller"))
+                        {
+                            //获取控制器注释
+                            summaryNode = node.SelectSingleNode("summary");
+                            string key = controllerName.Remove(controllerName.Length - cCount, cCount);
+                            if (summaryNode != null && !string.IsNullOrEmpty(summaryNode.InnerText) && !controllerDescDict.ContainsKey(key))
+                            {
+                                controllerDescDict.TryAdd(key, summaryNode.InnerText.Trim());
+                            }
+                        }
+                    }
+                }
+            }
+            return controllerDescDict;
+        }
+    }
+}
\ No newline at end of file
diff --git a/FactorySystemApi/Controllers/SwaggerHelper/SwaggerController.cs b/FactorySystemApi/Controllers/SwaggerHelper/SwaggerController.cs
new file mode 100644
index 0000000..542a78a
--- /dev/null
+++ b/FactorySystemApi/Controllers/SwaggerHelper/SwaggerController.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Web.Http;
+using System.Web.Mvc;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// Swagger接口文档
+    /// 
+    public class SwaggerController : Controller
+    {
+        /// 
+        /// 接口文档首页
+        /// 
+        /// 
+        public ActionResult Index()
+        {
+            return Redirect("/Swagger/ui/index");
+        }
+    }
+}
diff --git a/FactorySystemApi/Controllers/TaskController.cs b/FactorySystemApi/Controllers/TaskController.cs
new file mode 100644
index 0000000..30a5efd
--- /dev/null
+++ b/FactorySystemApi/Controllers/TaskController.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Http;
+using FactorySystemApi.Sap_Group;
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.EnumModel;
+using FactorySystemModel.RequestModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 任务
+    /// 
+    [UserLoginFilter]
+    public class TaskController : ApiController
+    {
+        private readonly TaskBll _taskBll = new TaskBll();
+
+        /// 
+        /// 根据当前用户获取任务列表
+        /// 
+        [HttpPost]
+        public ApiResult GetPageList(TaskQuery tq)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (tq.FIsUser && Request.Properties["token"] is ApiAuthInfo user)
+                {
+                    tq.FUserID = user.FID.ToString();
+                }
+                apiResult.Data = new
+                {
+                    List = _taskBll.GetList(tq, out var totalNumber),
+                    Total = totalNumber
+                };
+            }, apiResult, Request);
+        }
+
+        /// 
+        /// 物料组复核
+        /// 
+        [HttpPost]
+        public ApiResult ReviewMaterialGroup(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                apiResult.Data = _taskBll.ReviewMaterialGroup(inParam, user.FID);
+            }, apiResult, Request);
+        }
+
+        /// 
+        /// 组编号申请
+        /// 
+        [HttpPost]
+        public ApiResult DockMaterialGroup(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                apiResult.Data = 0;
+                List viewList = _taskBll.GetDockGroupView(inParam);
+                if (viewList.Count > 0)
+                {
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    try
+                    {
+                        List itemList = new List();
+                        foreach (TFS_ViewMaterial view in viewList)
+                        {
+                            TFS_Material material = BaseBll.GetTempModel(view.FMaterialID);
+                            Sap_Group.dt_pp071_reqHEAD reqHead = new Sap_Group.dt_pp071_reqHEAD
+                            {
+                                WERKS = view.FFactoryCode,//工厂
+                                DATUV = "20210101",//生效日期
+                                MATNR = string.IsNullOrEmpty(view.FBaseMaterialCode) ? material.FCode : view.FBaseMaterialCode,//物料
+                                PLNAL = "1",//组计数器
+                                VERWE = "1",//用途
+                                STATU = "4",//状态
+                                LOSVN = "0",//从批量
+                                LOSBS = "99999999",//到批量
+                                KTEXT = material.FCraftPathDesc,//工艺路线描述  
+                                ITEM = new Sap_Group.dt_pp071_reqHEADITEM[1]
+                            };
+                            bool is4 = view.FViewType == (int)Constant.ViewType.香基视图;
+                            reqHead.ITEM[0] = new Sap_Group.dt_pp071_reqHEADITEM()
+                            {
+                                BMSCH = view.FViewType == (int)Constant.ViewType.成品视图 ? "100" : "100000", //基本数量
+                                PLNME = view.FViewType == (int)Constant.ViewType.成品视图 ? "KG" : "G", //工序单位
+                                VORNR = "0010", //工序编号
+                                ARBPL = material.FWorkCenter, //工作中心
+                                STEUS = "ZP01", //控制码
+                                LTXA1 = material.FCraftDesc, //工序描述
+                                VGW01 = is4 ? "0.01" : "1", //人工(直接)
+                                VGE01 = "H", //人工直接工时单位
+                                VGW02 = "", // 人工(间接)?
+                                VGE02 = "", //人工间接工时单位?
+                                VGW03 = "", //机器工时
+                                VGE03 = "H", //机器工时单位
+                                VGW04 = is4 ? "0.01" : "100", //电
+                                VGE04 = "KWH", //电单位
+                                VGW05 = "", //水?
+                                VGE05 = "", //水单位?
+                                VGW06 = is4 ? "0.01" : "100", //蒸汽
+                                VGE06 = "TO", //蒸汽单位
+                                VGW07 = is4 ? "0.01" : "100", //物耗仓储运输
+                                VGE07 = "H", //物耗仓储运输单位
+                                VGW08 = is4 ? "0.01" : "100", //其他
+                                VGE08 = "H", //其他单位
+                                VGW09 = "", //环保支出?
+                                VGE09 = "", //环保支出单位?
+                            };
+                            itemList.Add(reqHead);
+                        }
+                        Sap_Group.si_pp071_mcs_senderService sapService = new Sap_Group.si_pp071_mcs_senderService()
+                        {
+                            Credentials = new System.Net.NetworkCredential()
+                            {
+                                UserName = AppSettingsHelper.GetAppSettingVal("Sap_UserName"),
+                                Password = AppSettingsHelper.GetAppSettingVal("Sap_Password")
+                            }
+                        };
+                        Sap_Group.dt_pp071_req reqDatas = new Sap_Group.dt_pp071_req()
+                        {
+                            SOURCESYS = "MCS",
+                            TARGETSYS = "SAP",
+                            UPDATETIME = DateTime.Now.ToString("yyyyMMddHHmmss"),
+                            HEAD = itemList.ToArray()
+                        };
+                        Sap_Group.dt_pp071_resDATA[] resData = sapService.si_pp071_mcs_sender(reqDatas);
+                        ExceptionHelper.AddSystemJournal(Request, reqDatas, resData);
+                        for (int i = 0; i < viewList.Count; i++)
+                        {
+                            viewList[i].FGroupCode = (string.IsNullOrEmpty(resData[i].PLNNR) || resData[i].PLNNR == "null") ? "" : resData[i].PLNNR;
+                        }
+                        if (viewList.Find(s => string.IsNullOrEmpty(s.FGroupCode)) == null)
+                        {
+                            OperateLogBll.Add(viewList[0].FTeamID, (int)Constant.TaskType.组编号申请, "组编号申请对接成功", user.FID);
+                            apiResult.Data = _taskBll.DockMaterialGroup(viewList, user.FID);
+                        }
+                        else
+                        {
+                            string errorMsg = "";
+                            foreach (dt_pp071_resDATA res in resData)
+                            {
+                                errorMsg += res.MATNR + " :" + res.MSGTX;
+                            }
+                            OperateLogBll.Add(viewList[0].FTeamID, (int)Constant.TaskType.组编号申请, "组编号申请对接失败:" + errorMsg + "。", user.FID);
+                            apiResult.Data = 0;
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        ExceptionHelper.WriteMessage("组编号申请对接失败:" + ex.Message, 1);
+                        OperateLogBll.Add(viewList[0].FTeamID, (int)Constant.TaskType.组编号申请, "组编号申请对接失败:" + ex.Message, user.FID);
+                    }
+                }
+            }, apiResult, Request, inParam);
+        }
+
+
+        /// 
+        /// 确认流程完成
+        /// 
+        [HttpPost]
+        public ApiResult SureTeamWork(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                apiResult.Data = _taskBll.SureTeamWork(int.Parse(inParam["FTeamID"].ToString()));
+            }, apiResult, Request);
+        }
+    }
+}
diff --git a/FactorySystemApi/Controllers/TeamworkController.cs b/FactorySystemApi/Controllers/TeamworkController.cs
new file mode 100644
index 0000000..db01944
--- /dev/null
+++ b/FactorySystemApi/Controllers/TeamworkController.cs
@@ -0,0 +1,1044 @@
+using FactorySystemBll;
+using FactorySystemCommon;
+using FactorySystemModel.BusinessModel;
+using FactorySystemModel.EnumModel;
+using FactorySystemModel.ResponseModel;
+using FactorySystemModel.SqlSugarModel;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Web.Http;
+using System.Data;
+using System.IO;
+using Newtonsoft.Json.Linq;
+using FactorySystemApi.Plm_Formula;
+using System.Linq;
+
+namespace FactorySystemApi.Controllers
+{
+    /// 
+    /// 协同接口
+    /// 
+    [UserLoginFilter]
+    public class TeamworkController : BaseController
+    {
+        /// 
+        /// 数据处理层
+        /// 
+        public readonly TeamworkBll TeamworkBll = new TeamworkBll();
+        /// 
+        /// 事项操作日志
+        /// 
+        public readonly OperateLogBll OperateLogBll = new OperateLogBll();
+        /// 
+        /// 初始化
+        /// 
+        public TeamworkController()
+        {
+            //设置可新增、修改字段
+            InsertField = UpdateField = "";
+        }
+
+        /// 
+        /// 获取子项集合
+        /// 
+        [HttpPost]
+        public ApiResult GetTeamworkPageList(Dictionary pageParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                apiResult.Data = new
+                {
+                    List = TeamworkBll.GetTeamworkPageList(pageParam, user.FID, out int totalCount),
+                    Total = totalCount
+                };
+            }, apiResult, Request, pageParam);
+        }
+
+        /// 
+        /// 获取流程集合
+        /// 
+        [HttpPost]
+        public ApiResult GetTeamProcessList(Dictionary pageParam)
+        {
+            return GetTPageList(pageParam, null, "FGroup,FType");
+        }
+
+        /// 
+        /// 新增协同
+        /// 
+        [HttpPost]
+        public override ApiResult InsertDataModel(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                //对接获取
+                //inParam.Add("FMdmCode", GetMdmCode(inParam));
+
+                
+
+                var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
+                inParam.Add("FMdmCode", ts.TotalSeconds.ToString("F0"));
+                if (!inParam.ContainsKey("FMdmCode") || string.IsNullOrEmpty(inParam["FMdmCode"].ToString().Trim()))
+                {
+                    apiResult.Error("获取MDM失败");
+                }
+                else if (TeamworkBll.CheckTeamName(inParam["FSaleCode"].ToString()))
+                {
+                    apiResult.Error("销售号已存在!");
+                }
+                else
+                {
+                    inParam.TryGetValue("FState", out object state);
+                    if (null == state) { 
+                        inParam["FState"] = state = 1; 
+                    }
+                    inParam.Remove("FID");
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    if (inParam.ContainsKey("FAddUser")) { 
+                        inParam["FAddUser"] = user.FID; 
+                    } else { 
+                        inParam.Add("FAddUser", user.FID);
+                    }
+                    if (inParam.ContainsKey("FEditUser")) { 
+                        inParam["FEditUser"] = user.FID;
+                    }  else { 
+                        inParam.Add("FEditUser", user.FID);
+                    }
+                    if (inParam.ContainsKey("FEditDate")) {
+                        inParam["FEditDate"] = DateTime.Now; 
+                    } else {
+                        inParam.Add("FEditDate", DateTime.Now); 
+                    }
+                    //TUser tUser = BaseBll.GetTempModel(user.FID);
+                    TFS_Factory factory = BaseBll.GetTempModel(int.Parse(inParam["factoryValue"].ToString()));
+                    if (inParam.ContainsKey("factoryValue")) 
+                    {
+                        inParam.Remove("factoryValue");
+                    }
+                    inParam.Add("FCreateFactoryID", factory.FID);
+                    inParam.Add("FCreateFactoryCode", factory.FCode);
+                    inParam.Add("FCreateFactoryType", factory.FType);
+                    if (factory.FType != (int)Constant.FactoryType.单工厂) {
+                        factory = BaseBll.GetTempModel(factory.FFactoryID);
+                        inParam.Add("FProdFactoryID", factory.FID);
+                        inParam.Add("FProdFactoryCode", factory.FCode);
+                    } else {
+                        inParam.Add("FProdFactoryID", factory.FID);
+                        inParam.Add("FProdFactoryCode", factory.FCode);
+                    }
+                    //创建 TFS_FTeamwork 
+                    int teamId = BaseBll.InsertDataModel(inParam, "TFS_FTeamwork");
+                    apiResult.Data = teamId;
+                    if (teamId > 0) {
+                        inParam.Add("FID", teamId);
+                        //创建流程
+                        int resultProcessCreate = TeamworkBll.CreateProcessData(teamId, user.FID);
+                        if (resultProcessCreate > 0) {
+                            //直接走下一步
+                            if (state.ToString().Contains("1")) {
+                                TFS_FTeamwork teamwork = BaseBll.GetTempModel(teamId);
+                                List materialList = TeamworkBll.CheckMaterialListByTest(teamwork.FTestCode, teamwork.FVersionCode);
+                                if (materialList.Count == 0) {
+                                    TeamworkBll.CreateProductView(teamwork, null, user.FID);
+                                    //开始BOM下载
+                                    DockGetBomData(teamwork.FID, user.FID);
+                                } else {
+                                    TeamworkBll.CreateProductView(teamwork, materialList, user.FID);
+                                    TeamworkBll.HasMaterialTestCode(teamwork);
+                                    //补充包材规格
+                                    BaseBll.CreateTaskData(teamId, user.FID, "14");
+                                    //成品视图
+                                    BaseBll.CreateTaskData(teamwork.FID, user.FID, "3", teamwork.FCreateFactoryID + "," + teamwork.FProdFactoryID);
+                                    //成品视图物料组复核,有权限的所有
+                                    BaseBll.CreateTaskData(teamwork.FID, user.FID, "12");
+                                    teamwork = BaseBll.GetTempModel(teamId);
+                                    if (teamwork.FPackID == -1) {
+                                        //新增新包材事项
+                                        BaseBll.CreateTaskData(teamId, user.FID, "9");
+                                        BaseBll.UpdateTeamProcess(teamId, (int)Constant.ProcessType.组装BOM包含新包材, 2, 1);
+                                    } else {
+                                        BaseBll.UpdateTeamProcess(teamId, (int)Constant.ProcessType.组装BOM包含新包材, 3, 2);
+                                    }
+                                    BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.协同发起, 3, 2);
+                                    BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.成品视图, 2, 1);
+                                }
+                            }
+                            else
+                            {
+                                BaseBll.CreateTaskData(teamId, user.FID, ((int)Constant.TaskType.配方选择).ToString());
+                                BaseBll.UpdateTeamProcess(teamId, (int)Constant.ProcessType.协同发起, 1, 1);
+                            }
+                        }
+                        else
+                        {
+                            BaseBll.DeleteDataById(teamId, "TFS_FTeamwork", true);
+                            apiResult.Error("流程创建失败,请稍后重试");
+                        }
+                        TeamworkBll.ChangeTeamProcess(teamId);
+                    }
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 修改协同
+        /// 
+        [HttpPost]
+        public override ApiResult UpdateDataModel(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                inParam.TryGetValue("FState", out object state);
+                if (null == state) { inParam["FState"] = state = 1; }
+                inParam.Remove("FEditUser");
+                inParam.Remove("FEditDate");
+                inParam.Add("FEditUser", user.FID);
+                inParam.Add("FEditDate", DateTime.Now);
+                apiResult.Data = UpdateData(inParam);
+                int updateCount = BaseBll.UpdateDataModel(inParam, "TFS_FTeamwork");
+                if (updateCount > 0)
+                {
+                    if (state.ToString().Contains("1"))
+                    {
+                        int teamId = int.Parse(inParam["FID"].ToString());
+                        TFS_FTeamwork teamwork = BaseBll.GetTempModel(teamId);
+                        List materialList = TeamworkBll.CheckMaterialListByTest(teamwork.FTestCode, teamwork.FVersionCode);
+                        if (materialList.Count == 0)
+                        {
+                            TeamworkBll.CreateProductView(teamwork, null, user.FID);
+                            //开始BOM下载
+                            DockGetBomData(teamwork.FID, user.FID);
+                        }
+                        else
+                        {
+                            TeamworkBll.CreateProductView(teamwork, materialList, user.FID);
+                            TeamworkBll.HasMaterialTestCode(teamwork);
+
+                            if (teamwork.FPackID == -1)
+                            {
+                                BaseBll.CreateTaskData(teamId, user.FID, "9");//新增新包材事项
+                                BaseBll.UpdateTeamProcess(teamId, (int)Constant.ProcessType.组装BOM包含新包材, 2, 1);
+                            }
+                            else
+                            {
+                                BaseBll.UpdateTeamProcess(teamId, (int)Constant.ProcessType.组装BOM包含新包材, 3, 2);
+                            }
+                            BaseBll.CreateTaskData(teamId, user.FID, "14");//补充包材规格
+                            BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.协同发起, 3, 2);
+                            BaseBll.UpdateTeamProcess(teamId, (int)Constant.ProcessType.成品视图, 2, 1);
+                        }
+                        TeamworkBll.ChangeTeamProcess(teamId);
+                    }
+                }
+                apiResult.Data = inParam["FID"];
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 手动对接BOM下载
+        /// 
+        [HttpPost]
+        public ApiResult DockDownBomData(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam.ContainsKey("FTeamID"))
+                {
+                    ApiAuthInfo user = Request.Properties["token"] as ApiAuthInfo;
+                    int teamId = int.Parse(inParam["FTeamID"].ToString());
+                    apiResult.Data = DockGetBomData(teamId, user.FID);
+                }
+                else
+                {
+                    apiResult.Error("获取协同信息失败");
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        /// 
+        /// 获取协同视图信息
+        /// 
+        [HttpPost]
+        public ApiResult GetTeamworkView(Dictionary inParam)
+        {
+            ApiResult apiResult = new ApiResult();
+            return ExceptionHelper.TryReturnException(() =>
+            {
+                if (inParam.ContainsKey("FTeamID"))
+                {
+                    inParam.TryGetValue("FViewType", out object objType);
+                    int intType = null == objType ? (int)Constant.TeamViewType.物料视图 : int.Parse(objType.ToString());
+                    string selectSql = "", joinSql = "", whereSql = string.Format("TFS_FTeamwork.FID={0} ", inParam["FTeamID"]);
+                    string basePath = AppDomain.CurrentDomain.BaseDirectory.Trim('\\');
+                    string savePath = basePath + string.Format("\\File\\Temp\\{0}_{1}\\", inParam["FTeamID"], intType);
+                    if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
+                    string tempPath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                    if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath);
+                    switch (intType)
+                    {
+                        case (int)Constant.TeamViewType.配方视图:
+                            savePath += Constant.TeamViewType.配方视图.ToString();
+                            //原始配方
+                            whereSql = string.Format(@"dbo.StrExistInStr(TFS_ViewMaterial.FMaterialID,(select FMaterialFormulaIDs from TFS_FTeamwork where FID={0}))>0
+                            and TFS_ViewMaterial.FFactoryID=(select FProdFactoryID from TFS_FTeamwork where FID={0})", inParam["FTeamID"]);
+                            //生产、计划配方
+                            joinSql = string.Format(@"(dbo.StrExistInStr(TFS_ViewMaterial.FMaterialID,(select FMaterialFormulaIDs from TFS_FTeamwork where FID={0}))>0
+                            and TFS_Material.FSuccedaneumID<1)and TFS_ViewMaterial.FFactoryID=(select FProdFactoryID from TFS_FTeamwork where FID={0})",
+                            inParam["FTeamID"]);
+                            break;
+                        case (int)Constant.TeamViewType.生产工艺路线视图:
+                            savePath += Constant.TeamViewType.生产工艺路线视图.ToString();
+                            //只要生产工厂
+                            whereSql += string.Format(" and TFS_ViewMaterial.FFactoryID in(select FID from TFS_Factory where FType={0})", (int)Constant.FactoryType.单工厂);
+                            //视图类型
+                            List tempInt3_1 = new List
+                            {
+                                (int)Constant.ViewType.成品视图,
+                                (int)Constant.ViewType.半成品视图,
+                                (int)Constant.ViewType.香基视图,
+                                (int)Constant.ViewType.中间品视图
+                            };
+                            whereSql += string.Format(" and TFS_ViewMaterial.FViewType in({0})", string.Join(",", tempInt3_1));
+                            break;
+                        case (int)Constant.TeamViewType.生产版本视图:
+                            savePath += Constant.TeamViewType.生产版本视图.ToString();
+                            //只要生产工厂
+                            whereSql += string.Format(" and TFS_ViewMaterial.FFactoryID in(select FID from TFS_Factory where FType={0})", (int)Constant.FactoryType.单工厂);
+                            //视图类型
+                            List tempInt4_1 = new List
+                            {
+                                (int)Constant.ViewType.成品视图,
+                                (int)Constant.ViewType.半成品视图,
+                                (int)Constant.ViewType.中间品视图,
+                                (int)Constant.ViewType.香基视图
+                            };
+                            whereSql += string.Format(" and TFS_ViewMaterial.FViewType in({0})", string.Join(",", tempInt4_1));
+                            break;
+                        case (int)Constant.TeamViewType.组装BOM视图:
+                            savePath += Constant.TeamViewType.组装BOM视图.ToString();
+                            //一级半成品、包材→无替代料的
+                            whereSql = string.Format(@"(dbo.StrExistInStr(TFS_ViewMaterial.FMaterialID,(select FMaterialHalfIDs from TFS_FTeamwork where FID={0}))>0 or 
+                            TFS_ViewMaterial.FMaterialID in(select FMaterialID from TFS_PackageChild where FTeamID={0} or 
+                            FPackageID=(select FPackID from TFS_FTeamwork where FID={0})))
+                            ", inParam["FTeamID"]);
+                            //只要生产工厂
+                            whereSql += string.Format(" and TFS_ViewMaterial.FFactoryID=(select FProdFactoryID from TFS_FTeamwork where FID={0})", inParam["FTeamID"]);
+                            break;
+                        case (int)Constant.TeamViewType.物料视图:
+                        default:
+                            savePath += Constant.TeamViewType.物料视图.ToString();
+                            whereSql += " and TFS_ViewMaterial.FMaterialID>0";
+                            break;
+                    }
+                    savePath += ".xlsx";
+
+                    bool hasFinish = inParam.ContainsKey("FFinish");
+                    if (hasFinish) savePath = savePath.Replace("\\File\\Temp\\", "\\File\\View\\");
+                    if (!File.Exists(savePath) || !hasFinish)
+                    {
+                        if (inParam.ContainsKey("HalfId")) {
+                            CreateExeclFile(intType, savePath, selectSql, whereSql, joinSql, inParam["FTeamID"].ToString(), inParam["HalfId"].ToString());
+                        } else {
+                            CreateExeclFile(intType, savePath, selectSql, whereSql, joinSql, inParam["FTeamID"].ToString(), "");
+                        }
+                    }
+                    if (inParam.TryGetValue("FType", out objType) && objType.ToString().Equals("1"))
+                    {
+                        if (intType == (int)Constant.TeamViewType.配方视图)
+                        {
+                            apiResult.Data = new
+                            {
+                                List1 = NPOIHelper.ImportExceltoDt(savePath.Replace(".xlsx", "1.xlsx")),
+                                List2 = NPOIHelper.ImportExceltoDt(savePath.Replace(".xlsx", "2.xlsx")),
+                                List3 = NPOIHelper.ImportExceltoDt(savePath.Replace(".xlsx", "3.xlsx"))
+                            };
+                        }
+                        else if (intType == (int)Constant.TeamViewType.生产版本视图 || intType == (int)Constant.TeamViewType.组装BOM视图)
+                        {
+                            //生产和计划
+                            apiResult.Data = new
+                            {
+                                List1 = NPOIHelper.ImportExceltoDt(savePath.Replace(".xlsx", "1.xlsx")),
+                                List2 = NPOIHelper.ImportExceltoDt(savePath.Replace(".xlsx", "2.xlsx")),
+                            };
+                        }
+                        else
+                        {
+                            apiResult.Data = new
+                            {
+                                List1 = NPOIHelper.ImportExceltoDt(savePath)
+                            };
+                        }
+                    }
+                    else
+                    {
+                        apiResult.Data = Request.RequestUri.AbsoluteUri.Replace(Request.RequestUri.AbsolutePath, "") + savePath.Replace(basePath, "").Replace("\\", "/");
+                    }
+                }
+                else
+                {
+                    apiResult.Error("获取协同信息失败");
+                }
+            }, apiResult, Request, inParam);
+        }
+
+        #region 对接相关
+
+        /// 
+        /// 对接获取Bom数据
+        /// 
+        private int DockGetBomData(int teamId, int userId)
+        {
+            int result = 0;
+            List specifList = new List() { new Specifications() };
+            TFS_FTeamwork teamwork = BaseBll.GetTempModel(teamId);
+            try
+            {
+                TFS_Formula formula = BaseBll.GetTempModel(teamwork.FFormulaID);
+                OAService oAService = new OAService();
+                specifList[0].Code = formula.FTestCode;
+                specifList[0].Version = formula.FVersionCode;
+                RestResult restResult = oAService.GetSpecificationsList(specifList.ToArray());
+                string bomStr = restResult.data == null ? "[]" : restResult.data.ToString();
+                ExceptionHelper.AddSystemJournal(Request, specifList[0], restResult, userId, "DockGetBomData");
+                List bomList = JsonConvert.DeserializeObject>(bomStr);
+                if (bomList != null && bomList.Count > 0)
+                {
+                    //BOM数据梳理
+                    Dictionary bomResult = CheckBomMaterial(bomList, 0, new Random());
+                    bomResult["formulaIds"] = bomResult["formulaIds"].ToString().Replace(",,", ",").Trim(',');
+                    bomResult["halfIds"] = bomResult["halfIds"].ToString().Replace(",,", ",").Trim(',');
+                    List mateList = (List)bomResult["mateList"];
+                    List viewList = (List)bomResult["viewList"];
+                    //日志分开写12
+                    ExceptionHelper.AddSystemJournal(Request, null, mateList, userId, "CheckBomMaterial1");
+                    ExceptionHelper.AddSystemJournal(Request, null, viewList, userId, "CheckBomMaterial2");
+                    //BOM数据处理
+                    if (TeamworkBll.AnalysisBomData(mateList, viewList, teamwork.FID, userId, null, bomResult["halfIds"].ToString()))
+                    {
+                        mateList.Clear(); viewList.Clear();
+                        List formulaList = (List)bomResult["formulaList"];
+                        //日志分开写3
+                        ExceptionHelper.AddSystemJournal(Request, null, new { formulaIds = bomResult["formulaIds"], halfIds = bomResult["halfIds"], formulaList }, userId, "CheckBomMaterial3");
+                        //2022-09-22:要求写入配方中间表(有子集就是配方,走配方逻辑)
+                        new FormulaBll().DockingRecipeData(formulaList, userId);
+                        //修改协同主数据
+                        Dictionary upParam = new Dictionary
+                        {
+                            { "FID", teamwork.FID },
+                            { "FBomState", 1 },
+                            { "FBomJson", bomStr }
+                        };
+                        BaseBll.UpdateDataModel(upParam, "TFS_FTeamwork");
+                        //协同发起事项
+                        BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.协同发起, 3, 2);
+                        BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.成品视图, 2, 1);
+                        if (teamwork.FPackID == -1)
+                        {
+                            BaseBll.CreateTaskData(teamwork.FID, userId, "9");//新增新包材事项
+                            BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.组装BOM包含新包材, 2, 1);
+                        }
+                        else
+                        {
+                            BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.组装BOM包含新包材, 3, 2);
+                        }
+                        //成品视图
+                        BaseBll.CreateTaskData(teamwork.FID, userId, "3", teamwork.FCreateFactoryID + "," + teamwork.FProdFactoryID);
+                        //补充包材规格
+                        BaseBll.CreateTaskData(teamwork.FID, userId, "14");
+                        //成品视图物料组复核,有权限的所有
+                        BaseBll.CreateTaskData(teamwork.FID, userId, "12");
+                        OperateLogBll.Add(teamwork.FID, 2, "BOM下载成功", userId);
+                        TeamworkBll.ChangeTeamProcess(teamwork.FID);
+                        result = 1;
+                    }
+                }
+                else
+                {
+                    OperateLogBll.Add(teamwork.FID, 2, "BOM下载失败,BOM数据为空", userId);
+                    BaseBll.CreateTaskData(teamwork.FID, userId, ((int)Constant.TaskType.BOM下载).ToString());
+                    BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.协同发起, 2, 1);
+                    TeamworkBll.ChangeTeamProcess(teamwork.FID);
+                }
+            }
+            catch (Exception ex)
+            {
+                OperateLogBll.Add(teamwork.FID, 2, "BOM下载失败,请稍后重试(对接)", userId);
+                BaseBll.CreateTaskData(teamwork.FID, userId, ((int)Constant.TaskType.BOM下载).ToString());
+                BaseBll.UpdateTeamProcess(teamwork.FID, (int)Constant.ProcessType.协同发起, 2, 1);
+                ExceptionHelper.AddSystemJournal(Request, specifList[0], ex.Message, userId, "DockGetBomData");
+            }
+            //日志添加
+            return result;
+        }
+
+        /// 
+        /// 递归梳理BOM
+        /// 
+        /// 需要解析的集合
+        /// 层级
+        /// 内部定义会有问题,需要外部传入
+        /// 父子级关系ID
+        private Dictionary CheckBomMaterial(List dataList, int materLevel, Random random, int parentId = -1)
+        {
+            List mateList = new List();
+            List viewList = new List();
+            List formulaList = new List();
+            string ids1 = "", ids2 = "";//需求变更ids2弃用
+            if (dataList != null && dataList.Count > 0)
+            {
+                foreach (BomModel item in dataList)
+                {
+                    int dataId = random.Next(1, 99) * random.Next(1, 99);
+                    dataId += random.Next(10, 99) * random.Next(10, 99) * 2;
+                    dataId += random.Next(1, 99) * random.Next(1, 99) * 10;
+                    TFS_Material material = new TFS_Material()
+                    {
+                        FID = DateTime.Now.DayOfYear * 10 + dataId,
+                        FPlmCode = item.Code,
+                        FName = item.Name,
+                        //FCode = item.Code,
+                        FType = item.Type.ToLower(),
+                        FTestCode = item.TestNO,
+                        FVersionCode = item.Version,
+                        FParentID = parentId
+                    };
+
+                    TFS_ViewMaterial view = new TFS_ViewMaterial()
+                    {
+                        //FBaseFameCode = "",
+                        //FBaseMaterialCode = material.FCode,
+                        FBaseTestCode = material.FTestCode,
+                        FExtraQuantity = item.Quantity,
+                        FBaseMaterialDesc = material.FName,
+                        FLevel = materLevel,
+                        FBomMaterialID = material.FID
+                    };
+                    if (material.FType == "a" || material.FType == "f")
+                    {
+                        material.FType = "20";
+                    }
+                    else if (material.FType == "b" || material.FType == "bb" || material.FType == "i" || material.FType == "iz")
+                    {
+                        material.FType = "30";
+                    }
+                    else
+                    {
+                        material.FType = "40";
+                    }
+
+                    if (materLevel == 0)
+                    {
+                        material.FType = "20";
+                        ids2 = material.FID + "";
+                    }
+                    if (material.FType == "40")
+                    {
+                        material.FCode = material.FPlmCode;
+                        material.FPlmCode = "";
+                    }
+
+                    int.TryParse(material.FType, out int mType);
+                    if (materLevel == 1) ids1 += material.FID + ",";
+                    switch (mType)
+                    {
+                        case (int)Constant.MaterialType.产成品:
+                            view.FViewType = 1;
+                            break;
+                        case (int)Constant.MaterialType.半成品:
+                            view.FViewType = (int)Constant.ViewType.半成品视图;
+                            break;
+                        case (int)Constant.MaterialType.中间品:
+                            view.FViewType = material.FName.Contains("香基") ? (int)Constant.ViewType.香基视图 : (int)Constant.ViewType.中间品视图;
+                            break;
+                        case (int)Constant.MaterialType.原辅料:
+                            view.FViewType = (int)Constant.ViewType.原料视图;
+                            view.FBaseMaterialCode = material.FCode;
+                            //默认值不同
+                            view.FPurchaseCompany = "kg";
+                            view.FPurchaseCompanyCount = "1";
+                            view.FPurchaseBaseCompanyCount = "1000";
+                            view.FSaleAccountSettingGroup = "15";
+                            view.FStorageTotalShelfLife = "7300";
+                            view.FMRP2ExternalStoragePlace = "1000";
+                            view.FPlanUnlimitedOverDelivery = "";
+                            view.FAccountPriceControl = "V";
+                            view.FAccountPriceDetermine = "2";
+                            view.FAccountAccessType = "1000";
+                            view.FAccountSaleOrderInventory = "1010";
+                            break;
+                    }
+                    mateList.Add(material);
+                    viewList.Add(view);
+
+                    if (item.Specifications != null && item.Specifications.Count > 0)
+                    {
+                        Dictionary childData = CheckBomMaterial(item.Specifications, materLevel + 1, random, material.FID);
+                        if (materLevel >= 1)
+                        {
+                            formulaList.Add(new TFS_Formula()
+                            {
+                                FID = -1,
+                                FName = material.FName,
+                                FType = material.FType,
+                                FTestCode = material.FTestCode,
+                                FVersionCode = material.FVersionCode,
+                                FPlmCode = string.IsNullOrEmpty(material.FPlmCode) ? material.FCode : material.FPlmCode
+                            });
+                        }
+                        mateList.AddRange((List)childData["mateList"]);
+                        viewList.AddRange((List)childData["viewList"]);
+                        formulaList.AddRange((List)childData["formulaList"]);
+                        ids1 = ids1.Trim(',') + "," + childData["formulaIds"] + ",";
+                        ids2 = ids2.Trim(',') + "," + childData["halfIds"] + ",";
+                    }
+                }
+            }
+            return new Dictionary
+            {
+                { "mateList", mateList },
+                { "viewList", viewList },
+                { "formulaList", formulaList },
+                { "formulaIds", ids1.Trim(',') },
+                { "halfIds", ids2.Trim(',') }
+            };
+        }
+
+        /// 
+        /// 获取JObject值
+        /// 
+        private string GetValue(JObject item, string optStr)
+        {
+            string[] options = optStr.Split('&');
+            for (int i = 0; i < options.Length; i++)
+            {
+                if (item.ContainsKey(options[i])) return item[options[i]].ToString();
+            }
+            return "";
+        }
+
+        #endregion
+
+        #region 内部方法
+
+        /// 
+        /// 创建Excel文件
+        /// 
+        private void CreateExeclFile(int intType, string savePath, string selectSql, string whereSql, string joinSql, string teamId, string halfId)
+        {
+            List filedList = TeamworkBll.GetTeamworkViewField(intType);
+            foreach (TFS_ViewFieldInfo field in filedList)
+            {
+                if (string.IsNullOrEmpty(field.FField))
+                {
+                    selectSql += string.Format("'{0}' as '{1}'", string.IsNullOrEmpty(field.FDefault) ? "" : field.FDefault, field.FName);
+                }
+                else
+                {
+                    selectSql += string.Format("isnull({0},'') as '{1}'", field.FField, field.FName);
+                }
+                selectSql += ",";
+            }
+            selectSql = selectSql.Replace("@FTeamID@", teamId);
+            selectSql = selectSql.Replace("@FMaterialID@", halfId);
+            DataTable dataList = new DataTable();
+            if (intType == (int)Constant.TeamViewType.组装BOM视图)
+            {
+                dataList = TeamworkBll.GetTeamworkViewData2(selectSql.Trim(','), whereSql, joinSql);
+                dataList.Columns.Remove("FMaterialID");
+                DataTable dataList2 = dataList.Copy();
+                List hasField = new List();
+                hasField.Add(dataList.Columns.Contains("BOM用途"));
+                hasField.Add(dataList.Columns.Contains("子项序号"));
+                hasField.Add(dataList.Columns.Contains("组件损耗率"));
+                hasField.Add(dataList.Columns.Contains("固定损耗数量"));
+                int rowCount = dataList.Rows.Count;
+
+                //生产组装BOM处理
+                for (int i = 0; i < rowCount; i++)
+                {
+                    if (hasField[0]) dataList.Rows[i]["BOM用途"] = "Y";
+                    if (hasField[1]) dataList.Rows[i]["子项序号"] = (i + 1) * 10;
+                    if (hasField[2]) dataList.Rows[i]["组件损耗率"] = "";
+                    if (hasField[3]) dataList.Rows[i]["固定损耗数量"] = "";
+                    dataList.Rows[i]["可选文本"] = "生产组装BOM";
+                }
+                NPOIHelper.ExportDTtoExcel(dataList, "Sheet1", savePath.Replace(".xlsx", "1.xlsx"));
+
+                //计划组装BOM处理
+                DataTable dataList3 = dataList2.Copy();
+                for (int i = 0; i < rowCount; i++)
+                {
+                    if (hasField[1])
+                    {
+                        dataList2.Rows[i]["子项序号"] = (i + 1) * 10;
+                    }
+                    if (hasField[3] && !string.IsNullOrEmpty(dataList2.Rows[i]["固定损耗数量"].ToString()) && !dataList2.Rows[i]["固定损耗数量"].ToString().Equals("0")) 
+                    {
+                        dataList3.Rows[i]["组件数量"] = "";
+                        dataList3.Rows[i]["组件损耗率"] = "";
+                        dataList3.Rows[i]["固定损耗数量"] = "X";
+                        if (hasField[1]) dataList3.Rows[i]["子项序号"] = (i + rowCount + 1) * 10;
+                        dataList3.Rows[i]["可选文本"] = "计划组装BOM";
+                        dataList2.ImportRow(dataList3.Rows[i]);
+                    }
+                    dataList2.Rows[i]["固定损耗数量"] = "";
+                    dataList2.Rows[i]["可选文本"] = "计划组装BOM";
+                }
+                NPOIHelper.ExportDTtoExcel(dataList2, "Sheet1", savePath.Replace(".xlsx", "2.xlsx"));
+                for (int i = 0; i < dataList2.Rows.Count; i++)
+                {
+                    if (i == 0) dataList.Rows.Add("[换色]");
+                    dataList.ImportRow(dataList2.Rows[i]);
+                }
+            }
+            else if (intType == (int)Constant.TeamViewType.配方视图)
+            {
+                //原始配方
+                DataTable dataList1 = TeamworkBll.GetTeamworkViewData2(selectSql.Trim(','), whereSql);
+                TFS_FTeamwork teamwork = BaseBll.GetTempModel("FID=" + teamId);
+                List bfList = JsonConvert.DeserializeObject>(teamwork.FBomFormula);
+                //SetBFData(null, bfList, dataList1);
+                List forIdList = teamwork.FMaterialFormulaIDs.Split(',').ToList();
+
+                DataTable tempTable = dataList1.Copy();
+                tempTable.Rows.Clear();
+                DataTable totalTable = tempTable.Copy();
+
+                List rowList = SetXHData(null, bfList, dataList1, forIdList, 1);
+                foreach (var item in rowList)
+                {
+                    tempTable.Rows.Add(item.ItemArray);
+                    totalTable.Rows.Add(item.ItemArray);
+                }
+                tempTable.Columns.Remove("FMaterialID");
+                NPOIHelper.ExportDTtoExcel(tempTable, "Sheet1", savePath.Replace(".xlsx", "1.xlsx"));
+                tempTable = dataList1.Copy();
+                tempTable.Rows.Clear();
+
+                rowList = SetXHData(null, bfList, dataList1, forIdList, 2);
+                totalTable.Rows.Add("[换色]");
+                foreach (var item in rowList)
+                {
+                    tempTable.Rows.Add(item.ItemArray);
+                    totalTable.Rows.Add(item.ItemArray);
+                }
+                tempTable.Columns.Remove("FMaterialID");
+                NPOIHelper.ExportDTtoExcel(tempTable, "Sheet1", savePath.Replace(".xlsx", "2.xlsx"));
+                tempTable = dataList1.Copy();
+                tempTable.Rows.Clear();
+
+                rowList = SetXHData(null, bfList, dataList1, forIdList, 3);
+                totalTable.Rows.Add("[换色]");
+                foreach (var item in rowList)
+                {
+                    tempTable.Rows.Add(item.ItemArray);
+                    totalTable.Rows.Add(item.ItemArray);
+                }
+                tempTable.Columns.Remove("FMaterialID");
+                NPOIHelper.ExportDTtoExcel(tempTable, "Sheet1", savePath.Replace(".xlsx", "3.xlsx"));
+                tempTable.Rows.Clear();
+
+                dataList = totalTable;
+                dataList.Columns.Remove("FMaterialID");
+
+                /*dataList1.Columns.Remove("FMaterialID");
+                List hasField = new List
+                {
+                    dataList1.Columns.Contains("BOM用途"),
+                    dataList1.Columns.Contains("子项序号"),
+                    dataList1.Columns.Contains("组件损耗率"),
+                    dataList1.Columns.Contains("固定损耗数量"),
+                    dataList1.Columns.Contains("BOM项目文本2")
+                };
+                int rowCount = dataList1.Rows.Count;
+                for (int i = 0; i < rowCount; i++)
+                {
+                    //if (hasField[0]) dataList1.Rows[i]["BOM用途"] = "1";
+                    if (hasField[1]) dataList1.Rows[i]["子项序号"] = (i + 1) * 10;
+                    if (hasField[2]) dataList1.Rows[i]["组件损耗率"] = "";
+                    if (hasField[3]) dataList1.Rows[i]["固定损耗数量"] = "";
+                    if (hasField[4]) dataList1.Rows[i]["BOM项目文本2"] = "";
+                    dataList1.Rows[i]["可选文本"] = "原始BOM配方";
+                    dataList.ImportRow(dataList1.Rows[i]);
+                }
+                NPOIHelper.ExportDTtoExcel(dataList1, "Sheet1", savePath.Replace(".xlsx", "1.xlsx"));
+                dataList = dataList1.Copy();
+                //生产配方
+                DataTable dataList2 = TeamworkBll.GetTeamworkViewData2(selectSql.Trim(','), joinSql.Trim(','));
+                SetBFData(null, bfList, dataList2);
+                dataList2.Columns.Remove("FMaterialID");
+                DataTable dataList3 = dataList2.Copy();
+                string fuIds = "";
+                dataList.Rows.Add("[换色]");
+                rowCount = dataList2.Rows.Count;
+                for (int i = 0; i < rowCount; i++)
+                {
+                    if (hasField[0]) dataList2.Rows[i]["BOM用途"] = "Y";
+                    if (hasField[1]) dataList2.Rows[i]["子项序号"] = (i + 1) * 10;
+                    if (hasField[2]) dataList2.Rows[i]["组件损耗率"] = "";
+                    if (hasField[3]) dataList2.Rows[i]["固定损耗数量"] = "";
+                    if (hasField[4])
+                    {
+                        if (!string.IsNullOrEmpty(dataList3.Rows[i]["BOM项目文本2"].ToString()))
+                        {
+                            fuIds += dataList3.Rows[i]["BOM项目文本2"].ToString() + ",";
+                        }
+                        dataList2.Rows[i]["BOM项目文本2"] = "";
+                    }
+                    dataList2.Rows[i]["可选文本"] = "生产BOM配方";
+                    dataList.ImportRow(dataList2.Rows[i]);
+                }
+                if (!string.IsNullOrEmpty(fuIds))
+                {
+                    joinSql = string.Format("TFS_ViewMaterial.FMaterialID in({0}) and ", fuIds.Trim(',')) + joinSql.Substring(joinSql.IndexOf("TFS_ViewMaterial.FFactoryID"));
+                    DataTable tempList = TeamworkBll.GetTeamworkViewData2(selectSql.Trim(','), joinSql.Trim(','));
+                    tempList.Columns.Remove("FMaterialID");
+                    foreach (DataRow dr in tempList.Rows)
+                    {
+                        dataList2.ImportRow(dr);
+                        dataList3.ImportRow(dr);
+                        if (hasField[0])
+                        {
+                            dataList2.Rows[dataList2.Rows.Count - 1]["BOM用途"] = "Y";
+                            dataList3.Rows[dataList3.Rows.Count - 1]["BOM用途"] = "1";
+                        }
+                        if (hasField[1])
+                        {
+                            dataList2.Rows[dataList2.Rows.Count - 1]["子项序号"] = dataList2.Rows.Count * 10;
+                            dataList3.Rows[dataList3.Rows.Count - 1]["子项序号"] = dataList2.Rows.Count * 10;
+                        }
+                        if (hasField[2])
+                        {
+                            dataList2.Rows[dataList2.Rows.Count - 1]["组件损耗率"] = "";
+                            dataList3.Rows[dataList3.Rows.Count - 1]["组件损耗率"] = "";
+                        }
+                        if (hasField[3])
+                        {
+                            dataList2.Rows[dataList2.Rows.Count - 1]["固定损耗数量"] = "";
+                        }
+                        if (hasField[4])
+                        {
+                            dataList2.Rows[dataList2.Rows.Count - 1]["BOM项目文本2"] = "";
+                            dataList3.Rows[dataList3.Rows.Count - 1]["BOM项目文本2"] = "";
+                        }
+                        dataList2.Rows[dataList2.Rows.Count - 1]["可选文本"] = "生产BOM配方";
+                        dataList3.Rows[dataList3.Rows.Count - 1]["可选文本"] = "计划BOM配方";
+                        dataList.ImportRow(dataList2.Rows[dataList2.Rows.Count - 1]);
+                    }
+                }
+                NPOIHelper.ExportDTtoExcel(dataList2, "Sheet1", savePath.Replace(".xlsx", "2.xlsx"));
+                //计划配方
+                dataList.Rows.Add("[换色]");
+                rowCount = dataList3.Rows.Count;
+                for (int i = 0; i < rowCount; i++)
+                {
+                    if (hasField[0]) dataList3.Rows[i]["BOM用途"] = "1";
+                    if (hasField[1]) dataList3.Rows[i]["子项序号"] = (i + 1) * 10;
+                    if (hasField[3])
+                    {
+                        if (!string.IsNullOrEmpty(dataList3.Rows[i]["固定损耗数量"].ToString()))
+                        {
+                            DataRow dr = dataList3.Copy().Rows[i];
+                            dr["组件数量"] = dataList3.Rows[i]["固定损耗数量"];
+                            dr["组件损耗率"] = "";
+                            dr["固定损耗数量"] = "X";
+                            if (hasField[4]) dr["BOM项目文本2"] = "";
+                            if (hasField[1]) dr["子项序号"] = (dataList3.Rows.Count + 1) * 10;
+                            dr["可选文本"] = "计划BOM配方";
+                            dataList3.ImportRow(dr);
+                        }
+                        dataList3.Rows[i]["固定损耗数量"] = "";
+                        if (hasField[4]) dataList3.Rows[i]["BOM项目文本2"] = "";
+                    }
+                    dataList3.Rows[i]["可选文本"] = "计划BOM配方";
+                }
+                NPOIHelper.ExportDTtoExcel(dataList3, "Sheet1", savePath.Replace(".xlsx", "3.xlsx"));
+                rowCount = dataList3.Rows.Count;
+                for (int i = 0; i < rowCount; i++) { dataList.ImportRow(dataList3.Rows[i]); }*/
+            }
+            else
+            {
+                dataList = TeamworkBll.GetTeamworkViewData(selectSql.Trim(','), whereSql, joinSql);
+                dataList.Columns.Remove("FMaterialID");
+                if (intType == (int)Constant.TeamViewType.生产版本视图)
+                {
+                    NPOIHelper.ExportDTtoExcel(dataList, "Sheet1", savePath.Replace(".xlsx", "1.xlsx"));
+                    int rowCount = dataList.Rows.Count;
+                    bool isChange = dataList.Columns.Contains("BOM用途");
+
+                    DataTable dataList2 = dataList.Copy();
+                    for (int i = 0; i < rowCount; i++)
+                    {
+                        if (isChange && dataList2.Rows[i]["BOM用途"].ToString() == "1")
+                        {
+                            dataList2.Rows[i]["BOM用途"] = "Y";
+                        }
+                    }
+                    NPOIHelper.ExportDTtoExcel(dataList2, "Sheet1", savePath.Replace(".xlsx", "2.xlsx"));
+                    for (int i = 0; i < rowCount; i++)
+                    {
+                        if (i == 0) dataList.Rows.Add("[换色]");
+                        dataList.ImportRow(dataList2.Rows[i]);
+                    }
+                }
+            }
+            if (intType == (int)Constant.TeamViewType.物料视图)
+            {
+                //處理名字點
+            }
+            NPOIHelper.ExportDTtoExcel(dataList, "Sheet1", savePath);
+        }
+
+        /// 
+        /// 梳理父项
+        /// 
+        private void SetBFData(TFS_ViewMaterial parent, List bfList, DataTable dtList)
+        {
+            if (bfList != null && bfList.Count > 0)
+            {
+                foreach (var item in bfList)
+                {
+                    if (parent != null)
+                    {
+                        DataRow[] rows = dtList.Select("FMaterialID=" + item.mId);
+                        foreach (var row in rows)
+                        {
+                            row["父项编码"] = parent.FBaseMaterialCode;
+                            row["父项描述"] = parent.FBaseMaterialDesc;
+                            row["基本数量"] = parent.FExtraQuantity;
+                        }
+                    }
+                    if (item.childs.Count > 0)
+                    {
+                        SetBFData(BaseBll.GetTempModel("FMaterialID=" + item.mId), item.childs, dtList);
+                    }
+                }
+            }
+        }
+
+        /// 
+        /// 梳理序号(type:1原始,2生产,3计划)
+        /// 
+        private List SetXHData(BomFormulaDto parent, List bfList, DataTable dtList, List forIdList, int type)
+        {
+            if (bfList != null && bfList.Count > 0)
+            {
+                //物料、替代、副产、固损
+                List> rowList = new List>() { new List(), new List(), new List(), new List() };
+                foreach (BomFormulaDto item in bfList)
+                {
+                    DataRow[] rows = dtList.Select("FMaterialID=" + item.mId);
+                    if (rows != null && rows.Length > 0)
+                    {
+                        string mId = rows[0]["FMaterialID"].ToString();
+                        if (forIdList.IndexOf(mId) != -1)
+                        {
+                            DataRow main = dtList.Copy().Select("FMaterialID=" + item.mId)[0];
+                            if (parent != null)
+                            {
+                                TFS_ViewMaterial temp = BaseBll.GetTempModel