Example #1
0
        /// <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("Неизвестный тип накладной реализации.");
        }
Example #2
0
        /// <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);
        }
Example #5
0
 /// <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);
     }
 }
Example #6
0
        /// <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)));
                }
            }
        }
Example #7
0
        /// <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);
            }
        }
Example #8
0
        /// <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);
        }
Example #10
0
        /// <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);
        }
Example #11
0
        /// <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);
        }
Example #12
0
        /// <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);
            }
        }