using DS.Module.Core; using DS.Module.Core.Enums; using DS.WMS.Core.Application.Entity; using DS.WMS.Core.Application.Method; using DS.WMS.Core.Code.Entity; using DS.WMS.Core.Invoice.Dtos; using DS.WMS.Core.Invoice.Interface; using DS.WMS.Core.Sys.Entity; using DS.WMS.Core.Sys.Interface; using Masuit.Tools; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using SqlSugar; namespace DS.WMS.Core.Invoice.Method { /// /// 发票开出接口服务 /// public sealed class InvoiceIssuanceService : ApplicationServiceBase, IInvoiceIssuanceService { static readonly ApiFox api; static InvoiceIssuanceService() { api = new ApiFox(); } readonly Lazy CommonService; readonly Lazy InvoiceService; /// /// 初始化并加载配置 /// /// public InvoiceIssuanceService(IServiceProvider provider) : base(provider) { var config = provider.GetRequiredService(); CommonService = new Lazy(provider.GetRequiredService()); InvoiceService = new Lazy(provider.GetRequiredService()); } /// /// 发起开票请求 /// /// 发票ID /// public async Task InitiateAsync(params long[] ids) { ArgumentNullException.ThrowIfNull(ids, nameof(ids)); long userId = long.Parse(User.UserId); var userInfo = await Db.Queryable().Where(x => x.Id == userId).Select(x => new { x.UserName, x.IdCardNo }).FirstAsync(); if (string.IsNullOrEmpty(userInfo.IdCardNo)) return DataResult.FailedWithDesc(MultiLanguageConst.DrawerIDNumberIsNull); //请求参数设置 InvoiceIssuanceRequest request = new() { order = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).Select(x => new InvoiceInfo { invoiceType = x.Type == null ? "1" : ((int)x.Type).ToString(), orderNo = x.BillNO, email = x.Email, buyerTaxNum = x.CustomerTaxID, buyerName = x.InvoiceHeader, buyerAddress = x.CustomerAddressTel, buyerTel = "", gmfkhh = x.CustomerBankName, gmfzh = x.CustomerAccount, skyhmc = x.BankName, skyhzh = x.Account, checker = x.Checker, payee = x.Payee, invoiceLine = x.CategoryCode, //---------金额项--------- hjse = x.InvoiceAmount * x.TaxRate, hjje = x.InvoiceAmount - x.InvoiceAmount * x.TaxRate, jshj = x.InvoiceAmount, clerk = userInfo.UserName, //开票人 kprzjhm = userInfo.IdCardNo, //证件号 kprzjlx = "201", //身份证, remark = x.Note, //---------发票明细--------- invoiceDetail = SqlFunc.Subqueryable().Where(y => y.ApplicationId == x.Id) .LeftJoin((y, z) => y.CodeId == z.Id).ToList((y, z) => new InvoiceDetailInfo { mxxh = SqlFunc.RowNumber(y.Id), //x.InvoiceDetails.IndexOf(y) + 1, xmmc = y.Name, spfwjc = string.Empty, specType = y.Specification, unit = y.Unit, num = y.Quantity.ToString(), price = y.TaxUnitPrice.ToString(), taxExcludedAmount = y.Amount - y.TaxAmount, taxRate = y.TaxRate.ToString(), tax = y.TaxAmount, taxIncludedAmount = y.Amount, goodsCode = z.Code, //商品和服务税收分类合并编码 invoiceLineProperty = "00", favouredPolicyFlag = z.PreferentialPolicyDescription }) }).ToListAsync() }; if (request.order.Count == 0 || request.order.Any(x => x.invoiceDetail.Count == 0)) return DataResult.FailedWithDesc(MultiLanguageConst.InvoiceIncomplete); //var result = await api.PostAsync>("/api/Invoice/services", request); var order = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)).FirstAsync(); //如果开票中所属机构为空,则取用户的orgId var userOrgId = (order.SaleDeptId != null && order.SaleDeptId > 0) ? order.SaleDeptId : User.OrgId; //var orgauthinfo = Db.Queryable().LeftJoin((t, a) => t.Id == a.OrgId) // .Where((t, a) => t.Id == userOrgId) // .Select((t, a) => new // { // a.Key, // a.Secret // }).First(); //改版 取消从组织机构api授权获取key信息, 改为从"接口账户维护"中获取 var orgauthinfo = await TenantDb.Queryable().Where(x => x.OrgId==userOrgId && x.AccountType == "Invoice").FirstAsync(); if (orgauthinfo == null) { //未匹配到请求密钥,请检查 return DataResult.Failed("未匹配到请求密钥,请检查"); } api.DefaultHeaders.Clear(); api.DefaultHeaders.Add("USER_KEY", orgauthinfo.AppKey); api.DefaultHeaders.Add("USER_SECRET", orgauthinfo.AppSecret); var result = await api.PostAsync>(AppSetting.app(new string[] { "InvoiceApi", "BaseUrl" }) + "/api/Invoice/services", request); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); var invResult = result.Data; if (invResult.success) { string sn = invResult.data; //开票成功返回流水号 var invoices = ids.Select(x => new Entity.Invoice { Id = x, SN = sn, ApiType = InvoiceApiType.Default }).ToList(); await TenantDb.Updateable(invoices).UpdateColumns(x => new { x.SN, x.ApiType }).ExecuteCommandAsync(); await UpdateInvoiceNumberAsync(sn); return DataResult.Successed("开票已提交", data: sn); } else { if (invResult.code == 1) return await InitiateAsync(ids); return DataResult.Failed("开票API返回错误:" + invResult.msg); } } /// /// 更新发票号码 /// /// 开票流水号 /// public async Task UpdateInvoiceNumberAsync(string sn) { var invResult = DataResult.Success; var invoices = await TenantDb.Queryable() .Where(x => x.SN == sn && !SqlFunc.IsNullOrEmpty(x.InvoiceNO)) .Select(x => new Entity.Invoice { SN = x.SN, BillNO = x.BillNO, InvoiceNO = x.InvoiceNO, Type = x.Type, ApiCode = x.ApiCode, ApiStatus = x.ApiStatus, PDFUrl = x.PDFUrl }).ToListAsync(); invResult.Data = invoices; if (invoices.Count > 0) return invResult; var order = await TenantDb.Queryable().Where(x => x.SN == sn).FirstAsync(); //如果开票中所属机构为空,则取用户的orgId var userOrgId = (order.SaleDeptId != null && order.SaleDeptId > 0) ? order.SaleDeptId : User.OrgId; //var orgauthinfo = Db.Queryable().LeftJoin((t, a) => t.Id == a.OrgId) // .Where((t, a) => t.Id == userOrgId) // .Select((t, a) => new // { // a.Key, // a.Secret // }).First(); var orgauthinfo = await TenantDb.Queryable().Where(x => x.OrgId == userOrgId&&x.AccountType== "Invoice").FirstAsync(); if (orgauthinfo == null) { //未匹配到请求密钥,请检查 return DataResult.Failed("未匹配到请求密钥,请检查"); } api.DefaultHeaders.Clear(); api.DefaultHeaders.Add("USER_KEY", orgauthinfo.AppKey); api.DefaultHeaders.Add("USER_SECRET", orgauthinfo.AppSecret); var result = await api.PostAsync>(AppSetting.app(["InvoiceApi", "BaseUrl"]) + "/api/Invoice/GetInvoiceState", new { guid = sn }); if (!result.Succeeded) return DataResult.Failed(result.Message, result.MultiCode); var queryResult = result.Data; if (queryResult == null || !queryResult.success) return DataResult.Failed(queryResult?.msg); if (queryResult.data.Order?.Count > 0) { var billNumbers = queryResult.data.Order.Select(x => x.orderNo); //获取发票ID及类型 var list = await TenantDb.Queryable().Where(x => billNumbers.Contains(x.BillNO)) .Select(x => new { x.Id, x.BillNO, x.Type, x.Mode }).ToListAsync(); DateTime dtNow = DateTime.Now; long userId = long.Parse(User.UserId); foreach (var item in queryResult.data.Order) { var inv = new Entity.Invoice { SN = sn, BillNO = item.orderNo, InvoiceNO = item.fphm, ApiCode = item.State.ToString(), ApiStatus = item.UpMessage, PDFUrl = item.FileUrl, IsLocked = true, LockTime = dtNow, LockUserId = userId }; var storedItem = list.Find(x => x.BillNO == inv.BillNO); if (storedItem == null) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); inv.Id = storedItem.Id; inv.Type = storedItem.Type; inv.Mode = storedItem.Mode; invoices.Add(inv); switch (item.State) { case 1: inv.ApiStatus = "已提交待上传"; break; case 2: inv.ApiStatus = "已上传待开票"; break; case 3: inv.ApiStatus = "开票成功"; break; } } await TenantDb.Ado.BeginTranAsync(); try { var redIds = invoices.Where(x => x.Type == InvoiceType.Red).Select(x => x.Id).ToList(); //开出红票时,需要删除蓝票的费用明细 if (redIds.Count > 0) { //获取蓝票费用明细ID var ids = await TenantDb.Queryable() .InnerJoin((d, i) => d.ApplicationId == i.Id) .Where((d, i) => redIds.Contains(i.RedId.Value)) .Select((d, i) => d.Id).ToArrayAsync(); //删除蓝票费用明细 await InvoiceService.Value.DeleteDetailAsync(ids); } await TenantDb.Updateable(invoices).UpdateColumns(x => new { x.InvoiceNO, x.ApiCode, x.ApiStatus, x.PDFUrl, x.IsLocked, x.LockUserId, x.LockTime }).ExecuteCommandAsync(); //回写发票申请的状态 if (invoices.Exists(x => x.Mode == InvoiceMode.Applcation && x.Type == InvoiceType.Blue)) await InvoiceService.Value.RefreshApplicationStatus(invoices.Select(x => x.InvoiceNO)!); await TenantDb.Ado.CommitTranAsync(); } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } invResult.Data = invoices; return invResult; } /// /// 更新发票号码 /// /// /// public async Task UpdateInvoiceNumberAsync(long id) { var model = await TenantDb.Queryable().Where(x => x.Id == id) .Select(x => new { x.Id, x.SN, x.SaleDeptId, x.ApiType }).FirstAsync(); if (model == null) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); return await UpdateInvoiceNumberAsync(model.SN); } /// /// 发票冲红 /// /// /// public async Task ReverseAsync(InvoiceReversalRequest request) { var blueInvoice = await TenantDb.Queryable() .WhereIF(request.InvoiceId.HasValue, x => x.Id == request.InvoiceId) .WhereIF(!request.orderNo.IsNullOrEmpty(), x => x.BillNO == request.orderNo) .FirstAsync(); if (blueInvoice == null) return DataResult.FailedWithDesc("传入的发票ID或编号无效"); if (blueInvoice.Type == InvoiceType.Red) return DataResult.FailedWithDesc("红票无法被冲红"); if (blueInvoice.IsSettled) return DataResult.FailedWithDesc("此发票已结算"); var redInvoice = blueInvoice.DeepClone(); var sequence = CommonService.Value.GetSequenceNext(); redInvoice.Id = 0; redInvoice.BillNO = sequence.Data; redInvoice.SN = request.senid; redInvoice.Type = InvoiceType.Red; redInvoice.InvoiceAmount = 0 - redInvoice.InvoiceAmount; redInvoice.ApiCode = redInvoice.ApiStatus = redInvoice.PDFUrl = null; redInvoice.CreateBy = long.Parse(User.UserId); redInvoice.CreateTime = DateTime.Now; redInvoice.UpdateBy = null; redInvoice.UpdateTime = null; redInvoice.Details = await TenantDb.Queryable().Where(x => x.ApplicationId == blueInvoice.Id).ToListAsync(); foreach (var detail in redInvoice.Details) { detail.Id = detail.ApplicationId = 0; detail.ApplyAmount = 0 - detail.ApplyAmount; detail.OriginalAmount = 0 - detail.OriginalAmount; detail.OriginalProcessedAmount = detail.ProcessedAmount = 0; } redInvoice.InvoiceDetails = await TenantDb.Queryable().Where(x => x.ApplicationId == blueInvoice.Id).ToListAsync(); if (redInvoice.InvoiceDetails.Count == 0) redInvoice.InvoiceDetails.AddRange(blueInvoice.InvoiceDetails); foreach (var detail in redInvoice.InvoiceDetails) { detail.Id = detail.ApplicationId = 0; detail.Amount = 0 - detail.Amount; detail.TaxUnitPrice = 0 - detail.TaxUnitPrice; detail.UnitPrice = 0 - detail.UnitPrice; } await TenantDb.Ado.BeginTranAsync(); try { await TenantDb.InsertNav(redInvoice).Include(x => x.Details).Include(x => x.InvoiceDetails).ExecuteCommandAsync(); blueInvoice.RedId = redInvoice.Id; blueInvoice.IsSetRed = true; blueInvoice.RedCode = request.chyyDm; if (string.IsNullOrEmpty(request.Reason)) { switch (request.chyyDm) { case "01": request.Reason = "开票有误"; break; case "02": request.Reason = "销货退回"; break; case "03": request.Reason = "服务中止"; break; case "04": request.Reason = "销售折让"; break; default: request.Reason = "直接冲红"; break; } } blueInvoice.RedReason = request.Reason; await TenantDb.Updateable(blueInvoice).UpdateColumns(x => new { x.IsSetRed, x.RedId, x.RedCode, x.RedReason }).ExecuteCommandAsync(); //如果开票中所属机构为空,则取用户的orgId var userOrgId = (blueInvoice.SaleDeptId != null && blueInvoice.SaleDeptId > 0) ? blueInvoice.SaleDeptId : User.OrgId; //接口请求key密钥 //var orgauthinfo = Db.Queryable().Where(t => t.OrgId == userOrgId).First(); var orgauthinfo = await TenantDb.Queryable().Where(x => x.OrgId == userOrgId && x.AccountType == "Invoice").FirstAsync(); if (orgauthinfo == null) { //未匹配到请求密钥,请检查 return DataResult.Failed("未匹配到请求密钥,请检查"); } api.DefaultHeaders.Clear(); api.DefaultHeaders.Add("USER_KEY", orgauthinfo.AppKey); api.DefaultHeaders.Add("USER_SECRET", orgauthinfo.AppSecret); request.orderNo = blueInvoice.BillNO; //蓝票业务号 var result = await api.PostAsync>(AppSetting.app(["InvoiceApi", "BaseUrl"]) + "/api/Invoice/RedInvoicing", request); if (result.Data == null || !result.Data.success) return DataResult.Failed(result.Data == null ? "请求失败" : "开票API返回错误:" + result.Data.msg); request.qrlx = "Y"; result = await api.PostAsync>(AppSetting.app(["InvoiceApi", "BaseUrl"]) + "/api/Invoice/RedInvoicing", request); if (result.Data == null || !result.Data.success) return DataResult.Failed(result.Data == null ? "请求失败" : "开票API返回错误:" + result.Data.msg); await TenantDb.Ado.CommitTranAsync(); var result2 = DataResult.Success; result2.Data = redInvoice; return result2; } catch (Exception ex) { await TenantDb.Ado.RollbackTranAsync(); await ex.LogAsync(Db); return DataResult.FailedWithDesc(nameof(MultiLanguageConst.Operation_Failed)); } } /// /// 发送邮件 /// /// 发票ID /// public async Task SendMailAsync(long id) { var inv = await TenantDb.Queryable().Where(x => x.Id == id) .Select(x => new { orderNo = x.BillNO, x.SaleDeptId, x.InvoiceNO }).FirstAsync(); if (inv == null) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.EmptyData)); if (string.IsNullOrEmpty(inv.InvoiceNO)) return DataResult.Failed("发票尚未开出"); //如果开票中所属机构为空,则取用户的orgId var userOrgId = (inv.SaleDeptId != null && inv.SaleDeptId > 0) ? inv.SaleDeptId : User.OrgId; //接口请求key密钥 //var orgauthinfo = Db.Queryable().Where(t => t.OrgId == userOrgId).First(); var orgauthinfo = await TenantDb.Queryable().Where(x => x.OrgId == userOrgId && x.AccountType == "Invoice").FirstAsync(); if (orgauthinfo == null) { //未匹配到请求密钥,请检查 return DataResult.Failed("未匹配到请求密钥,请检查"); } api.DefaultHeaders.Clear(); api.DefaultHeaders.Add("USER_KEY", orgauthinfo.AppKey); api.DefaultHeaders.Add("USER_SECRET", orgauthinfo.AppSecret); var result = await api.PostAsync>(AppSetting.app(["InvoiceApi", "BaseUrl"]) + "/api/Invoice/InvoiceToEmil", inv); if (result.Data == null || !result.Data.success) return DataResult.Failed(result.Data == null ? "请求失败" : "开票API返回错误:" + result.Data.msg); return DataResult.Success; } /// /// 添加租户信息 /// /// 租户信息 /// /// 为null时引发。 public async Task AddTenantAsync(Tenant tenant) { ArgumentNullException.ThrowIfNull(tenant, nameof(tenant)); var result = await api.PostAsync("/api/Login/AddTenant", tenant); return result.Data; } } }