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

Add transferrable until #118

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
22 changes: 22 additions & 0 deletions alembic/versions/8d205370475_add_transferrable_until.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""add_transferrable_until

Revision ID: 8d205370475
Revises: 4246213b032b
Create Date: 2016-11-08 14:12:26.061615

"""

# revision identifiers, used by Alembic.
revision = '8d205370475'
down_revision = '4246213b032b'

from alembic import op
import sqlalchemy as sa


def upgrade():
op.add_column('item', sa.Column('transferrable_until', sa.DateTime(), nullable=True))


def downgrade():
op.drop_column('item', 'transferrable_until')
1 change: 1 addition & 0 deletions boxoffice/forms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-

from .order import *
from .assignee import *
12 changes: 12 additions & 0 deletions boxoffice/forms/assignee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-

from baseframe import __
import baseframe.forms as forms

__all__ = ['AssigneeForm']


class AssigneeForm(forms.Form):
email = forms.EmailField(__("Email"), validators=[forms.validators.DataRequired(), forms.validators.Length(max=80)])
fullname = forms.StringField(__("Full name"), validators=[forms.validators.DataRequired()])
phone = forms.StringField(__("Phone number"), validators=[forms.validators.Length(max=16)])
4 changes: 2 additions & 2 deletions boxoffice/mailclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ def send_line_item_cancellation_mail(line_item_id, subject="Ticket Cancellation"


@job('boxoffice')
def send_ticket_assignment_mail(line_item_id):
def send_ticket_assignment_mail(line_item_id, recipient_list, cc_list=[]):
"""
Sends a confirmation email once details are filled and ticket has been assigned.
"""
with app.test_request_context():
line_item = LineItem.query.get(line_item_id)
order = line_item.order
subject = order.item_collection.title + ": Here's your ticket"
msg = Message(subject=subject, recipients=[line_item.current_assignee.email], bcc=[order.buyer_email])
msg = Message(subject=subject, recipients=recipient_list, cc=cc_list)
html = email_transform(render_template('ticket_assignment_mail.html', order=order, org=order.organization, line_item=line_item, base_url=app.config['BASE_URL']))
msg.html = html
msg.body = html2text(html)
Expand Down
1 change: 1 addition & 0 deletions boxoffice/models/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Item(BaseScopedNameMixin, db.Model):
assignee_details = db.Column(JsonDict, default={}, nullable=False)

cancellable_until = db.Column(db.DateTime, nullable=True)
transferrable_until = db.Column(db.DateTime, nullable=True)

def current_price(self):
"""
Expand Down
4 changes: 4 additions & 0 deletions boxoffice/models/line_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ def fetch_all_details(cls, item_collection):
line_item_query = db.select([cls.id, Order.invoice_no, Item.title, cls.base_amount, cls.discounted_amount, cls.final_amount, DiscountPolicy.title, DiscountCoupon.code, Order.buyer_fullname, Order.buyer_email, Order.buyer_phone, Assignee.fullname, Assignee.email, Assignee.phone, Assignee.details, OrderSession.utm_campaign, OrderSession.utm_source, OrderSession.utm_medium, OrderSession.utm_term, OrderSession.utm_content, OrderSession.utm_id, OrderSession.gclid, OrderSession.referrer]).select_from(line_item_join).where(cls.status == LINE_ITEM_STATUS.CONFIRMED).where(Order.item_collection == item_collection).order_by('created_at')
return db.session.execute(line_item_query).fetchall()

def is_transferrable(self):
return self.is_confirmed and (datetime.datetime.now() < self.item.transferrable_until
if self.item.transferrable_until else True)


def get_availability(cls, item_ids):
"""Returns a dict -> {'item_id': ('item title', 'quantity_total', 'line_item_count')}"""
Expand Down
5 changes: 3 additions & 2 deletions boxoffice/static/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ a.boxoffice-button:focus {
-webkit-transition: 0.2s ease all;
}

.group-input:disabled {
.group-input:disabled,
.group-input.disabled {
position: relative;
background-color: transparent;
color: #CCC;
Expand Down Expand Up @@ -521,7 +522,7 @@ a.boxoffice-button:focus {
text-align: center;
}

.attendee-form-title {
.assignee-form-title {
font-size: 18px;
font-weight: bold;
padding: 15px 0;
Expand Down
55 changes: 32 additions & 23 deletions boxoffice/static/js/views/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,15 @@ window.Boxoffice.Order = {
data: {
order_id: data.order_id,
access_token: data.access_token,
eventName: data.item_collection_name,
line_items: data.line_items,
buyer_name: data.buyer_name,
buyer_email: data.buyer_email,
buyer_phone: data.buyer_phone
},
scrollTop: function(line_item_seq){
//Scroll to the corresponding line_item.
var domElem = order.ractive.nodes[ 'item-' + line_item_seq ];
$('html,body').animate({ scrollTop: $(domElem).offset().top }, '300');
var dom_elem = order.ractive.nodes[ 'item-' + line_item_seq ];
$('html,body').animate({ scrollTop: $(dom_elem).offset().top }, '300');
},
viewTicket: function(event, line_item, line_item_seq) {
event.original.preventDefault();
Expand Down Expand Up @@ -102,9 +101,9 @@ window.Boxoffice.Order = {
}
order.ractive.set(line_item + '.toAssign', true);
},
addAttendeeDetails: function(event, line_item, line_item_seq, line_item_id) {
addAssigneeDetails: function(event, line_item, line_item_seq, line_item_id) {

var validationConfig = [{
var validation_config = [{
name: 'fullname',
rules: 'required'
},
Expand All @@ -118,55 +117,57 @@ window.Boxoffice.Order = {
}
];

var attendeeForm = 'attendee-form-' + line_item_seq;
var assignee_form = 'assignee-form-' + line_item_seq;

var formValidator = new FormValidator(attendeeForm, validationConfig, function(errors, event) {
var form_validator = new FormValidator(assignee_form, validation_config, function(errors, event) {
event.preventDefault();
order.ractive.set(line_item + '.assignee.errormsg', '');
order.ractive.set(line_item + '.errorMsg', '');
if (errors.length > 0) {
order.ractive.set(line_item + '.assignee.errormsg.'+ errors[0].name, errors[0].message);
order.ractive.scrollTop(line_item_seq);
}
else {
order.ractive.set(line_item + '.assigningTicket', true);
order.ractive.sendAttendeeDetails(line_item, line_item_seq, line_item_id);
order.ractive.sendAssigneeDetails(line_item, line_item_seq, line_item_id);
}
});

formValidator.setMessage('required', 'Please fill out the %s field');
formValidator.setMessage('valid_email', 'Please enter a valid email');
form_validator.setMessage('required', 'Please fill out the %s field');
form_validator.setMessage('valid_email', 'Please enter a valid email');

formValidator.registerCallback('validate_phone', function(phone) {
form_validator.registerCallback('validate_phone', function(phone) {
//Remove all punctations (except +) and letters
phone = phone.replace(/[^0-9+]/g,'');
order.ractive.set(line_item + '.assignee.phone', phone);

var validPhone = /^\+[0-9]+$/;

if (phone.length > 16) {
formValidator.setMessage('validate_phone', 'Please enter a valid mobile number');
form_validator.setMessage('validate_phone', 'Please enter a valid mobile number');
return false;
}
else if (phone.match(validPhone)) {
//Indian number starting with '+91'
if (phone.indexOf('+91') === 0 && phone.length != 13) {
formValidator.setMessage('validate_phone', 'Please enter a valid Indian mobile number');
form_validator.setMessage('validate_phone', 'Please enter a valid Indian mobile number');
return false;
}
}
else {
formValidator.setMessage('validate_phone', "Please prefix your phone number with '+' and country code.");
form_validator.setMessage('validate_phone', "Please prefix your phone number with '+' and country code.");
return false;
}
});
},
sendAttendeeDetails: function(line_item, line_item_seq, line_item_id) {
var attendeeForm = 'attendee-details-' + line_item_seq;
var formElements = $('#'+ attendeeForm).serializeArray();
var attendeeDetails ={};
for (var formIndex=0; formIndex < formElements.length; formIndex++) {
if (formElements[formIndex].value) {
attendeeDetails[formElements[formIndex].name] = formElements[formIndex].value;
sendAssigneeDetails: function(line_item, line_item_seq, line_item_id) {
var assignee_form = 'assignee-details-' + line_item_seq;
var form_elements = $('#'+ assignee_form).serializeArray();
var assignee_details ={};

for (var form_index=0; form_index < form_elements.length; form_index++) {
if (form_elements[form_index].value) {
assignee_details[form_elements[form_index].name] = form_elements[form_index].value;
}
}

Expand All @@ -175,7 +176,7 @@ window.Boxoffice.Order = {
type: Boxoffice.Order.config.assign.method,
contentType: 'application/json',
data: JSON.stringify({
attendee: attendeeDetails,
assignee: assignee_details,
line_item_id: line_item_id
}),
timeout: 30000,
Expand All @@ -186,12 +187,20 @@ window.Boxoffice.Order = {
order.ractive.set(line_item + '.toAssign', false);
order.ractive.set(line_item + '.isTicketAssigned', true);
order.ractive.scrollTop(line_item_seq);
order.ractive.set(line_item + '.assignee', data.result.assignee);
},
error: function(response) {
var ajaxLoad = this;
ajaxLoad.retries -= 1;
if (response.readyState === 4) {
order.ractive.set(line_item + '.errorMsg', 'Server error');
var error_text;
if (response.status === 500) {
error_text = "Server Error";
}
else {
error_text = JSON.parse(response.responseText).error_description;
}
order.ractive.set(line_item + '.errorMsg', error_text);
order.ractive.set(line_item + '.assigningTicket', false);
} else if (response.readyState === 0) {
if (ajaxLoad.retries < 0) {
Expand Down
3 changes: 2 additions & 1 deletion boxoffice/static/sass/_layout.sass
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ a.boxoffice-button:focus
-moz-transition: 0.2s ease all
-webkit-transition: 0.2s ease all

.group-input:disabled
.group-input:disabled,
.group-input.disabled
position: relative
background-color: transparent
color: $color-form-disabled-label
Expand Down
2 changes: 1 addition & 1 deletion boxoffice/static/sass/_order.sass
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
margin: 0 0 5px
text-align: center

.attendee-form-title
.assignee-form-title
font-size: 18px
font-weight: bold
padding: 15px 0
Expand Down
26 changes: 15 additions & 11 deletions boxoffice/templates/order.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ <h1 class="header">{{order.item_collection.title}} Ticket Assignment</h1>
</div>

<div class="row" id="boxoffice-order">
<p class="text-center" id="notify-msg"><span id="error-description">Loading. </span>If this takes too long, please mail {{order.item_collection.organization.contact_email}} with your receipt number.</p>
<p class="text-center" id="notify-msg"><span id="error-description">Loading. If you are behind a firewall or using any script blocking extension, please ensure your browser can load static.hasgeek.co.in</span><br>For further queries, please write to us at {{order.item_collection.organization.contact_email}} with your receipt number.</p>
{%- raw %}
<script id='boxoffice-ticket-template' type='text/ractive'>
{{#line_items:line_item}}
Expand Down Expand Up @@ -53,7 +53,11 @@ <h1 class="header">{{order.item_collection.title}} Ticket Assignment</h1>
<form class="assignee-form" role="form" name="assignee-form">
{{#if assignee.id || isTicketAssigned}}
<div class="text-center continue-btn-wrapper">
<button class="boxoffice-button boxoffice-button-action" on-click="assign(event, event.keypath, true)">Transfer/Edit</button>
{{#is_transferrable}}
<button class="boxoffice-button boxoffice-button-action" on-click="assign(event, event.keypath, true)">Transfer/Edit</button>
{{else}}
<button class="boxoffice-button boxoffice-button-action" on-click="assign(event, event.keypath, true)">Edit</button>
{{/}}
</div>
{{else}}
<p class="assign-form-title">Assign this ticket</p>
Expand All @@ -76,24 +80,24 @@ <h1 class="header">{{order.item_collection.title}} Ticket Assignment</h1>
</div>
{{else}}
<div class="content-box clearfix" intro='fly:{"x":20,"y":"0"}'>
<h4 class="text-center attendee-form-title">Please fill the attendee details</h4>
<form class="attendee-form" role="form" name="attendee-form-{{seq}}" id="attendee-details-{{seq}}">
<h4 class="text-center assignee-form-title">Please fill the attendee details</h4>
<form class="assignee-form" role="form" name="assignee-form-{{seq}}" id="assignee-details-{{seq}}">
<div class="group">
<input class="group-input {{#filled || assignee.fullname}}filled{{/}}" type="text" name="fullname" value="{{assignee.fullname}}" on-blur="inputFieldEdit(event, event.keypath)">
<input class="group-input {{#filled || assignee.fullname}}filled{{/}} {{#if assignee.id && assignee.fullname && !is_transferrable}}disabled{{/if}}" type="text" name="fullname" value="{{assignee.fullname}}" {{#if assignee.id && assignee.fullname && !is_transferrable}}readonly="readonly"{{/if}} on-blur="inputFieldEdit(event, event.keypath)" twoway="false">
<span class="bar"></span>
<label class="group-label">Fullname</label>
{{#assignee.errormsg.fullname}}<p class="form-error-msg">{{ assignee.errormsg.fullname }}</p>{{/}}
</div>

<div class="group">
<input class="group-input {{#filled || assignee.email}}filled{{/}}" type="text" name="email" value="{{assignee.email}}" on-blur="inputFieldEdit(event, event.keypath)">
<input class="group-input {{#filled || assignee.email}}filled{{/}} {{#if assignee.id && assignee.email && !is_transferrable}}disabled{{/if}}" type="text" name="email" value="{{assignee.email}}" {{#if assignee.id && assignee.email && !is_transferrable}}readonly="readonly"{{/if}} on-blur="inputFieldEdit(event, event.keypath)" twoway="false">
<span class="bar"></span>
<label class="group-label">Email</label>
{{#assignee.errormsg.email}}<p class="form-error-msg">{{ assignee.errormsg.email }}</p>{{/}}
</div>

<div class="group">
<input class="group-input {{#filled || assignee.phone}}filled{{/}}" type="text" name="phone" value="{{assignee.phone}}" on-blur="inputFieldEdit(event, event.keypath)">
<input class="group-input {{#filled || assignee.phone}}filled{{/}}" type="text" name="phone" value="{{assignee.phone}}" on-blur="inputFieldEdit(event, event.keypath)" twoway="false">
<span class="bar"></span>
<label class="group-label">Phone</label>
{{#assignee.errormsg.phone}}<p class="form-error-msg">{{ assignee.errormsg.phone }}</p>{{/}}
Expand All @@ -102,14 +106,14 @@ <h4 class="text-center attendee-form-title">Please fill the attendee details</h4
{{#assignee_details: field}}
{{#if field_type === 'string'}}
<div class="group">
<input class="group-input {{#filled || assignee.details[field]}}filled{{/}}" type="text" name="{{field}}" value="{{assignee.details[field]}}" on-blur="inputFieldEdit(event, event.keypath)">
<input class="group-input {{#filled || assignee.details[field]}}filled{{/}}" type="text" name="{{field}}" value="{{assignee.details[field]}}" on-blur="inputFieldEdit(event, event.keypath)" twoway="false">
<span class="bar"></span>
<label class="group-label">{{label}}</label>
</div>
{{elseif field_type === 'select'}}
<div class="group-select">
<p class="field-title filled">{{label}}</p>
<select name="{{field}}" value="{{assignee.details[field]}}">
<select name="{{field}}" value="{{assignee.details[field]}}" twoway="false">
{{#options: option}}
<option value="{{options[option]}}">{{options[option]}}</option>
{{/options}}
Expand All @@ -123,13 +127,13 @@ <h4 class="text-center attendee-form-title">Please fill the attendee details</h4
{{elseif field_type === 'textbox'}}
<div class="group">
<label class="field-title">{{label}}</label>
<textarea class="form-control" name="{{field}}" value="{{assignee.details[field]}}"></textarea>
<textarea class="form-control" name="{{field}}" value="{{assignee.details[field]}}" twoway="false"></textarea>
</div>
{{/if}}
{{/}}
<div class="assign-btn-wrapper">
<button type="button" class="boxoffice-button boxoffice-button-info" on-click="viewTicket(event, event.keypath, seq)">Back</button>
<button type="submit" class="boxoffice-button boxoffice-button-action" on-click="addAttendeeDetails(event, event.keypath, seq, id)" {{#assigningTicket}}disabled{{/}}>
<button type="submit" class="boxoffice-button boxoffice-button-action" on-click="addAssigneeDetails(event, event.keypath, seq, id)" {{#assigningTicket}}disabled{{/}}>
{{#if assignee.id || isTicketAssigned}}Update{{else}}Assign ticket{{/if}}
{{#assigningTicket}}<i class="fa fa-spinner fa-spin" intro='fly:{"x":0,"y":"0"}'>{{/}}
</button>
Expand Down
1 change: 1 addition & 0 deletions boxoffice/views/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def jsonify_order(data):
'assignee': jsonify_assignee(line_item.current_assignee),
'is_confirmed': line_item.is_confirmed,
'is_cancelled': line_item.is_cancelled,
'is_transferrable': line_item.is_transferrable(),
'cancelled_at': date_format(line_item.cancelled_at) if line_item.cancelled_at else "",
})
return jsonify(order_id=order.id, access_token=order.access_token,
Expand Down
Loading