Exemple #1
0
 def get_initial(self):
     settings = EventSettings.get_solo()
     attrs = {
         attr: getattr(settings, attr)
         for attr in EventSettingsForm().fields
     }
     attrs.update({'initialized': True})
     return attrs
Exemple #2
0
    def _setup_base(self):
        s = EventSettings.get_solo()
        s.initialized = True
        s.receipt_address = "Foo"
        s.save()

        self.session = cashdesk_session_before_factory(create_items=False)
        self.troubleshooter = user_factory(troubleshooter=True,
                                           superuser=False,
                                           password='******')
        self.backoffice_user = user_factory(troubleshooter=True,
                                            backoffice=True,
                                            password='******')
        self.cashier1 = user_factory(password='******')
        self.cashier2 = user_factory(password='******')

        self.item_full = Item.objects.create(name='Wristband red',
                                             description='Full pass',
                                             initial_stock=200)
        self.item_d1 = Item.objects.create(name='Wristband 1',
                                           description='Day 1',
                                           initial_stock=100)
        self.item_d2 = Item.objects.create(name='Wristband 2',
                                           description='Day 2',
                                           initial_stock=100)
        self.item_transport = Item.objects.create(
            name='Public transport',
            description='Public transport ticket',
            initial_stock=100,
            is_receipt=True)
        self.prod_full = Product.objects.create(name='Full pass',
                                                price=100,
                                                tax_rate=19)
        self.prod_d1 = Product.objects.create(name='Day pass Day 1',
                                              price=35,
                                              tax_rate=19)
        self.prod_d2 = Product.objects.create(name='Day pass Day 2',
                                              price=35,
                                              tax_rate=19)
        self.prod_transport = Product.objects.create(name='Public transport',
                                                     price=16.7,
                                                     tax_rate=0)
        ProductItem.objects.create(product=self.prod_full,
                                   item=self.item_full,
                                   amount=1)
        ProductItem.objects.create(product=self.prod_d1,
                                   item=self.item_d1,
                                   amount=1)
        ProductItem.objects.create(product=self.prod_d2,
                                   item=self.item_d2,
                                   amount=1)
        ProductItem.objects.create(product=self.prod_transport,
                                   item=self.item_transport,
                                   amount=1)
        self.desk1 = Cashdesk.objects.create(name='Desk 1',
                                             ip_address='10.1.1.1')
        self.desk2 = Cashdesk.objects.create(name='Desk 2',
                                             ip_address='10.1.1.2')
Exemple #3
0
def event_settings():
    settings = EventSettings.get_solo()
    settings.invoice_address = 'Foo Conferences\n42 Bar St\nBaz City'
    settings.save()
    return settings
Exemple #4
0
    def _build_receipt(self, transaction: Transaction) -> Union[None, str]:
        from postix.core.models import EventSettings
        settings = EventSettings.get_solo()
        total_sum = 0
        position_lines = list()
        tax_sums = defaultdict(int)
        tax_symbols = dict()

        positions = transaction.positions.exclude(type='redeem',
                                                  value=Decimal('0.00'))

        if not positions.exists():
            return

        # Caution! This code assumes that cancellations are always made on transaction level
        cancellations = positions.filter(type='reverse')
        if cancellations.exists():
            cancels = cancellations.first().reverses.transaction
        else:
            cancels = None

        for position in transaction.positions.all():
            if position.value == 0:
                continue
            total_sum += position.value
            tax_sums[position.tax_rate] += position.tax_value
            if position.tax_rate not in tax_symbols:
                tax_symbols[position.tax_rate] = ascii_uppercase[len(tax_sums)
                                                                 - 1]

            formatargs = {
                'product_name': position.product.name,
                'tax_str': tax_symbols[position.tax_rate],
                'gap': ' ' * (29 - len(position.product.name)),
                'price': self._format_number(position.value),
                'upgrade': _('Upgrade'),
                'upgradegap': ' ' * (29 - len(_('Upgrade'))),
            }
            if position.has_constraint_bypass:
                position_lines.append(' {product_name}'.format(**formatargs))
                position_lines.append(
                    ' {upgrade} ({tax_str}){upgradegap} {price}'.format(
                        **formatargs))
            else:
                pos_str = ' {product_name} ({tax_str}){gap} {price}'.format(
                    **formatargs)
                position_lines.append(pos_str)
        total_taxes = sum(tax_sums.values())

        if not position_lines:
            return

        # Only get a new receipt ID after all early outs have passed
        # to make sure that we'll end up with actual consecutive numbers
        if not transaction.receipt_id:
            transaction.set_receipt_id(retry=3)
            is_copy = False
        else:
            is_copy = True

        receipt = bytearray([self.ESC, 0x61, 1]).decode()  # center text
        receipt += bytearray([self.ESC, 0x45, 1]).decode()  # emphasize
        receipt += settings.name + '\r\n\r\n'
        receipt += bytearray([self.ESC, 0x45, 0]).decode()  # de-emphasize
        if settings.receipt_address is not None:
            receipt += settings.receipt_address + '\r\n\r\n'

        if cancels:
            receipt += bytearray([self.ESC, 0x45, 1]).decode()  # emphasize
            receipt += _('Cancellation') + '\r\n'
            receipt += bytearray([self.ESC, 0x45, 0]).decode()  # de-emphasize
            receipt += _('for receipt {}').format(
                cancels.receipt_id) + '\r\n\r\n'

        if is_copy:
            receipt += bytearray([self.ESC, 0x45, 1]).decode()  # emphasize
            receipt += _('Receipt copy') + '\r\n\r\n'
            receipt += bytearray([self.ESC, 0x45, 0]).decode()  # de-emphasize

        receipt += SEPARATOR
        receipt += " {: <26}            EUR\r\n".format(_('Ticket'))
        receipt += SEPARATOR

        receipt += '\r\n'.join(position_lines)
        receipt += '\r\n'
        receipt += SEPARATOR
        receipt += bytearray(
            [self.ESC, 0x61,
             2]).decode()  # right-align text (0 would be left-align)
        receipt += _("Net sum:  {}").format(
            self._format_number(total_sum - total_taxes))
        receipt += '\r\n'

        for tax in sorted(list(tax_symbols))[::-1]:
            receipt += _(
                "Tax {tax_rate}% ({tax_identifier}):  {tax_amount}").format(
                    tax_rate=tax,
                    tax_identifier=tax_symbols[tax],
                    tax_amount=self._format_number(tax_sums[tax]),
                )
            receipt += '\r\n'

        receipt += _("Total:  {}").format(self._format_number(total_sum))
        receipt += '\r\n' * 3
        receipt += bytearray([self.ESC, 0x61, 1]).decode()  # center text
        receipt += settings.receipt_footer + '\r\n'
        tz = timezone.get_current_timezone()
        receipt += '{} {}\r\n'.format(
            transaction.datetime.astimezone(tz).strftime("%d.%m.%Y %H:%M"),
            transaction.session.cashdesk.name,
        )
        receipt += _('Receipt number: {}').format(transaction.receipt_id)
        receipt += '\r\n\r\n\r\n'
        return receipt
Exemple #5
0
    def _build_attendance(self, arrived: List['PreorderPosition'],
                          not_arrived: List['PreorderPosition']):
        def shorten(line):
            if len(line) <= 40:
                return line
            return line[:39] + '...'

        def build_lines(position):
            information_lines = [
                '  ' + l.strip() for l in position.information.split('\n')
                if l.strip()
            ]
            information_lines = [
                info.split('–', maxsplit=1)[-1].strip()
                for info in information_lines
            ]
            return information_lines

        from postix.core.models import EventSettings
        settings = EventSettings.get_solo()
        arrived_lines = [build_lines(position) for position in arrived]
        not_arrived_lines = [build_lines(position) for position in not_arrived]

        attendance = bytearray([self.ESC, 0x61, 1]).decode()  # center text
        attendance += bytearray([self.ESC, 0x45, 1]).decode()  # emphasize
        attendance += settings.name + ' ' + _('Attendance') + '\r\n'
        attendance += timezone.now().strftime("%Y-%m-%d %H:%M") + '\r\n'
        attendance += bytearray([self.ESC, 0x45, 0]).decode()  # de-emphasize

        if arrived_lines:
            arrived_lines = sum(arrived_lines, [])
            attendance += bytearray([self.ESC, 0x61,
                                     1]).decode()  # center text
            attendance += '\r\n'
            count = '{count} '.format(count=len(arrived))
            seplen = (40 - len(count + _('Arrived'))) // 2
            attendance += SEPARATOR_CHAR * seplen + ' ' + count + _(
                'Arrived') + ' ' + SEPARATOR_CHAR * seplen + '\r\n'
            attendance += bytearray(
                [self.ESC, 0x61,
                 0]).decode()  # right-align text (2 would be left-align)
            attendance += '\r\n'.join(arrived_lines)
            attendance += '\r\n'

        if not_arrived_lines:
            not_arrived_lines = sum(not_arrived_lines, [])
            attendance += bytearray([self.ESC, 0x61,
                                     1]).decode()  # center text
            attendance += '\r\n'
            count = '{count} '.format(count=len(not_arrived))
            seplen = (40 - len(count + _('Not arrived'))) // 2
            attendance += SEPARATOR_CHAR * seplen + ' ' + count + _(
                'Not arrived') + ' ' + SEPARATOR_CHAR * seplen + '\r\n'
            attendance += bytearray(
                [self.ESC, 0x61,
                 2]).decode()  # right-align text (0 would be left-align)
            attendance += '\r\n'.join(not_arrived_lines)
            attendance += '\r\n'

        attendance += bytearray([self.ESC, 0x61, 1]).decode()  # center text
        attendance += '\r\n'
        return attendance
Exemple #6
0
def generate_invoice(transaction: Transaction, address: str) -> str:
    path = transaction.get_invoice_path()
    if path:
        return path

    _buffer = BytesIO()
    settings = EventSettings.get_solo()
    doc = get_default_document(_buffer, footer=settings.invoice_footer)
    style = get_paragraph_style()

    # Header
    our_address = settings.invoice_address.replace('\n', '<br />')
    our_address = Paragraph(our_address, style['Normal'])
    our_title = Paragraph(_('Invoice from'), style['Heading5'])

    their_address = address.replace('\n', '<br />')
    their_address = Paragraph(their_address, style['Normal'])
    their_title = Paragraph(_('Invoice to'), style['Heading5'])

    data = [[their_title, '', our_title], [their_address, '', our_address]]
    header = Table(
        data=data,
        colWidths=[doc.width * 0.3, doc.width * 0.3, doc.width * 0.4],
        style=TableStyle([
            ('FONTSIZE', (0, 0), (2, 1), FONTSIZE),
            ('VALIGN', (0, 0), (2, 1), 'TOP'),
        ]),
    )
    date = Table(
        data=[[now().strftime('%Y-%m-%d')]],
        colWidths=[doc.width],
        style=TableStyle([
            ('ALIGN', (0, 0), (0, 0), 'RIGHT'),
        ]),
    )
    invoice_title = Paragraph(_('Invoice for receipt {}').format(transaction.receipt_id), style['Heading1'])

    data = [[_('Product'), _('Tax rate'), _('Net'), _('Gross')], ]
    total_tax = 0
    for position in transaction.positions.all():
        total_tax += position.tax_value
        data.append([
            position.product.name,
            '{} %'.format(position.tax_rate),
            CURRENCY.format(position.value - position.tax_value,),
            CURRENCY.format(position.value)
        ])
    data.append([_('Included taxes'), '', '', CURRENCY.format(total_tax)])
    data.append([_('Invoice total'), '', '', CURRENCY.format(transaction.value)])
    last_row = len(data) - 1

    transaction_table = Table(
        data=data,
        colWidths=[doc.width * 0.5] + [doc.width * 0.5 / 3] * 3,
        style=TableStyle([
            ('FONTSIZE', (0, 0), (3, last_row), FONTSIZE),
            # TODO: register bold font and use here: ('FACE', (0,0), (3,0), 'boldfontname'),
            ('ALIGN', (0, 0), (1, last_row), 'LEFT'),
            ('ALIGN', (2, 0), (3, last_row), 'RIGHT'),
            ('LINEABOVE', (0, 1), (3, 1), 1.0, colors.black),
            ('LINEABOVE', (3, last_row - 1), (3, last_row - 1), 1.0, colors.black),
            ('LINEABOVE', (3, last_row), (3, last_row), 1.2, colors.black),
        ]),
    )
    disclaimer_text = _('This invoice is only valid with receipt #{}.').format(transaction.receipt_id)
    disclaimer_text += _('The invoice total has already been paid.')
    disclaimer = Paragraph(disclaimer_text, style['Normal'])

    story = [
        header, Spacer(1, 15 * mm), date, invoice_title, Spacer(1, 25 * mm), transaction_table, Spacer(1, 25 * mm),
        disclaimer
    ]
    doc.build(story)
    _buffer.seek(0)
    stored_name = default_storage.save(transaction.get_invoice_path(allow_nonexistent=True), ContentFile(_buffer.read()))
    return stored_name