using Business.Core.Utilities;
using Business.Domain;
using Business.Dto;
using Business.EntityFrameworkCore.SqlRepositories;
using Business.SaleForecast;
using Business.StructuredDB.WMS;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace Business.SaleForecastManagement
{
///
/// 产能分析
///
public class MonthlyCapacityLoadAppService : ApplicationService, IMonthlyCapacityLoadAppService, ITransientDependency
{
#region 服务
///
/// 物料
///
private IRepository _ic_item;
///
/// 生产线明细
///
private ISqlRepository _prodLineDetail;
///
/// 工作日历数据
///
private ISqlRepository _shopCalendarWorkCtr;
///
/// 产线休息时间记录表
///
private ISqlRepository _qualityLineWorkDetail;
///
/// 节假日记录表
///
private ISqlRepository _holidayMaster;
///
/// 工作单元
///
private readonly IUnitOfWorkManager _unitOfWorkManager;
///
/// 日志
///
private readonly ICurrentTenant _currentTenant;
///
/// 年度生产需求大纲
///
private IRepository _yearDemandManagement;
///
/// 年度生产需求大纲历史记录
///
private IRepository _yearDemandManagementHistory;
///
/// 国内终端预测
///
private IRepository _domesticTerminalFcst;
///
/// 海外销售预测
///
private IRepository _overseasSaleFcst;
///
/// 平台预测收集
///
private IRepository _platformFcstCollect;
///
/// 标准物料规格型号设置表
///
private IRepository _standardItemModelSet;
///
/// 库存明细表
///
private ISqlRepository _locationDetail;
///
/// 工单
///
private ISqlRepository _workOrdMaster;
///
/// 整体需求计划
///
private IRepository _overallDemandPlan;
///
/// 月度产能共识表
///
private IRepository _monthlyProdCapacity;
///
/// 月度产能共识明细表
///
private IRepository _monthlyProdCapacityDtl;
///
/// SKU版本维护表
///
private IRepository _skuVersionSet;
///
/// 计划订单表
///
private IRepository _crm_planorder;
///
/// 平台库存监控月份
///
private IRepository _platStockMonitorSetting;
///
/// 平台库存表
///
private IRepository _platformInventory;
///
/// 参数设置表
///
private ISqlRepository _generalizedCodeMaster;
///
/// SAP库存表
///
private ISqlRepository _SAPInv;
///
/// 雪花算法
///
SnowFlake help = new SnowFlake();
#endregion
#region 构造函数
///
/// 构造函数
///
public MonthlyCapacityLoadAppService(
IRepository ic_item,
ISqlRepository prodLineDetail,
ISqlRepository shopCalendarWorkCtr,
ISqlRepository qualityLineWorkDetail,
ISqlRepository holidayMaster,
IRepository monthlyProdCapacity,
IUnitOfWorkManager unitOfWorkManager,
ICurrentTenant currentTenant,
IRepository standardItemModelSet,
IRepository yearDemandManagement,
IRepository domesticTerminalFcst,
IRepository overseasSaleFcst,
IRepository platformFcstCollect,
IRepository yearDemandManagementHistory,
ISqlRepository locationDetail,
ISqlRepository workOrdMaster,
IRepository overallDemandPlan,
IRepository monthlyProdCapacityDtl,
IRepository skuVersionSet,
IRepository crm_planorder,
IRepository platStockMonitorSetting,
IRepository platformInventory,
ISqlRepository generalizedCodeMaster,
ISqlRepository SAPInv
)
{
_ic_item = ic_item;
_prodLineDetail = prodLineDetail;
_shopCalendarWorkCtr = shopCalendarWorkCtr;
_qualityLineWorkDetail = qualityLineWorkDetail;
_holidayMaster = holidayMaster;
_monthlyProdCapacity = monthlyProdCapacity;
_unitOfWorkManager = unitOfWorkManager;
_currentTenant = currentTenant;
_standardItemModelSet = standardItemModelSet;
_yearDemandManagement= yearDemandManagement;
_domesticTerminalFcst= domesticTerminalFcst;
_overseasSaleFcst = overseasSaleFcst;
_platformFcstCollect= platformFcstCollect;
_yearDemandManagementHistory= yearDemandManagementHistory;
_locationDetail = locationDetail;
_workOrdMaster = workOrdMaster;
_overallDemandPlan = overallDemandPlan;
_monthlyProdCapacityDtl = monthlyProdCapacityDtl;
_skuVersionSet= skuVersionSet;
_crm_planorder= crm_planorder;
_platStockMonitorSetting = platStockMonitorSetting;
_platformInventory = platformInventory;
_generalizedCodeMaster = generalizedCodeMaster;
_SAPInv = SAPInv;
}
#endregion
///
/// 月度需求预测更新
///
///
///
///
public async Task DemandAnalysis(InputDto input)
{
//1.0 获取当前年年度生产需求大纲
List yearDemands = _yearDemandManagement.GetListAsync(p => p.Year == input.year && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result.OrderBy(p=>p.OrderNum).ToList();
//1.0 获取下一年年度生产大纲
List nextYearDemands = _yearDemandManagement.GetListAsync(p => p.Year == (input.year+1) && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result.OrderBy(p => p.OrderNum).ToList();
//记录历史版本:当前年+下一年
var updateMonth = input.year.ToString() + "-" + input.month.ToString("00");
var histories = ObjectMapper.Map, List>(yearDemands);
histories.ForEach(p => {
p.UpdateMonth = updateMonth;
});
var nextHistories = ObjectMapper.Map, List>(nextYearDemands);
nextHistories.ForEach(p => {
p.UpdateMonth = updateMonth;
});
//1.1、获取海外销售预测数据
List overseasSales = _overseasSaleFcst.GetListAsync(p => p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//1.2、获取平台预测收集数据
List platformFcsts = _platformFcstCollect.GetListAsync(p => p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//1.3、获取国内终端预测-T1汇总数据
List domesticFcst = _domesticTerminalFcst.GetListAsync(p =>p.TypeEnum == 2 && p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//1.3.1、获取T2-海王,T2-国科数据
List T2Fcsts = _domesticTerminalFcst.GetListAsync(p => (p.TypeEnum == 4 || p.TypeEnum == 5) && p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//1.4、根据年度生产大纲,获取规格型号对应的补货周期
List allModels = yearDemands.Select(p=>p.Model).Distinct().ToList();
if (nextYearDemands.Any())
{
allModels.AddRange(nextYearDemands.Select(p => p.Model).ToList());
allModels = allModels.Distinct().ToList();
}
List standards = _standardItemModelSet.GetListAsync(p => allModels.Contains(p.Model) && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//1.5、获取规格型号对应的标准SKU数据:获取最小包装单位
List itemNums = standards.Select(p=>p.ItemNumber).Distinct().ToList();
List items = _ic_item.GetListAsync(p=> itemNums.Contains(p.number) && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//1.6、获取成品库存、灭菌库存、在制库存(会从SAP同步的库存表更新到LocationDetail、ic_item表中)
//List sAPInvs = _SAPInv.Select(p => p.WERKS == input.factory_id.ToString() && itemNums.Contains(p.MATNR));
List locations = _locationDetail.Select(p => p.Domain == input.factory_id.ToString() && itemNums.Contains(p.ItemNum) && p.IsActive);
//1.7、获取节假日设置
List holidays = _holidayMaster.Select(p => (p.Dated.Value.Year == input.year || p.Dated.Value.Year == (input.year + 1)) && p.Domain == input.factory_id.ToString() && p.IsActive);
//1.8、获取平台库存监控月份设置
List monitorSettings = _platStockMonitorSetting.GetListAsync(p => p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//1.9、获取客户要求交期在N+1月的销售订单
//List<>
#region 数据校验
//1、校验当前年的年度生产大纲是否导入
if (!yearDemands.Any())
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "请导入" + input.year + "年的年度生产需求后再操作!", _currentTenant.Id.ToString());
return "NO|请导入" + input.year + "年的年度生产需求后再操作!";
}
//2、如果当前月份大于10,则需校验下一年的年度生产大纲
if (input.month > 10 && !nextYearDemands.Any())
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "请导入" + (input.year+1) + "年的年度生产需求后再操作!", _currentTenant.Id.ToString());
return "NO|请导入" + (input.year + 1) + "年的年度生产需求后再操作!";
}
//3、校验当前月份的海外销售预测是否导入
if (!overseasSales.Any())
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "请导入" + input.year + "年" + input.month + "月海外销售预测后再操作!", _currentTenant.Id.ToString());
return "NO|请导入" + input.year + "年" + input.month + "月海外销售预测后再操作!";
}
//4、校验当前月份的平台预测收集是否导入
if (!platformFcsts.Any())
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "请导入" + input.year + "年" + input.month + "月平台需求收集后再操作!", _currentTenant.Id.ToString());
return "NO|请导入" + input.year + "年" + input.month + "月平台需求收集后再操作!";
}
//5、校验当前月份的国内终端预测是否导入
if (!domesticFcst.Any())
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "请导入" + input.year + "年" + input.month + "月国内终端预测后再操作!", _currentTenant.Id.ToString());
return "NO|请导入" + input.year + "年" + input.month + "月国内终端预测后再操作!";
}
//6、校验终端+平台导入的规格型号是否已维护标准SKU
if (allModels.Count() != standards.Count())
{
allModels.RemoveAll(standards.Select(p=>p.Model).ToList());
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "规格型号【" + string.Join(",",allModels) + "】没有维护标准SKU,请维护后再操作!", _currentTenant.Id.ToString());
return "NO|规格型号【" + string.Join(",", allModels) + "】没有维护标准SKU,请维护后再操作!";
}
//7、校验是否已维护补货周期
var zeros = standards.Where(p => p.ReplenishCycle == 0).ToList();
if (zeros.Any())
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "规格型号【" + string.Join(",", zeros.Select(p=>p.Model).ToList()) + "】没有维护补货周期,请维护后再操作!", _currentTenant.Id.ToString());
return "NO|规格型号【" + string.Join(",", zeros.Select(p => p.Model).ToList()) + "】没有维护补货周期,请维护后再操作!";
}
#endregion
//计算当前年月的N0,N+1,N+2
List planMons = new List();
int newYear = input.month == 12 ? input.year + 1 : input.year;
int newMonth = input.month == 12 ? 1 : input.month + 1;
string strN1 = newYear.ToString() + "-" + newMonth.ToString("00");
planMons.Add(strN1);
newYear = newMonth == 12 ? newYear + 1 : newYear;
newMonth = newMonth == 12 ? 1 : newMonth + 1;
string strN2 = newYear.ToString() + "-" + newMonth.ToString("00");
planMons.Add(strN2);
//需要回写的数据
List updates = new List();
#region 计算国内终端预测,回写年度生产大纲
//获取导入的T1、平台数据
List gnModels = new List();
if (domesticFcst.Any())
{
gnModels.AddRange(domesticFcst.Select(p => p.Model).ToList());
}
if (platformFcsts.Any())
{
gnModels.AddRange(platformFcsts.Select(p => p.Model).ToList());
}
gnModels = gnModels.Distinct().ToList();
foreach (var gnModel in gnModels)
{
//N+1月
YearDemandManagement monthN1 = new YearDemandManagement();
//N+2月
YearDemandManagement monthN2 = new YearDemandManagement();
//获取需要回写的数据
if (input.month < 11)
{
monthN1 = yearDemands.FirstOrDefault(p => p.Area == "国内" && p.Model == gnModel && p.PlanMonth == strN1);
monthN2 = yearDemands.FirstOrDefault(p => p.Area == "国内" && p.Model == gnModel && p.PlanMonth == strN2);
}
if (input.month == 11)
{
monthN1 = yearDemands.FirstOrDefault(p => p.Area == "国内" && p.Model == gnModel && p.PlanMonth == strN1);
monthN2 = nextYearDemands.FirstOrDefault(p => p.Area == "国内" && p.Model == gnModel && p.PlanMonth == strN2);
}
if (input.month == 12)
{
monthN1 = nextYearDemands.FirstOrDefault(p => p.Area == "国内" && p.Model == gnModel && p.PlanMonth == strN1);
monthN2 = nextYearDemands.FirstOrDefault(p => p.Area == "国内" && p.Model == gnModel && p.PlanMonth == strN2);
}
if (monthN1 == null || monthN2 == null)
{
continue;
}
//T1
var sumT1N1 = domesticFcst.Where(p => p.Model == gnModel && p.PlanMonth == strN1).Sum(p=>p.Qty);
var sumT1N2 = domesticFcst.Where(p => p.Model == gnModel && p.PlanMonth == strN2).Sum(p => p.Qty);
//平台
var sumPN1 = platformFcsts.Where(p => p.Model == gnModel && p.PlanMonth == strN1).Sum(p => p.Qty);
var sumPN2 = platformFcsts.Where(p => p.Model == gnModel && p.PlanMonth == strN2).Sum(p => p.Qty);
//合计
var sumN1 = sumT1N1+ sumPN1;
var sumN2 = sumT1N2 + sumPN2;
//当前规格型号对应标准SKU的最小包装单位、补货周期
var curStd = standards.FirstOrDefault(p => p.Model == gnModel);
decimal packQty = 1m;//最小包装单位
decimal cycle = 0m;//补货周期
//获取成品库存、在制库存、灭菌库存,参与计算
decimal cpQty = 0m;
decimal zzQty = 0m;
decimal mjQty = 0m;
if (curStd != null)
{
var curItem = items.FirstOrDefault(p => p.number == curStd.ItemNumber);
packQty = curItem == null ? 1 : (curItem.minpackqty.GetValueOrDefault() == 0.0m ? 1 : curItem.minpackqty.Value);
cycle = curStd.ReplenishCycle;
//成品库存
cpQty = locations.Where(p => p.ItemNum == curStd.ItemNumber && p.Location == "8001").Sum(p => Convert.ToDecimal(p.QtyOnHand));
//在制库存
zzQty = locations.Where(p => p.ItemNum == curStd.ItemNumber && p.Location == "8000").Sum(p => Convert.ToDecimal(p.QtyOnHand));
//灭菌库存
mjQty = locations.Where(p => p.ItemNum == curStd.ItemNumber && p.Location == "5008").Sum(p => Convert.ToDecimal(p.QtyOnHand));
}
//N+1月使用N+2月的再订货点参与计算
decimal rop = CalcRop(strN2+"-01", sumN2, packQty, holidays, cycle);
monthN1.Qty = Math.Ceiling((sumN1 / 2 + sumN2 / 2 + rop - cpQty - zzQty - mjQty) / packQty) * packQty;
//N+2月使用本月的需求量,下一月的工作天数
rop = CalcRop(Convert.ToDateTime(strN2 + "-01").AddMonths(1).ToString("yyyy-MM-dd"), sumN2, packQty, holidays, cycle);
monthN2.Qty = Math.Ceiling((sumN2 + rop - cpQty - zzQty - mjQty) / packQty) * packQty;
//负数置0
monthN1.Qty = monthN1.Qty < 0 ? 0m : monthN1.Qty;
monthN2.Qty = monthN2.Qty < 0 ? 0m : monthN2.Qty;
//记录需要回写的数据
updates.Add(monthN1);
updates.Add(monthN2);
}
#endregion
#region 海外销售预测,回写年度生产大纲
List hwModels = overseasSales.Select(p => p.Model).Distinct().ToList();
foreach (var hwModel in hwModels)
{
//N+1月
YearDemandManagement monthN1 = new YearDemandManagement();
//N+2月
YearDemandManagement monthN2 = new YearDemandManagement();
//获取需要回写的数据
if (input.month < 11)
{
monthN1 = yearDemands.FirstOrDefault(p => p.Area == "海外" && p.Model == hwModel && p.PlanMonth == strN1);
monthN2 = yearDemands.FirstOrDefault(p => p.Area == "海外" && p.Model == hwModel && p.PlanMonth == strN2);
}
if (input.month == 11)
{
monthN1 = yearDemands.FirstOrDefault(p => p.Area == "海外" && p.Model == hwModel && p.PlanMonth == strN1);
monthN2 = nextYearDemands.FirstOrDefault(p => p.Area == "海外" && p.Model == hwModel && p.PlanMonth == strN2);
}
if (input.month == 12)
{
monthN1 = nextYearDemands.FirstOrDefault(p => p.Area == "海外" && p.Model == hwModel && p.PlanMonth == strN1);
monthN2 = nextYearDemands.FirstOrDefault(p => p.Area == "海外" && p.Model == hwModel && p.PlanMonth == strN2);
}
if (monthN1 == null || monthN2 == null) {
continue;
}
var sumN1 = overseasSales.Where(p => p.Model == hwModel && p.PlanMonth == strN1).Sum(p => p.Qty);
var sumN2 = overseasSales.Where(p => p.Model == hwModel && p.PlanMonth == strN2).Sum(p => p.Qty);
//当前规格型号对应标准SKU的最小包装单位、补货周期
var curStd = standards.FirstOrDefault(p => p.Model == hwModel);
decimal packQty = 1m;//最小包装单位
//获取成品库存、在制库存、灭菌库存,参与计算
decimal cpQty = 0m;
decimal zzQty = 0m;
decimal mjQty = 0m;
if (curStd != null)
{
var curItem = items.FirstOrDefault(p => p.number == curStd.ItemNumber);
packQty = curItem == null ? 1 : (curItem.minpackqty.GetValueOrDefault() == 0.0m ? 1 : curItem.minpackqty.Value);
//成品库存
cpQty = locations.Where(p => p.ItemNum == curStd.ItemNumber && p.Location == "8001").Sum(p => Convert.ToDecimal(p.QtyOnHand));
//在制库存
zzQty = locations.Where(p => p.ItemNum == curStd.ItemNumber && p.Location == "8000").Sum(p => Convert.ToDecimal(p.QtyOnHand));
//灭菌库存
mjQty = locations.Where(p => p.ItemNum == curStd.ItemNumber && p.Location == "5008").Sum(p => Convert.ToDecimal(p.QtyOnHand));
}
//海外生产需求量=当月的50%+下一月的50%-成品库存-在制库存-灭菌库存
//TODO:获取成品库存、在制库存、灭菌库存,参与运算
//计算N+1月,N+2月
monthN1.Qty = Math.Ceiling((sumN1 / 2 + sumN2 / 2 - cpQty - zzQty - mjQty) / packQty) * packQty;
monthN2.Qty = Math.Ceiling((sumN2 - cpQty - zzQty - mjQty) / packQty) *packQty; ;
//负数置0
monthN1.Qty = monthN1.Qty < 0 ? 0m : monthN1.Qty;
monthN2.Qty = monthN2.Qty < 0 ? 0m : monthN2.Qty;
//记录需要回写的数据
updates.Add(monthN1);
updates.Add(monthN2);
}
#endregion
//生成月度产能共识
var capacityDto = CapacityAnalysis(input, updates, planMons);
//生成下一个月的补货计划
var nextMonPFcsts = platformFcsts.Where(p => p.PlanMonth == strN2).ToList();
var nextMonDFcsts = domesticFcst.Where(p => p.PlanMonth == strN2).ToList();
var nextMonT2Fcsts = T2Fcsts.Where(p => p.PlanMonth == strN2).ToList();
var nextMonMonitors = monitorSettings.Where(p => p.PlanMonth == strN1).ToList();
var replenishs = MonthlyReplenish(input,strN1, nextMonPFcsts, nextMonDFcsts, nextMonT2Fcsts, standards, items, holidays, nextMonMonitors, locations);
//保存数据
using (var unitOfWork = _unitOfWorkManager.Begin(false, true))
{
try
{
//判断是否存在当前年月的历史版本,不存在则插入
var dbHistory = _yearDemandManagementHistory.GetListAsync(p=>p.Year ==input.year && p.UpdateMonth == updateMonth && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id).Result;
if (!dbHistory.Any())
{
await _yearDemandManagementHistory.InsertManyAsync(histories);
}
if (input.month > 10)
{
//判断是否存在下一年月的历史版本,不存在则插入
dbHistory = _yearDemandManagementHistory.GetListAsync(p =>(p.Year == input.year + 1) && p.UpdateMonth == updateMonth && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id).Result;
if (!dbHistory.Any())
{
await _yearDemandManagementHistory.InsertManyAsync(nextHistories);
}
}
//回写年度生产大纲
await _yearDemandManagement.UpdateManyAsync(updates);
//先删除产能共识、产能共识明细
await _monthlyProdCapacity.HardDeleteAsync(p=>p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id);
await _monthlyProdCapacityDtl.HardDeleteAsync(p => p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id);
await _monthlyProdCapacity.InsertManyAsync(capacityDto.mains);
await _monthlyProdCapacityDtl.InsertManyAsync(capacityDto.details);
//生成下一月的补货计划
await _crm_planorder.HardDeleteAsync(p=>p.PlanMonth == strN1 && p.Source =="系统运算" && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id);
await _crm_planorder.InsertManyAsync(replenishs);
await unitOfWork.CompleteAsync();
}
catch (Exception e)
{
unitOfWork.Dispose();
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "【" + input.year + "年" + input.month + "月】月度需求预测更新失败:" + e.Message, _currentTenant.Id.ToString());
return "NO|" + e.Message;
};
}
return "OK|刷新成功!";
}
///
/// 生成月度产能共识
///
///
///
public MonthlyCapacityDto CapacityAnalysis(InputDto input,List yearDemands, List planMons)
{
//1、获取数据
//1.1 根据规格型号获取物料数据
List models = yearDemands.Select(p => p.Model).Distinct().ToList();
List standards = _standardItemModelSet.GetListAsync(p => models.Contains(p.Model) && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id).Result;
//1.3 根据物料编码获取产线数据
List lineDtls = _prodLineDetail.Select(p => standards.Select(m => m.ItemNumber).Contains(p.Part) && p.Domain == input.factory_id.ToString() && p.IsActive).OrderBy(p => p.Line).ToList();
//1.4 根据产线获取工作日历数据和产线休息配置数据
List calendars = _shopCalendarWorkCtr.Select(p => lineDtls.Select(m => m.Line).Contains(p.ProdLine) && p.Domain == input.factory_id.ToString() && p.IsActive);
List lineWorks = _qualityLineWorkDetail.Select(p => lineDtls.Select(m => m.Line).Contains(p.ProdLine) && p.Domain == input.factory_id.ToString() && p.IsActive);
//1.5 获取当前年和下一年的节假日配置数据
List holidays = _holidayMaster.Select(p => (p.Dated.Value.Year == input.year || p.Dated.Value.Year == (input.year + 1)) && p.Domain == input.factory_id.ToString() && p.IsActive);
//月度产能共识表
List capacities = new List();
//月度产能共识明细表
List capacityDtls = new List();
//产线
List lines = lineDtls.Select(p => p.Line).Distinct().ToList();
foreach (var item in lines)
{
//计算每天工作时间
var curCal = calendars.FirstOrDefault(p => p.ProdLine == item);
if (curCal == null)
{
continue;
}
foreach (var pm in planMons)
{
//添加月度产能共识产能效率数据
MonthlyProdCapacity dtl = new MonthlyProdCapacity();
dtl.Year = input.year;
dtl.Month = input.month;
dtl.PlanMonth = pm;
dtl.ProdLine = item;
dtl.DailyWorks = curCal.ShiftsHours1 + curCal.ShiftsHours2 + curCal.ShiftsHours3 + curCal.ShiftsHours4;
dtl.FlightQty = 1;
//计算当月工作天数
var curHoildays = holidays.Where(p => p.Dated.Value.Year == Convert.ToInt16(pm.Substring(0,4)) && p.Dated.Value.Month == Convert.ToInt16(pm.Substring(5, 2))).ToList();
//当月天数
int days = DateTime.DaysInMonth(input.year, input.month);
//当月周末天数
int weekDays = CalcWeekDays(days, Convert.ToDateTime(pm + "-01"));
dtl.MonthWorks = days - weekDays - curHoildays.Where(p => p.Ufld1 == "休假").Count() + curHoildays.Where(p => p.Ufld1 == "调班").Count();
dtl.AvailableTimes = dtl.DailyWorks * dtl.MonthWorks;
//计算产线耗时
var curLines = lineDtls.Where(p => p.Line == item).ToList();
decimal sumTimes = 0.00m;
List lineDtos = new List();
foreach (var ld in curLines)
{
if (lineDtos.Exists(p=>p.Part == ld.Part && p.Line == ld.Line))
{
continue;
}
var curStand = standards.FirstOrDefault(p => p.ItemNumber == ld.Part);
if (curStand == null)
{
continue;
}
var curDemands = yearDemands.Where(p => p.Model == curStand.Model && p.PlanMonth == pm).ToList();
var line = curLines.Where(p=>p.Part == ld.Part && p.Line == ld.Line).OrderByDescending(p => p.Op).First();
sumTimes += line.Rate == 0 ? 0 : (Math.Ceiling(curDemands.Sum(p => p.Qty) / line.Rate));
MonthlyProdCapacityDtl capacityDtl = new MonthlyProdCapacityDtl();
capacityDtl.Year = input.year;
capacityDtl.Month = input.month;
capacityDtl.PlanMonth = pm;
capacityDtl.ProdLine = item;
capacityDtl.Model = curStand.Model;
capacityDtl.Qty = curDemands.Sum(p => p.Qty);
capacityDtl.tenant_id = input.tenant_id;
capacityDtl.company_id = input.company_id;
capacityDtl.factory_id = input.factory_id;
capacityDtl.org_id = input.org_id;
capacityDtl.create_by = input.create_by;
capacityDtl.create_by_name = input.create_by_name;
capacityDtl.create_time = DateTime.Now;
capacityDtls.Add(capacityDtl);
lineDtos.Add(new ProdLineDto {
Part = ld.Part,
Line= ld.Line
});
}
dtl.NeedWorks = sumTimes;
dtl.ProdRate = 100;
dtl.Rate = dtl.AvailableTimes == 0 ? 0 : Math.Floor(dtl.NeedWorks / dtl.AvailableTimes * 100);
dtl.IsOverTime = dtl.NeedWorks > dtl.AvailableTimes ? "是" : "否";
dtl.OverTimes = dtl.IsOverTime == "是" ? (dtl.NeedWorks - dtl.AvailableTimes) : 0;
dtl.tenant_id = input.tenant_id;
dtl.company_id= input.company_id;
dtl.factory_id = input.factory_id;
dtl.org_id= input.org_id;
dtl.create_by= input.create_by;
dtl.create_by_name = input.create_by_name;
dtl.create_time = DateTime.Now;
capacities.Add(dtl);
}
}
return new MonthlyCapacityDto{
mains = capacities,
details = capacityDtls
};
}
///
/// T1、平台自动补货
///
///
/// 计划补货月份:N+1月
/// 平台预测
/// T1预测
/// 标准SKU设置
/// 物料
/// 节假日
/// 库存监控月份设置
/// SAP同步库存
///
public List MonthlyReplenish(InputDto input,string strN1, List platformFcsts, List domesticFcsts, List T2Fcsts, List standards, List items, List holidays, List monitorSettings, List locations)
{
List planorders = new List();
//获取T1中规格型号对应的不同版本的物料编码
var T1Models = domesticFcsts.Select(p => p.Model).Distinct().ToList();
List skus = _skuVersionSet.GetListAsync(p=> T1Models.Contains(p.Model) && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result;
//获取T1库存
//List itemNums = skus.Select(p=>p.ItemNum).Distinct().ToList();
//List locationDetails = _locationDetail.Select(p=> itemNums.Contains(p.ItemNum) && p.Domain == input.factory_id.ToString() && p.IsActive);
//获取临期库存设置
GeneralizedCodeMaster master = _generalizedCodeMaster.Select(p => p.Domain == input.factory_id.ToString() && p.IsActive && p.Val == "LongPeriodItemPlanMonth").FirstOrDefault();
int month = master == null ? 0 : Convert.ToInt16(master.UDeci1);
DateTime strTime = Convert.ToDateTime(platformFcsts[0].PlanMonth + "-01").AddMonths(month);
//获取平台库存
var PModels = platformFcsts.Select(p => p.Model).Distinct().ToList();
List pInventories = _platformInventory.GetListAsync(p=> PModels.Contains(p.SpecificationModel) && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && p.PeriodOfValidity >= strTime).Result;
//计算T1补货
foreach (var item in T1Models)
{
//获取当前规格型号对应的预测数据
var curFcsts = domesticFcsts.Where(p=>p.Model == item).ToList();
//当前规格型号对应的库存数量
decimal sumQty = 0.00m;
//当前的物料版本
var curSkus = skus.Where(p => p.Model == item).ToList();
if (curSkus.Any())
{
//sumQty = sAPInvs.Where(p => curSkus.Select(p => p.ItemNum).Contains(p.MATNR)).Sum(p => Convert.ToDecimal(p.LABST));
sumQty = locations.Where(p => curSkus.Select(p => p.ItemNum).Contains(p.ItemNum) && p.Location == "8001").Sum(p => p.QtyOnHand);
}
//计算Rop
//当前规格型号对应标准SKU的最小包装单位、补货周期
var curStd = standards.FirstOrDefault(p => p.Model == item);
decimal packQty = 1m;//最小包装单位
decimal cycle = 0m;//补货周期
if (curStd != null)
{
var curItem = items.FirstOrDefault(p => p.number == curStd.ItemNumber);
packQty = curItem == null ? 1 : (curItem.minpackqty.GetValueOrDefault() == 0.0m ? 1 : curItem.minpackqty.Value);
cycle = curStd.ReplenishCycle;
}
//N+1月使用N+2月的再订货点参与计算
decimal rop = CalcRop(curFcsts[0].PlanMonth + "-01", curFcsts.Sum(p=>p.Qty), packQty, holidays, cycle);
if(sumQty < rop)
{
planorders.Add(new crm_planorder {
PlanMonth = strN1,
Model = curFcsts[0].Model,
ItemNum = curStd?.ItemNumber,
ProdLine = curFcsts[0].ProdLine,
ProdType = "",
Qty = rop,
Type = "计划单-T1直发补货"
});
}
}
//计算平台补货:海王
var hwFcsts = platformFcsts.Where(p => p.Platform == "海王").ToList();
var hwModels = hwFcsts.Select(p=>p.Model).Distinct().ToList();
foreach (var item in hwModels)
{
//获取当前规格型号对应的平台预测数据
var curFcsts = hwFcsts.Where(p => p.Model == item).ToList();
//当前规格型号对应的库存数量
decimal sumQty = 0.00m;
//当前规格型号对应标准SKU的最小包装单位、补货周期
var curStd = standards.FirstOrDefault(p => p.Model == item);
decimal packQty = 1m;//最小包装单位
decimal cycle = 0m;//补货周期
if (curStd != null)
{
var curItem = items.FirstOrDefault(p => p.number == curStd.ItemNumber);
packQty = curItem == null ? 1 : (curItem.minpackqty.GetValueOrDefault() == 0.0m ? 1 : curItem.minpackqty.Value);
cycle = curStd.ReplenishCycle;
}
//T2海王预测数据
var curT2Hws = T2Fcsts.Where(p => p.Model == item && p.TypeEnum == 4).ToList();
//计算Rop:N+1月使用N+2月的再订货点参与计算
decimal rop = CalcRop(curFcsts[0].PlanMonth + "-01", curT2Hws.Sum(p => p.Qty), packQty, holidays, cycle);
//获取海王当前规格型号库存
sumQty = pInventories.Where(p => p.SpecificationModel == item && p.Code == "HW0001").Sum(p=>p.InventoryQuantity);
//获取N+1月的库存监控设置
var curMonitor = monitorSettings.FirstOrDefault(p => p.Platform == "海王" && p.ProdType == curFcsts[0].ProdType);
if (curMonitor == null)
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "海王未维护产品类型为【"+ curFcsts[0].ProdType + "】的库存监控月份", _currentTenant.Id.ToString());
continue;
}
//最小覆盖月份库存
var minQty = rop * curMonitor.MinTimes;
//最大库存月份库存
var maxQty = rop * curMonitor.MaxTimes;
//需补货数量
decimal needQty = 0m;
if (sumQty >= minQty && sumQty < maxQty)//大于等于最低库存覆盖月,小于最高库存覆盖月
{
planorders.Add(new crm_planorder
{
PlanMonth = strN1,
Model = curFcsts[0].Model,
ItemNum = curStd?.ItemNumber,
ProdLine = curT2Hws[0].ProdLine,
ProdType = "",
Qty = maxQty - sumQty,
Type = "计划单-T2平台补货"
});
}
if (sumQty < minQty)//低于最低库存覆盖月
{
while (sumQty < minQty)
{
sumQty += rop;
needQty += rop;
}
planorders.Add(new crm_planorder
{
PlanMonth = strN1,
Model = curFcsts[0].Model,
ItemNum = curStd?.ItemNumber,
ProdLine = curT2Hws[0].ProdLine,
ProdType = "",
Qty = needQty,
Type = "计划单-T2平台补货"
});
}
}
//计算平台补货:国科
var gkFcsts = platformFcsts.Where(p => p.Platform == "国科").ToList();
var gkModels = gkFcsts.Select(p => p.Model).Distinct().ToList();
foreach (var item in gkModels)
{
//获取当前规格型号对应的预测数据
var curFcsts = gkFcsts.Where(p => p.Model == item).ToList();
//当前规格型号对应的库存数量
decimal sumQty = 0.00m;
//当前规格型号对应标准SKU的最小包装单位、补货周期
var curStd = standards.FirstOrDefault(p => p.Model == item);
decimal packQty = 1m;//最小包装单位
decimal cycle = 0m;//补货周期
if (curStd != null)
{
var curItem = items.FirstOrDefault(p => p.number == curStd.ItemNumber);
packQty = curItem == null ? 1 : (curItem.minpackqty.GetValueOrDefault() == 0.0m ? 1 : curItem.minpackqty.Value);
cycle = curStd.ReplenishCycle;
}
//T2国科预测数据
var curT2Gks = T2Fcsts.Where(p => p.Model == item && p.TypeEnum == 5).ToList();
//计算Rop:N+1月使用N+2月的再订货点参与计算
decimal rop = CalcRop(curFcsts[0].PlanMonth + "-01", curT2Gks.Sum(p => p.Qty), packQty, holidays, cycle);
//获取国科当前规格型号库存
sumQty = pInventories.Where(p => p.SpecificationModel == item && p.Code == "GK0001").Sum(p => p.InventoryQuantity);
//获取N+1月的库存监控设置
var curMonitor = monitorSettings.FirstOrDefault(p => p.Platform == "国科" && p.ProdType == curFcsts[0].ProdType);
if (curMonitor == null)
{
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("DemandAnalysis", "国科未维护产品类型为【" + curFcsts[0].ProdType + "】的库存监控月份", _currentTenant.Id.ToString());
continue;
}
//最小覆盖月份库存
var minQty = rop * curMonitor.MinTimes;
//最大库存月份库存
var maxQty = rop * curMonitor.MaxTimes;
//需补货数量
decimal needQty = 0m;
if (sumQty >= minQty && sumQty < maxQty)
{
planorders.Add(new crm_planorder
{
PlanMonth = strN1,
Model = curFcsts[0].Model,
ItemNum = curStd?.ItemNumber,
ProdLine = curT2Gks[0].ProdLine,
ProdType = "",
Qty = maxQty - sumQty,
Type = "计划单-T2平台补货"
});
}
if (sumQty < minQty)
{
while (sumQty < minQty)
{
sumQty += rop;
needQty += rop;
}
planorders.Add(new crm_planorder
{
PlanMonth = strN1,
Model = curFcsts[0].Model,
ItemNum = curStd?.ItemNumber,
ProdLine = curT2Gks[0].ProdLine,
ProdType = "",
Qty = needQty,
Type = "计划单-T2平台补货"
});
}
}
planorders.ForEach(p => {
p.Source = "系统运算";
p.tenant_id = input.tenant_id;
p.company_id = input.company_id;
p.factory_id = input.factory_id;
p.org_id = input.org_id;
p.create_by = input.create_by;
p.create_by_name = input.create_by_name;
p.create_time = DateTime.Now;
});
return planorders;
}
///
/// 计算再订货点
///
/// 计划年月(yyyy-MM-dd)
/// 需求量
/// 最小包装数量
/// 节假日
/// 补货周期
///
private decimal CalcRop(string planMonth,decimal qty,decimal packQty,List holidays,decimal replenishCycle)
{
decimal rop = 0.0m;
//获取当月天数
int year = Convert.ToInt16(planMonth.Substring(0, 4));
int month = Convert.ToInt16(planMonth.Substring(5, 2));
int days = DateTime.DaysInMonth(year, month);
//计算当前月的周末天数
int weeks = CalcWeekDays(days, Convert.ToDateTime(planMonth));
//获取当前年月的节假日设置
var curHolidays = holidays.Where(p => p.Dated.Value.Year == year && p.Dated.Value.Month == month).ToList();
//当月工作天数
int workDays = days - weeks - curHolidays.Where(p => p.Ufld1 == "休假").Count() + curHolidays.Where(p => p.Ufld1 == "调班").Count();
//rop = (需求量/月工作天数*补货周期)=>按照最小包装单位元整
rop = Math.Ceiling(qty / workDays * replenishCycle / packQty) * packQty;
return rop;
}
///
/// 计算当月有多少个周末
///
///
///
///
private int CalcWeekDays(int days, DateTime startDay)
{
int sumDays = 0;
for (int i = 0; i < days; i++)
{
int weekDays = (int)startDay.AddDays(i).DayOfWeek;
if (weekDays == 0 || weekDays == 6)
{
sumDays++;
}
}
return sumDays;
}
///
/// 生成整体需求计划
///
///
///
///
public async Task OverallDemandPlan(InputDto input)
{
//1.1、获取海外销售预测数据
List overseasSales = _overseasSaleFcst.GetListAsync(p => p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result.OrderBy(p => p.OrderNum).ToList();
//1.2、获取平台预测收集数据
List platformFcsts = _platformFcstCollect.GetListAsync(p => p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result.OrderBy(p=>p.OrderNum).ToList();
//1.3、获取国内终端预测-T1汇总数据
List domesticFcsts = _domesticTerminalFcst.GetListAsync(p => p.TypeEnum == 2 && p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id && !p.IsDeleted).Result.OrderBy(p => p.OrderNum).ToList();
//计算当前年月的N+1,N+2
string strN0 = input.year.ToString() + "-" + input.month.ToString("00");
int newYear = input.month == 12 ? input.year + 1 : input.year;
int newMonth = input.month == 12 ? 1 : input.month + 1;
string strN1 = newYear.ToString() + "-" + newMonth.ToString("00");
newYear = newMonth == 12 ? newYear + 1 : newYear;
newMonth = newMonth == 12 ? 1 : newMonth + 1;
string strN2 = newYear.ToString() + "-" + newMonth.ToString("00");
//整体需求计划
List plans = new List();
//计算海外
List hwModels = overseasSales.Select(p => p.Model).Distinct().ToList();
int OrderNum = 1;
foreach (var item in hwModels)
{
var curFcsts = overseasSales.Where(p => p.Model == item).ToList();
OverallDemandPlan plan = new OverallDemandPlan();
plan.Area = "海外";
plan.Model = item;
plan.PlanMonth = strN0;
plan.Qty = curFcsts.Where(p => p.PlanMonth == strN0).Sum(p => p.Qty);
plan.OrderNum = OrderNum;
plans.Add(plan);
plan = new OverallDemandPlan();
plan.Area = "海外";
plan.Model = item;
plan.PlanMonth = strN1;
plan.Qty = curFcsts.Where(p => p.PlanMonth == strN1).Sum(p => p.Qty);
plan.OrderNum = OrderNum;
plans.Add(plan);
plan = new OverallDemandPlan();
plan.Area = "海外";
plan.Model = item;
plan.PlanMonth = strN2;
plan.Qty = curFcsts.Where(p => p.PlanMonth == strN2).Sum(p => p.Qty);
plan.OrderNum = OrderNum;
plans.Add(plan);
OrderNum += 1;
}
//计算国内
List gnModels = domesticFcsts.Select(p=>p.Model).ToList();
gnModels.AddRange(platformFcsts.Select(p => p.Model).ToList());
gnModels = gnModels.Distinct().ToList();
OrderNum = 1;
foreach (var item in gnModels)
{
var curDFcsts = domesticFcsts.Where(p => p.Model == item).ToList();
var curPFcsts = platformFcsts.Where(p => p.Model == item).ToList();
OverallDemandPlan plan = new OverallDemandPlan();
plan.Area = "国内";
plan.Model = item;
plan.PlanMonth = strN0;
plan.Qty = curDFcsts.Where(p => p.PlanMonth == strN0).Sum(p => p.Qty) + curPFcsts.Where(p => p.PlanMonth == strN0).Sum(p => p.Qty);
plan.OrderNum = OrderNum;
plans.Add(plan);
plan = new OverallDemandPlan();
plan.Area = "国内";
plan.Model = item;
plan.PlanMonth = strN1;
plan.Qty = curDFcsts.Where(p => p.PlanMonth == strN1).Sum(p => p.Qty) + curPFcsts.Where(p => p.PlanMonth == strN1).Sum(p => p.Qty);
plan.OrderNum = OrderNum;
plans.Add(plan);
plan = new OverallDemandPlan();
plan.Area = "国内";
plan.Model = item;
plan.PlanMonth = strN2;
plan.Qty = curDFcsts.Where(p => p.PlanMonth == strN2).Sum(p => p.Qty) + curPFcsts.Where(p => p.PlanMonth == strN2).Sum(p => p.Qty);
plan.OrderNum = OrderNum;
plans.Add(plan);
OrderNum += 1;
}
plans.ForEach(p => {
p.Year= input.year;
p.Month= input.month;
p.tenant_id = input.tenant_id;
p.company_id = input.company_id;
p.factory_id= input.factory_id;
p.org_id= input.org_id;
p.create_by = input.create_by;
p.create_by_name= input.create_by_name;
p.create_time = DateTime.Now;
});
//保存数据
using (var unitOfWork = _unitOfWorkManager.Begin(false, true))
{
try
{
//先删除
await _overallDemandPlan.HardDeleteAsync(p=>p.Year == input.year && p.Month == input.month && p.tenant_id == input.tenant_id && p.company_id == input.company_id && p.factory_id == input.factory_id);
//保存整体需求计划
await _overallDemandPlan.InsertManyAsync(plans);
await unitOfWork.CompleteAsync();
}
catch (Exception e)
{
unitOfWork.Dispose();
new NLogHelper("MonthlyCapacityLoadAppService").WriteLog("OverallDemandPlan", "生成【" + input.year + "年" + input.month + "月】整体需求计划失败:" + e.Message, _currentTenant.Id.ToString());
return "NO|" + e.Message;
};
}
return "OK|刷新成功!";
}
}
}