Repository: fossasia/open-event-server

Issue:
Implement ticket page on specific event dashboard
- [x] On `event/event-ID/` implement a tab "Tickets" on second position.
- [x] Add ticket section with an overview of sales of ticket types as in sample below

![screenshot-www eventnook com 2016-07-31 19-11-04](https://cloud.githubusercontent.com/assets/1583873/17278046/e4c5c166-5752-11e6-9c5f-f9dafa9a0a85.png)



Discussion:

Resolving patch:
diff --git a/app/helpers/ticketing.py b/app/helpers/ticketing.py
--- a/app/helpers/ticketing.py
+++ b/app/helpers/ticketing.py
@@ -56,6 +56,24 @@ def get_orders_of_user(user_id=None, upcoming_events=True):
                 .filter(Order.status == 'completed')\
                 .filter(Event.end_time < datetime.now())
 
+    @staticmethod
+    def get_orders(event_id=None, status=None):
+        if event_id:
+            if status:
+                orders = Order.query.filter_by(event_id=event_id).filter_by(status=status).all()
+            else:
+                orders = Order.query.filter_by(event_id=event_id).all()
+        else:
+            if status:
+                orders = Order.query.filter_by(status=status).all()
+            else:
+                orders = Order.query.all()
+        return orders
+
+    @staticmethod
+    def get_orders_count(event_id, status='completed'):
+        return get_count(Order.query.filter_by(event_id=event_id).filter_by(status=status))
+
     @staticmethod
     def get_order_expiry():
         return 10
diff --git a/app/views/admin/admin.py b/app/views/admin/admin.py
--- a/app/views/admin/admin.py
+++ b/app/views/admin/admin.py
@@ -17,6 +17,7 @@
 from app.views.admin.models_views.speakers import SpeakersView
 from app.views.admin.home import MyHomeView
 from app.views.admin.models_views.sponsors import SponsorsView
+from app.views.admin.models_views.ticket_sales import TicketSalesView
 from app.views.public.event_detail import EventDetailView
 from app.views.public.explore import ExploreView
 from app.views.public.pages import BasicPagesView
@@ -60,6 +61,8 @@ def _add_views(self):
         self.admin.add_view(MyTicketsView(name='MyTicketsView', url='/mytickets', endpoint="my_tickets"))
         self.admin.add_view(MySessionView(name='MySessions', url='/events/mysessions', endpoint="my_sessions"))
         self.admin.add_view(EventsView(name='Events', url='/events', endpoint="events"))
+
+        # Event level
         self.admin.add_view(SpeakersView(name='Speakers', url='/events/<int:event_id>/speakers', endpoint="event_speakers"))
         self.admin.add_view(SponsorsView(name='Sponsors', url='/events/<int:event_id>/sponsors', endpoint="event_sponsors"))
         self.admin.add_view(SessionsView(name='Sessions', url='/events/<int:event_id>/sessions', endpoint="event_sessions"))
@@ -69,6 +72,8 @@ def _add_views(self):
         self.admin.add_view(RoleView(name='Role', url='/events/<int:event_id>/roles', endpoint="event_roles"))
         self.admin.add_view(ProfileView(name='Profile', url='/profile', endpoint="profile"))
         self.admin.add_view(InviteView(name='Invite', url='/events/<int:event_id>/invite', endpoint="event_invites"))
+        self.admin.add_view(TicketSalesView(name='Tickets', url='/events/<int:event_id>/tickets',
+                                            endpoint="event_ticket_sales"))
         self.admin.add_view(SettingsView(name='Settings', url='/settings', endpoint="settings"))
 
         # Super Admin pages
diff --git a/app/views/admin/models_views/ticket_sales.py b/app/views/admin/models_views/ticket_sales.py
new file mode 100644
--- /dev/null
+++ b/app/views/admin/models_views/ticket_sales.py
@@ -0,0 +1,77 @@
+import flask_login
+from flask_admin import BaseView, expose
+
+from app.helpers.cache import cache
+from app.helpers.data_getter import DataGetter
+from app.helpers.ticketing import TicketingManager
+from app.models.ticket import Ticket
+
+
+class TicketSalesView(BaseView):
+    @cache.memoize(50)
+    def get_ticket(self, ticket_id):
+        return Ticket.query.get(ticket_id)
+
+    @expose('/')
+    @flask_login.login_required
+    def display_ticket_stats(self, event_id):
+        event = DataGetter.get_event(event_id)
+        orders = TicketingManager.get_orders(event_id)
+
+        completed_count = 0
+        completed_amount = 0
+        tickets_count = 0
+
+        orders_summary = {
+            'completed': {
+                'class': 'success',
+                'tickets_count': 0,
+                'orders_count': 0,
+                'total_sales': 0
+            },
+            'pending': {
+                'class': 'warning',
+                'tickets_count': 0,
+                'orders_count': 0,
+                'total_sales': 0
+            },
+            'expired': {
+                'class': 'danger',
+                'tickets_count': 0,
+                'orders_count': 0,
+                'total_sales': 0
+            }
+        }
+
+        tickets_summary = {}
+
+        for ticket in event.tickets:
+            tickets_summary[str(ticket.id)] = {
+                'name': ticket.name,
+                'quantity': ticket.quantity,
+                'completed': {
+                    'tickets_count': 0,
+                    'sales': 0
+                },
+                'pending': {
+                    'tickets_count': 0,
+                    'sales': 0
+                },
+                'expired': {
+                    'class': 'danger',
+                    'tickets_count': 0,
+                    'sales': 0
+                }
+            }
+
+        for order in orders:
+            orders_summary[str(order.status)]['orders_count'] += 1
+            orders_summary[str(order.status)]['total_sales'] += order.amount
+            for order_ticket in order.tickets:
+                orders_summary[str(order.status)]['tickets_count'] += order_ticket.quantity
+                ticket = self.get_ticket(order_ticket.ticket_id)
+                tickets_summary[str(ticket.id)][str(order.status)]['tickets_count'] += order_ticket.quantity
+                tickets_summary[str(ticket.id)][str(order.status)]['sales'] += order_ticket.quantity * ticket.price
+
+        return self.render('/gentelella/admin/event/tickets/tickets.html', event=event, event_id=event_id,
+                           orders_summary=orders_summary, tickets_summary=tickets_summary)


Test patch:
diff --git a/tests/views/admin/test_tickets_stats.py b/tests/views/admin/test_tickets_stats.py
new file mode 100644
--- /dev/null
+++ b/tests/views/admin/test_tickets_stats.py
@@ -0,0 +1,33 @@
+import unittest
+from datetime import datetime
+
+from flask import url_for
+
+from app.helpers.data import save_to_db
+from app.helpers.ticketing import TicketingManager
+from app import current_app as app
+from tests.views.guest.test_ticketing import create_order
+from tests.views.view_test_case import OpenEventViewTestCase
+
+class TestsTicketsStats(OpenEventViewTestCase):
+
+    def test_tickets_view(self):
+        with app.test_request_context():
+            event, ticket, identifier = create_order(self)
+            order = TicketingManager.get_order_by_identifier(identifier)
+            order.user_id = self.super_admin.id
+            order.brand = "Visa"
+            order.completed_at = datetime.now()
+            order.status = "completed"
+            order.last4 = "1234"
+            order.exp_month = "12"
+            order.exp_year = "2050"
+            order.paid_via = "stripe"
+            order.payment_mode = "card"
+            save_to_db(order)
+            response = self.app.get(url_for('event_ticket_sales.display_ticket_stats', event_id=event.id),
+                                    follow_redirects=True)
+            self.assertTrue('Completed' in response.data, msg=response.data)
+
+if __name__ == '__main__':
+    unittest.main()