# -*- coding: utf-8 -*-
import json
from gsxws.core import GsxError
from datetime import datetime, timedelta
from django.http import QueryDict
from django.db.models import Q
from django.utils import timezone
from django.contrib import messages
from django.core.cache import cache
from django.http import HttpResponse
from django.db import DatabaseError
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import permission_required
from wkhtmltopdf.views import PDFTemplateView
from servo.lib.utils import paginate
from servo.models.order import *
from servo.forms.orders import *
from servo.forms.repairs import StatusForm
from servo.models import Note, User, Device, Customer
from servo.models.common import (Tag, FlaggedItem, GsxAccount,)
from servo.models.repair import (Checklist,
ChecklistItem,
Repair,
ChecklistItemValue,)
def prepare_list_view(request, args):
"""
Lists service orders matching specified criteria
"""
data = {'title': _("Orders")}
form = OrderSearchForm(request, args)
if request.session.get("current_queue"):
del(request.session['current_queue'])
if request.session.get("return_to"):
del(request.session['return_to'])
if request.user.customer:
orders = Order.objects.filter(customer=request.user.customer)
else:
orders = Order.objects.filter(location__in=request.user.locations.all())
if args.get("state"):
orders = orders.filter(state__in=args.getlist("state"))
start_date = args.get("start_date")
if start_date:
end_date = args.get('end_date') or timezone.now()
orders = orders.filter(created_at__range=[start_date, end_date])
if args.get("status_older_than"):
days = int(args.get("status_older_than"))
limit = datetime.now() - timedelta(days=days)
orders = orders.filter(status_started_at__lt=limit)
if args.get("assigned_to"):
users = args.getlist("assigned_to")
orders = orders.filter(user__in=users)
if args.get("followed_by"):
users = args.getlist("followed_by")
orders = orders.filter(followed_by__in=users)
if args.get("created_by"):
users = args.getlist("created_by")
orders = orders.filter(created_by__in=users)
if args.get("customer"):
customer = int(args['customer'][0])
if customer == 0:
orders = orders.filter(customer__pk=None)
else:
orders = orders.filter(customer__tree_id=customer)
if args.get("spec"):
spec = args['spec'][0]
if spec == "None":
orders = orders.filter(devices=None)
else:
orders = orders.filter(devices__slug=spec)
if args.get("device"):
orders = orders.filter(devices__pk=args['device'])
if args.get("queue"):
queue = args.getlist("queue")
orders = orders.filter(queue__in=queue)
if args.get("checkin_location"):
ci_location = args.getlist("checkin_location")
orders = orders.filter(checkin_location__in=ci_location)
if args.get("location"):
location = args.getlist("location")
orders = orders.filter(location__in=location)
if args.get("label"):
orders = orders.filter(tags__in=args.getlist("label"))
if args.get("status"):
status = args.getlist("status")
if args['status'][0] == 'None':
orders = orders.filter(status__pk=None)
else:
orders = orders.filter(status__status__in=status)
if args.get("color"):
color = args.getlist("color")
now = timezone.now()
if "grey" in color:
orders = orders.filter(status=None)
if "green" in color:
orders = orders.filter(status_limit_green__gte=now)
if "yellow" in color:
orders = orders.filter(status_limit_yellow__gte=now,
status_limit_green__lte=now)
if "red" in color:
orders = orders.filter(status_limit_yellow__lte=now)
page = request.GET.get("page")
data['form'] = form
data['queryset'] = orders
data['orders'] = paginate(orders.distinct(), page, 100)
data['subtitle'] = _("%d search results") % orders.count()
return data
def prepare_detail_view(request, pk):
"""
Prepares the view for whenever we're dealing with a specific order
"""
order = get_object_or_404(Order, pk=pk)
request.session['current_order_id'] = None
request.session['current_order_code'] = None
request.session['current_order_customer'] = None
title = _(u'Order %s') % order.code
priorities = Queue.PRIORITIES
followers = order.followed_by.all()
locations = Location.objects.filter(enabled=True)
queues = request.user.queues.all()
users = order.get_available_users(request.user)
# wrap the customer in a list for easier recursetree
if order.customer is not None:
customer = order.customer.get_ancestors(include_self=True)
title = u'%s | %s' % (title, order.customer.name)
else:
customer = []
statuses = []
checklists = []
if order.queue is not None:
checklists = Checklist.objects.filter(queues=order.queue)
statuses = order.queue.queuestatus_set.all()
if order.is_editable:
request.session['current_order_id'] = order.pk
request.session['current_order_code'] = order.code
request.session['return_to'] = order.get_absolute_url()
if order.customer:
request.session['current_order_customer'] = order.customer.pk
return locals()
@permission_required("servo.change_order")
def close(request, pk):
"""Close this Service Order."""
order = get_object_or_404(Order, pk=pk)
if request.method == 'POST':
try:
order.close(request.user)
except Exception as e:
messages.error(request, e)
return redirect(order)
if request.session.get("current_order_id"):
del(request.session['current_order_id'])
del(request.session['current_order_code'])
del(request.session['current_order_customer'])
messages.success(request, _('Order %s closed') % order.code)
return redirect(order)
data = {'order': order, 'action': reverse(close, args=[pk])}
return render(request, "orders/close.html", data)
@permission_required("servo.delete_order")
def reopen_order(request, pk):
"""Open a closed order."""
order = get_object_or_404(Order, pk=pk)
msg = order.reopen(request.user)
messages.success(request, msg)
return redirect(order)
@permission_required("servo.add_order")
def create(request, sn=None, device_id=None,
product_id=None,
note_id=None,
customer_id=None):
"""Create a new Service Order."""
order = Order(created_by=request.user)
if customer_id is not None:
order.customer_id = customer_id
try:
order.save()
except Exception as e:
messages.error(request, e)
return redirect(list_orders)
messages.success(request, _("Order %s created") % order.code)
# create service order from a new device
if sn is not None:
order.add_device_sn(sn, request.user)
if device_id is not None:
device = Device.objects.get(pk=device_id)
order.add_device(device, request.user)
# creating an order from a product
if product_id is not None:
return redirect(add_product, order.pk, product_id)
# creating an order from a note
if note_id is not None:
note = Note.objects.get(pk=note_id)
note.order = order
note.save()
# try to match a customer
if note.sender:
try:
customer = Customer.objects.get(email=note.sender)
order.customer = customer
order.save()
except Customer.DoesNotExist:
pass
return redirect(order)
def list_orders(request):
"""
orders/index
"""
args = request.GET.copy()
default = {'state': Order.STATE_QUEUED}
if len(args) < 2: # search form not submitted
f = request.session.get("order_search_filter", default)
args = QueryDict('', mutable=True)
args.update(f)
request.session['order_search_filter'] = args
data = prepare_list_view(request, args)
return render(request, "orders/index.html", data)
@permission_required("servo.change_order")
def toggle_tag(request, order_id, tag_id):
tag = get_object_or_404(Tag, pk=tag_id)
order = get_object_or_404(Order, pk=order_id)
if tag not in order.tags.all():
order.add_tag(tag)
else:
order.tags.remove(tag)
return HttpResponse(tag.title)
@permission_required("servo.change_order")
def toggle_task(request, order_id, item_id):
"""Toggle a given Check List item in this order."""
checklist_item = get_object_or_404(ChecklistItem, pk=item_id)
try:
item = ChecklistItemValue.objects.get(order_id=order_id,
item=checklist_item)
item.delete()
except ChecklistItemValue.DoesNotExist:
item = ChecklistItemValue()
item.item = checklist_item
item.order_id = order_id
item.checked_by = request.user
item.save()
return HttpResponse(checklist_item.title)
def repair(request, order_id, repair_id):
"""Show the corresponding GSX Repair for this Service Order."""
repair = get_object_or_404(Repair, pk=repair_id)
data = prepare_detail_view(request, order_id)
data['repair'] = repair
try:
repair.connect_gsx(request.user)
details = repair.get_details()
try:
data['notes'] = details.notes.encode('utf-8')
except AttributeError:
pass
data['status'] = repair.update_status(request.user)
except Exception as e:
messages.error(request, e)
data['parts'] = repair.servicepart_set.all()
if request.method == 'POST':
data['status_form'] = StatusForm(request.POST)
if data['status_form'].is_valid():
code = data['status_form'].cleaned_data['status']
try:
repair.set_status_code(code)
new_status = repair.get_status_code_display()
msg = _('Repair status updated to %s') % new_status
messages.success(request, msg)
except Exception as e:
messages.error(request, e)
else:
data['status_form'] = StatusForm(initial={'status': repair.status_code})
return render(request, "orders/repair.html", data)
@permission_required("servo.change_order")
def complete_repair(request, order_id, repair_id):
"""Mark this repair as complete in GSX."""
repair = get_object_or_404(Repair, pk=repair_id)
if request.method == 'POST':
try:
repair.close(request.user)
msg = _(u"Repair %s marked complete.") % repair.confirmation
messages.success(request, msg)
except GsxError as e:
messages.error(request, e)
return redirect(repair.order)
return render(request, 'orders/close_repair.html', locals())
@csrf_exempt
@permission_required("servo.change_order")
def accessories(request, pk, device_id):
from django.utils import safestring
if request.POST.get('name'):
a = Accessory(name=request.POST['name'])
a.order_id = pk
a.device_id = device_id
a.save()
choice_list = []
choices = Accessory.objects.distinct('name')
for c in choices:
choice_list.append(c.name)
action = reverse('orders-accessories', args=[pk, device_id])
selected = Accessory.objects.filter(order_id=pk, device_id=device_id)
choices_json = safestring.mark_safe(json.dumps(choice_list))
return render(request, 'devices/accessories_edit.html', locals())
@permission_required('servo.change_order')
def delete_accessory(request, order_id, device_id, pk):
Accessory.objects.filter(pk=pk).delete()
return accessories(request, order_id, device_id)
@permission_required("servo.change_order")
def edit(request, pk):
data = prepare_detail_view(request, pk)
data['note_tags'] = Tag.objects.filter(type='note')
return render(request, "orders/edit.html", data)
@permission_required('servo.delete_order')
def delete(request, pk):
order = get_object_or_404(Order, pk=pk)
if request.method == "POST":
return_to = order.get_queue_url()
try:
order.delete()
del(request.session['current_order_id'])
del(request.session['current_order_code'])
del(request.session['current_order_customer'])
messages.success(request, _(u'Order %s deleted') % order.code)
return redirect(return_to)
except Exception as e:
ed = {'order': order.code, 'error': e}
msg = _(u'Cannot delete order %(order)s: %(error)s') % ed
messages.error(request, msg)
return redirect(order)
action = request.path
return render(request, "orders/delete_order.html", locals())
@permission_required('servo.change_order')
def toggle_follow(request, order_id):
order = get_object_or_404(Order, pk=order_id)
data = {'icon': "open", 'action': _("Follow")}
if request.user in order.followed_by.all():
order.followed_by.remove(request.user)
else:
order.followed_by.add(request.user)
data = {'icon': "close", 'action': _("Unfollow")}
if request.is_ajax():
return render(request, "orders/toggle_follow.html", data)
return redirect(order)
def toggle_flagged(request, pk):
order = get_object_or_404(Order, pk=pk)
t = FlaggedItem(content_object=order, flagged_by=request.user)
t.save()
@permission_required("servo.change_order")
def remove_user(request, pk, user_id):
"""
Removes this user from the follower list, unsets assignee
"""
order = get_object_or_404(Order, pk=pk)
user = get_object_or_404(User, pk=user_id)
try:
order.remove_follower(user)
if user == order.user:
order.set_user(None, request.user)
msg = _('User %s removed from followers') % user
order.notify("unset_user", msg, request.user)
except Exception as e:
messages.error(request, e)
return redirect(order)
@permission_required("servo.change_order")
def update_order(request, pk, what, what_id):
"""
Updates some things about an order
"""
order = get_object_or_404(Order, pk=pk)
what_id = int(what_id)
if order.state is Order.STATE_CLOSED:
messages.error(request, _("Closed orders cannot be modified"))
return redirect(order)
if what == "user":
if request.method == "POST":
fullname = request.POST.get("user")
try:
user = User.active.get(full_name=fullname)
if order.user is None:
order.set_user(user, request.user)
else:
order.add_follower(user)
order.save()
except User.DoesNotExist:
messages.error(request, _(u"User %s not found") % fullname)
elif what_id > 0:
user = User.objects.get(pk=what_id)
order.set_user(user, request.user)
if what == "queue":
order.set_queue(what_id, request.user)
if what == "status":
order.set_status(what_id, request.user)
if what == "priority":
order.priority = what_id
order.save()
if what == "place" and request.method == "POST":
place = request.POST.get("place")
order.notify("set_place", place, request.user)
order.place = place
order.save()
if what == "label" and request.method == "POST":
label = request.POST.get("label")
try:
tag = Tag.objects.get(title=label, type="order")
order.add_tag(tag, request.user)
except Tag.DoesNotExist:
messages.error(request, _(u"Label %s does not exist") % label)
if what == "checkin":
location = Location.objects.get(pk=what_id)
order.checkin_location = location
messages.success(request, _('Order updated'))
order.save()
if what == "checkout":
location = Location.objects.get(pk=what_id)
order.checkout_location = location
messages.success(request, _('Order updated'))
order.save()
if what == "location":
location = Location.objects.get(pk=what_id)
msg = order.set_location(location, request.user)
messages.success(request, msg)
request.session['current_order_id'] = order.pk
request.session['current_order_code'] = order.code
if order.queue:
request.session['current_order_queue'] = order.queue.pk
return redirect(order)
def put_on_paper(request, pk, kind="confirmation", fmt='html'):
"""
'Print' was taken?
"""
order = get_object_or_404(Order, pk=pk)
data = order.get_print_dict(kind)
template = order.get_print_template(kind)
if fmt == 'pdf':
fn = data.get('title') + '.pdf'
view = PDFTemplateView(request=request, template_name=template, filename=fn)
return view.render_to_response(data)
return render(request, template, data)
@permission_required("servo.change_order")
def add_device(request, pk, device_id=None, sn=None):
"""
Add a device to a service order.
Use device_id with existing devices or
sn for new devices (which should have gone through GSX search)
"""
order = get_object_or_404(Order, pk=pk)
if device_id is not None:
device = Device.objects.get(pk=device_id)
if sn is not None:
sn = sn.upper()
# not using get() since SNs are not unique
device = Device.objects.filter(sn=sn).first()
if device is None:
try:
device = Device.from_gsx(sn)
device.save()
except Exception as e:
messages.error(request, e)
return redirect(order)
try:
event = order.add_device(device, request.user)
messages.success(request, event)
except Exception as e:
messages.error(request, e)
return redirect(order)
if order.customer:
order.customer.devices.add(device)
return redirect(order)
@permission_required("servo.change_order")
def remove_device(request, order_id, device_id):
action = request.path
order = Order.objects.get(pk=order_id)
device = Device.objects.get(pk=device_id)
if request.method == "POST":
msg = order.remove_device(device, request.user)
messages.info(request, msg)
return redirect(order)
return render(request, "orders/remove_device.html", locals())
def events(request, order_id):
data = prepare_detail_view(request, order_id)
return render(request, "orders/events.html", data)
def device_from_product(request, pk, item_id):
"""
Turns a SOI into a device and attaches it to this order
"""
order = get_object_or_404(Order, pk=pk)
soi = ServiceOrderItem.objects.get(pk=item_id)
try:
GsxAccount.default(request.user, order.queue)
device = Device.from_gsx(soi.sn, user=request.user)
device.save()
event = order.add_device(device, request.user)
messages.success(request, event)
except Exception as e:
messages.error(request, e)
return redirect(order)
@permission_required('servo.change_order')
def reserve_products(request, pk):
order = get_object_or_404(Order, pk=pk)
if request.method == 'POST':
for p in order.products.all():
p.reserve_product()
msg = _(u"Products of order %s reserved") % order.code
order.notify("products_reserved", msg, request.user)
messages.info(request, msg)
return redirect(order)
return render(request, "orders/reserve_products.html", locals())
@permission_required("servo.change_order")
def edit_product(request, pk, item_id):
"""Edit a product added to an order."""
order = Order.objects.get(pk=pk)
item = get_object_or_404(ServiceOrderItem, pk=item_id)
if not item.kbb_sn and item.product.part_type == "REPLACEMENT":
try:
device = order.devices.all()[0]
item.kbb_sn = device.sn
except IndexError:
pass # Probably no device in the order
if item.product.component_code:
try:
GsxAccount.default(request.user, order.queue)
except Exception as e:
return render(request, "snippets/error_modal.html", {'error': e})
form = OrderItemForm(instance=item)
if request.method == "POST":
form = OrderItemForm(request.POST, instance=item)
if form.is_valid():
try:
item = form.save()
# Set whoever set the KBB sn as the one who replaced the part
if item.kbb_sn and not item.replaced_by:
item.replaced_by = request.user
item.save()
messages.success(request, _(u"Product %s saved") % item.code)
return redirect(order)
except Exception as e:
messages.error(request, e)
product = item.product
title = product.code
prices = json.dumps(item.product.get_price())
return render(request, "orders/edit_product.html", locals())
@permission_required("servo.change_order")
def add_product(request, pk, product_id):
"Adds this product to this Sales Order"
order = Order.objects.get(pk=pk)
product = Product.objects.get(pk=product_id)
order.add_product(product, 1, request.user)
messages.success(request, _(u'Product %s added') % product.code)
return redirect(order)
@permission_required("servo.change_order")
def add_part(request, pk, device, code):
"""
Adds a part for this device to this order
"""
gsx_product = cache.get(code)
order = Order.objects.get(pk=pk)
device = Device.objects.get(pk=device)
try:
product = Product.objects.get(code=code)
if not product.fixed_price:
product.update_price(gsx_product)
except Product.DoesNotExist:
product = gsx_product
product.save()
try:
tag, created = TaggedItem.objects.get_or_create(
content_type__model="product",
object_id=product.pk,
tag=device.description
)
tag.save()
except DatabaseError:
pass
order.add_product(product, 1, request.user)
return render(request, "orders/list_products.html", locals())
def choose_product(request, order_id):
pass
@permission_required("servo.change_order")
def report_product(request, pk, item_id):
product = ServiceOrderItem.objects.get(pk=item_id)
product.should_report = not product.should_report
product.save()
if product.should_report:
return HttpResponse('')
return HttpResponse('')
@permission_required("servo.change_order")
def report_device(request, pk, device_id):
device = OrderDevice.objects.get(pk=device_id)
device.should_report = not device.should_report
device.save()
if device.should_report:
return HttpResponse('')
return HttpResponse('')
@permission_required('servo.change_order')
def remove_product(request, pk, item_id):
order = get_object_or_404(Order, pk=pk)
# The following is to help those who hit Back after removing a product
try:
item = ServiceOrderItem.objects.get(pk=item_id)
except ServiceOrderItem.DoesNotExist:
messages.error(request, _("Order item does not exist"))
return redirect(order)
if request.method == 'POST':
msg = order.remove_product(item, request.user)
messages.info(request, msg)
return redirect(order)
return render(request, 'orders/remove_product.html', locals())
@permission_required('servo.change_order')
def products(request, pk, item_id=None, action='list'):
order = Order.objects.get(pk=pk)
if action == 'list':
return render(request, 'orders/products.html', {'order': order})
@permission_required('servo.change_order')
def list_products(request, pk):
order = get_object_or_404(Order, pk=pk)
return render(request, "orders/list_products.html", locals())
def parts(request, order_id, device_id, queue_id):
"""
Selects parts for this device in this order
"""
order = Order.objects.get(pk=order_id)
device = Device.objects.get(pk=device_id)
title = device.description
url = reverse('devices-parts', args=[device_id, order_id, queue_id])
if order.queue is not None:
request.session['current_queue'] = order.queue.pk
return render(request, "orders/parts.html", locals())
@permission_required("servo.change_order")
def select_customer(request, pk, customer_id):
"""
Selects a specific customer for this order
"""
order = get_object_or_404(Order, pk=pk)
order.customer_id = customer_id
order.save()
return redirect(order)
@permission_required("servo.change_order")
def choose_customer(request, pk):
"""
Lets the user search for a customer for this order
"""
if request.method == "POST":
customers = Customer.objects.none()
kind = request.POST.get('kind')
query = request.POST.get('name')
if len(query) > 2:
customers = Customer.objects.filter(
Q(fullname__icontains=query) | Q(email__icontains=query) | Q(phone__contains=query)
)
if kind == 'companies':
customers = customers.filter(is_company=True)
if kind == 'contacts':
customers = customers.filter(is_company=False)
data = {'customers': customers, 'order_id': pk}
return render(request, "customers/choose-list.html", data)
data = {'action': request.path}
return render(request, 'customers/choose.html', data)
@permission_required("servo.change_order")
def remove_customer(request, pk, customer_id):
if request.method == "POST":
order = Order.objects.get(pk=pk)
customer = Customer.objects.get(pk=customer_id)
order.customer = None
order.save()
msg = _(u"Customer %s removed") % customer.name
order.notify("customer_removed", msg, request.user)
messages.success(request, msg)
return redirect(order)
data = {'action': request.path}
return render(request, "orders/remove_customer.html", data)
@permission_required("servo.add_order")
def copy_order(request, pk):
order = get_object_or_404(Order, pk=pk)
new_order = order.duplicate(request.user)
return redirect(new_order)
def history(request, pk, device):
device = get_object_or_404(Device, pk=device)
orders = device.order_set.exclude(pk=pk)
return render(request, "orders/history.html", locals())
@permission_required("servo.batch_process")
def batch_process(request):
form = BatchProcessForm()
title = _('Batch Processing')
if request.method == 'POST':
form = BatchProcessForm(request.POST)
if form.is_valid():
from servo.tasks import batch_process
batch_process.delay(request.user, form.cleaned_data)
messages.success(request, _('Request accepted for batch processing'))
return render(request, "orders/batch_process.html", locals())
def download_results(request):
import csv
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="orders.csv"'
writer = csv.writer(response)
header = [
'CODE',
'CUSTOMER',
'CREATED_AT',
'ASSIGNED_TO',
'CHECKED_IN',
'LOCATION'
]
writer.writerow(header)
for o in request.session['order_queryset']:
row = [o.code, o.customer, o.created_at,
o.user, o.checkin_location, o.location]
coded = [unicode(s).encode('utf-8') for s in row]
writer.writerow(coded)
return response