# -*- 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.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from django.views.decorators.cache import cache_page 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 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, Configuration, 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 is "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): """ Closes 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): 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"): """ 'Print' was taken? """ conf = Configuration.conf() order = get_object_or_404(Order, pk=pk) title = _(u"Service Order #%s") % order.code notes = order.note_set.filter(is_reported=True) template = order.get_print_template(kind) if kind == "receipt": try: invoice = order.invoice_set.latest() except Exception as e: pass return render(request, template, locals()) @permission_required("servo.change_order") def add_device(request, pk, device_id=None, sn=None): """ Adds a device to a service order using 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): """ Edits 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