def discount_status(request, form): ''' Summarises the usage of a given discount. ''' discounts = form.cleaned_data["discount"] items = commerce.DiscountItem.objects.filter( Q(discount__in=discounts), ).select_related("cart", "product", "product__category") items = group_by_cart_status( items, ["discount"], ["discount", "discount__description"], ) headings = [ "Discount", "Paid", "Reserved", "Unreserved", "Refunded", ] data = [] for item in items: data.append([ item["discount__description"], item["total_paid"], item["total_reserved"], item["total_unreserved"], item["total_refunded"], ]) return ListReport("Usage by item", headings, data)
def sales_payment_summary(): ''' Summarises paid items and payments. ''' def value_or_zero(aggregate, key): return aggregate[key] or 0 def sum_amount(payment_set): a = payment_set.values("amount").aggregate(total=Sum("amount")) return value_or_zero(a, "total") headings = ["Category", "Total"] data = [] # Summarise all sales made (= income.) sales = commerce.LineItem.objects.filter( invoice__status=commerce.Invoice.STATUS_PAID, ).values( "price", "quantity").aggregate(total=Sum(F("price") * F("quantity"), output_field=CURRENCY()), ) sales = value_or_zero(sales, "total") all_payments = sum_amount(commerce.PaymentBase.objects.all()) # Manual payments # Credit notes generated (total) # Payments made by credit note # Claimed credit notes all_credit_notes = 0 - sum_amount(commerce.CreditNote.objects.all()) unclaimed_credit_notes = 0 - sum_amount(commerce.CreditNote.unclaimed()) claimed_credit_notes = sum_amount( commerce.CreditNoteApplication.objects.all()) refunded_credit_notes = 0 - sum_amount(commerce.CreditNote.refunded()) data.append(["Items on paid invoices", sales]) data.append(["All payments", all_payments]) data.append(["Sales - Payments ", sales - all_payments]) data.append(["All credit notes", all_credit_notes]) data.append(["Credit notes paid on invoices", claimed_credit_notes]) data.append(["Credit notes refunded", refunded_credit_notes]) data.append(["Unclaimed credit notes", unclaimed_credit_notes]) data.append([ "Credit notes - (claimed credit notes + unclaimed credit notes)", all_credit_notes - claimed_credit_notes - refunded_credit_notes - unclaimed_credit_notes ]) return ListReport("Sales and Payments Summary", headings, data)
def items_sold(): ''' Summarises the items sold and discounts granted for a given set of products, or products from categories. ''' data = None headings = None line_items = commerce.LineItem.objects.filter( invoice__status=commerce.Invoice.STATUS_PAID, ).select_related( "invoice") line_items = line_items.order_by( # sqlite requires an order_by for .values() to work "-price", "description", ).values( "price", "description", ).annotate(total_quantity=Sum("quantity"), ) print(line_items) headings = ["Description", "Quantity", "Price", "Total"] data = [] total_income = 0 for line in line_items: cost = line["total_quantity"] * line["price"] data.append([ line["description"], line["total_quantity"], line["price"], cost, ]) total_income += cost data.append([ "(TOTAL)", "--", "--", total_income, ]) return ListReport("Items sold", headings, data)
def paid_invoices_by_date(request, form): ''' Shows the number of paid invoices containing given products or categories per day. ''' products = form.cleaned_data["product"] categories = form.cleaned_data["category"] invoices = commerce.Invoice.objects.filter( (Q(lineitem__product__in=products) | Q(lineitem__product__category__in=categories)), status=commerce.Invoice.STATUS_PAID, ) # Invoices with payments will be paid at the time of their latest payment payments = commerce.PaymentBase.objects.all() payments = payments.filter(invoice__in=invoices, ) payments = payments.order_by("invoice") invoice_max_time = payments.values("invoice").annotate( max_time=Max("time")) # Zero-value invoices will have no payments, so they're paid at issue time zero_value_invoices = invoices.filter(value=0) times = itertools.chain( (line["max_time"] for line in invoice_max_time), (invoice.issue_time for invoice in zero_value_invoices), ) by_date = collections.defaultdict(int) for time in times: date = datetime.datetime(year=time.year, month=time.month, day=time.day) by_date[date] += 1 data = [(date, count) for date, count in sorted(by_date.items())] data = [(date.strftime("%Y-%m-%d"), count) for date, count in data] return ListReport( "Paid Invoices By Date", ["date", "count"], data, )
def product_status(request, form): ''' Summarises the inventory status of the given items, grouping by invoice status. ''' products = form.cleaned_data["product"] categories = form.cleaned_data["category"] items = commerce.ProductItem.objects.filter( Q(product__in=products) | Q(product__category__in=categories), ).select_related( "cart", "product") items = group_by_cart_status( items, ["product__category__order", "product__order"], ["product", "product__category__name", "product__name"], ) headings = [ "Product", "Paid", "Reserved", "Unreserved", "Refunded", ] data = [] for item in items: data.append([ "%s - %s" % (item["product__category__name"], item["product__name"]), item["total_paid"], item["total_reserved"], item["total_unreserved"], item["total_refunded"], ]) return ListReport("Inventory", headings, data)
def manifest(request, form): ''' Produces the registration manifest for people with the given product type. ''' products = form.cleaned_data["product"] categories = form.cleaned_data["category"] line_items = (Q(lineitem__product__in=products) | Q(lineitem__product__category__in=categories)) invoices = commerce.Invoice.objects.filter( line_items, status=commerce.Invoice.STATUS_PAID, ).select_related("cart", "user", "user__attendee", "user__attendee__attendeeprofilebase") users = set(i.user for i in invoices) carts = commerce.Cart.objects.filter(user__in=users) items = commerce.ProductItem.objects.filter(cart__in=carts).select_related( "product", "product__category", "cart", "cart__user", "cart__user__attendee", "cart__user__attendee__attendeeprofilebase").order_by( "product__category__order", "product__order") users = {} for item in items: cart = item.cart if cart.user not in users: users[cart.user] = {"unpaid": [], "paid": [], "refunded": []} items = users[cart.user] if cart.status == commerce.Cart.STATUS_ACTIVE: items["unpaid"].append(item) elif cart.status == commerce.Cart.STATUS_PAID: items["paid"].append(item) elif cart.status == commerce.Cart.STATUS_RELEASED: items["refunded"].append(item) users_by_name = list(users.keys()) users_by_name.sort( key=(lambda i: i.attendee.attendeeprofilebase.attendee_name().lower())) headings = ["User ID", "Name", "Paid", "Unpaid", "Refunded"] def format_items(item_list): strings = [] for item in item_list: strings.append('%d x %s' % (item.quantity, str(item.product))) return ", \n".join(strings) output = [] for user in users_by_name: items = users[user] output.append([ user.id, user.attendee.attendeeprofilebase.attendee_name(), format_items(items["paid"]), format_items(items["unpaid"]), format_items(items["refunded"]), ]) return ListReport("Manifest", headings, output)
def attendee_data(request, form, user_id=None): ''' Lists attendees for a given product/category selection along with profile data.''' status_display = { commerce.Cart.STATUS_ACTIVE: "Unpaid", commerce.Cart.STATUS_PAID: "Paid", commerce.Cart.STATUS_RELEASED: "Refunded", } output = [] by_category = ( form.cleaned_data["group_by"] == forms.GroupByForm.GROUP_BY_CATEGORY) products = form.cleaned_data["product"] categories = form.cleaned_data["category"] fields = form.cleaned_data["fields"] name_field = AttendeeProfile.name_field() items = commerce.ProductItem.objects.filter( Q(product__in=products) | Q(product__category__in=categories), ).exclude( cart__status=commerce.Cart.STATUS_RELEASED).select_related( "cart", "cart__user", "product", "product__category", ).order_by("cart__status") # Add invoice nag link links = [] invoice_mailout = reverse(views.invoice_mailout, args=[]) invoice_mailout += "?" + request.META["QUERY_STRING"] links += [ ( invoice_mailout + "&status=1", "Send invoice reminders", ), ( invoice_mailout + "&status=2", "Send mail for paid invoices", ), ] if items.count() > 0: output.append(Links("Actions", links)) # Make sure we select all of the related fields related_fields = set( field for field in fields if isinstance(AttendeeProfile._meta.get_field(field), RelatedField)) # Get all of the relevant attendee profiles in one hit. profiles = AttendeeProfile.objects.filter( attendee__user__cart__productitem__in=items).select_related( "attendee__user").prefetch_related(*related_fields) by_user = {} for profile in profiles: by_user[profile.attendee.user] = profile cart = "attendee__user__cart" cart_status = cart + "__status" # noqa product = cart + "__productitem__product" product_name = product + "__name" category = product + "__category" category_name = category + "__name" if by_category: grouping_fields = (category, category_name) order_by = (category, ) first_column = "Category" group_name = lambda i: "%s" % (i[category_name], ) # noqa else: grouping_fields = (product, product_name, category_name) order_by = (category, ) first_column = "Product" group_name = lambda i: "%s - %s" % (i[category_name], i[product_name] ) # noqa # Group the responses per-field. for field in fields: concrete_field = AttendeeProfile._meta.get_field(field) field_verbose = concrete_field.verbose_name # Render the correct values for related fields if field in related_fields: # Get all of the IDs that will appear all_ids = profiles.order_by(field).values(field) all_ids = [i[field] for i in all_ids if i[field] is not None] # Get all of the concrete objects for those IDs model = concrete_field.related_model all_objects = model.objects.filter(id__in=all_ids) all_objects_by_id = dict((i.id, i) for i in all_objects) # Define a function to render those IDs. def display_field(value): if value in all_objects_by_id: return all_objects_by_id[value] else: return None else: def display_field(value): return value status_count = lambda status: Case( When( # noqa attendee__user__cart__status=status, then=Value(1), ), default=Value(0), output_field=models.fields.IntegerField(), ) paid_count = status_count(commerce.Cart.STATUS_PAID) unpaid_count = status_count(commerce.Cart.STATUS_ACTIVE) groups = profiles.order_by(*(order_by + (field, ))).values( *(grouping_fields + (field, ))).annotate( paid_count=Sum(paid_count), unpaid_count=Sum(unpaid_count), ) output.append( ListReport( "Grouped by %s" % field_verbose, [first_column, field_verbose, "paid", "unpaid"], [( group_name(group), display_field(group[field]), group["paid_count"] or 0, group["unpaid_count"] or 0, ) for group in groups], )) # DO the report for individual attendees field_names = [ AttendeeProfile._meta.get_field(field).verbose_name for field in fields ] def display_field(profile, field): field_type = AttendeeProfile._meta.get_field(field) attr = getattr(profile, field) if isinstance(field_type, models.ManyToManyField): return [str(i) for i in attr.all()] or "" else: return attr headings = ["User ID", "Name", "Email", "Product", "Item Status"] headings.extend(field_names) data = [] for item in items: profile = by_user[item.cart.user] line = [ item.cart.user.id, getattr(profile, name_field), profile.attendee.user.email, item.product, status_display[item.cart.status], ] + [display_field(profile, field) for field in fields] data.append(line) output.append( AttendeeListReport("Attendees by item with profile data", headings, data, link_view=attendee)) return output
def attendee(request, form, user_id=None): ''' Returns a list of all manifested attendees if no attendee is specified, else displays the attendee manifest. ''' if user_id is None and form.cleaned_data["user"] is not None: user_id = form.cleaned_data["user"] if user_id is None: return attendee_list(request) print(user_id) attendee = people.Attendee.objects.get(user__id=user_id) name = attendee.attendeeprofilebase.attendee_name() reports = [] profile_data = [] try: profile = people.AttendeeProfileBase.objects.get_subclass( attendee=attendee) fields = profile._meta.get_fields() except people.AttendeeProfileBase.DoesNotExist: fields = [] exclude = set(["attendeeprofilebase_ptr", "id"]) for field in fields: if field.name in exclude: # Not actually important continue if not hasattr(field, "verbose_name"): continue # Not a publicly visible field value = getattr(profile, field.name) if isinstance(field, models.ManyToManyField): value = ", ".join(str(i) for i in value.all()) profile_data.append((field.verbose_name, value)) cart = CartController.for_user(attendee.user) reservation = cart.cart.reservation_duration + cart.cart.time_last_updated profile_data.append(("Current cart reserved until", reservation)) reports.append(ListReport("Profile", ["", ""], profile_data)) links = [] links.append(( reverse(views.badge, args=[user_id]), "View badge", )) links.append(( reverse(views.amend_registration, args=[user_id]), "Amend current cart", )) links.append(( reverse(views.extend_reservation, args=[user_id]), "Extend reservation", )) reports.append(Links("Actions for " + name, links)) # Paid and pending products ic = ItemController(attendee.user) reports.append( ListReport( "Paid Products", ["Product", "Quantity"], [(pq.product, pq.quantity) for pq in ic.items_purchased()], )) reports.append( ListReport( "Unpaid Products", ["Product", "Quantity"], [(pq.product, pq.quantity) for pq in ic.items_pending()], )) # Invoices invoices = commerce.Invoice.objects.filter(user=attendee.user, ) reports.append( QuerysetReport( "Invoices", ["id", "get_status_display", "value"], invoices, headings=["Invoice ID", "Status", "Value"], link_view=views.invoice, )) # Credit Notes credit_notes = commerce.CreditNote.objects.filter( invoice__user=attendee.user, ).select_related("invoice", "creditnoteapplication", "creditnoterefund") reports.append( QuerysetReport( "Credit Notes", ["id", "status", "value"], credit_notes, link_view=views.credit_note, )) # All payments payments = commerce.PaymentBase.objects.filter( invoice__user=attendee.user, ).select_related("invoice") reports.append( QuerysetReport( "Payments", ["invoice__id", "id", "reference", "amount"], payments, link_view=views.invoice, )) return reports