mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Fix LN invoices (#1955)
* Fix LN invoices This commit adds more to the previous LN fix in the case of a partial payment to an invoice. While it generated a new LN invoice after 1 partial payment was made, there were some new issues uncovered: * Any other subsequent partial payments was not listened to and did not generate an invoice ( fixed by listeneing to received payment event and makng sure that the status was already set `to partialPaid`) * Any other subsequent partial payments caused a DbConcurrency error and did not generate an invoice ( Fixed in `MarkUnassigned`)
This commit is contained in:
@@ -300,32 +300,35 @@ namespace BTCPayServer.Controllers
|
|||||||
return RedirectToAction(nameof(PullPaymentController.ViewPullPayment),
|
return RedirectToAction(nameof(PullPaymentController.ViewPullPayment),
|
||||||
"PullPayment",
|
"PullPayment",
|
||||||
new { pullPaymentId = ppId });
|
new { pullPaymentId = ppId });
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private InvoiceDetailsModel InvoicePopulatePayments(InvoiceEntity invoice)
|
private InvoiceDetailsModel InvoicePopulatePayments(InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
var model = new InvoiceDetailsModel();
|
return new InvoiceDetailsModel
|
||||||
model.Archived = invoice.Archived;
|
|
||||||
model.Payments = invoice.GetPayments();
|
|
||||||
foreach (var data in invoice.GetPaymentMethods())
|
|
||||||
{
|
{
|
||||||
var accounting = data.Calculate();
|
Archived = invoice.Archived,
|
||||||
var paymentMethodId = data.GetId();
|
Payments = invoice.GetPayments(),
|
||||||
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
|
CryptoPayments = invoice.GetPaymentMethods().Select(
|
||||||
|
data =>
|
||||||
cryptoPayment.PaymentMethodId = paymentMethodId;
|
{
|
||||||
cryptoPayment.PaymentMethod = paymentMethodId.ToPrettyString();
|
var accounting = data.Calculate();
|
||||||
cryptoPayment.Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
var paymentMethodId = data.GetId();
|
||||||
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
return new InvoiceDetailsModel.CryptoPayment
|
||||||
cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
{
|
||||||
var paymentMethodDetails = data.GetPaymentMethodDetails();
|
PaymentMethodId = paymentMethodId,
|
||||||
cryptoPayment.Address = paymentMethodDetails.GetPaymentDestination();
|
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||||
cryptoPayment.Rate = ExchangeRate(data);
|
Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC),
|
||||||
model.CryptoPayments.Add(cryptoPayment);
|
paymentMethodId.CryptoCode),
|
||||||
}
|
Paid = _CurrencyNameTable.DisplayFormatCurrency(
|
||||||
return model;
|
accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC),
|
||||||
|
paymentMethodId.CryptoCode),
|
||||||
|
Overpaid = _CurrencyNameTable.DisplayFormatCurrency(
|
||||||
|
accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode),
|
||||||
|
Address = data.GetPaymentMethodDetails().GetPaymentDestination(),
|
||||||
|
Rate = ExchangeRate(data)
|
||||||
|
};
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("invoices/{invoiceId}/archive")]
|
[HttpPost("invoices/{invoiceId}/archive")]
|
||||||
|
|||||||
@@ -29,12 +29,20 @@ namespace BTCPayServer.HostedServices
|
|||||||
public List<object> Events { get; set; } = new List<object>();
|
public List<object> Events { get; set; } = new List<object>();
|
||||||
|
|
||||||
bool _Dirty = false;
|
bool _Dirty = false;
|
||||||
|
private bool _Unaffect;
|
||||||
|
|
||||||
public void MarkDirty()
|
public void MarkDirty()
|
||||||
{
|
{
|
||||||
_Dirty = true;
|
_Dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UnaffectAddresses()
|
||||||
|
{
|
||||||
|
_Unaffect = true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Dirty => _Dirty;
|
public bool Dirty => _Dirty;
|
||||||
|
public bool Unaffect => _Unaffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly InvoiceRepository _InvoiceRepository;
|
readonly InvoiceRepository _InvoiceRepository;
|
||||||
@@ -63,15 +71,12 @@ namespace BTCPayServer.HostedServices
|
|||||||
if (invoice.Status == InvoiceStatus.New && invoice.ExpirationTime <= DateTimeOffset.UtcNow)
|
if (invoice.Status == InvoiceStatus.New && invoice.ExpirationTime <= DateTimeOffset.UtcNow)
|
||||||
{
|
{
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
context.UnaffectAddresses();
|
||||||
|
|
||||||
invoice.Status = InvoiceStatus.Expired;
|
invoice.Status = InvoiceStatus.Expired;
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Expired));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Expired));
|
||||||
if (invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
if (invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.ExpiredPaidPartial));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.ExpiredPaidPartial));
|
||||||
}
|
}
|
||||||
|
|
||||||
var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray();
|
|
||||||
var allPaymentMethods = invoice.GetPaymentMethods();
|
var allPaymentMethods = invoice.GetPaymentMethods();
|
||||||
var paymentMethod = GetNearestClearedPayment(allPaymentMethods, out var accounting);
|
var paymentMethod = GetNearestClearedPayment(allPaymentMethods, out var accounting);
|
||||||
if (paymentMethod == null)
|
if (paymentMethod == null)
|
||||||
@@ -85,7 +90,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.PaidInFull));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.PaidInFull));
|
||||||
invoice.Status = InvoiceStatus.Paid;
|
invoice.Status = InvoiceStatus.Paid;
|
||||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
|
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
context.UnaffectAddresses();
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
}
|
}
|
||||||
else if (invoice.Status == InvoiceStatus.Expired && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidLate)
|
else if (invoice.Status == InvoiceStatus.Expired && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidLate)
|
||||||
@@ -136,14 +141,14 @@ namespace BTCPayServer.HostedServices
|
|||||||
// And not enough amount confirmed
|
// And not enough amount confirmed
|
||||||
(confirmedAccounting.Paid < accounting.MinimumTotalDue))
|
(confirmedAccounting.Paid < accounting.MinimumTotalDue))
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
context.UnaffectAddresses();
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.FailedToConfirm));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.FailedToConfirm));
|
||||||
invoice.Status = InvoiceStatus.Invalid;
|
invoice.Status = InvoiceStatus.Invalid;
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
}
|
}
|
||||||
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
|
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
context.UnaffectAddresses();
|
||||||
invoice.Status = InvoiceStatus.Confirmed;
|
invoice.Status = InvoiceStatus.Confirmed;
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Confirmed));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Confirmed));
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
@@ -279,6 +284,10 @@ namespace BTCPayServer.HostedServices
|
|||||||
break;
|
break;
|
||||||
var updateContext = new UpdateInvoiceContext(invoice);
|
var updateContext = new UpdateInvoiceContext(invoice);
|
||||||
await UpdateInvoice(updateContext);
|
await UpdateInvoice(updateContext);
|
||||||
|
if (updateContext.Unaffect)
|
||||||
|
{
|
||||||
|
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||||
|
}
|
||||||
if (updateContext.Dirty)
|
if (updateContext.Dirty)
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
||||||
|
|||||||
@@ -25,10 +25,5 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
{
|
{
|
||||||
return 0.0m;
|
return 0.0m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPaymentDetails(IPaymentMethodDetails newPaymentMethodDetails)
|
|
||||||
{
|
|
||||||
BOLT11 = newPaymentMethodDetails.GetPaymentDestination();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,76 +137,32 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
readonly CompositeDisposable leases = new CompositeDisposable();
|
readonly CompositeDisposable leases = new CompositeDisposable();
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(inv =>
|
leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(async inv =>
|
||||||
{
|
{
|
||||||
if (inv.Name == InvoiceEvent.Created)
|
if (inv.Name == InvoiceEvent.Created)
|
||||||
{
|
{
|
||||||
_CheckInvoices.Writer.TryWrite(inv.Invoice.Id);
|
_CheckInvoices.Writer.TryWrite(inv.Invoice.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inv.Name == InvoiceEvent.ReceivedPayment && inv.Invoice.Status == InvoiceStatus.New && inv.Invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||||
|
{
|
||||||
|
var pm = inv.Invoice.GetPaymentMethods().First();
|
||||||
|
if (pm.Calculate().Due.GetValue(pm.Network as BTCPayNetwork) > 0m)
|
||||||
|
{
|
||||||
|
await CreateNewLNInvoiceForBTCPayInvoice(inv.Invoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceDataChangedEvent>(async inv =>
|
leases.Add(_Aggregator.Subscribe<Events.InvoiceDataChangedEvent>(async inv =>
|
||||||
{
|
{
|
||||||
if (inv.State.Status == InvoiceStatus.New &&
|
if (inv.State.Status == InvoiceStatus.New &&
|
||||||
inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||||
{
|
{
|
||||||
|
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId);
|
||||||
var paymentMethods = invoice.GetPaymentMethods()
|
await CreateNewLNInvoiceForBTCPayInvoice(invoice);
|
||||||
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike).ToArray();
|
|
||||||
var store = await _storeRepository.FindStore(invoice.StoreId);
|
|
||||||
if (paymentMethods.Any())
|
|
||||||
{
|
|
||||||
var logs = new InvoiceLogs();
|
|
||||||
logs.Write("Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.", InvoiceEventData.EventSeverity.Info);
|
|
||||||
foreach (var paymentMethod in paymentMethods)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var supportedMethod =
|
|
||||||
invoice.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(
|
|
||||||
paymentMethod.GetId()).First();
|
|
||||||
var prepObj =
|
|
||||||
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store,
|
|
||||||
paymentMethod.Network);
|
|
||||||
var newPaymentMethodDetails =
|
|
||||||
await _lightningLikePaymentHandler.CreatePaymentMethodDetails(
|
|
||||||
logs, supportedMethod,
|
|
||||||
paymentMethod, store, paymentMethod.Network, prepObj);
|
|
||||||
|
|
||||||
|
|
||||||
var instanceListenerKey = (paymentMethod.Network.CryptoCode,
|
|
||||||
supportedMethod.GetLightningUrl().ToString());
|
|
||||||
if (_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
|
|
||||||
{
|
|
||||||
|
|
||||||
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
|
|
||||||
paymentMethod.Network);
|
|
||||||
|
|
||||||
instanceListener.AddListenedInvoice(new ListenedInvoice()
|
|
||||||
{
|
|
||||||
Expiration = invoice.ExpirationTime,
|
|
||||||
Uri = supportedMethod.GetLightningUrl().BaseUri.AbsoluteUri,
|
|
||||||
PaymentMethodDetails = (LightningLikePaymentMethodDetails) newPaymentMethodDetails,
|
|
||||||
SupportedPaymentMethod = supportedMethod,
|
|
||||||
PaymentMethod = paymentMethod,
|
|
||||||
Network = (BTCPayNetwork) paymentMethod.Network,
|
|
||||||
InvoiceId = invoice.Id
|
|
||||||
});
|
|
||||||
|
|
||||||
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
|
|
||||||
newPaymentMethodDetails, paymentMethod.GetId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
|
|
||||||
InvoiceEventData.EventSeverity.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
|
|
||||||
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}));
|
}));
|
||||||
_CheckingInvoice = CheckingInvoice(_Cts.Token);
|
_CheckingInvoice = CheckingInvoice(_Cts.Token);
|
||||||
_ListenPoller = new Timer(async s =>
|
_ListenPoller = new Timer(async s =>
|
||||||
@@ -224,6 +180,66 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CreateNewLNInvoiceForBTCPayInvoice(InvoiceEntity invoice)
|
||||||
|
{
|
||||||
|
var paymentMethods = invoice.GetPaymentMethods()
|
||||||
|
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike)
|
||||||
|
.ToArray();
|
||||||
|
var store = await _storeRepository.FindStore(invoice.StoreId);
|
||||||
|
if (paymentMethods.Any())
|
||||||
|
{
|
||||||
|
var logs = new InvoiceLogs();
|
||||||
|
logs.Write(
|
||||||
|
"Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.",
|
||||||
|
InvoiceEventData.EventSeverity.Info);
|
||||||
|
foreach (var paymentMethod in paymentMethods)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var supportedMethod = invoice
|
||||||
|
.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(paymentMethod.GetId()).First();
|
||||||
|
var prepObj =
|
||||||
|
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store, paymentMethod.Network);
|
||||||
|
var newPaymentMethodDetails =
|
||||||
|
(LightningLikePaymentMethodDetails)(await _lightningLikePaymentHandler
|
||||||
|
.CreatePaymentMethodDetails(logs, supportedMethod, paymentMethod, store,
|
||||||
|
paymentMethod.Network, prepObj));
|
||||||
|
|
||||||
|
var instanceListenerKey = (paymentMethod.Network.CryptoCode,
|
||||||
|
supportedMethod.GetLightningUrl().ToString());
|
||||||
|
if (_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener))
|
||||||
|
{
|
||||||
|
await _InvoiceRepository.NewPaymentDetails(invoice.Id, newPaymentMethodDetails,
|
||||||
|
paymentMethod.Network);
|
||||||
|
|
||||||
|
instanceListener.AddListenedInvoice(new ListenedInvoice()
|
||||||
|
{
|
||||||
|
Expiration = invoice.ExpirationTime,
|
||||||
|
Uri = supportedMethod.GetLightningUrl().BaseUri.AbsoluteUri,
|
||||||
|
PaymentMethodDetails = newPaymentMethodDetails,
|
||||||
|
SupportedPaymentMethod = supportedMethod,
|
||||||
|
PaymentMethod = paymentMethod,
|
||||||
|
Network = (BTCPayNetwork)paymentMethod.Network,
|
||||||
|
InvoiceId = invoice.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
|
||||||
|
newPaymentMethodDetails, paymentMethod.GetId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
|
||||||
|
InvoiceEventData.EventSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
|
||||||
|
_CheckInvoices.Writer.TryWrite(invoice.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0);
|
TimeSpan _PollInterval = TimeSpan.FromMinutes(1.0);
|
||||||
public TimeSpan PollInterval
|
public TimeSpan PollInterval
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ retry:
|
|||||||
var existingPaymentMethod = paymentMethod.GetPaymentMethodDetails();
|
var existingPaymentMethod = paymentMethod.GetPaymentMethodDetails();
|
||||||
if (existingPaymentMethod.GetPaymentDestination() != null)
|
if (existingPaymentMethod.GetPaymentDestination() != null)
|
||||||
{
|
{
|
||||||
MarkUnassigned(invoiceId, invoiceEntity, context, paymentMethod.GetId());
|
MarkUnassigned(invoiceId, context, paymentMethod.GetId());
|
||||||
}
|
}
|
||||||
paymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
paymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
@@ -323,36 +323,29 @@ retry:
|
|||||||
catch (DbUpdateException) { } // Probably the invoice does not exists anymore
|
catch (DbUpdateException) { } // Probably the invoice does not exists anymore
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MarkUnassigned(string invoiceId, InvoiceEntity entity, ApplicationDbContext context, PaymentMethodId paymentMethodId)
|
private static void MarkUnassigned(string invoiceId, ApplicationDbContext context,
|
||||||
|
PaymentMethodId paymentMethodId)
|
||||||
{
|
{
|
||||||
foreach (var address in entity.GetPaymentMethods())
|
var paymentMethodIdStr = paymentMethodId?.ToString();
|
||||||
|
var addresses = context.HistoricalAddressInvoices.Where(data =>
|
||||||
|
(data.InvoiceDataId == invoiceId && paymentMethodIdStr == null ||
|
||||||
|
data.CryptoCode == paymentMethodIdStr) &&
|
||||||
|
data.UnAssigned == null);
|
||||||
|
foreach (var historicalAddressInvoiceData in addresses)
|
||||||
{
|
{
|
||||||
if (paymentMethodId != null && paymentMethodId != address.GetId())
|
historicalAddressInvoiceData.UnAssigned = DateTimeOffset.UtcNow;
|
||||||
continue;
|
|
||||||
var historical = new HistoricalAddressInvoiceData();
|
|
||||||
historical.InvoiceDataId = invoiceId;
|
|
||||||
historical.SetAddress(address.GetPaymentMethodDetails().GetPaymentDestination(), address.GetId().ToString());
|
|
||||||
historical.UnAssigned = DateTimeOffset.UtcNow;
|
|
||||||
context.Attach(historical);
|
|
||||||
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UnaffectAddress(string invoiceId)
|
public async Task UnaffectAddress(string invoiceId)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
await using var context = _ContextFactory.CreateContext();
|
||||||
|
MarkUnassigned(invoiceId, context, null);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
await context.SaveChangesAsync();
|
||||||
if (invoiceData == null)
|
|
||||||
return;
|
|
||||||
var invoiceEntity = invoiceData.GetBlob(_Networks);
|
|
||||||
MarkUnassigned(invoiceId, invoiceEntity, context, null);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
catch (DbUpdateException) { } //Possibly, it was unassigned before
|
|
||||||
}
|
}
|
||||||
|
catch (DbUpdateException) { } //Possibly, it was unassigned before
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] SearchInvoice(string searchTerms)
|
private string[] SearchInvoice(string searchTerms)
|
||||||
|
|||||||
@@ -205,8 +205,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<partial name="ListInvoicesPaymentsPartial" model="(Model, false)" />
|
<partial name="ListInvoicesPaymentsPartial" model="(Model, true)" />
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|||||||
Reference in New Issue
Block a user