def before_create_object(self, data, view_kwargs): """ before create object method for OrderListPost Class :param data: :param view_kwargs: :return: """ validate_attendees(data['ticket_holders']) if data.get('cancel_note'): del data['cancel_note'] if data.get('payment_mode') != 'free' and not data.get('amount'): raise ConflictError( {'pointer': '/data/attributes/amount'}, "Amount cannot be null for a paid order", ) if not data.get('amount'): data['amount'] = 0 # Apply discount only if the user is not event admin if data.get('discount_code') and not has_access( 'is_coorganizer', event_id=data['event']): validate_discount_code( data['discount_code'], ticket_holders=data['ticket_holders'], event_id=data['event'], )
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_discount_code_id(db): discount, tickets = _create_discount_code(db) assert validate_discount_code(discount.id, tickets=tickets) == discount assert validate_discount_code(str(discount.id), tickets=tickets) == discount
def validate(self, tickets=None, ticket_holders=None, event_id=None): return validate_discount_code(self, tickets=tickets, ticket_holders=ticket_holders, event_id=event_id)