You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

508 lines
23 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
{
/// <summary>
/// 发票开出接口服务
/// </summary>
public sealed class InvoiceIssuanceService : ApplicationServiceBase, IInvoiceIssuanceService
{
static readonly ApiFox api;
static InvoiceIssuanceService()
{
api = new ApiFox();
}
readonly Lazy<ICommonService> CommonService;
readonly Lazy<IGeneralInvoiceService> InvoiceService;
/// <summary>
/// 初始化并加载配置
/// </summary>
/// <param name="provider"></param>
public InvoiceIssuanceService(IServiceProvider provider) : base(provider)
{
var config = provider.GetRequiredService<IConfiguration>();
CommonService = new Lazy<ICommonService>(provider.GetRequiredService<ICommonService>());
InvoiceService = new Lazy<IGeneralInvoiceService>(provider.GetRequiredService<IGeneralInvoiceService>());
}
/// <summary>
/// 发起开票请求
/// </summary>
/// <param name="ids">发票ID</param>
/// <returns></returns>
public async Task<DataResult> InitiateAsync(params long[] ids)
{
ArgumentNullException.ThrowIfNull(ids, nameof(ids));
long userId = long.Parse(User.UserId);
var userInfo = await Db.Queryable<SysUser>().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<Entity.Invoice>().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<InvoiceDetail>().Where(y => y.ApplicationId == x.Id)
.LeftJoin<CodeInvoice>((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<InvoiceResult<string>>("/api/Invoice/services", request);
var order = await TenantDb.Queryable<Entity.Invoice>().Where(x => ids.Contains(x.Id)).FirstAsync();
//如果开票中所属机构为空,则取用户的orgId
var userOrgId = (order.SaleDeptId != null && order.SaleDeptId > 0) ? order.SaleDeptId : User.OrgId;
//var orgauthinfo = Db.Queryable<SysOrg>().LeftJoin<SysOrgAuth>((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<CodeThirdParty>().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<InvoiceResult<string>>(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);
}
}
/// <summary>
/// 更新发票号码
/// </summary>
/// <param name="sn">开票流水号</param>
/// <returns></returns>
public async Task<DataResult> UpdateInvoiceNumberAsync(string sn)
{
var invResult = DataResult.Success;
var invoices = await TenantDb.Queryable<Entity.Invoice>()
.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<Entity.Invoice>().Where(x => x.SN == sn).FirstAsync();
//如果开票中所属机构为空,则取用户的orgId
var userOrgId = (order.SaleDeptId != null && order.SaleDeptId > 0) ? order.SaleDeptId : User.OrgId;
//var orgauthinfo = Db.Queryable<SysOrg>().LeftJoin<SysOrgAuth>((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<CodeThirdParty>().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<InvoiceResult<InvoiceQuery>>(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<Entity.Invoice>().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<ApplicationDetail>()
.InnerJoin<Entity.Invoice>((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;
}
/// <summary>
/// 更新发票号码
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<DataResult> UpdateInvoiceNumberAsync(long id)
{
var model = await TenantDb.Queryable<Entity.Invoice>().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);
}
/// <summary>
/// 发票冲红
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResult> ReverseAsync(InvoiceReversalRequest request)
{
var blueInvoice = await TenantDb.Queryable<Entity.Invoice>()
.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<Entity.Invoice>();
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<ApplicationDetail>().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<InvoiceDetail>().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<SysOrgAuth>().Where(t => t.OrgId == userOrgId).First();
var orgauthinfo = await TenantDb.Queryable<CodeThirdParty>().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<InvoiceResult<SetRedConfirmation>>(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<InvoiceResult<SetRedConfirmation>>(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));
}
}
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="id">发票ID</param>
/// <returns></returns>
public async Task<DataResult> SendMailAsync(long id)
{
var inv = await TenantDb.Queryable<Entity.Invoice>().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<SysOrgAuth>().Where(t => t.OrgId == userOrgId).First();
var orgauthinfo = await TenantDb.Queryable<CodeThirdParty>().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<InvoiceResult<SetRedConfirmation>>(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;
}
/// <summary>
/// 添加租户信息
/// </summary>
/// <param name="tenant">租户信息</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException">当<paramref name="tenant"/>为null时引发。</exception>
public async Task<string> AddTenantAsync(Tenant tenant)
{
ArgumentNullException.ThrowIfNull(tenant, nameof(tenant));
var result = await api.PostAsync<string>("/api/Login/AddTenant", tenant);
return result.Data;
}
}
}