Грубая сила для определения суммы возврата в домене платежа / счета-фактуры

Я работаю в крупной шведской страховой компании. Я отвечаю за службу, которая сопоставляет банковские платежи со счетами. Я не буду подробно останавливаться на этой логике сопоставления. Но одним из результатов матча является то, что сумма платежа отличается от суммы в счете. У нас есть IAmountDeviationStrategy который помечен на каждом счете-фактуре. Здесь RefundStrategy

public class RefundStrategy : IAmountDeviationStrategy
{
    public AmountDeviationStrategy Strategy => AmountDeviationStrategy.Refund;

    public PaymentInvoiceMatchResult Execute(MatchJob job)
    {
        if (job.Diff < 0)
            return new PaymentInvoiceMatchResultBuilder(job)
                .PaymentCategory(PaymentCategory.WrongPayment)
                .RefundAll(RefundReason.UnderPaid);

        var invoiceAmount = Math.Abs(job.Invoice.Amount);
        var payingPayments = GetPaymentsComboClosestToInvoiceAmount(job.Payments, invoiceAmount);

        var refunds = job.Payments.Except(payingPayments).ToList();
        var result = new PaymentInvoiceMatchResultBuilder(job, refunds)
            .PaymentCategory(PaymentCategory.WrongPayment)
            .Refund(refunds);

        if (payingPayments.Sum(p => p.Amount) != invoiceAmount)
            result.Refund<RefundSplitPaymentCommand>(payingPayments.First(p => p.Amount >= job.Diff), split => split.Amount = payingPayments.Sum(p => p.Amount) - invoiceAmount);

        return result
            .QueueCommand(new MapInvoicePaymentsCommand { InvoiceId = job.Invoice.ExternalId, Payments = payingPayments.Select(p => p.ExternalId) });
    }

    private IReadOnlyCollection<IPaymentMatchInfo> GetPaymentsComboClosestToInvoiceAmount(IReadOnlyCollection<IPaymentMatchInfo> payments, decimal invoiceAmount)
    {
        var combos = Enumerable.Range(1, payments.Count)
            .SelectMany(i => new Combinations<IPaymentMatchInfo>(payments, new ReferenceCompare<IPaymentMatchInfo>(), i))
            .ToList();

        var payingPayments = combos
            .Where(c => c.Sum(p => p.Amount) >= invoiceAmount)
            .OrderBy(combo => Math.Abs(combo.Sum(p => p.Amount) - invoiceAmount))
            .First();

        return payingPayments;
    }

Если платеж не соответствует счету, мы просто возвращаем платеж. Если его переплачивают, начинается самое интересное. Платежей может быть несколько. Чаще всего клиент заплатил один раз и переплатил. Вторым по распространенности является двойная оплата, то есть два платежа, которые в два раза превышают сумму счета. Третий и редкий случай — это множественные платежи, которые случайным образом переплачиваются (а не вдвое больше).

Во всяком случае, я грубо взламываю эту проблему. Я нахожу все комбинации платежей, используя небольшой класс Combination https://pastebin.com/BDvRmmgD (Не мной написано)

Когда у меня есть все комбинации, я беру одну, наиболее близкую к сумме счета. Я возвращаю деньги всем остальным. Затем я проверяю, соответствуют ли выплачиваемые платежи сумме (что происходит в сценарии с двойной оплатой). Если они не суммируют, я делю одну из выплат и возвращаю переплаченную часть.

Затем я повторно запускаю соответствующую команду MapInvoicePaymentsCommand. На этот раз счет-фактура и платеж попадут в путь оплаченного результата.

Я думаю, что грубая сила довольно элегантна. Платежей никогда не будет больше, чем несколько, поэтому это не будет проблемой для производительности.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *