Пример #1
0
        /// <summary>
        ///  相殺データ準備 相殺データより 入金データを作成し、<see cref="Matching.ReceiptId"/>を修正する処理
        /// </summary>
        /// <param name="matchings"></param>
        /// <param name="loginUserId"></param>
        /// <param name="updateAt"></param>
        private async Task <List <Receipt> > PrepareNettingDataAsync(
            MatchingSource source, int loginUserId, DateTime updateAt,
            CancellationToken token = default(CancellationToken))
        {
            var matchings  = source.Matchings;
            var nettingIds = matchings.Where(x => x.IsNetting).Select(x => x.ReceiptId).Distinct();
            var result     = new List <Receipt>();

            foreach (var nettingId in nettingIds)
            {
                var netting = await nettingQueryProcessor.GetByIdAsync(nettingId, token);

                var receipt       = netting.ConvertToReceiptInput(loginUserId, updateAt);
                var receiptResult = await matchingQueryProcessor.SaveMatchingReceiptAsync(receipt, token);

                result.Add(receiptResult);

                if (!string.IsNullOrEmpty(netting.ReceiptMemo))
                {
                    await addReceiptMemoQueryProcessor.SaveAsync(receiptResult.Id, netting.ReceiptMemo, token);
                }
                await updateNettingQueryProcessor.UpdateMatchingNettingAsync(netting.CompanyId, receiptResult.Id, netting.Id, CancelFlg : 0, token : token);

                // performance issue
                foreach (var matching in matchings.Where(x => x.IsNetting && x.ReceiptId == nettingId))
                {
                    matching.ReceiptId = receiptResult.Id;
                }
                foreach (var rcpt in source.Receipts.Where(x => x.NettingId == nettingId))
                {
                    rcpt.Id = receiptResult.Id;
                }
            }
            return(result);
        }
Пример #2
0
        public async Task <MatchingResult> SaveAsync(
            MatchingSource source,
            ApplicationControl applicationControl,
            CancellationToken token = default(CancellationToken))
        {
            var companyId = source.Billings.First().CompanyId;

            if (applicationControl == null)
            {
                applicationControl = await applicationControlQueryProcessor.GetAsync(companyId, token);
            }

            var billings  = source.Billings;
            var receipts  = source.Receipts;
            var matchings = source.Matchings;
            var header    = source.MatchingHeader;
            var matchingBillingDiscounts = source.MatchingBillingDiscounts;
            var billingDiscounts         = source.BillingDiscounts;
            var billingScheduledIncomes  = source.BillingScheduledIncomes;
            var loginUserId               = source.LoginUserId;
            var updateAt                  = source.UpdateAt;
            var matchingProcessType       = source.MatchingProcessType;
            var customerId                = source.CustomerId;
            var paymentAgencyId           = source.PaymentAgencyId;
            var advanceReceivedCustomerId = source.AdvanceReceivedCustomerId;

            var matchingResults        = new List <Matching>();
            var advanceReceivedResults = new List <AdvanceReceived>();
            var clientKey = source.ClientKey;

            foreach (var billing in billings)
            {
                billing.UpdateBy    = loginUserId;
                billing.NewUpdateAt = updateAt;
                if (applicationControl.UseDeclaredAmount == 0)
                {
                    billing.OffsetAmount = 0;
                }
                var result = await addMatchingQueryProcessor.UpdateBillingForMatchingAsync(billing, token);

                if (result != 1)
                {
                    return new MatchingResult {
                               MatchingErrorType = MatchingErrorType.BillingRemainChanged
                    }
                }
                ;
            }

            foreach (var receipt in receipts)
            {
                receipt.UpdateBy    = loginUserId;
                receipt.NewUpdateAt = updateAt;
                var result = await addMatchingQueryProcessor.UpdateReceiptForMatchingAsync(receipt, token);

                if (result != 1)
                {
                    return new MatchingResult {
                               MatchingErrorType = MatchingErrorType.ReceiptRemainChanged
                    }
                }
                ;
                if (receipt.ReceiptHeaderId.HasValue)
                {
                    var receiptHeaderId = receipt.ReceiptHeaderId.Value;

                    await matchingQueryProcessor.UpdateReceiptHeaderAsync(receiptHeaderId, loginUserId, updateAt, token);
                }
            }

            header.MatchingProcessType = matchingProcessType;
            header.CustomerId          = customerId;
            header.PaymentAgencyId     = paymentAgencyId;
            header.Memo     = header.Memo ?? string.Empty;
            header.Approved = (applicationControl.UseAuthorization == 1) ? 0 : 1;
            header.CreateBy = loginUserId;
            header.UpdateBy = loginUserId;
            header.CreateAt = updateAt;
            header.UpdateAt = updateAt;

            var headerResult = await addMatchingQueryProcessor.SaveMatchingHeaderAsync(header, token);

            if (headerResult == null)
            {
                return new MatchingResult {
                           MatchingErrorType = MatchingErrorType.DBError
                }
            }
            ;

            var advanceRecievedReciptId = new HashSet <long>();

            foreach (var matching in matchings)
            {
                matching.MatchingHeaderId = headerResult.Id;

                matching.CreateBy = loginUserId;
                matching.CreateAt = updateAt;
                matching.UpdateBy = loginUserId;
                matching.UpdateAt = updateAt;
                if (!advanceReceivedCustomerId.HasValue &&
                    matching.AdvanceReceivedOccured == 1)
                {
                    matching.AdvanceReceivedOccured = 0;
                }

                var matchingResult = await addMatchingQueryProcessor.SaveMatchingAsync(matching, token);

                matchingResults.Add(matchingResult);

                if (applicationControl.UseDiscount == 1)
                {
                    foreach (var matchingBillingDiscount in matchingBillingDiscounts.Where(x => x.MatchingId == matching.Id))
                    {
                        matchingBillingDiscount.MatchingId = matchingResult.Id;
                        await addMatchingBillingDiscountQueryProcessor.SaveAsync(matchingBillingDiscount, token);
                    }
                }

                if (advanceReceivedCustomerId.HasValue &&
                    matching.ReceiptRemain > 0M &&
                    !advanceRecievedReciptId.Contains(matching.ReceiptId))
                {
                    var originalReceiptId = matching.ReceiptId;
                    advanceRecievedReciptId.Add(originalReceiptId);

                    var receiptItem = receipts.Find(item => (item.Id == originalReceiptId));
                    if (matchingProcessType == 1 && advanceReceivedCustomerId != 0)
                    {
                        receiptItem.CustomerId = advanceReceivedCustomerId;
                    }
                    var advReceipt = await addReceiptQueryProcessor.AddAdvanceReceivedAsync(
                        originalReceiptId, advanceReceivedCustomerId, loginUserId, updateAt, updateAt, token);

                    if (advReceipt != null)
                    {
                        advanceReceivedResults.Add(new AdvanceReceived
                        {
                            ReceiptId         = advReceipt.Id,
                            OriginalReceiptId = advReceipt.OriginalReceiptId.Value,
                            UpdateAt          = advReceipt.UpdateAt,
                            ReceiptCategoryId = advReceipt.ReceiptCategoryId,
                            LoginUserId       = advReceipt.UpdateBy,
                        });
                        var receipt = (await matchingQueryProcessor.SearchReceiptByIdAsync(new long[] { advReceipt.Id }, token)).First();
                        await matchingQueryProcessor.SaveWorkReceiptTargetAsync(clientKey, receipt.Id,
                                                                                receipt.CompanyId, receipt.CurrencyId, receipt.PayerName,
                                                                                receipt.BankCode, receipt.BranchCode, receipt.PayerCode,
                                                                                receipt.SourceBankName, receipt.SourceBranchName, receipt.CollationKey, receipt.CustomerId,
                                                                                token);

                        await matchingQueryProcessor.SaveWorkCollationAsync(clientKey, advReceipt.Id, customerId ?? 0, paymentAgencyId ?? 0, advanceReceivedCustomerId ?? 0,
                                                                            receipt.PayerName, receipt.PayerCode,
                                                                            receipt.BankCode, receipt.BranchCode,
                                                                            receipt.SourceBankName, receipt.SourceBranchName, receipt.CollationKey, receipt.ReceiptAmount,
                                                                            token);
                    }

                    await updateReceiptQueryProcessor.UpdateOriginalRemainAsync(originalReceiptId, loginUserId, updateAt, token);

                    if (matchingResult != null)
                    {
                        var advanceReceipt_matching = matching;
                        advanceReceipt_matching.Id = matchingResult.Id;
                        await addMatchingQueryProcessor.UpdateMatchingAsync(advanceReceipt_matching, token);
                    }
                }

                #region 期日入金データ消込
                if (matching.UseCashOnDueDates == 1)
                {
                    Billing b = null;
                    Receipt r = null;
                    if (matchingProcessType == 0)
                    {
                        b = billings.FirstOrDefault(x => x.Id == matching.BillingId);
                        r = receipts.FirstOrDefault(x => x.Id == matching.ReceiptId);
                    }
                    else
                    {
                        b = await billingGetByIdQueryProcessor.GetByIdAsync(matching.BillingId, token);

                        r = await receiptGetByIdQueryProcessor.GetByIdAsync(matching.ReceiptId, token);
                    }
                    var newbill = b.ConvertScheduledIncome(r, matching.Amount);
                    newbill.CreateBy = loginUserId;
                    newbill.CreateAt = updateAt;
                    newbill.UpdateBy = loginUserId;
                    newbill.UpdateAt = updateAt;
                    var scheduled_billing_result = await addMatchingQueryProcessor.SaveMatchingBillingAsync(newbill, token);

                    foreach (var income in billingScheduledIncomes.Where(x => x.MatchingId == matching.Id))
                    {
                        income.BillingId  = scheduled_billing_result.Id;
                        income.MatchingId = matchingResult.Id;
                        income.CreateBy   = loginUserId;
                        income.CreateAt   = updateAt;
                        await addBillingScheduledIncomeQueryProcessor.SaveAsync(income);
                    }
                }
                #endregion
            }

            if (applicationControl.UseDiscount == 1) //歩引き対応
            {
                foreach (var billingId in billingDiscounts)
                {
                    await updateBillingDiscountQueryProcessor.UpdateAssignmentFlagAsync(billingId, AssignmentFlag : 1, token : token);
                }
            }
            return(new MatchingResult
            {
                ProcessResult = new ProcessResult {
                    Result = true
                },
                Matchings = matchingResults,
                AdvanceReceiveds = advanceReceivedResults,
            });
        }
    }
}
Пример #3
0
        /// <summary>
        /// 消込登録前の金額検証処理 消込処理実施時の残額とDBの残額の検証
        /// </summary>
        /// <param name="source"></param>
        /// <param name="setter"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        private async Task <bool> ValidateMatchingDataAsync(
            MatchingSource source,
            Action <MatchingResult> setter,
            CancellationToken token = default(CancellationToken))
        {
            var billingIds = new HashSet <long>();
            var receiptIds = new HashSet <long>();
            var nettingIds = new HashSet <long>();

            foreach (var matching in source.Matchings)
            {
                if (!billingIds.Contains(matching.BillingId))
                {
                    billingIds.Add(matching.BillingId);
                }
                if (!matching.IsNetting && !receiptIds.Contains(matching.ReceiptId))
                {
                    receiptIds.Add(matching.ReceiptId);
                }
                if (matching.IsNetting && !nettingIds.Contains(matching.ReceiptId))
                {
                    nettingIds.Add(matching.ReceiptId);
                }
            }

            var billing = await matchingQueryProcessor.GetBillingRemainAmountAsync(billingIds, token);

            var dbBillingTotal  = billing.RemainAmount;
            var dbDiscountTotal = billing.DiscountAmount;

            if (dbBillingTotal != source.BillingRemainTotal)
            {
                setter?.Invoke(new MatchingResult
                {
                    MatchingErrorType = MatchingErrorType.BillingRemainChanged,
                });
                return(false);
            }
            if (dbDiscountTotal != source.BillingDiscountTotal)
            {
                setter?.Invoke(new MatchingResult
                {
                    MatchingErrorType = MatchingErrorType.BillingDiscountChanged,
                });
                return(false);
            }

            var dbReceiptTotal = 0M;
            var dbNettingTotal = 0M;

            if (receiptIds.Any())
            {
                dbReceiptTotal = await matchingQueryProcessor.GetReceiptRemainAmountAsync(receiptIds, token);
            }

            if (nettingIds.Any())
            {
                dbNettingTotal = await matchingQueryProcessor.GetNettingRemainAmountAsync(nettingIds, token);
            }

            dbReceiptTotal = dbReceiptTotal + dbNettingTotal;


            if (dbReceiptTotal != source.ReceiptRemainTotal)
            {
                setter?.Invoke(new MatchingResult
                {
                    MatchingErrorType = MatchingErrorType.ReceiptRemainChanged,
                });
                return(false);
            }

            return(true);
        }
Пример #4
0
        public async Task <MatchingResult> MatchAsync(
            MatchingSource source,
            CancellationToken token    = default(CancellationToken),
            IProgressNotifier notifier = null)
        {
            var matchings        = source.Matchings;
            var loginUserId      = source.LoginUserId;
            var companyId        = source.CompanyId;
            var customerId       = source.CustomerId ?? 0;
            var paymentAgencyId  = source.PaymentAgencyId ?? 0;
            var childCustomerIds = source.ChildCustomerIds;
            var useKanaLearning  = source.UseKanaLearning == 1;
            var useFeeLearning   = source.UseFeeLearning == 1;

            var appControl = await applicationControlQueryProcessor.GetAsync(companyId, token);

            var updateAt = await dbSystemDateTimeQueryProcessor.GetAsync(token);

            source.UpdateAt = updateAt;

            var useAuthorization = appControl?.UseAuthorization == 1;
            var nettingReceipts  = new List <Receipt>();
            // DBサーバーから取得する

            var billings = new List <Billing>();
            var receipts = new List <Receipt>();
            var matchingBillingDiscounts = new List <MatchingBillingDiscount>();
            var billingScheduledIncomes  = new List <BillingScheduledIncome>();
            var billingDiscounts         = new HashSet <long>();

            MatchingResult validateResult = null;

            if (!(await ValidateMatchingDataAsync(source,
                                                  x => validateResult = x,
                                                  token)))
            {
                return(validateResult);
            }

            var currencyId = source.Matchings.First().CurrencyId;


            using (var scope = transactionScopeBuilder.Create())
            {
                #region 相殺データ変換
                nettingReceipts = await PrepareNettingDataAsync(source, loginUserId, updateAt, token);

                foreach (var r in nettingReceipts)
                {
                    var item = source.Receipts.First(x => x.Id == r.Id);
                    item.UpdateAt = r.UpdateAt;
                }
                #endregion

                #region matchingHeader
                var header = source.MatchingHeader;
                header.MatchingProcessType = 1;
                header.Approved            = useAuthorization ? 0 : 1;
                header.CreateBy            = loginUserId;
                header.UpdateBy            = loginUserId;
                header.CreateAt            = updateAt;
                header.UpdateAt            = updateAt;
                #endregion

                #region 手数料学習
                var bankFee = header.BankTransferFee;
                if (useFeeLearning)
                {
                    await SaveBankTransferFeeAsync(customerId, paymentAgencyId, currencyId, bankFee, updateAt, loginUserId, token);
                }
                #endregion

                #region 債権代表者登録
                if (childCustomerIds.Any())
                {
                    var isParent = 1;
                    await updateCustomerQueryProcessor.UpdateIsParentAsync(isParent, loginUserId, new[] { customerId }, token);

                    foreach (var childId in childCustomerIds)
                    {
                        var group = new CustomerGroup();
                        group.ParentCustomerId = customerId;
                        group.ChildCustomerId  = childId;
                        group.CreateAt         = updateAt;
                        group.CreateBy         = loginUserId;
                        group.UpdateAt         = updateAt;
                        group.UpdateBy         = loginUserId;
                        await addCustomerGroupQueryProcessor.SaveAsync(group, token);
                    }
                }
                #endregion

                #region  学習履歴の登録
                if (useKanaLearning)
                {
                    await SaveKanaLearningAsync(matchings, companyId, customerId, paymentAgencyId, loginUserId, updateAt);
                }
                #endregion

                var matchingResult = await matchingSaveProcessor.SaveAsync(source, appControl, token);

                if (matchingResult.ProcessResult.Result)
                {
                    scope.Complete();
                }

                matchingResult.NettingReceipts = nettingReceipts;

                return(matchingResult);
            }
        }
Пример #5
0
        public async Task <MatchingResult> MatchAsync(
            IEnumerable <Collation> collations,
            CollationSearch option,
            CancellationToken token    = default(CancellationToken),
            IProgressNotifier notifier = null)
        {
            var appControl = await applicationControlQueryProcessor.GetAsync(option.CompanyId, token);

            var nettingReceipts = new List <Receipt>();

            var updateAt = await dbSystemDateTimeQueryProcessor.GetAsync(token);

            var matchingOrders = await matchingOrderQueryProcessor.GetItemsAsync(option.CompanyId, token);

            var matchingBillingOrder = matchingOrders.Where(x => x.TransactionCategory == 1 && x.Available == 1).ToArray();
            var matchingReceiptOrder = matchingOrders.Where(x => x.TransactionCategory == 2 && x.Available == 1).ToArray();

            var            matchings        = new List <Matching>();
            var            advanceReceiveds = new List <AdvanceReceived>();
            List <Billing> billings         = null;
            List <Netting> nettings         = null;
            List <Receipt> receipts         = null;
            var            index            = 0;

            using (var scope = transactionScopeBuilder.Create())
            {
                foreach (var collation in collations)
                {
                    // 請求データ取得
                    var billingSearchOption = new MatchingBillingSearch
                    {
                        ClientKey        = option.ClientKey,
                        ParentCustomerId = collation.CustomerId,
                        PaymentAgencyId  = collation.PaymentAgencyId,
                        CurrencyId       = collation.CurrencyId,
                    };

                    billings = (await matchingQueryProcessor.GetBillingsForSequentialMatchingAsync(billingSearchOption, matchingBillingOrder, token)).ToList();
                    var billingAmount = billings.Sum(item => (item.RemainAmount - item.DiscountAmount - item.OffsetAmount));
                    var billingCount  = billings.Count;

                    // 請求データチェック
                    if (billingAmount != collation.BillingAmount ||
                        billingCount != collation.BillingCount)
                    {
                        notifier?.Abort();
                        return(new MatchingResult
                        {
                            MatchingErrorType = MatchingErrorType.BillingRemainChanged,
                            ErrorIndex = index,
                        });
                    }

                    // 相殺データ取得
                    nettings = (await matchingQueryProcessor.SearchMatchingNettingAsync(option, collation, token)).ToList();

                    // 相殺データ登録
                    var createdReceipstFromNetting = new List <Receipt>();
                    foreach (var netting in nettings)
                    {
                        var receipt        = netting.ConvertToReceiptInput(option.LoginUserId, updateAt);
                        var nettingReceipt = await matchingQueryProcessor.SaveMatchingReceiptAsync(receipt, token);

                        if (!string.IsNullOrEmpty(netting.ReceiptMemo))
                        {
                            await addReceiptMemoQueryProcessor.SaveAsync(nettingReceipt.Id, netting.ReceiptMemo, token);
                        }
                        netting.ReceiptId = nettingReceipt.Id;
                        createdReceipstFromNetting.Add(nettingReceipt);
                    }

                    // 入金データ取得
                    var receiptSearch = new MatchingReceiptSearch
                    {
                        ClientKey           = option.ClientKey,
                        CompanyId           = option.CompanyId,
                        CurrencyId          = collation.CurrencyId,
                        ParentCustomerId    = collation.CustomerId,
                        PaymentAgencyId     = collation.PaymentAgencyId,
                        UseScheduledPayment = appControl.UseScheduledPayment,
                    };

                    receipts = (await matchingQueryProcessor.GetReceiptsForSequentialMatchingAsync(receiptSearch, matchingReceiptOrder, token)).ToList();

                    var receiptAmount      = receipts.Sum(item => (item.RemainAmount));
                    var receiptCount       = receipts.Count();
                    var hasAdvanceReceived = receipts.Exists(item => item.UseAdvanceReceived == 1);

                    // 入金データチェック
                    if (receiptAmount != collation.ReceiptAmount ||
                        receiptCount != collation.ReceiptCount)
                    {
                        notifier?.Abort();
                        return(new MatchingResult
                        {
                            MatchingErrorType = MatchingErrorType.ReceiptRemainChanged,
                            ErrorIndex = index,
                        });
                    }

                    foreach (var netting in nettings)
                    {
                        await updateNettingQueryProcessor.UpdateMatchingNettingAsync(netting.CompanyId, netting.ReceiptId.Value, netting.Id, CancelFlg : 0, token : token);

                        foreach (var receipt in receipts
                                 .Where(x => x.NettingId.HasValue && x.NettingId == netting.Id))
                        {
                            receipt.Id = netting.ReceiptId.Value;
                        }
                    }

                    // 前受処理日付取得
                    var recordedAt = option.GetRecordedAt(hasAdvanceReceived ? billings : null);
                    option.AdvanceReceivedRecordedAt = recordedAt;

                    var requestSource = new MatchingSource
                    {
                        Billings        = billings,
                        Receipts        = receipts,
                        BankTransferFee = collation.BankTransferFee,
                        TaxDifference   = collation.TaxDifference,
                    };
                    var source = await matchingSolveProcessor.SolveAsync(requestSource, option, appControl, token);

                    foreach (var netting in nettings)
                    {
                        foreach (var matching in source.Matchings.Where(x => x.IsNetting && x.ReceiptId == netting.Id))
                        {
                            matching.ReceiptId = netting.ReceiptId.Value;
                        }
                    }

                    if (collation.UseFeeLearning == 1 && collation.BankTransferFee != 0M)
                    {
                        await SaveBankTransferFeeAsync(collation, option, updateAt, token);
                    }

                    // データの更新処理
                    int?customerId = collation.CustomerId;
                    if (customerId == 0)
                    {
                        customerId = null;
                    }

                    int?paymentAgencyId = collation.PaymentAgencyId;
                    if (paymentAgencyId == 0)
                    {
                        paymentAgencyId = null;
                    }

                    source.LoginUserId               = option.LoginUserId;
                    source.UpdateAt                  = updateAt;
                    source.MatchingProcessType       = 0;
                    source.CustomerId                = customerId;
                    source.PaymentAgencyId           = paymentAgencyId;
                    source.AdvanceReceivedCustomerId = option.DoTransferAdvanceReceived ? customerId : null;
                    source.ClientKey                 = option.ClientKey;

                    foreach (var r in createdReceipstFromNetting)
                    {
                        var item = receipts.First(x => x.Id == r.Id);
                        item.UpdateAt = r.UpdateAt;
                    }

                    var matchingResult = await matchingSaveProcessor.SaveAsync(source, appControl, token);

                    if (!matchingResult.ProcessResult.Result)
                    {
                        notifier?.Abort();
                        return(matchingResult);
                    }
                    matchings.AddRange(matchingResult.Matchings);
                    advanceReceiveds.AddRange(matchingResult.AdvanceReceiveds);
                    nettingReceipts.AddRange(createdReceipstFromNetting);

                    notifier?.UpdateState();
                    index++;
                }

                scope.Complete();
            }


            return(new MatchingResult
            {
                ProcessResult = new ProcessResult {
                    Result = true
                },
                Matchings = matchings,
                AdvanceReceiveds = advanceReceiveds,
                NettingReceipts = nettingReceipts
            });
        }
Пример #6
0
        public async Task <MatchingSource> SolveAsync(
            MatchingSource source,
            CollationSearch option,
            ApplicationControl control = null,
            CancellationToken token    = default(CancellationToken))
        {
            if (control == null)
            {
                control = await applicationControlQueryProcessor.GetAsync(option.CompanyId, token);
            }

            var useCashOnDueDates       = control.UseCashOnDueDates;
            var useScheduledPayment     = control.UseScheduledPayment;
            var useDeclaredAmount       = control.UseDeclaredAmount;
            int?doCreateAdvanceReceived = option.DoTransferAdvanceReceived ? 1 : 0;
            var taxDifference           = source.TaxDifference;
            var bankTransferFee         = source.BankTransferFee;


            var billingItems = source.Billings;
            var receiptItems = source.Receipts;

            if (!(billingItems?.Any() ?? false))
            {
                throw new ArgumentNullException("Billings");
            }

            if (!(receiptItems?.Any() ?? false))
            {
                throw new ArgumentNullException("Receipts");
            }

            int companyId;
            int currencyId;
            {
                var billing = billingItems.First();
                companyId  = billing.CompanyId;
                currencyId = billing.CurrencyId;
            }

            var isAllMinusBilling = true;
            var billingTotal      = 0M;

            var billingPair = GetBillingPair(billingItems, useScheduledPayment, useDeclaredAmount,
                                             ref doCreateAdvanceReceived, ref billingTotal, ref isAllMinusBilling);

            var isAllMinusReceipt = true;
            var receiptTotal      = 0M;
            var receiptPair       = GetReceiptPair(receiptItems, ref receiptTotal, ref isAllMinusReceipt);

            var isAllMinus = isAllMinusBilling && isAllMinusReceipt;
            var taxDiff    = taxDifference;
            var bankFee    = bankTransferFee;
            var isEqual    = (billingTotal == receiptTotal + bankFee - taxDiff);
            Func <decimal, decimal, decimal> amountSolver = Math.Min;

            if (isAllMinus)
            {
                amountSolver = Math.Max;
            }
            var header = new MatchingHeader
            {
                Id              = 1,
                CompanyId       = companyId,
                CurrencyId      = currencyId,
                BankTransferFee = bankFee,
                TaxDifference   = taxDiff,
            };

            var recordedAt = option.AdvanceReceivedRecordedAt;

            var matchings = new List <Matching>();
            var billingScheduledIncomes  = new List <BillingScheduledIncome>();
            var matchingBillingDiscounts = new List <MatchingBillingDiscount>();
            var billingDiscounts         = new HashSet <long>();
            var discountTotal            = 0M;
            var beforBillingRemainTotal  = 0M;
            var beforReceiptRemainTotal  = 0M;

            var     bindex      = 0;
            var     rindex      = 0;
            var     nextBilling = true;
            var     nextReceipt = true;
            Billing bill        = null;
            Receipt rcpt        = null;

            while (bindex < billingItems.Count &&
                   rindex < receiptItems.Count &&
                   (nextBilling || nextReceipt))
            {
                if (token.IsCancellationRequested)
                {
                    throw new OperationCanceledException();
                }

                if (nextBilling)
                {
                    bill = billingItems[bindex];
                }
                if (nextReceipt)
                {
                    rcpt = receiptItems[rindex];
                }
                var isLastBill = bindex == billingItems.Count - 1;
                var isLastRcpt = rindex == receiptItems.Count - 1;

                var discount    = bill.DiscountAmount;
                var billTaxDiff = (0M < taxDiff) ? taxDiff : 0M;
                var rcptTaxDiff = (0M < taxDiff) ? 0M : -taxDiff;

                var matching = new Matching();
                matching.Id               = matchings.Count;
                matching.CompanyId        = companyId;
                matching.CurrencyId       = currencyId;
                matching.MatchingHeaderId = header.Id;
                matching.ReceiptId        = rcpt.NettingId ?? rcpt.Id;
                matching.PayerName        = rcpt.PayerName;
                matching.SourceBankName   = rcpt.SourceBankName;
                matching.SourceBranchName = rcpt.SourceBranchName;
                matching.IsNetting        = rcpt.NettingId.HasValue;
                matching.RecordedAt       = (rcpt.OriginalReceiptId.HasValue ? recordedAt : null) ?? rcpt.RecordedAt;
                matching.ReceiptHeaderId  = rcpt.ReceiptHeaderId;
                if (useCashOnDueDates == 1 && rcpt.UseCashOnDueDates == 1)
                {
                    matching.UseCashOnDueDates = 1;
                }

                matching.BillingId = bill.Id;
                if (useScheduledPayment == 1 && useDeclaredAmount == 1)
                {
                    matching.OffsetAmount = bill.OffsetAmount;
                }

                if (discount != 0M)
                {
                    #region billing discount
                    matching.DiscountAmount1 = bill.DiscountAmount1;
                    matching.DiscountAmount2 = bill.DiscountAmount2;
                    matching.DiscountAmount3 = bill.DiscountAmount3;
                    matching.DiscountAmount4 = bill.DiscountAmount4;
                    matching.DiscountAmount5 = bill.DiscountAmount5;
                    if (!billingDiscounts.Contains(bill.Id))
                    {
                        billingDiscounts.Add(bill.Id);
                    }
                    matchingBillingDiscounts.AddRange(ConvertBillingToDiscounts(bill, matching.Id));
                    discountTotal       += discount;
                    bill.DiscountAmount  = 0M;
                    bill.DiscountAmount1 = 0M;
                    bill.DiscountAmount2 = 0M;
                    bill.DiscountAmount3 = 0M;
                    bill.DiscountAmount4 = 0M;
                    bill.DiscountAmount5 = 0M;
                    #endregion
                }
                matching.BankTransferFee = bankFee;
                matching.TaxDifference   = taxDiff;
                matching.Memo            = bill.Memo;
                var billAmount = billingPair[bindex] - rcptTaxDiff - bankFee - discount;
                var rcptAmount = receiptPair[rindex] - billTaxDiff;

                matching.Amount
                    = (isEqual && isLastRcpt && !isLastBill) ? billAmount
                    : (isEqual && !isLastRcpt && isLastBill) ? rcptAmount
                    : amountSolver(billAmount, rcptAmount);

                header.Amount += matching.Amount;

                if (matching.Amount == 0M && taxDiff == 0M && bankFee == 0M && discount == 0M)
                {
                    break;
                }

                var billAssignAmount = (matching.Amount + rcptTaxDiff + bankFee + discount);
                var rcptAssignAmount = (matching.Amount + billTaxDiff);
                if (nextBilling)
                {
                    beforBillingRemainTotal += bill.RemainAmount;
                    bill.AssignmentAmount    = billAssignAmount;
                }
                else
                {
                    bill.AssignmentAmount += billAssignAmount;
                }

                if (nextReceipt)
                {
                    beforReceiptRemainTotal += rcpt.RemainAmount;
                    rcpt.AssignmentAmount    = rcptAssignAmount;
                }
                else
                {
                    rcpt.AssignmentAmount += rcptAssignAmount;
                }

                billingPair[bindex] -= billAssignAmount;
                receiptPair[rindex] -= rcptAssignAmount;

                nextBilling = billingPair[bindex] == 0M && !isLastBill;
                nextReceipt = receiptPair[rindex] == 0M && !isLastRcpt;

                matchings.Add(matching);


                if (!isEqual)
                {
                    if (!nextBilling && isLastBill && receiptPair[rindex] > 0M && !rcpt.OriginalReceiptId.HasValue)
                    {
                        matching.AdvanceReceivedOccured = 1;
                    }
                    if (!nextBilling && isLastBill && receiptPair[rindex] != 0M)
                    {
                        foreach (var m in matchings.Where(x => x.ReceiptId == rcpt.Id))
                        {
                            m.ReceiptRemain = receiptPair[rindex];
                        }
                    }
                    if (!nextReceipt && isLastRcpt && billingPair[bindex] != 0M)
                    {
                        foreach (var m in matchings.Where(x => x.BillingId == bill.Id))
                        {
                            m.BillingRemain = billingPair[bindex];
                        }
                    }
                    if (billingPair[bindex] == 0M && isLastBill ||
                        receiptPair[rindex] == 0M && isLastRcpt)
                    {
                        break;
                    }
                }

                if (nextBilling)
                {
                    bindex++;
                }
                if (nextReceipt)
                {
                    rindex++;
                }

                taxDiff = 0M;
                bankFee = 0M;
            }

            var remainType = 0;

            var billingRemainTotal = billingPair.Sum(x => x.Value);
            var receiptRemainTotal = receiptPair.Sum(x => x.Value);
            if (billingRemainTotal == 0M && receiptRemainTotal == 0M)
            {
                remainType = 0;
            }
            else if (billingRemainTotal != 0M)
            {
                remainType = 1;
            }
            else
            {
                if (!rcpt.OriginalReceiptId.HasValue &&
                    option.UseAdvanceReceived &&
                    receiptPair[rindex] > 0M)
                {
                    remainType = 3;
                }
                else
                {
                    remainType = 2;
                }
            }

            if (useCashOnDueDates == 1)
            {
                billingScheduledIncomes.AddRange(ConvertMatchingToScheduled(matchings));
            }


            billingItems        = billingItems.Take(bindex + 1).ToList();
            receiptItems        = receiptItems.Take(rindex + 1).ToList();
            header.BillingCount = billingItems.Count;
            header.ReceiptCount = receiptItems.Count;

            if (token.IsCancellationRequested)
            {
                throw new OperationCanceledException();
            }

            return(new MatchingSource
            {
                RemainType = remainType,
                Matchings = matchings,
                Billings = billingItems,
                Receipts = receiptItems,
                BillingDiscounts = billingDiscounts,
                MatchingBillingDiscounts = matchingBillingDiscounts,
                MatchingHeader = header,
                BillingScheduledIncomes = billingScheduledIncomes,
                BillingRemainTotal = beforBillingRemainTotal,
                ReceiptRemainTotal = beforReceiptRemainTotal,
                BillingDiscountTotal = discountTotal
            });
        }