using MyCode.Project.Domain.Message.Request.KingDee; using MyCode.Project.Domain.Message.Request.KingDee.SaveModel; using MyCode.Project.Domain.Message.Response.KingDee.K3Result; using MyCode.Project.Domain.Message.Response.KingDee.K3Result.Model; using MyCode.Project.Domain.Model; using MyCode.Project.Domain.Repositories; using MyCode.Project.Infrastructure.Common; using MyCode.Project.Infrastructure.Exceptions; using MyCode.Project.OutSideService; using MyCode.Project.Services.IServices; using System; using System.Collections.Generic; using System.Linq; namespace MyCode.Project.Services.Implementation { /// /// 采购入库单服务实现 /// 功能:处理采购入库单推送到金蝶云星空的业务逻辑 /// public class PurchaseStockInService : ServiceBase, IPurchaseStockInService { private const string BILL_TYPE_CODE = "RKD01_SYS"; private const string OWNER_TYPE = "BD_OwnerOrg"; private const string EXCHANGE_TYPE = "HLTX01_SYS"; private const string STOCK_STATUS = "KCZT01_SYS"; private const string DEFAULT_ORG = "100"; private const string DEFAULT_CURRENCY = "PRE001"; private const string DEFAULT_UNIT = "Pcs"; private const string ROW_TYPE_STANDARD = "Standard"; private const string ROW_TYPE_SERVICE = "Service"; private const string FORM_ID = "PUR_PurchaseStockIn"; private IPushKingDeeGoodsDocInRepository _pushKingDeeGoodsDocInRepository; private IBusiOrderGoodsDocInRepository _busiOrderGoodsDocInRepository; private IYTKJTShopParameterRepository _yTKJTShopParameterRepository; private IKingDeeService _kingDeeService; public PurchaseStockInService( IPushKingDeeGoodsDocInRepository pushKingDeeGoodsDocInRepository, IBusiOrderGoodsDocInRepository busiOrderGoodsDocInRepository, IYTKJTShopParameterRepository yTKJTShopParameterRepository, IKingDeeService kingDeeService) { _pushKingDeeGoodsDocInRepository = pushKingDeeGoodsDocInRepository; _busiOrderGoodsDocInRepository = busiOrderGoodsDocInRepository; _yTKJTShopParameterRepository = yTKJTShopParameterRepository; _kingDeeService = kingDeeService; } /// /// 推送采购入库单到金蝶云星空 /// /// PushKingDeeGoodsDocIn表的主键ID(Guid格式的字符串) /// 返回金蝶API的响应结果(JSON格式) public string PushPurchaseStockInToKingDee(string id) { var goodsDocIn = _pushKingDeeGoodsDocInRepository .Queryable() .Where(t => id == t.ID.ToString()) .First(); if (goodsDocIn == null) { throw new BaseException($"未找到ID为 {id} 的采购入库单记录"); } if (goodsDocIn.Status != 0 && goodsDocIn.Status != 1) { throw new BaseException($"采购入库单 {goodsDocIn.GoodsdocNo} 已经推送过,状态为:{goodsDocIn.Status},不允许重复推送"); } var param = _yTKJTShopParameterRepository .Queryable() .Where(t => t.FDOCUMENTSTATUS == "C") .First(); if (param == null) { throw new BaseException("没有找到已审核的门店参数配置,请联系管理员检查门店参数表"); } if (param.FPURCHASERECEIVING == "0") { throw new BaseException("门店参数配置中采购入库单同步功能未启用,请联系管理员检查门店参数配置"); } if (param.FPURCHASERECEIVING == "1") { var response = PushKingdeePurchaseStockIn(goodsDocIn, param); string result = JsonHelper.ToJson(response); if (response.IsSuccess) { var allRecords = _pushKingDeeGoodsDocInRepository .Queryable() .Where(t => t.GoodsdocNo == goodsDocIn.GoodsdocNo) .ToList(); foreach (var record in allRecords) { record.Status = 2; _pushKingDeeGoodsDocInRepository.Update(record); } } return result; } return ""; } private K3CloudResponseStatus PushKingdeePurchaseStockIn(PushKingDeeGoodsDocIn goodsDocIn, YTKJTShopParameter param) { // 从BusiOrderGoodsDocIn表获取明细数据 var detailList = _busiOrderGoodsDocInRepository .Queryable() .Where(t => t.GoodsdocNo == goodsDocIn.GoodsdocNo) .ToList(); if (detailList == null || detailList.Count == 0) { throw new BaseException($"采购入库单 {goodsDocIn.GoodsdocNo} 没有找到明细数据,无法推送"); } var vendorCodes = detailList.Where(d => !string.IsNullOrEmpty(d.VendCode)) .Select(d => d.VendCode) .Distinct() .ToList(); if (vendorCodes.Count > 1) { throw new BaseException($"采购入库单 {goodsDocIn.GoodsdocNo} 明细行中存在不同的供应商编码:{string.Join(",", vendorCodes)},无法推送"); } // 获取第一条明细用于主表信息 var firstDetail = detailList.FirstOrDefault(); if (firstDetail == null) { throw new BaseException($"采购入库单 {goodsDocIn.GoodsdocNo} 明细数据为空,无法推送"); } // 获取采购组织ID string purchaseOrgId = param.FPURCHASEORGID?.ToString() ?? param.FSALEORGID?.ToString() ?? DEFAULT_ORG; // 获取仓库编码 string warehouseCode = firstDetail.WarehouseCode ?? param.FPURCHASINGWAREHOUSECODE ?? ""; // 数据校验 if (string.IsNullOrEmpty(firstDetail.VendCode)) { throw new BaseException($"采购入库单 {goodsDocIn.GoodsdocNo} 供应商编码为空,无法推送"); } if (string.IsNullOrEmpty(warehouseCode)) { throw new BaseException($"采购入库单 {goodsDocIn.GoodsdocNo} 仓库编码为空,无法推送"); } // 构建金蝶API的明细行数据 var entryList = detailList.Select((n, index) => BuildEntryItem(n, param, purchaseOrgId, warehouseCode)).ToList(); if (entryList.Count == 0) { throw new BaseException($"采购入库单 {goodsDocIn.GoodsdocNo} 明细数据转换失败,无法推送"); } // 构建金蝶API的主表数据 var model = BuildMainModel(goodsDocIn, firstDetail, param, purchaseOrgId, entryList); // 构建金蝶API请求对象 BillSave billSave = new BillSave() { Model = model, IsAutoSubmitAndAudit = true, }; // 调用金蝶服务保存单据 var responseStatus = _kingDeeService.Save(FORM_ID, billSave); return responseStatus; } /// /// 构建明细行数据 /// private FPurchaseStockInEntryItem BuildEntryItem(BusiOrderGoodsDocIn detail, YTKJTShopParameter param, string orgId, string warehouseCode) { // 根据入库类型动态设置FRowType(如果入库类型包含"服务"或"费用"等关键字,则设置为Service,否则为Standard) string rowType = ROW_TYPE_STANDARD; if (!string.IsNullOrEmpty(detail.InouttypeName)) { string inoutType = detail.InouttypeName.ToUpper(); if (inoutType.Contains("服务") || inoutType.Contains("费用") || inoutType.Contains("SERVICE") || inoutType.Contains("FEE")) { rowType = ROW_TYPE_SERVICE; } } // 计算折扣后金额(无税金额) decimal? allAmountExceptDisCount = detail.BaceCurrencyNoTaxAmount ?? 0; return new FPurchaseStockInEntryItem() { FRowType = rowType, FMaterialId = new FMaterialId() { FNumber = detail.SkuBarcode ?? "" }, FUnitID = new FUnitID() { FNumber = detail.UnitName ?? DEFAULT_UNIT }, FMaterialDesc = detail.GoodsName ?? "", FWWPickMtlQty = 0, FRealQty = detail.Quantity ?? 0, FPriceUnitID = new FPriceUnitID() { FNumber = detail.UnitName ?? DEFAULT_UNIT }, FPrice = detail.BaceCurrencyNoTaxPrice ?? 0, FDisPriceQty = 0, FStockStatusId = new FStockStatusId() { FNumber = STOCK_STATUS }, FGiveAway = false, FOWNERTYPEID = OWNER_TYPE, FExtAuxUnitQty = 0, FCheckInComing = false, FIsReceiveUpdateStock = false, FInvoicedJoinQty = 0, FPriceBaseQty = detail.Quantity ?? 0, FRemainInStockUnitId = new FRemainInStockUnitId() { FNumber = detail.UnitName ?? DEFAULT_UNIT }, FBILLINGCLOSE = false, FRemainInStockQty = detail.Quantity ?? 0, FRemainInStockBaseQty = detail.Quantity ?? 0, FAPNotJoinQty = detail.Quantity ?? 0, FTaxPrice = detail.BaceCurrencyWithTaxPrice ?? 0, FEntryTaxRate = detail.TaxRate ?? (decimal?)param.FTAXRATE, FDiscountRate = 0, FCostPrice = detail.BaceCurrencyCostPrice ?? 0, FAuxUnitQty = 0, FOWNERID = new FOwnerId() { FNumber = orgId }, FSRCBILLTYPEID = detail.SourceBillNo, FSRCBillNo = detail.SourceBillNo ?? detail.BillNo ?? "", FAllAmountExceptDisCount = allAmountExceptDisCount, FPriceDiscount = 0, FConsumeSumQty = 0, FBaseConsumeSumQty = 0, FRejectsDiscountAmount = 0, FSalOutStockEntryId = 0, FBeforeDisPriceQty = 0, FPayableEntryID = 0, FSUBREQBILLSEQ = 0, FSUBREQENTRYID = 0, FBatchNo = detail.BatchNo ?? "", FSerialNo = detail.SerialNo ?? "" }; } private PurchaseStockInModel BuildMainModel(PushKingDeeGoodsDocIn goodsDocIn, BusiOrderGoodsDocIn firstDetail, YTKJTShopParameter param, string orgId, List entryList) { string dateStr = goodsDocIn.InOutDate2?.ToString("yyyy-MM-dd") ?? DateTime.Now.ToString("yyyy-MM-dd"); return new PurchaseStockInModel() { FID = 0, FBillTypeID = new FBillTypeID() { FNUMBER = BILL_TYPE_CODE }, FDate = dateStr, FStockOrgId = new FStockOrgId() { FNumber = orgId }, FDemandOrgId = new FDemandOrgId() { FNumber = orgId }, FCorrespondOrgId = new FCorrespondOrgId() { FNumber = orgId }, FPurchaseOrgId = new FPurchaseOrgId() { FNumber = orgId }, FOwnerTypeIdHead = OWNER_TYPE, FOwnerIdHead = new FOwnerIdHead() { FNumber = orgId }, FCDateOffsetValue = 0, FSplitBillType = "A", FSalOutStockOrgId = new FSalOutStockOrgId() { FNumber = orgId }, FInStockFin = new FInStockFin() { FSettleOrgId = new FSettleOrgId() { FNumber = orgId }, FSettleCurrId = new FSettleCurrId() { FNumber = goodsDocIn.CurrencyCode ?? DEFAULT_CURRENCY }, FIsIncludedTax = true, FPriceTimePoint = "1", FLocalCurrId = new FLocalCurrId() { FNumber = goodsDocIn.CurrencyCode ?? DEFAULT_CURRENCY }, FExchangeTypeId = new FExchangeTypeId() { FNumber = EXCHANGE_TYPE }, FExchangeRate = goodsDocIn.CurrencyRate ?? 1m, FISPRICEEXCLUDETAX = true, FAllDisCount = 0m, FHSExchangeRate = 1m }, FInStockEntry = entryList }; } } }