/// <summary> /// Общая сумма всех возвратов по накладной реализации /// </summary> /// <param name="saleWaybill">Накладная реализации</param> /// <returns>Сумма</returns> public decimal GetTotalReservedByReturnSumForSaleWaybill(SaleWaybill saleWaybill) { if (saleWaybill.Is <ExpenditureWaybill>()) { ExpenditureWaybill expenditureWaybill = saleWaybill.As <ExpenditureWaybill>(); return(expenditureWaybillIndicatorService.GetTotalReservedByReturnSumForSaleWaybill(expenditureWaybill)); } throw new Exception("Неизвестный тип накладной реализации."); }
/// <summary> /// Расчет неоплаченного остатка по накладной реализации /// </summary> /// <param name="saleWaybill">Накладная реализации</param> /// <returns>Неоплаченный остаток</returns> public decimal CalculateDebtRemainder(SaleWaybill saleWaybill) { if (saleWaybill.Is <ExpenditureWaybill>()) { ExpenditureWaybill expenditureWaybill = saleWaybill.As <ExpenditureWaybill>(); return(expenditureWaybillIndicatorService.CalculateDebtRemainder(expenditureWaybill)); } throw new Exception("Неизвестный тип накладной реализации."); }
/// <summary> /// Общая сумма возвратов (в том числе еще не принятых возвратов) по накладной реализации /// </summary> /// <param name="saleWaybillRow">Накладная</param> /// <returns>Сумма</returns> public decimal GetTotalReservedByReturnSumForSaleWaybill(SaleWaybill saleWaybill) { decimal totalReturnedSum = 0M; if (saleWaybill.IsShipped) { foreach (var row in saleWaybill.Rows) { totalReturnedSum += row.ReservedByReturnCount * (row.SalePrice.HasValue ? row.SalePrice.Value : 0); } } return(totalReturnedSum); }
/// <summary> /// Общая сумма возвратов по накладной реализации /// </summary> /// <param name="saleWaybill">Накладная</param> /// <returns>Сумма</returns> public decimal GetTotalReturnedSumForSaleWaybill(SaleWaybill saleWaybill) { decimal totalReturnedSum = 0M; if (saleWaybill.IsShipped) { foreach (var row in saleWaybill.Rows) { totalReturnedSum += Math.Round(row.ReceiptedReturnCount * row.SalePrice ?? 0, 2); } } return(totalReturnedSum); }
/// <summary> /// Выполнение проверок на блокировки. /// Проверки всегда выполняются в определенном порядке: /// 1. Проверка на просрочку/связи с просрочившей организацией /// 2. Проверка на ручную блокировку /// 3. Проверка на блокировку по кредитному лимиту /// </summary> /// <param name="checkSet">Набор проверок для выполнения</param> /// <param name="deal">Сделка, по которой проверяются блокировки</param> private void PerformCheck(CheckSet checkSet, Deal deal, SaleWaybill transientSaleWaybill) { if (checkSet.CheckForPaymentDelayBlocking) { CheckForPaymentDelayBlocking(deal); } if (checkSet.CheckForManualBlocking) { CheckForManualBlocking(deal); } if (checkSet.CheckForCreditLimitBlocking) { CheckForCreditLimitBlocking(deal, transientSaleWaybill); } }
/// <summary> /// Проверка, не заблокирована ли сделка по кредитному лимиту /// Выполняется только при проведении операций по непринятым накладным реализации с отсрочкой платежа. /// Таким образом, накладная реализации всегда имеет квоту с отсрочкой платежа. /// </summary> /// <param name="deal">Сделка</param> private void CheckForCreditLimitBlocking(Deal deal, SaleWaybill transientSaleWaybill) { decimal?currentCreditLimitSum; decimal creditLimitRemainder = dealIndicatorService.CalculateCreditLimitRemainder(deal, out currentCreditLimitSum, transientSaleWaybill); ValidationUtils.NotNull(currentCreditLimitSum, "Текущая квота по сделке не предусматривает отсрочку платежа."); if (currentCreditLimitSum != 0) // Безлимитный кредит соответствует значению 0, при нем блокировка невозможна { if (creditLimitRemainder < 0) { throw new Exception(String.Format("Данная операция невозможна, произошло превышение кредитного лимита по сделке на {0} р.", (-creditLimitRemainder).ForDisplay(ValueDisplayType.Money))); } } }
/// <summary> /// Отмена всех расшифровок распределения оплаты, разнесенных на данную накладную /// </summary> /// <param name="saleWaybill">Накладная реализации</param> public void CancelSaleWaybillPaymentDistribution(SaleWaybill saleWaybill) { // Если накладная имела нулевую сумму, ни одна расшифровка создана не была, но признак полной оплаты стоял. Сбрасываем его всегда saleWaybill.IsFullyPaid = false; // Получаем все расшифровки распределения оплаты по данной накладной реализации и кэшируем все их платежные документы и их коллекции расшифровок var dealPaymentDocumentDistributionList = dealPaymentDocumentRepository .GetDealPaymentDocumentDistributionsForDestinationSaleWaybills(saleWaybillRepository.GetSaleWaybillIQueryable(saleWaybill.Id)); dealPaymentDocumentRepository.LoadSourceDealPaymentDocumentDistributions(dealPaymentDocumentDistributionList); // начинаем с удаления разнесения с максимальной суммой, т.е. отрицательные разнесения удаляем последними foreach (var dealPaymentDocumentDistribution in dealPaymentDocumentDistributionList.OrderByDescending(x => x.Sum)) { dealPaymentDocumentDistribution.SourceDealPaymentDocument.RemoveDealPaymentDocumentDistribution(dealPaymentDocumentDistribution); } }
/// <summary> /// Попытаться оплатить накладную реализации из аванса (т.е. неразнесенных остатков платежных документов по сделке и команде). /// После разнесения может оставаться неоплаченный остаток у накладной или неразнесенные остатки платежных документов. /// Если накладная не имеет положительного остатка, расшифровки распределения оплаты не будут созданы. /// При полной оплате накладной реализации происходит установка признака того, что накладная полностью оплачена /// </summary> /// <param name="saleWaybill">Накладная реализации, на которую выполняется разнесение платежных документов</param> public void PaySaleWaybillOnAccept(SaleWaybill saleWaybill, DateTime currentDate) { // если сумма в ОЦ по накладной реализации = 0, выставляем признак полной оплаты и выходим if (saleWaybill.Is <ExpenditureWaybill>() && saleWaybill.As <ExpenditureWaybill>().SalePriceSum == 0) { saleWaybill.IsFullyPaid = true; return; } // При проводке данной накладной неоплаченный остаток равен сумме накладной в ОЦ var debtRemainder = saleWaybill.As <ExpenditureWaybill>().SalePriceSum; // получение информации о неразнесенных частях платежных документов var dealPaymentUndistributedPartsInfo = GetTotalDealPaymentUndistributedPartsInfo(saleWaybill.Deal, saleWaybill.Team); // разносим неоплаченные остатки на накладную PaySaleWaybill(dealPaymentUndistributedPartsInfo, saleWaybill, debtRemainder, debtRemainder, currentDate); }
/// <summary> /// Расчет суммы накладной реализации с учетом всех сделанных по ней возвратов /// </summary> /// <param name="sale"></param> /// <returns></returns> public decimal CalculateSaleWaybillCostWithReturns(SaleWaybill sale, ReturnFromClientWaybill returnFromClientWaybillToExclude = null) { // Сумма всех возвратов по накладной реализации var returnedRow = returnFromClientWaybillRepository.Query <ReturnFromClientWaybillRow>(); // TODO рефакторить, ну кто так через OneOf пишет? Надо выбрать в RetWaybillRow SaleWaybillRow, а в нем - SaleWaybill.Id !!! - Олег returnedRow.OneOf(x => x.SaleWaybillRow.Id, sale.Rows.Select(x => x.Id)) .Restriction <ReturnFromClientWaybill>(x => x.ReturnFromClientWaybill) .Where(x => x.State == ReturnFromClientWaybillState.Receipted); if (returnFromClientWaybillToExclude != null) { returnedRow.Where(x => x.ReturnFromClientWaybill.Id != returnFromClientWaybillToExclude.Id); } var returnedSum = returnedRow.ToList <ReturnFromClientWaybillRow>() .Sum(x => x.SalePrice.Value * x.ReturnCount); // Отпускная цена уже должна быть установлена (иметь Value) // Вычисляем сумму накладной реализации decimal saleSum = sale.As <ExpenditureWaybill>().SalePriceSum; return(saleSum - returnedSum); }
/// <summary> /// Расчет остатка кредитного лимита по всем постоплатным накладным реализации для данной сделки. /// Накладная реализации должна иметь квоту с постоплатой (т.е. с отсрочкой платежа). /// Если же накладная реализации имеет квоту с предоплатой, currentCreditLimitSum будет равно null. /// Если квота накладной реализации имеет безлимитный кредит, currentCreditLimitSum будет равно 0. /// </summary> /// <param name="deal">Сделка</param> /// <param name="currentCreditLimitSum">Текущее значение кредитного лимита (0 - безлимитный кредит)</param> /// <returns>Остаток кредитного лимита</returns> public decimal CalculateCreditLimitRemainder(Deal deal, out decimal?currentCreditLimitSum, SaleWaybill transientSaleWaybill) { var quota = transientSaleWaybill.Quota; if (quota.IsPrepayment) { currentCreditLimitSum = null; return(0); } currentCreditLimitSum = quota.CreditLimitSum; if (currentCreditLimitSum == 0) // безлимитный кредит { return(0); } decimal totalDebtRemainder = 0; Guid saleWaybillToExcludeId = transientSaleWaybill != null ? transientSaleWaybill.Id : Guid.Empty; var sales = dealRepository.Query <SaleWaybill>() .Where(x => x.Deal.Id == deal.Id && x.IsPrepayment == false && x.IsFullyPaid == false && x.Id != saleWaybillToExcludeId && x.AcceptanceDate != null) .ToList <SaleWaybill>(); foreach (SaleWaybill saleWaybill in sales) { totalDebtRemainder += saleWaybillIndicatorService.CalculateDebtRemainder(saleWaybill); } if (transientSaleWaybill != null) { totalDebtRemainder += saleWaybillIndicatorService.CalculateDebtRemainder(transientSaleWaybill); } //К кредитному лимиту прибавляем неразнесенные оплаты и вычитаем нераспределенное начальное сальдо клиента. var maxSaleSum = currentCreditLimitSum.Value + deal.UndistributedDealPaymentFromClientSum - deal.UnpaidDebtToInitialBalance; return(maxSaleSum - totalDebtRemainder); }
/// <summary> /// Проверка, не мешают ли блокировки операции по данной сделке /// </summary> /// <param name="operation">Тип проводимой операции</param> /// <param name="deal">Сделка, по которой проверяются блокировки</param> public void CheckForBlocking(BlockingDependentOperation operation, Deal deal, SaleWaybill transientSaleWaybill) { CheckSet checkSet; switch (operation) { case BlockingDependentOperation.CreateExpenditureWaybill: checkSet = new CheckSet { CheckForManualBlocking = true }; break; case BlockingDependentOperation.SavePrePaymentExpenditureWaybillRow: checkSet = new CheckSet { CheckForManualBlocking = true }; break; case BlockingDependentOperation.SavePostPaymentExpenditureWaybillRow: checkSet = new CheckSet { CheckForManualBlocking = true }; break; case BlockingDependentOperation.AcceptPrePaymentExpenditureWaybill: checkSet = new CheckSet { CheckForManualBlocking = true, CheckForPaymentDelayBlocking = true }; break; case BlockingDependentOperation.AcceptPostPaymentExpenditureWaybill: checkSet = new CheckSet { CheckForPaymentDelayBlocking = true, CheckForManualBlocking = true, CheckForCreditLimitBlocking = true, }; break; case BlockingDependentOperation.ShipUnpaidPostPaymentExpenditureWaybill: checkSet = new CheckSet { CheckForPaymentDelayBlocking = true, CheckForManualBlocking = true }; break; default: throw new Exception("Неизвестный тип операции."); } ; PerformCheck(checkSet, deal, transientSaleWaybill); }
/// <summary> /// Разнести неразнесенные части платежных документов на накладную реализации. /// После разнесения у накладной реализации может оставаться неоплаченный остаток. /// Если до разнесения накладная реализации не имеет положительного неоплаченного остатка, разнесение платежного документа не будет создано. /// При полной оплате накладной реализации происходит установка признака того, что накладная реализации полностью оплачена. /// </summary> /// <param name="dealPaymentUndistributedPartsInfo">Список неразнесенных частей платежных документов</param> /// <param name="saleWaybill">Накладная реализации (единственная), на которую выполняется разнесение платежных документов</param> /// <param name="debtRemainder">Текущий неоплаченный остаток по накладной</param> /// <param name="sumToDistribute">Сумма к разнесению</param> /// <param name="currentDate">Дата операции</param> private void PaySaleWaybill(List <DealPaymentUndistributedPartInfo> dealPaymentUndistributedPartsInfo, SaleWaybill saleWaybill, decimal debtRemainder, decimal sumToDistribute, DateTime currentDate) { // разносим неразнесенные части оплат foreach (var distributionPartInfo in dealPaymentUndistributedPartsInfo.OrderBy(x => x.AppearenceDate)) { var dealPaymentDocument = distributionPartInfo.DealPaymentDocument; // cумма создаваемого разнесения платежного документа decimal sum = Math.Min(distributionPartInfo.Sum, sumToDistribute); // вычитаем разносимую сумму distributionPartInfo.Sum -= sum; sumToDistribute -= sum; // дата создаваемого разнесения var distributionDate = DateTimeUtils.GetMaxDate(distributionPartInfo.AppearenceDate, saleWaybill.AcceptanceDate.Value); ValidationUtils.Assert(dealPaymentDocument.Is <DealPaymentFromClient>() || dealPaymentDocument.Is <DealCreditInitialBalanceCorrection>(), "Платежный документ имеет недопустимый тип."); ValidationUtils.Assert(sum > 0, "Сумма для разнесения должна быть положительной."); if (saleWaybill.Is <ExpenditureWaybill>()) // Если появятся еще типы накладных реализации, вместо "накладная реализации товаров" выводить их названия { ExpenditureWaybill expenditureWaybill = saleWaybill.As <ExpenditureWaybill>(); ValidationUtils.Assert(expenditureWaybill.IsAccepted, String.Format("Невозможно разнести платежный документ на накладную реализации товаров {0} со статусом «{1}».", expenditureWaybill.Name, expenditureWaybill.State.GetDisplayName())); ValidationUtils.Assert(sum <= debtRemainder, String.Format("Сумма для разнесения ({0} р.) превышает неоплаченный остаток накладной реализации товаров {1} ({2} р.)", sum.ForDisplay(ValueDisplayType.Money), expenditureWaybill.Name, debtRemainder.ForDisplay(ValueDisplayType.Money))); ValidationUtils.Assert(dealPaymentDocument.Team == saleWaybill.Team, String.Format("Невозможно оплатить накладную реализации «{0}», т.к. она относится к другой команде.", saleWaybill.Name)); // формируем разнесение var dealPaymentDocumentDistributionToSaleWaybill = new DealPaymentDocumentDistributionToSaleWaybill(dealPaymentDocument, expenditureWaybill, sum, distributionDate, currentDate) { SourceDistributionToReturnFromClientWaybill = distributionPartInfo.DealPaymentDocumentDistributionToReturnFromClientWaybill }; dealPaymentDocument.AddDealPaymentDocumentDistribution(dealPaymentDocumentDistributionToSaleWaybill); debtRemainder -= sum; // Если накладная реализации товаров полностью оплачена, ставим ей признак полной оплаты expenditureWaybill.IsFullyPaid = debtRemainder <= 0M; } // и выходим if (saleWaybill.IsFullyPaid) { break; } } // удаляем полностью разнесенные части var undistributedPartsToDelete = dealPaymentUndistributedPartsInfo.Where(x => x.Sum == 0).ToList(); foreach (var item in undistributedPartsToDelete) { dealPaymentUndistributedPartsInfo.Remove(item); } }