Настройка печати QR-кода на пречеке в iiko

Для интеграции с Чаевыми достаточно стандартных функций создания QR-кодов в iiko: необходимо добавить шаблон документа с блоком для Чаевых и настроить его.

Ссылка в QR-коде

Ссылка на оставление чаевых, которая формируется в QR-коде, имеет следующий вид:

https://tips.yandex.ru/guest/payment/{waiterId}?sum=1&table_name=1, где:

  • {waiterId} – код сотрудника из личного кабинета Чаевых, указывается в блоке переменных шаблона;
  • sum – сумма заказа, передается из документа.
  • table_name – номер стола, передается из документа.

Совет

Для настройки функции официант по умолчанию используйте параметр {defaultWaiterId} – официант по умолчанию.

Настройка шаблона

Важно

Для настройки шаблона потребуется доступ в iikoOffice (бэк-офис).

Ниже приводится пошаговая инструкция по настройке шаблона чека в системе iiko.

  1. Добавьте новый шаблон. Для этого перейдите в подраздел АдминистрированиеШаблоны чеков и в группе Шаблоны нажмите кнопку Добавить:

    Примечание

    Если подраздел Шаблоны чеков отсутствует – у текущего пользователя не хватает полномочий. Запросите повышенные права у сотрудника, обслуживающего iiko.

  2. Для добавленного шаблона выберите тип Пречек, отметьте опции Razor-шаблон и Основной:

    Примечание

    Если в разделе уже присутствует шаблон пречека, помеченный как Основной, это значит, что уже используется измененный шаблон – необходимо скопировать его содержимое в новый шаблон и изменить код согласно пункту 3 данной инструкции.

  3. Отредактируйте код шаблона чека, согласно пунктам ниже:

    3.1 После списка директив

    @using System.Collections.Generic
    @using System.Globalization
    @using System.Linq
    @using Resto.Front.PrintTemplates.Cheques.Razor
    @using Resto.Front.PrintTemplates.Cheques.Razor.TemplateModels
    

    добавьте строку:

    @using System.Text.RegularExpressions
    

    3.2 Перейдите к следующему фрагменту кода:

    @inherits TemplateBase<IBillCheque>
    @{
        var order = Model.Order;
    }
    

    и после строки var order = Model.Order;, перед закрывающей фигурной скобкой, добавьте следующий блок:

    var defaultWaiterId = 0; //////Код официанта по умолчанию
    var fullSum = order.GetFullSum() - order.DiscountItems.Where(di => !di.Type.PrintProductItemInPrecheque).Sum(di => di.GetDiscountSum());
    var categorizedDiscountItems = new List<IDiscountItem>();
    var nonCategorizedDiscountItems = new List<IDiscountItem>();
    
    foreach (var discountItem in order.DiscountItems.Where(di => di.Type.PrintProductItemInPrecheque && di.DiscountSums.Count > 0))
    {
        if (discountItem.IsCategorized)
        {
            categorizedDiscountItems.Add(discountItem);
        }
        else
        {
            nonCategorizedDiscountItems.Add(discountItem);
        }
    }
    
    var subTotal = fullSum - categorizedDiscountItems.Sum(di => di.GetDiscountSum());
    var totalWithoutDiscounts = subTotal - nonCategorizedDiscountItems.Sum(di => di.GetDiscountSum());
    var prepay = order.PrePayments.Sum(prepayItem => prepayItem.Sum);
    var total = Math.Max(totalWithoutDiscounts + order.GetVatSumExcludedFromPrice() - prepay, 0m);
    
    // add my
    var nuberss=order.Waiter.GetNameOrEmpty();
    var codeMatches = Regex.Match(nuberss, "\\d{6,}$");
    bool codeFound = Regex.IsMatch(nuberss, "\\d{6,}$");
    string code = codeMatches.ToString()  ;
    var offNam = Regex.Match(nuberss, "\\D*");
    string offName = offNam.ToString();
    
    var urls = "https://tips.yandex.ru/guest/payment/";
    
    var urlParams = new Dictionary<string, string>
     {
      {"table", "&table_name="}
     };
    
    var ampSum = "?sum=";
    var SumT = Math.Round(totalWithoutDiscounts, 0, MidpointRounding.AwayFromZero);
    var codeFine =  Regex.Match(nuberss, "\\d{6,}");
    

    3.3 Найдите в коде такое условие:

    @if (Model.AdditionalServiceChequeInfo != null)
    {
        <left>@string.Format(Resources.AdditionalServiceHeaderOrderItemsAddedPattern, FormatLongDateTime(Model.CommonInfo.CurrentTime))</left>
    }
    

    Внесите в него следующие изменения: чтобы при печати не выводить код сотрудника на документе, удалите строку внутри фигурных скобок:

    <left>@string.Format(Resources.AdditionalServiceHeaderOrderItemsAddedPattern, FormatLongDateTime(Model.CommonInfo.CurrentTime))</left>
    

    и вместо нее добавьте новую, которая вырезает код сотрудника при печати:

    <left>@string.Format(Resources.BillHeaderWaiterPattern, offNam)</left>
    

    3.4 Перейдите к коду футера (нижнего блока документа):

    @* Footer (begin) *@
    <np />
        ...
    <np />
    @* Footer (end) *@
    

    После строки @* Footer (end) *@ добавьте следующий блок с текстом и QR-кодом:

    @if (codeFound)
    {
    <f2><doc>
    	<center>Чтобы не ждать</center>
    </doc>
    </f2>
    <doc>
    	<np/>
    	<center>Оставьте чаевые и отзыв по QR-коду
    	<np/>Отсканируйте камерой или приложением</center>
    	<np/>
    </doc>
    <center><split>
    	<qrcode size="small"		
    	correction="low">@urls@codeFine@ampSum@SumT@urlParams["table"]@order.Table.Number		
    	</qrcode>
    </split></center>
    <center>
    	<split>
    	<np />
    	Чаевые официанту приветствуются,
    	<np />
    	но остаются на ваше усмотрение
    	</split>
    </center>
    }
    else
    {
    if (defaultWaiterId != 0)
    {
    <f2><doc>
            <center>Чтобы не ждать</center>
        </doc>
        </f2>
        <doc>
            <np/>
            <center>Оставить чаевые и отзыв
            <np/>Отсканируйте QR-код камерой</center>
            <np/>
        </doc>
        <center><split>
            <qrcode size="medium"		
            correction="medium">@urls@defaultWaiterId@ampSum@SumT@urlParams["table"]@order.Table.Number		
            </qrcode>
        </split></center>
        <center>
            <split>
            <np />
            Чаевые официанту приветствуются,
            <np />
            но остаются на ваше усмотрение
            </split>
        </center>
        }
    }
    

    Оставшийся блок кода редактировать не нужно – он остается без изменений. Под катом ниже полный код шаблона чека, который получится после редактирования.

    Полный код шаблона чека
        @using System.Collections.Generic
        @using System.Globalization
        @using System.Linq
        @using Resto.Front.PrintTemplates.Cheques.Razor
        @using Resto.Front.PrintTemplates.Cheques.Razor.TemplateModels
        @using System.Text.RegularExpressions
    
        @inherits TemplateBase<IBillCheque>
        @{
            var order = Model.Order;
            var defaultWaiterId = 0; //////Код официанта по умолчанию
            var fullSum = order.GetFullSum() - order.DiscountItems.Where(di => !di.Type.PrintProductItemInPrecheque).Sum(di => di.GetDiscountSum());
            var categorizedDiscountItems = new List<IDiscountItem>();
            var nonCategorizedDiscountItems = new List<IDiscountItem>();
    
            foreach (var discountItem in order.DiscountItems.Where(di => di.Type.PrintProductItemInPrecheque && di.DiscountSums.Count > 0))
            {
                if (discountItem.IsCategorized)
                {
                    categorizedDiscountItems.Add(discountItem);
                }
                else
                {
                nonCategorizedDiscountItems.Add(discountItem);
                }
            }
    
            var subTotal = fullSum - categorizedDiscountItems.Sum(di => di.GetDiscountSum());
            var totalWithoutDiscounts = subTotal - nonCategorizedDiscountItems.Sum(di => di.GetDiscountSum());
            var prepay = order.PrePayments.Sum(prepayItem => prepayItem.Sum);
            var total = Math.Max(totalWithoutDiscounts + order.GetVatSumExcludedFromPrice() - prepay, 0m);
    
            // add my
            var nuberss=order.Waiter.GetNameOrEmpty();
            var codeMatches = Regex.Match(nuberss, "\\d{6,}$");
            bool codeFound = Regex.IsMatch(nuberss, "\\d{6,}$");
            string code = codeMatches.ToString()  ;
            var offNam = Regex.Match(nuberss, "\\D*");
            string offName = offNam.ToString();
    
            var urls = "https://tips.yandex.ru/guest/payment/";
    
            var urlParams = new Dictionary<string, string>
              {
               {"table", "&table_name="}
              };
    
            var ampSum = "?sum=";
            var SumT = Math.Round(totalWithoutDiscounts, 0, MidpointRounding.AwayFromZero);
            var codeFine =  Regex.Match(nuberss, "\\d{6,}");
        }
    
        <doc>
            @* Header (begin) *@
            @if (Model.AdditionalServiceChequeInfo == null)
            {
                <whitespace-preserve>@Raw(string.Join(Environment.NewLine, Model.Extensions.BeforeHeader))</whitespace-preserve>
            }
            <left><split><whitespace-preserve>@Model.CommonInfo.CafeSetup.BillHeader</whitespace-preserve></split></left>
    
            @if (Model.AdditionalServiceChequeInfo == null)
            {
                if (Model.RepeatBillNumber == 0)
                {
                    <center>@Resources.BillHeaderTitle</center>
                }
                else
                {
                    <center>@string.Format(Resources.RepeateBillHeaderTitleFormat, Model.RepeatBillNumber)</center>
                }
            }
            <pair fit="right" left="@string.Format(Resources.BillHeaderSectionPattern, order.Table.Section.Name)" right="@string.Format(Resources.BillHeaderTablePattern, order.Table.Number)" />
            <pair fit="right" left="@string.Format(Resources.BillHeaderOrderOpenPattern, FormatLongDateTime(order.OpenTime))" right="@string.Format(Resources.BillHeaderOrderNumberPattern, order.Number)" />
    
            @if (Model.AdditionalServiceChequeInfo != null)
            {
                <left>@string.Format(Resources.BillHeaderWaiterPattern, offNam)</left>
            }
            @foreach (var clientInfo in
                from discountItem in order.DiscountItems
                where discountItem.CardInfo != null
                select discountItem.CardInfo into cardInfo
                select string.IsNullOrWhiteSpace(cardInfo.MaskedCard) ? cardInfo.Owner : string.Format("{0} ({1})", cardInfo.Owner, cardInfo.MaskedCard) into clientInfo
                where !string.IsNullOrWhiteSpace(clientInfo)
                select clientInfo)
            {
                <left>@string.Format(Resources.ClientFormat, clientInfo)</left>
            }
    
            @if (Model.AdditionalServiceChequeInfo == null)
            {
                <whitespace-preserve>@Raw(string.Join(Environment.NewLine, Model.Extensions.AfterHeader))</whitespace-preserve>
            }
    
            @if (Model.AdditionalServiceChequeInfo != null)
            {
                if (order.ClientBinding != null && !string.IsNullOrWhiteSpace(order.ClientBinding.CardNumber))
                {
                    <left>@string.Format(Resources.CardPattern, order.ClientBinding.CardNumber)</left>
                }
                <np />
                <center>@Resources.AdditionalServiceHeaderTitle</center>
            }
            @* Header (end) *@
    
            @* Body (begin) *@
            <table>
                <columns>
                    <column />
                    <column align="right" autowidth="" />
                    <column width="2" />
                    <column align="right" autowidth="" />
                </columns>
                <cells>
                    @Guests()
                    <linecell />
                    @Summaries()
                </cells>
            </table>
            @* Body (end) *@
    
            @* Footer (begin) *@
            <np />
            @if (Model.AdditionalServiceChequeInfo == null)
            {
                <whitespace-preserve>@Raw(string.Join(Environment.NewLine, Model.Extensions.BeforeFooter))</whitespace-preserve>
            }
            <center><split><whitespace-preserve>@Model.CommonInfo.CafeSetup.BillFooter</whitespace-preserve></split></center>
            <np />
            <np />
            @if (Model.AdditionalServiceChequeInfo == null)
            {
                <whitespace-preserve>@Raw(string.Join(Environment.NewLine, Model.Extensions.AfterFooter))</whitespace-preserve>
            }
            <np />
            @* Footer (end) *@
    
            @if (codeFound)
            {
            <f2><doc>
                <center>Чтобы не ждать</center>
            </doc>
            </f2>
            <doc>
                <np/>
                <center>Оставьте чаевые и отзыв по QR-коду
                <np/>Отсканируйте камерой или приложением</center>
                <np/>
            </doc>
            <center><split>
                <qrcode size="small"
                correction="low">@urls@codeFine@ampSum@SumT@urlParams["table"]@order.Table.Number
                </qrcode>
            </split></center>
            <center>
                <split>
                <np />
                Чаевые официанту приветствуются,
                <np />
                но остаются на ваше усмотрение
                </split>
            </center>
            }
            else
            {
                if (defaultWaiterId != 0)
                {
                <f2><doc>
                    <center>Чтобы не ждать</center>
                </doc>
                </f2>
                <doc>
                    <np/>
                    <center>Оставить чаевые и отзыв
                    <np/>Отсканируйте QR-код камерой</center>
                    <np/>
                </doc>
                <center><split>
                    <qrcode size="medium"
                    correction="medium">@urls@defaultWaiterId@ampSum@SumT@urlParams["table"]@order.Table.Number
                    </qrcode>
                </split></center>
                <center>
                    <split>
                    <np />
                    Чаевые официанту приветствуются,
                    <np />
                    но остаются на ваше усмотрение
                    </split>
                </center>
                }
            }
        </doc>
    
        @helper Guests()
        {
            var order = Model.Order;
            Func<IOrderItem, bool> orderItemsFilter;
            if (Model.AdditionalServiceChequeInfo != null)
            {
                orderItemsFilter = orderItem => Model.AdditionalServiceChequeInfo.AddedOrderItems.Contains(orderItem);
            }
            else
            {
                orderItemsFilter = orderItem => orderItem.DeletionInfo == null;
            }
    
            var guestsWithItems = Model.Order.Table.Section.DisplayGuests
                ? order.Guests.Select(guest => new
                {
                    Guest = guest,
                    Items = guest.Items.Where(item => orderItemsFilter(item) && OrderItemsToPrintFilter(item, order.DiscountItems))
                })
                .Where(guestWithItems => guestWithItems.Items.Any()).ToList()
                : EnumerableEx.Return(new
                {
                    Guest = order.Guests.FirstOrDefault(),
                    Items = order.Guests.SelectMany(g => g.Items.Where(item => orderItemsFilter(item) && OrderItemsToPrintFilter(item, order.DiscountItems)))
                })
                .Where(guestWithItems => guestWithItems.Items.Any()).ToList();
    
            if (!guestsWithItems.Any())
            {
                return;
            }
    
            <linecell />
            <ct>@Resources.NameColumnHeader</ct>
            <ct>@Resources.ProductAmount</ct>
            <ct />
            <ct>@Resources.ResultSum</ct>
            <linecell />
    
            if (guestsWithItems.Count == 1)
            {
                @SingleGuest(guestsWithItems.Single().Items.ToList())
            }
            else
            {
                @OneOfMultipleGuests(guestsWithItems.First().Guest, guestsWithItems.First().Items.ToList())
                foreach (var guestWithItems in guestsWithItems.Skip(1))
                {
                    <linecell symbols=" " />
                    @OneOfMultipleGuests(guestWithItems.Guest, guestWithItems.Items.ToList())
                }
            }
        }
    
        @helper SingleGuest(IEnumerable<IOrderItem> items)
        {
            foreach (var comboGroup in items.OrderBy(i => i.OrderRank).GroupBy(i => i.Combo))
            {
                var combo = comboGroup.Key;
                var isPartOfCombo = combo != null;
                var additionalSpace = isPartOfCombo ? " " : string.Empty;
                if (isPartOfCombo)
                {
                    <ct>@combo.Name</ct>
                    <ct>@FormatAmount(combo.Amount)</ct>
                    <ct />
                    <ct>@FormatMoney(combo.Price * combo.Amount)</ct>
                }
                foreach (var orderItemGroup in comboGroup.OrderBy(item => item.OrderRank).GroupBy(_ => _, CreateComparer<IOrderItem>(AreOrderItemsEqual)))
                {
                    var totalAmount = orderItemGroup.Sum(orderItem => orderItem.Amount);
                    var totalCost = orderItemGroup.Sum(orderItem => orderItem.Cost);
    
                    var productItem = orderItemGroup.Key as IProductItem;
                    if (productItem != null && productItem.CompoundsInfo != null && productItem.CompoundsInfo.IsPrimaryComponent)
                    {
                        <ct><whitespace-preserve>@(additionalSpace + string.Format("{0} {1}", productItem.CompoundsInfo.ModifierSchemaName, productItem.ProductSize == null ? string.Empty : productItem.ProductSize.Name))</whitespace-preserve></ct>
                        <c colspan="3" />
    
                        // для разделенной пиццы комменты печатаем под схемой
                        if (Model.Order.Table.Section.PrintProductItemCommentInCheque && productItem.Comment != null && !productItem.Comment.Deleted)
                        {
                            <c>
                                <table cellspacing="0">
                                    <columns>
                                        <column width="2" />
                                        <column />
                                    </columns>
                                    <cells>
                                        <c />
                                        <c><split><whitespace-preserve>@(additionalSpace + productItem.Comment.Text)</whitespace-preserve></split></c>
                                    </cells>
                                </table>
                            </c>
                            <c colspan="3" />
                        }
    
                        // у пиццы не может быть удаленных модификаторов, поэтому берем весь список
                        foreach (var orderEntry in productItem.ModifierEntries.Where(orderEntry => ModifiersFilter(orderEntry, productItem, true)))
                        {
                            <ct><whitespace-preserve>@(additionalSpace + "  " + orderEntry.Product.Name)</whitespace-preserve></ct>
    
                            if (orderEntry.Amount != 1m)
                            {
                                <ct>@FormatAmount(orderEntry.Amount)</ct>
                            }
                            else
                            {
                                <ct />
                            }
                            <ct />
    
                            if (orderEntry.Cost != 0m)
                            {
                                <ct>@FormatMoney(orderEntry.Cost)</ct>
                            }
                            else
                            {
                                <ct />
                            }
    
                            @CategorizedDiscountsForOrderEntryGroup(new[] { orderEntry }, isPartOfCombo)
                        }
                    }
    
                    if (productItem != null && productItem.CompoundsInfo != null)
                    {
                        <ct><whitespace-preserve>@(additionalSpace + " 1/2 " + GetOrderEntryNameWithProductSize(productItem))</whitespace-preserve></ct>
                    }
                    else
                    {
                        <ct><whitespace-preserve>@(additionalSpace + GetOrderEntryNameWithProductSize(orderItemGroup.Key))</whitespace-preserve></ct>
                    }
    
                    <ct>@FormatAmount(totalAmount)</ct>
                    if (!isPartOfCombo)
                    {
                        <ct />
                        <ct>@FormatMoney(totalCost)</ct>
                    }
                    else
                    {
                        <c colspan="2" />
                    }
    
                    @CategorizedDiscountsForOrderEntryGroup(orderItemGroup.ToList<IOrderEntry>(), isPartOfCombo)
    
                    // здесь комменты для обычных блюд и целых пицц
                    if (Model.Order.Table.Section.PrintProductItemCommentInCheque && productItem != null && productItem.Comment != null && !productItem.Comment.Deleted && productItem.CompoundsInfo == null)
                    {
                        <c>
                            <table cellspacing="0">
                                <columns>
                                    <column width="2" />
                                    <column />
                                </columns>
                                <cells>
                                    <c />
                                    <c><split><whitespace-preserve>@(additionalSpace + productItem.Comment.Text)</whitespace-preserve></split></c>
                                </cells>
                            </table>
                        </c>
                        <c colspan="3" />
                    }
    
                    foreach (var orderEntry in orderItemGroup.Key.GetNotDeletedChildren().Where(orderEntry => ModifiersFilter(orderEntry, orderItemGroup.Key)).ToList())
                    {
                        <ct><whitespace-preserve>@(additionalSpace + "  " + orderEntry.Product.Name)</whitespace-preserve></ct>
    
                        if (orderEntry.Amount != 1m)
                        {
                            <ct>@FormatAmount(orderEntry.Amount)</ct>
                        }
                        else
                        {
                            <ct />
                        }
                        <ct />
    
                        if (orderEntry.Cost != 0m)
                        {
                            <ct>@FormatMoney(orderEntry.Cost)</ct>
                        }
                        else
                        {
                            <ct />
                        }
    
                        @CategorizedDiscountsForOrderEntryGroup(new[] { orderEntry }, isPartOfCombo)
                    }
                }
            }
        }
    
        @helper CategorizedDiscountsForOrderEntryGroup(ICollection<IOrderEntry> entries, bool isPartOfCombo)
        {
            var orderEntry = entries.First();
            var additionalSpace = isPartOfCombo ? " " : string.Empty;
            if (orderEntry.Cost != 0m)
            {
                var categorizedDiscounts = from discountItem in Model.Order.DiscountItems
                                        where discountItem.IsCategorized && discountItem.PrintDetailedInPrecheque
                                        let discountSum = entries.Sum(entry => discountItem.GetDiscountSumFor(entry))
                                        where discountSum != 0m
                                        select new
                                        {
                                            IsDiscount = discountSum > 0m,
                                            Sum = Math.Abs(discountSum),
                                            Percent = Math.Abs(CalculatePercent(entries.Sum(entry => entry.Cost), discountSum)),
                                            Name = discountItem.Type.PrintableName,
                                            discountItem.Type.DiscountBySum
                                        } into discount
                                        orderby discount.IsDiscount descending
                                        select discount;
    
                foreach (var categorizedDiscount in categorizedDiscounts)
                {
                    <c colspan="3">
                        <whitespace-preserve>@(additionalSpace + GetFormattedDiscountDescriptionForOrderItem(categorizedDiscount.IsDiscount, categorizedDiscount.Name, categorizedDiscount.DiscountBySum, categorizedDiscount.Percent))</whitespace-preserve>
                    </c>
                    <ct>@GetFormattedDiscountSum(categorizedDiscount.IsDiscount, categorizedDiscount.Sum)</ct>
                }
            }
        }
    
        @helper OneOfMultipleGuests(IGuest guest, ICollection<IOrderItem> items)
        {
            <c colspan="0">@guest.Name</c>
            @SingleGuest(items)
            <c colspan="3" />
            <c><line /></c>
    
            var includedEntries = items.SelectMany(item => item.ExpandIncludedEntries()).ToList();
            var total = includedEntries.Sum(orderEntry => orderEntry.Cost);
            var totalWithoutCategorizedDiscounts = total - (from orderEntry in includedEntries
                                                            from discountItem in Model.Order.DiscountItems
                                                            where discountItem.IsCategorized
                                                            select discountItem.GetDiscountSumFor(orderEntry)).Sum();
    
            var totalWithoutDiscounts = totalWithoutCategorizedDiscounts - (from orderEntry in includedEntries
                                                                            from discountItem in Model.Order.DiscountItems
                                                                            where !discountItem.IsCategorized
                                                                            select discountItem.GetDiscountSumFor(orderEntry)).Sum();
    
            if (totalWithoutCategorizedDiscounts != totalWithoutDiscounts)
            {
                <c colspan="3">@Resources.BillFooterTotalPlain</c>
                <ct>@FormatMoney(totalWithoutCategorizedDiscounts)</ct>
    
                var nonCategorizedDiscounts = from discountItem in Model.Order.DiscountItems
                                            where !discountItem.IsCategorized
                                            let discountSum = includedEntries.Sum(orderEntry => discountItem.GetDiscountSumFor(orderEntry))
                                            select new
                                            {
                                                IsDiscount = discountSum > 0m,
                                                Sum = Math.Abs(discountSum),
                                                Percent = Math.Abs(CalculatePercent(includedEntries.Sum(entry => entry.Cost), discountSum)),
                                                Name = discountItem.Type.PrintableName,
                                                discountItem.Type.DiscountBySum
                                            } into discount
                                            orderby discount.IsDiscount descending
                                            select discount;
    
                foreach (var nonCategorizedDiscount in nonCategorizedDiscounts)
                {
                    <c colspan="3">
                        @(nonCategorizedDiscount.DiscountBySum
                        ? GetFormattedDiscountDescriptionShort(nonCategorizedDiscount.IsDiscount, nonCategorizedDiscount.Name)
                        : GetFormattedDiscountDescriptionDetailed(nonCategorizedDiscount.IsDiscount, nonCategorizedDiscount.Name, nonCategorizedDiscount.Percent))
                    </c>
                    <ct>@GetFormattedDiscountSum(nonCategorizedDiscount.IsDiscount, nonCategorizedDiscount.Sum)</ct>
                }
            }
    
            <c colspan="3">@string.Format(Model.AdditionalServiceChequeInfo == null ? Resources.BillFooterTotalGuestPattern : Resources.AdditionalServiceFooterTotalGuestPattern, guest.Name)</c>
            <ct>@FormatMoney(totalWithoutDiscounts)</ct>
        }
    
        @helper Summaries()
        {
            var order = Model.Order;
            var fullSum = order.GetFullSum() - order.DiscountItems.Where(di => !di.Type.PrintProductItemInPrecheque).Sum(di => di.GetDiscountSum());
            var categorizedDiscountItems = new List<IDiscountItem>();
            var nonCategorizedDiscountItems = new List<IDiscountItem>();
    
            foreach (var discountItem in order.DiscountItems.Where(di => di.Type.PrintProductItemInPrecheque && di.DiscountSums.Count > 0))
            {
                if (discountItem.IsCategorized)
                {
                    categorizedDiscountItems.Add(discountItem);
                }
                else
                {
                    nonCategorizedDiscountItems.Add(discountItem);
                }
            }
    
            var subTotal = fullSum - categorizedDiscountItems.Sum(di => di.GetDiscountSum());
            var totalWithoutDiscounts = subTotal - nonCategorizedDiscountItems.Sum(di => di.GetDiscountSum());
            var prepay = order.PrePayments.Sum(prepayItem => prepayItem.Sum);
            var total = Math.Max(totalWithoutDiscounts + order.GetVatSumExcludedFromPrice() - prepay, 0m);
    
            if (Model.DiscountMarketingCampaigns != null)
            {
                total -= Model.DiscountMarketingCampaigns.TotalDiscount;
                totalWithoutDiscounts -= Model.DiscountMarketingCampaigns.TotalDiscount;
            }
    
            var vatSumsByVat = (Model.AdditionalServiceChequeInfo == null
                    ? order.GetIncludedEntries()
                    : Model.AdditionalServiceChequeInfo.AddedOrderItems.SelectMany(item => item.ExpandIncludedEntries()))
                .Where(orderEntry => !orderEntry.VatIncludedInPrice)
                .GroupBy(orderEntry => orderEntry.Vat)
                .Where(group => group.Key != 0m)
                .Select(group => new { Vat = group.Key, Sum = group.Sum(orderEntry => orderEntry.ExcludedVat) })
                .ToList();
    
            var vatSum = vatSumsByVat.Sum(vatWithSum => vatWithSum.Sum);
    
            if ((prepay != 0m || fullSum != total) && Model.AdditionalServiceChequeInfo == null)
            {
                <c colspan="3">@Resources.BillFooterFullSum</c>
                <ct>@FormatMoney(fullSum)</ct>
            }
    
            @PrintOrderDiscounts(categorizedDiscountItems, fullSum)
    
            if (categorizedDiscountItems.Any())
            {
                <c colspan="3">@Resources.BillFooterTotalPlain</c>
                <ct>@FormatMoney(subTotal)</ct>
            }
    
            @PrintOrderDiscounts(nonCategorizedDiscountItems, fullSum)
    
            if (Model.DiscountMarketingCampaigns != null)
            {
                foreach (var discountMarketingCampaign in Model.DiscountMarketingCampaigns.Campaigns)
                {
                    <c colspan="3">@discountMarketingCampaign.Name</c>
                    <ct>@("-" + FormatMoney(discountMarketingCampaign.TotalDiscount))</ct>
                }
            }
    
            if (prepay != 0m && (categorizedDiscountItems.Any() || nonCategorizedDiscountItems.Any()))
            {
                <c colspan="3">@Resources.BillFooterTotalWithoutDiscounts</c>
                <ct>@FormatMoney(totalWithoutDiscounts)</ct>
            }
    
            if (vatSum != 0m)
            {
                foreach (var vatWithSum in vatSumsByVat)
                {
                    <c colspan="3">@string.Format(Resources.VatFormat, vatWithSum.Vat)</c>
                    <ct>@string.Format(FormatMoney(vatWithSum.Sum))</ct>
                }
                if (vatSumsByVat.Count > 1)
                {
                    <c colspan="3">@Resources.VatSum</c>
                    <ct>@FormatMoney(vatSum)</ct>
                }
            }
    
            if (Model.AdditionalServiceChequeInfo != null)
            {
                <c colspan="3">@Resources.AdditionalServiceAddedFooterTotalUpper</c>
                <ct>@FormatMoney(Model.AdditionalServiceChequeInfo.AddedOrderItems.SelectMany(item => item.ExpandIncludedEntries()).Sum(orderEntry => orderEntry.Cost) + vatSum)</ct>
            }
    
            if (prepay != 0m)
            {
                <c colspan="3">@Resources.Prepay</c>
                <ct>@FormatMoney(prepay)</ct>
            }
    
            <c colspan="3">@(Model.AdditionalServiceChequeInfo == null ? Resources.BillFooterTotal : Resources.AdditionalServiceFooterTotalUpper)</c>
            <ct>@FormatMoney(total)</ct>
            foreach (var rate in order.FixedCurrencyRates)
            {
                <c colspan="2">
                    <right>
                        @string.Format(Resources.CurrencyRateFormat, rate.Key.IsoName, rate.Value.ToString("f4", CultureInfo.CurrentCulture), Model.CommonInfo.CafeSetup.CurrencyIsoName)
                    </right>
                </c>
                <ct />
                <ct>@string.Format(Resources.CurrencyFormat, rate.Key.IsoName, FormatMoney(GetSumInAdditionalCurrency(rate.Key, rate.Value, total), rate.Key.IsoName))</ct>
            }
    
            if (Model.AdditionalServiceChequeInfo != null && order.ClientBinding != null && order.ClientBinding.PaymentLimit.HasValue)
            {
                <c colspan="3">@Resources.AdditionalServiceLimit</c>
                <ct>@FormatMoney(order.ClientBinding.PaymentLimit.Value - total)</ct>
            }
    
            if (Model.DiscountMarketingCampaigns != null)
            {
                foreach (var discountMarketingCampaign in Model.DiscountMarketingCampaigns.Campaigns.Where(campaign => !string.IsNullOrWhiteSpace(campaign.BillComment)))
                {
                    <c colspan="4">@discountMarketingCampaign.BillComment</c>
                }
            }
        }
    
        @helper PrintOrderDiscounts(IEnumerable<IDiscountItem> discountItems, decimal fullSum)
        {
            foreach (var discountItem in discountItems.OrderByDescending(discountItem => discountItem.IsDiscount()))
            {
                <c colspan="3">
                    @((!discountItem.IsCategorized || discountItem.PrintDetailedInPrecheque) && !discountItem.Type.DiscountBySum
                        ? GetFormattedDiscountDescriptionDetailed(discountItem.IsDiscount(), discountItem.Type.PrintableName, Math.Abs(CalculatePercent(fullSum, discountItem.GetDiscountSum())))
                        : GetFormattedDiscountDescriptionShort(discountItem.IsDiscount(), discountItem.Type.PrintableName))
                </c>
                <ct>@GetFormattedDiscountSum(discountItem.IsDiscount(), Math.Abs(discountItem.GetDiscountSum()))</ct>
            }
        }
    
        @functions
        {
            /// <summary>
            /// Отфильтровывает флаерные блюда, для которых нет настройки "Печать в пречеке блюд по флаеру"
            /// </summary>
            private static bool OrderItemsToPrintFilter(IOrderItem orderItem, IEnumerable<IDiscountItem> discountItems)
            {
                return !(orderItem is IProductItem) || discountItems
                    .Where(discountItem => discountItem.DiscountSums.ContainsKey(orderItem))
                    .All(discountItem => discountItem.Type.PrintProductItemInPrecheque);
            }
    
            private bool AreOrderItemsEqual(IOrderItem x, IOrderItem y)
            {
                if (ReferenceEquals(x, y))
                    return true;
                if (x == null)
                    return y == null;
                if (y == null)
                    return false;
    
                var xProductItem = x as IProductItem;
                var yProductItem = y as IProductItem;
    
                if (xProductItem == null || yProductItem == null || !ProductItemCanBeMerged(xProductItem) || !ProductItemCanBeMerged(yProductItem))
                    return false;
                if (xProductItem.Product.Name != yProductItem.Product.Name)
                    return false;
                if (xProductItem.ProductSize == null ^ yProductItem.ProductSize == null)
                    return false;
                if (xProductItem.ProductSize != null && yProductItem.ProductSize != null && xProductItem.ProductSize.Name != yProductItem.ProductSize.Name)
                    return false;
                if (xProductItem.Price != yProductItem.Price)
                    return false;
                if (xProductItem.Price == 0m)
                    return true;
    
                var categorizedDiscounts = Model.Order.DiscountItems
                    .Where(discountItem => discountItem.IsCategorized && discountItem.PrintDetailedInPrecheque && discountItem.DiscountSums.Count > 0)
                    .ToList();
    
                var xCategorizedDiscountItems = categorizedDiscounts.Where(discountItem => discountItem.DiscountSums.ContainsKey(x));
                var yCategorizedDiscountItems = categorizedDiscounts.Where(discountItem => discountItem.DiscountSums.ContainsKey(y));
    
                return new HashSet<IDiscountItem>(xCategorizedDiscountItems).SetEquals(yCategorizedDiscountItems);
            }
    
            private bool ProductItemCanBeMerged(IProductItem productItem)
            {
                return productItem.CompoundsInfo == null &&
                    productItem.Amount - Math.Truncate(productItem.Amount) == 0m &&
                    productItem.GetNotDeletedChildren().Where(orderEntry => ModifiersFilter(orderEntry, productItem)).IsEmpty() &&
                    (productItem.Comment == null || productItem.Comment.Deleted || !Model.Order.Table.Section.PrintProductItemCommentInCheque);
            }
    
            private static bool CommonModifiersFilter(bool isCommonModifier, IProductItem parent, bool onlyCommonModifiers)
            {
                if (parent.CompoundsInfo != null && parent.CompoundsInfo.IsPrimaryComponent)
                {
                    if (onlyCommonModifiers && !isCommonModifier)
                        return false;
                    if (!onlyCommonModifiers && isCommonModifier)
                        return false;
                    return true;
                }
                return true;
            }
    
            private static bool ModifiersFilter(IOrderEntry orderEntry, IOrderItem parent, bool onlyCommonModifiers = false)
            {
                var parentProductItem = parent as IProductItem;
                if (parentProductItem != null && !CommonModifiersFilter(((IModifierEntry)orderEntry).IsCommonModifier, parentProductItem, onlyCommonModifiers))
                    return false;
    
                if (orderEntry.Cost > 0m)
                    return true;
    
                if (!orderEntry.Product.PrechequePrintable)
                    return false;
    
                var modifierEntry = orderEntry as IModifierEntry;
                if (modifierEntry == null)
                    return true;
    
                if (modifierEntry.ChildModifier == null)
                    return true;
    
                if (!modifierEntry.ChildModifier.HideIfDefaultAmount)
                    return true;
    
                var amountPerItem = modifierEntry.ChildModifier.AmountIndependentOfParentAmount
                    ? modifierEntry.Amount
                    : modifierEntry.Amount / GetParentAmount(parent, onlyCommonModifiers);
    
                return amountPerItem != modifierEntry.ChildModifier.DefaultAmount;
            }
    
            private static string GetFormattedDiscountDescriptionForOrderItem(bool isDiscount, string discountName, bool discountBySum, decimal absolutePercent)
            {
                return discountBySum
                    ? string.Format("  {0}", discountName)
                    : string.Format(isDiscount ? "  {0} (-{1})" : "  {0} (+{1})", discountName, FormatPercent(absolutePercent));
            }
    
            private static string GetFormattedDiscountDescriptionShort(bool isDiscount, string discountName)
            {
                return string.Format(isDiscount ? Resources.BillFooterDiscountNamePatternShort : Resources.BillFooterIncreaseNamePatternShort, discountName);
            }
    
            private static string GetFormattedDiscountDescriptionDetailed(bool isDiscount, string discountName, decimal absolutePercent)
            {
                return string.Format(isDiscount ? Resources.BillFooterDiscountNamePatternDetailed : Resources.BillFooterIncreaseNamePatternDetailed,
                    discountName, FormatPercent(absolutePercent));
            }
    
            private static string GetFormattedDiscountSum(bool isDiscount, decimal absoluteSum)
            {
                return (isDiscount ? "-" : "+") + FormatMoney(absoluteSum);
            }
    
            private static string GetOrderEntryNameWithProductSize(IOrderEntry orderEntry)
            {
                var productItem = orderEntry as IProductItem;
                return (productItem == null || productItem.ProductSize == null || productItem.CompoundsInfo != null)
                    ? orderEntry.Product.Name
                    : string.Format(Resources.ProductNameWithSizeFormat, productItem.Product.Name, productItem.ProductSize.Name);
            }
    
            private static decimal GetParentAmount(IOrderItem parent, bool onlyCommonModifiers)
            {
                return onlyCommonModifiers ? parent.Amount * 2 : parent.Amount;
            }
        }
    
  4. Нажмите на кнопку Сохранить, а затем Сохранить изменения на общей вкладке раздела.

Готово! Теперь на пречеке будет печататься QR-код для перевода чаевых сотруднику заведения.

Предыдущая
Следующая