Files
WMS-Api/src/WMS.Web.Domain/Services/OutStockService.cs
2024-03-05 14:01:51 +08:00

345 lines
18 KiB
C#

using AutoMapper;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WMS.Web.Core.Dto;
using WMS.Web.Core.Dto.Erp;
using WMS.Web.Core.Dto.Erp.OutStock;
using WMS.Web.Core.Dto.Inventory;
using WMS.Web.Core.Dto.Login;
using WMS.Web.Core.Dto.OutStock;
using WMS.Web.Core.Dto.TakeStock;
using WMS.Web.Core.Help;
using WMS.Web.Core.Internal.Results;
using WMS.Web.Domain.Entitys;
using WMS.Web.Domain.Infrastructure;
using WMS.Web.Domain.IService;
using WMS.Web.Domain.IService.Public;
using WMS.Web.Domain.Values;
using WMS.Web.Domain.Values.Erp;
using WMS.Web.Domain.Values.Single;
namespace WMS.Web.Domain.Services
{
/// <summary>
/// 出库服务
/// </summary>
public class OutStockService : IOutStockService
{
private readonly IMapper _mapper;
private readonly ILoginService _loginService;
public readonly IBasicsRepositories _transactionRepositories;
private readonly IOutStockRepositories _outStockRepositories;
private readonly IOutStockTaskRepositories _outStockTaskRepositories;
private readonly IOutStockTaskService _outStockTaskService;
private readonly IErpService _erpService;
private readonly ISerialNumberService _serialNumberService;
private readonly IBoxInventoryService _boxInventoryService;
private readonly IInventoryInOutDetailsService _inventoryInOutDetailsService;
private readonly IBoxInventoryRepositories _boxInventoryRepositories;
private readonly ILogger<OutStockService> _logger;
private readonly IBoxRepositories _boxRepositories;
private readonly ISingleDataService _singleDataService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IInStockTaskBoxService _inStockTaskService;
public OutStockService(IMapper mapper, ILoginService loginService,
IBasicsRepositories transactionRepositories,
IOutStockRepositories outStockRepositories, IOutStockTaskRepositories outStockTaskRepositories,
IOutStockTaskService outStockTaskService, IErpService erpService, ISerialNumberService serialNumberService,
IBoxInventoryService boxInventoryService, IInventoryInOutDetailsService inventoryInOutDetailsService,
IBoxInventoryRepositories boxInventoryRepositories, ILogger<OutStockService> logger, IBoxRepositories boxRepositories,
ISingleDataService singleDataService, IServiceScopeFactory serviceScopeFactory, IInStockTaskBoxService inStockTaskService)
{
_mapper = mapper;
_loginService = loginService;
_transactionRepositories = transactionRepositories;
_outStockRepositories = outStockRepositories;
_outStockTaskRepositories = outStockTaskRepositories;
_outStockTaskService = outStockTaskService;
_erpService = erpService;
_serialNumberService = serialNumberService;
_boxInventoryService = boxInventoryService;
_inventoryInOutDetailsService = inventoryInOutDetailsService;
_boxInventoryRepositories = boxInventoryRepositories;
_logger = logger;
_boxRepositories = boxRepositories;
_singleDataService = singleDataService;
_serviceScopeFactory = serviceScopeFactory;
_inStockTaskService = inStockTaskService;
}
/// <summary>
/// 出库单
/// </summary>
/// <param name="dto"></param>
/// <param name="loginInfo"></param>
/// <returns></returns>
public async Task<Result> Save(SaveOutStockRequest dto, LoginInDto loginInfo)
{
_logger.LogInformation($"出库:{JsonConvert.SerializeObject(dto)} 操作人:{loginInfo.UserInfo.StaffId}");
var outStockTask = await _outStockTaskRepositories.Get(dto.TaskId);
if (outStockTask == null) return Result.ReFailure(ResultCodes.OutStockTaskNoData);
if (outStockTask.Status == OutStockStatus.Already)
return Result.ReFailure(ResultCodes.OutStockTaskAlready);
if (outStockTask.Status == OutStockStatus.Repeal)
return Result.ReFailure(ResultCodes.OutStockTaskRepeal);
//上传的物料在任务单里没有找到不能出库
var ex = dto.Details.Select(s => s.MaterialId).Except(outStockTask.Details.Select(s => s.MaterialId)).ToList();
if (ex.Count() > 0 && dto.Method == 2)
return Result.ReFailure(ResultCodes.OutStockMaterialError);
var boxIds = dto.Details.Select(s => s.BoxId).ToList();
var boxInventoryList = await _boxInventoryRepositories.GetList(boxIds);
foreach (var boxid in boxIds)
{
var inventory = boxInventoryList.FirstOrDefault(f => f.BoxId == boxid);
if (inventory == null)
{
var box = await _boxRepositories.Get(boxid);
return Result.ReFailure($"箱号:{box?.BoxBillNo} 不存在库存", 70008);
}
if (!inventory.StockCode.Equals(outStockTask.StockCode))
{
var box = await _boxRepositories.Get(boxid);
var stockCodeName = _singleDataService.GetSingleData(SingleAction.StocksJoinOrgCode, loginInfo.UserInfo.CompanyId, outStockTask.StockCode + outStockTask.OrgCode);
return Result.ReFailure($"箱号:{box?.BoxBillNo}在仓库:{stockCodeName} 不存在库存", 70009);
}
}
//1.需要验证物料对应箱和序列号是否存在库存
//如果是按箱出库从库存拿取数据
if (dto.Method == 1)
{
dto.Details.Clear();
//获取箱子对应的所有库存
//过滤掉不同仓库和不同组织的
boxInventoryList = boxInventoryList.Where(w => w.StockCode == outStockTask.StockCode && w.OrgCode == outStockTask.OrgCode).ToList();
//组装dto
var boxInventoryDetails = boxInventoryList.SelectMany(s => s.Details).ToList();
foreach (var b in boxInventoryDetails)
{
var num = boxInventoryDetails.Where(w => w.MaterialId == b.MaterialId).Sum(s => s.Qty);
var taskDetail = outStockTask.Details.FirstOrDefault(f => f.MaterialId == b.MaterialId);
if (taskDetail == null) return Result.ReFailure(ResultCodes.BoxOutStockTaskMaterialError);
//箱子里该物料的总数量大于出库单(应出库数量-已出库数量) 不能出库
if (num > (taskDetail.AccruedQty - taskDetail.RealityQty))
return Result.ReFailure(ResultCodes.BoxNumberError);
var box = boxInventoryList.FirstOrDefault(f => f.Id == b.Fid);
if (box == null) return Result.ReFailure(ResultCodes.BoxNoData);
var dtod = _mapper.Map<SaveOutStockDetailsRequest>(b);
dtod.BoxId = box.BoxId;
dtod.SubStockId = box.SubStockId;
dto.Details.Add(dtod);
}
}
var mIds = dto.Details.GroupBy(g => g.MaterialId).Select(s => s.Key).ToList();
OutStock entity = new OutStock();
foreach (var mid in mIds)
{
//任务单明细
var tDetail = outStockTask.Details.FirstOrDefault(f => f.MaterialId == mid);
var dtoDetails = dto.Details.Where(w => w.MaterialId == mid);
var qty = dtoDetails.Sum(s => s.Qty);
if (tDetail == null) continue;
var res = outStockTask.OutStock(mid, qty, loginInfo.UserInfo.StaffId);
if (!res.IsSuccess) return res;
var outd = _mapper.Map<OutStockDetails>(tDetail);
//循环添加erp同步明细
foreach (var c in res.Data)
{
var tErpDetail = tDetail.ErpDetails.FirstOrDefault(f => f.Erp_DetailId == c.erpDetailId);
var oErpDetail = _mapper.Map<OutStockErpDetails>(tErpDetail);
oErpDetail.Qty = c.qty;
outd.ErpDetails.Add(oErpDetail);
}
var boxs = dto.Details.Where(w => w.MaterialId == mid).ToList();
outd.BoxsDetails = _mapper.Map<List<OutStockBoxsDetails>>(boxs);
outd.Qty = qty;
outd.SerialNumbers.AddRange(dtoDetails.SelectMany(s => s.SerialNumbers));
entity.Details.Add(outd);
}
entity.Create(loginInfo.UserInfo.StaffId, outStockTask, dto.Method);
IDbContextTransaction _transaction = _transactionRepositories.GetTransaction();
Result res_Rollback = Result.ReSuccess();
bool isSuccess = true;
//修改库存
if (res_Rollback.IsSuccess)
{
entity = await _outStockRepositories.Add(entity, false);
if (entity == null) res_Rollback = Result.ReFailure(ResultCodes.DateWriteError);
}
if (res_Rollback.IsSuccess)
{
outStockTask = await _outStockTaskRepositories.Edit(outStockTask, false);
if (outStockTask == null) res_Rollback = Result.ReFailure(ResultCodes.DateWriteError);
}
if (res_Rollback.IsSuccess)
{
var res_change = await _serialNumberService.OutStock(entity, loginInfo, false);
if (!res_change.IsSuccess) res_Rollback = res_change;
}
if (res_Rollback.IsSuccess)
{
var res_Inventory = await _boxInventoryService.GenerateOutStockBox(entity, false);
if (!res_Inventory.IsSuccess) res_Rollback = res_Inventory;
}
if (res_Rollback.IsSuccess)
{
boxIds = entity.Details.SelectMany(s => s.BoxsDetails).Select(s => s.BoxId).ToList();
var res_InStockTask = await _inStockTaskService.UnBind(boxIds, false);
if (!res_InStockTask.IsSuccess) res_Rollback = res_InStockTask;
}
//提交事务
isSuccess = _transactionRepositories.CommitTransaction(res_Rollback.IsSuccess ? false : true, _transaction);
if (!res_Rollback.IsSuccess) return res_Rollback;
if (!isSuccess)
return Result.ReFailure(ResultCodes.DateWriteError);
//取消同步金蝶
//if (entity.Type == OutStockType.Sal)
//{
// OperateRequest oRequest = new OperateRequest();
// oRequest.Ids.Add(entity.Id);
// await Sync(oRequest, loginInfo, false);
//}
return Result.ReSuccess();
}
/// <summary>
/// 同步金蝶
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
public Task<Result> Sync(OperateRequest dto, LoginInDto loginInfo, bool isRepeatSync = true)
{
var list = _outStockRepositories.GetEntityList(dto.Ids).GetAwaiter().GetResult();
list = list.Where(w => w.Type == OutStockType.Sal).ToList();
var isSuccess = true;
if (isRepeatSync)
{
list = list.Where(w => w.SuccessSync == SyncStatus.Fail).ToList();
list.ForEach(f => f.RepeatSync());
isSuccess = _outStockRepositories.EditEntityList(list, true).GetAwaiter().GetResult();
}
Task.Run(async () =>
{
foreach (var entity in list)
{
var res = await SalOutStock(entity, loginInfo);
if (!res.IsSuccess)
_logger.LogError($"出库单同步失败:{res.Message}");
}
});
return Task.FromResult(Result.ReSuccess());
}
/// <summary>
/// 同步金蝶销售出库
/// </summary>
/// <returns></returns>
private async Task<Result> SalOutStock(OutStock entity, LoginInDto loginInfo)
{
var scope = _serviceScopeFactory.CreateScope();
var sc_outStockRepositories = scope.ServiceProvider.GetRequiredService<IOutStockRepositories>();
if (entity.Type != OutStockType.Sal) return Result.ReSuccess();
if (entity.SuccessSync == SyncStatus.Success) return Result.ReSuccess();
//List<string> failList = entity.Details.SelectMany(s => s.ErpDetails)
// .Where(w => w.SuccessSync == SyncStatus.Fail).GroupBy(g => g.SourceBillNo)
// .Select(s => s.Key).ToList();//同步失败的来源单号
// //找到单据里需要同步的单据
//var sourcNos = entity.Details.SelectMany(s => s.ErpDetails).GroupBy(s => s.SourceBillNo).Select(s => s.Key);
var erpDetails = entity.Details.SelectMany(s => s.ErpDetails).Where(w => w.SuccessSync == SyncStatus.SyncIng).ToList();
foreach (var s in erpDetails)
{
var erp_details = entity.Details
.SelectMany(s => s.ErpDetails)
.Where(w => w.SourceBillNo.Equals(s)).Select(s => s.Erp_DetailId).ToList();
var erpDto = new ErpPushDto()
{
RuleId = "DeliveryNotice-OutStock",//转换规则内码 发货通知单下推销售出库单
FormId = FormIdParam.SAL_DELIVERYNOTICE.ToString(),
//Numbers = new List<string>() { s },
TargetFormId = FormIdParam.SAL_OUTSTOCK.ToString(),
DetailsId = s.Erp_DetailId.ToString()
//DetailsId = string.Join(",", erp_details)
};
//下推金蝶
var res = await this.Push(erpDto, s, entity.BillNo, scope);
if (res.result.IsSuccess)
entity.SyncSuccess(s.Erp_DetailId, loginInfo?.UserInfo?.StaffId ?? 0, res.erpBillNo);
else
entity.SyncFail(res.result.Message, s.Erp_DetailId, loginInfo?.UserInfo?.StaffId ?? 0, res.syncStatus);
}
//entity.SuccessSync = entity.SuccessSyncFail.Count() > 0 ? false : true;
//最好一条一条执行,否则执行失败 但是金蝶那边又同步成功 就会造成数据比价乱
_logger.LogInformation($"同步金蝶后写入数据库--开始:{JsonConvert.SerializeObject(entity)}");
var isSuccess = await sc_outStockRepositories.Edit(entity, true);
_logger.LogInformation($"同步金蝶后写入数据库--结束:{JsonConvert.SerializeObject(entity)} 结果:{isSuccess}");
if (entity == null) return Result.ReFailure(ResultCodes.DateWriteError);
return Result.ReSuccess();
}
/// <summary>
/// 下推
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
private async Task<(Result result, SyncStatus syncStatus, string erpBillNo)> Push(ErpPushDto dto, OutStockErpDetails erpDetail, string billNo, IServiceScope scope)
{
var sc_erpService = scope.ServiceProvider.GetRequiredService<IErpService>();
var res = await sc_erpService.Push(dto);
if (!res.IsSuccess)
{
_logger.LogInformation($"出库单->下推失败 单号:{billNo} erp明细Id:{erpDetail.Erp_DetailId} 错误:{res.Message}");
return (Result.ReFailure(res.Message, res.Status), SyncStatus.Fail, "");
}
string id = res.Data;
var resSalOutStock = await sc_erpService.BillQueryForSalOutStock(id);
var salOutStock = resSalOutStock.Data;
salOutStock.Details[0].Qty = erpDetail.Qty;
//{"Id":12709885,"Number":"XSCKD10629570","DIndex":0}
string formId = dto.TargetFormId.ToString();
_logger.LogInformation($"出库单->开始同步金蝶 单号:{billNo} erp明细Id:{erpDetail.Erp_DetailId} 数据: {JsonConvert.SerializeObject(dto)}");
var res_s = await sc_erpService.Save<ErpSalOutStockSaveDto>(salOutStock, formId);
if (!res_s.IsSuccess)
{
_logger.LogInformation($"出库单->修改数量失败 单号:{billNo} erp明细Id:{erpDetail.Erp_DetailId} 错误:{res_s.Message}");
return (Result.ReFailure(res_s.Message, res_s.Status), SyncStatus.SubmitFail, id);
}
//提交
_logger.LogInformation($"出库单->保存成功 开始提交 单号:{billNo} erp明细Id:{erpDetail.Erp_DetailId}");
ErpOperateDto o_dto = new ErpOperateDto(formId, res_s.Data);//res_s.Data
var resSubmit = await sc_erpService.Submit(o_dto, formId);
if (!resSubmit.IsSuccess)
{
_logger.LogInformation($"出库单->提交失败 单号:{billNo} erp明细Id:{erpDetail.Erp_DetailId} 错误:{resSubmit.Message}");
return (resSubmit, SyncStatus.SubmitFail, o_dto.Numbers.First());
}
//审核
_logger.LogInformation($"出库单->提交成功 开始审核 单号:{billNo} erp明细Id:{erpDetail.Erp_DetailId}");
resSubmit = await sc_erpService.Audit(o_dto, formId);
if (!resSubmit.IsSuccess)
{
_logger.LogInformation($"出库单->审核失败 单号:{billNo} erp明细Id:{erpDetail.Erp_DetailId} 错误:{resSubmit.Message}");
return (resSubmit, SyncStatus.CheckFail, o_dto.Numbers.First());
}
_logger.LogInformation($"同步金蝶成功");
return (Result.ReSuccess(), SyncStatus.Success, o_dto.Numbers.First());
}
}
}