Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New Create Order API #7047

Merged
merged 4 commits into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions app/api/attendees.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,7 @@ def before_post(self, args, kwargs, data):
"Ticket belongs to a different Event",
)
# Check if the ticket is already sold out or not.
if get_sold_and_reserved_tickets_count(ticket.id) >= ticket.quantity:
raise ConflictError(
{'pointer': '/data/attributes/ticket_id'}, "Ticket already sold out"
)
ticket.raise_if_unavailable()

if 'device_name_checkin' in data and data['device_name_checkin'] is not None:
if 'is_checked_in' not in data or not data['is_checked_in']:
Expand Down
125 changes: 41 additions & 84 deletions app/api/custom/orders.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import json

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'json' imported but unused

from datetime import datetime

import pytz
from flask import Blueprint, jsonify, make_response, request
from flask import Blueprint, app, jsonify, make_response, request

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'flask.app' imported but unused

from flask_jwt_extended import current_user, jwt_required
from sqlalchemy.orm.exc import NoResultFound

from app.api.auth import return_file
from app.api.custom.schema.order_amount import OrderAmountInputSchema
from app.api.helpers.db import get_count, safe_query
from app.api.helpers.errors import ForbiddenError, NotFoundError, UnprocessableEntityError
from app.api.helpers.files import make_frontend_url
Expand All @@ -18,7 +20,7 @@
from app.api.helpers.order import calculate_order_amount, create_pdf_tickets_for_holder
from app.api.helpers.permission_manager import has_access
from app.api.helpers.storage import UPLOAD_PATHS, generate_hash
from app.api.custom.schema.order_amount import OrderAmountInputSchema
from app.api.orders import validate_attendees
from app.api.schema.attendees import AttendeeSchema
from app.api.schema.orders import OrderSchema
from app.extensions.limiter import limiter
Expand Down Expand Up @@ -117,100 +119,55 @@ def resend_emails():
@order_blueprint.route('/calculate-amount', methods=['POST'])
def calculate_amount():
data, errors = OrderAmountInputSchema().load(request.get_json())
if errors:
return make_response(jsonify(errors), 422)
return jsonify(calculate_order_amount(data['tickets'], data.get('discount_code')))


@order_blueprint.route('/create-order', methods=['POST'])
@jwt_required
def create_order():
data = request.get_json()
tickets, discount_code = data['tickets'], data['discount-code']
attendee = data['attendee']
for attribute in attendee:
attendee[attribute.replace('-', '_')] = attendee.pop(attribute)
schema = AttendeeSchema()
json_api_attendee = {"data": {"attributes": data['attendee'], "type": "attendee"}}
result = schema.load(json_api_attendee)
if result.errors:
return make_response(jsonify(result.errors), 422)
ticket_ids = {int(ticket['id']) for ticket in tickets}
quantity = {int(ticket['id']): ticket['quantity'] for ticket in tickets}
ticket_list = (
db.session.query(Ticket)
.filter(Ticket.id.in_(ticket_ids))
.filter_by(event_id=data['event_id'], deleted_at=None)
.all()
data, errors = OrderAmountInputSchema().load(request.get_json())
if errors:
return make_response(jsonify(errors), 422)

tickets_dict = data['tickets']
order_amount = calculate_order_amount(tickets_dict, data.get('discount_code'))
ticket_ids = {ticket['id'] for ticket in tickets_dict}
ticket_map = {int(ticket['id']): ticket for ticket in tickets_dict}
tickets = (
Ticket.query.filter_by(deleted_at=None).filter(Ticket.id.in_(ticket_ids)).all()
)
ticket_ids_found = {ticket_information.id for ticket_information in ticket_list}
tickets_not_found = ticket_ids - ticket_ids_found
if tickets_not_found:
return make_response(
jsonify(
status='Order Unsuccessful',
error='Tickets with id {} were not found in Event {}.'.format(
tickets_not_found, data['event_id']
),
),
404,

if not tickets:
raise UnprocessableEntityError(
{'source': 'tickets'}, "Tickets missing in Order request",
)
for ticket_info in ticket_list:
if (
ticket_info.quantity
- get_count(
db.session.query(TicketHolder.id).filter_by(
ticket_id=int(ticket_info.id), deleted_at=None
)
)
) < quantity[ticket_info.id]:
return make_response(
jsonify(status='Order Unsuccessful', error='Ticket already sold out.'),
409,
)
attendee_list = []
for ticket in tickets:
for _ in range(ticket['quantity']):
attendee = TicketHolder(
**result[0], event_id=int(data['event_id']), ticket_id=int(ticket['id'])
)
db.session.add(attendee)
attendee_list.append(attendee)
# created_at not getting filled
ticket_pricing = calculate_order_amount(tickets, discount_code)
if not has_access('is_coorganizer', event_id=data['event_id']):
data['status'] = 'initializing'
# create on site attendees
# check if order already exists for this attendee.
# check for free tickets and verified user

event = tickets[0].event

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local variable 'event' is assigned to but never used


try:
attendees = []
for ticket in tickets:
for _ in range(ticket_map[ticket.id]['quantity']):
ticket.raise_if_unavailable()
attendees.append(TicketHolder(firstname='', lastname='', ticket=ticket))
db.session.commit()
except Exception as e:
db.session.rollback()
raise e

validate_attendees({attendee.id for attendee in attendees})
order = Order(
amount=ticket_pricing['total_amount'],
user_id=current_user.id,
event_id=int(data['event_id']),
status=data['status'],
amount=order_amount['total'],
event=event,
discount_code_id=data.get('discount_code'),
ticket_holders=attendees,
)
db.session.add(order)
db.session.commit()
db.session.refresh(order)
order_tickets = {}
for holder in attendee_list:
holder.order_id = order.id
db.session.add(holder)
if not order_tickets.get(holder.ticket_id):
order_tickets[holder.ticket_id] = 1
else:
order_tickets[holder.ticket_id] += 1
order.populate_and_save()

for ticket in order_tickets:
od = OrderTicket(
order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket]
)
db.session.add(od)

order.quantity = order.tickets_count
db.session.add(order)
db.session.commit()
db.session.refresh(order)
order_schema = OrderSchema()
return order_schema.dump(order)
return OrderSchema().dumps(order)


@order_blueprint.route('/complete-order/<order_id>', methods=['PATCH'])
Expand Down
2 changes: 1 addition & 1 deletion app/api/helpers/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def calculate_order_amount(tickets, discount_code=None):
from app.models.discount_code import DiscountCode
from app.api.helpers.ticketing import validate_tickets, validate_discount_code

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

Expand Down
188 changes: 92 additions & 96 deletions app/api/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,96 @@ def check_billing_info(data):
)


def on_order_completed(order):
# send e-mail and notifications if the order status is completed
if not (order.status == 'completed' or order.status == 'placed'):
return
# fetch tickets attachment
order_identifier = order.identifier

key = UPLOAD_PATHS['pdf']['tickets_all'].format(identifier=order_identifier)
ticket_path = (
'generated/tickets/{}/{}/'.format(key, generate_hash(key))
+ order_identifier
+ '.pdf'
)

key = UPLOAD_PATHS['pdf']['order'].format(identifier=order_identifier)
invoice_path = (
'generated/invoices/{}/{}/'.format(key, generate_hash(key))
+ order_identifier
+ '.pdf'
)

# send email and notifications.
send_email_to_attendees(
order=order,
purchaser_id=current_user.id,
attachments=[ticket_path, invoice_path],
)

send_notif_to_attendees(order, current_user.id)

if order.payment_mode in ['free', 'bank', 'cheque', 'onsite']:
order.completed_at = datetime.utcnow()

order_url = make_frontend_url(
path='/orders/{identifier}'.format(identifier=order.identifier)
)
for organizer in set(
order.event.organizers + order.event.coorganizers + [order.event.owner]
):
if not organizer:
continue
send_notif_ticket_purchase_organizer(
organizer,
order.invoice_number,
order_url,
order.event.name,
order.identifier,
)


def save_order(order):
order_tickets = {}
for holder in order.ticket_holders:
save_to_db(holder)
if not order_tickets.get(holder.ticket_id):
order_tickets[holder.ticket_id] = 1
else:
order_tickets[holder.ticket_id] += 1

order.user = current_user

# create pdf tickets.
create_pdf_tickets_for_holder(order)

for ticket in order_tickets:
od = OrderTicket(
order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket]
)
save_to_db(od)

order.quantity = order.tickets_count
save_to_db(order)

on_order_completed(order)


def validate_attendees(ticket_holders):
free_ticket_quantity = 0

for ticket_holder in validate_ticket_holders(ticket_holders):
if ticket_holder.ticket.type == 'free':
free_ticket_quantity += 1

if not current_user.is_verified and free_ticket_quantity == len(ticket_holders):
raise ForbiddenError(
{'pointer': '/data/relationships/user', 'code': 'unverified-user'},
"Unverified user cannot place free orders",
)


class OrdersListPost(ResourceList):
"""
OrderListPost class for OrderSchema
Expand Down Expand Up @@ -145,20 +235,7 @@ def before_create_object(self, data, view_kwargs):
:param view_kwargs:
:return:
"""

free_ticket_quantity = 0

for ticket_holder in validate_ticket_holders(data['ticket_holders']):
if ticket_holder.ticket.type == 'free':
free_ticket_quantity += 1

if not current_user.is_verified and free_ticket_quantity == len(
data['ticket_holders']
):
raise ForbiddenError(
{'pointer': '/data/relationships/user', 'code': 'unverified-user'},
"Unverified user cannot place free orders",
)
validate_attendees(data['ticket_holders'])

if data.get('cancel_note'):
del data['cancel_note']
Expand Down Expand Up @@ -189,88 +266,7 @@ def after_create_object(self, order, data, view_kwargs):
:param view_kwargs:
:return:
"""
order_tickets = {}
for holder in order.ticket_holders:
save_to_db(holder)
if not order_tickets.get(holder.ticket_id):
order_tickets[holder.ticket_id] = 1
else:
order_tickets[holder.ticket_id] += 1

order.user = current_user

# create pdf tickets.
create_pdf_tickets_for_holder(order)

for ticket in order_tickets:
od = OrderTicket(
order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket]
)
save_to_db(od)

order.quantity = order.tickets_count
save_to_db(order)
# if not has_access('is_coorganizer', event_id=data['event']):
# TicketingManager.calculate_update_amount(order)

# send e-mail and notifications if the order status is completed
if order.status == 'completed' or order.status == 'placed':
# fetch tickets attachment
order_identifier = order.identifier

key = UPLOAD_PATHS['pdf']['tickets_all'].format(identifier=order_identifier)
ticket_path = (
'generated/tickets/{}/{}/'.format(key, generate_hash(key))
+ order_identifier
+ '.pdf'
)

key = UPLOAD_PATHS['pdf']['order'].format(identifier=order_identifier)
invoice_path = (
'generated/invoices/{}/{}/'.format(key, generate_hash(key))
+ order_identifier
+ '.pdf'
)

# send email and notifications.
send_email_to_attendees(
order=order,
purchaser_id=current_user.id,
attachments=[ticket_path, invoice_path],
)

send_notif_to_attendees(order, current_user.id)

if order.payment_mode in ['free', 'bank', 'cheque', 'onsite']:
order.completed_at = datetime.utcnow()

order_url = make_frontend_url(
path='/orders/{identifier}'.format(identifier=order.identifier)
)
for organizer in order.event.organizers:
send_notif_ticket_purchase_organizer(
organizer,
order.invoice_number,
order_url,
order.event.name,
order.identifier,
)
for coorganizer in order.event.coorganizers:
send_notif_ticket_purchase_organizer(
coorganizer,
order.invoice_number,
order_url,
order.event.name,
order.identifier,
)
if order.event.owner:
send_notif_ticket_purchase_organizer(
order.event.owner,
order.invoice_number,
order_url,
order.event.name,
order.identifier,
)
save_order(order)

data['user_id'] = current_user.id

Expand Down
Loading