def test_reject_deleted_tickets(db):
    event = EventFactoryBasic()
    tickets = TicketSubFactory.create_batch(3, event=event)
    tickets.append(TicketSubFactory(deleted_at=datetime.now(), event=event))
    db.session.commit()

    with pytest.raises(ObjectNotFound, match=r'Tickets not found for IDs: .*'):
        validate_tickets([ticket.id for ticket in tickets])
def test_reject_tickets_of_different_events(db):
    tickets = TicketSubFactory.create_batch(3)
    db.session.commit()

    with pytest.raises(
            UnprocessableEntityError,
            match=r'All tickets must belong to same event. Found: .*',
    ):
        validate_tickets([ticket.id for ticket in tickets])
def calculate_order_amount(tickets, discount_code=None):
    from app.api.helpers.ticketing import validate_discount_code, validate_tickets
    from app.models.discount_code import DiscountCode

    ticket_ids = {ticket['id'] for ticket in tickets}
    ticket_map = {int(ticket['id']): ticket for ticket in tickets}
    fetched_tickets = validate_tickets(ticket_ids)

    if tickets and discount_code:
        discount_code = validate_discount_code(discount_code, tickets=tickets)

    event = tax = tax_included = fees = None
    total_amount = total_tax = total_discount = 0.0
    ticket_list = []
    for ticket in fetched_tickets:
        ticket_info = ticket_map[ticket.id]
        discount_amount = 0.0
        discount_data = None
        ticket_fee = 0.0

        quantity = ticket_info.get('quantity', 1)  # Default to single ticket
        if not event:
            event = ticket.event

            if event.deleted_at:
                raise ObjectNotFound({'pointer': 'tickets/event'},
                                     f'Event: {event.id} not found')

            fees = TicketFees.query.filter_by(
                currency=event.payment_currency).first()

        if not tax and event.tax:
            tax = event.tax
            tax_included = tax.is_tax_included_in_price

        if ticket.type == 'donation':
            price = ticket_info.get('price')
            if not price or price > ticket.max_price or price < ticket.min_price:
                raise UnprocessableEntityError(
                    {'pointer': 'tickets/price'},
                    f"Price for donation ticket should be present and within range "
                    f"{ticket.min_price} to {ticket.max_price}",
                )
        else:
            price = ticket.price if ticket.type != 'free' else 0.0

        if discount_code and ticket.type != 'free':
            code = (DiscountCode.query.with_parent(ticket).filter_by(
                id=discount_code.id).first())
            if code:
                if discount_code.id == code.id:
                    if code.type == 'amount':
                        discount_amount = min(code.value, price)
                        discount_percent = (discount_amount / price) * 100
                    else:
                        discount_amount = (price * code.value) / 100
                        discount_percent = code.value
                    discount_data = {
                        'code': discount_code.code,
                        'percent': round(discount_percent, 2),
                        'amount': round(discount_amount, 2),
                        'total': round(discount_amount * quantity, 2),
                    }

        total_discount += round(discount_amount * quantity, 2)
        if fees and not ticket.is_fee_absorbed:
            ticket_fee = fees.service_fee * (price * quantity) / 100
            if ticket_fee > fees.maximum_fee:
                ticket_fee = fees.maximum_fee
        sub_total = ticket_fee + (price - discount_amount) * quantity
        total_amount = total_amount + sub_total
        ticket_list.append({
            'id': ticket.id,
            'name': ticket.name,
            'price': price,
            'quantity': quantity,
            'discount': discount_data,
            'ticket_fee': round(ticket_fee, 2),
            'sub_total': round(sub_total, 2),
        })

    sub_total = total_amount
    tax_dict = None
    if tax:
        if tax_included:
            total_tax = total_amount - total_amount / (1 + tax.rate / 100)
        else:
            total_tax = total_amount * tax.rate / 100
            total_amount += total_tax
        tax_dict = dict(
            included=tax_included,
            amount=round(total_tax, 2),
            percent=tax.rate if tax else 0.0,
            name=tax.name,
        )

    return dict(
        tax=tax_dict,
        sub_total=round(sub_total, 2),
        total=round(total_amount, 2),
        discount=round(total_discount, 2),
        tickets=ticket_list,
    )
def test_validate_tickets(db):
    tickets = TicketSubFactory.create_batch(3, event=EventFactoryBasic())
    db.session.commit()

    assert validate_tickets([str(ticket.id) for ticket in tickets]) == tickets
def test_validate_empty_tickets():
    assert validate_tickets([]) == []