commit
1cf6645061
2 changed files with 103 additions and 37 deletions
|
@ -44,7 +44,7 @@ class InvoiceController(ForId, object):
|
|||
cart_controller.validate_cart() # Raises ValidationError on fail.
|
||||
|
||||
cls.update_old_invoices(cart)
|
||||
invoice = cls._generate(cart)
|
||||
invoice = cls._generate_from_cart(cart)
|
||||
|
||||
return cls(invoice)
|
||||
|
||||
|
@ -74,45 +74,57 @@ class InvoiceController(ForId, object):
|
|||
|
||||
@classmethod
|
||||
@transaction.atomic
|
||||
def _generate(cls, cart):
|
||||
def manual_invoice(cls, user, due_delta, description_price_pairs):
|
||||
''' Generates an invoice for arbitrary items, not held in a user's
|
||||
cart.
|
||||
|
||||
Arguments:
|
||||
user (User): The user the invoice is being generated for.
|
||||
due_delta (datetime.timedelta): The length until the invoice is
|
||||
due.
|
||||
description_price_pairs ([(str, long or Decimal), ...]): A list of
|
||||
pairs. Each pair consists of the description for each line item
|
||||
and the price for that line item. The price will be cast to
|
||||
Decimal.
|
||||
|
||||
Returns:
|
||||
an Invoice.
|
||||
'''
|
||||
|
||||
line_items = []
|
||||
for description, price in description_price_pairs:
|
||||
line_item = commerce.LineItem(
|
||||
description=description,
|
||||
quantity=1,
|
||||
price=Decimal(price),
|
||||
product=None,
|
||||
)
|
||||
line_items.append(line_item)
|
||||
|
||||
min_due_time = timezone.now() + due_delta
|
||||
return cls._generate(user, None, min_due_time, line_items)
|
||||
|
||||
@classmethod
|
||||
@transaction.atomic
|
||||
def _generate_from_cart(cls, cart):
|
||||
''' Generates an invoice for the given cart. '''
|
||||
|
||||
cart.refresh_from_db()
|
||||
|
||||
issued = timezone.now()
|
||||
reservation_limit = cart.reservation_duration + cart.time_last_updated
|
||||
# Never generate a due time that is before the issue time
|
||||
due = max(issued, reservation_limit)
|
||||
|
||||
# Get the invoice recipient
|
||||
profile = people.AttendeeProfileBase.objects.get_subclass(
|
||||
id=cart.user.attendee.attendeeprofilebase.id,
|
||||
)
|
||||
recipient = profile.invoice_recipient()
|
||||
invoice = commerce.Invoice.objects.create(
|
||||
user=cart.user,
|
||||
cart=cart,
|
||||
cart_revision=cart.revision,
|
||||
status=commerce.Invoice.STATUS_UNPAID,
|
||||
value=Decimal(),
|
||||
issue_time=issued,
|
||||
due_time=due,
|
||||
recipient=recipient,
|
||||
)
|
||||
# Generate the line items from the cart.
|
||||
|
||||
product_items = commerce.ProductItem.objects.filter(cart=cart)
|
||||
product_items = product_items.select_related(
|
||||
"product",
|
||||
"product__category",
|
||||
)
|
||||
|
||||
if len(product_items) == 0:
|
||||
raise ValidationError("Your cart is empty.")
|
||||
|
||||
product_items = product_items.order_by(
|
||||
"product__category__order", "product__order"
|
||||
)
|
||||
|
||||
if len(product_items) == 0:
|
||||
raise ValidationError("Your cart is empty.")
|
||||
|
||||
discount_items = commerce.DiscountItem.objects.filter(cart=cart)
|
||||
discount_items = discount_items.select_related(
|
||||
"discount",
|
||||
|
@ -120,8 +132,6 @@ class InvoiceController(ForId, object):
|
|||
"product__category",
|
||||
)
|
||||
|
||||
line_items = []
|
||||
|
||||
def format_product(product):
|
||||
return "%s - %s" % (product.category.name, product.name)
|
||||
|
||||
|
@ -129,35 +139,65 @@ class InvoiceController(ForId, object):
|
|||
description = discount.description
|
||||
return "%s (%s)" % (description, format_product(product))
|
||||
|
||||
invoice_value = Decimal()
|
||||
line_items = []
|
||||
|
||||
for item in product_items:
|
||||
product = item.product
|
||||
line_item = commerce.LineItem(
|
||||
invoice=invoice,
|
||||
description=format_product(product),
|
||||
quantity=item.quantity,
|
||||
price=product.price,
|
||||
product=product,
|
||||
)
|
||||
line_items.append(line_item)
|
||||
invoice_value += line_item.quantity * line_item.price
|
||||
for item in discount_items:
|
||||
line_item = commerce.LineItem(
|
||||
invoice=invoice,
|
||||
description=format_discount(item.discount, item.product),
|
||||
quantity=item.quantity,
|
||||
price=cls.resolve_discount_value(item) * -1,
|
||||
product=item.product,
|
||||
)
|
||||
line_items.append(line_item)
|
||||
invoice_value += line_item.quantity * line_item.price
|
||||
|
||||
# Generate the invoice
|
||||
|
||||
min_due_time = cart.reservation_duration + cart.time_last_updated
|
||||
|
||||
return cls._generate(cart.user, cart, min_due_time, line_items)
|
||||
|
||||
@classmethod
|
||||
@transaction.atomic
|
||||
def _generate(cls, user, cart, min_due_time, line_items):
|
||||
|
||||
# Never generate a due time that is before the issue time
|
||||
issued = timezone.now()
|
||||
due = max(issued, min_due_time)
|
||||
|
||||
# Get the invoice recipient
|
||||
profile = people.AttendeeProfileBase.objects.get_subclass(
|
||||
id=user.attendee.attendeeprofilebase.id,
|
||||
)
|
||||
recipient = profile.invoice_recipient()
|
||||
|
||||
invoice_value = sum(item.quantity * item.price for item in line_items)
|
||||
|
||||
invoice = commerce.Invoice.objects.create(
|
||||
user=user,
|
||||
cart=cart,
|
||||
cart_revision=cart.revision if cart else None,
|
||||
status=commerce.Invoice.STATUS_UNPAID,
|
||||
value=invoice_value,
|
||||
issue_time=issued,
|
||||
due_time=due,
|
||||
recipient=recipient,
|
||||
)
|
||||
|
||||
# Associate the line items with the invoice
|
||||
for line_item in line_items:
|
||||
line_item.invoice = invoice
|
||||
|
||||
commerce.LineItem.objects.bulk_create(line_items)
|
||||
|
||||
invoice.value = invoice_value
|
||||
|
||||
invoice.save()
|
||||
|
||||
cls.email_on_invoice_creation(invoice)
|
||||
|
||||
return invoice
|
||||
|
|
|
@ -580,6 +580,32 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
|||
cn2.credit_note.value,
|
||||
)
|
||||
|
||||
def test_can_generate_manual_invoice(self):
|
||||
|
||||
description_price_pairs = [
|
||||
("Item 1", 15),
|
||||
("Item 2", 30),
|
||||
]
|
||||
|
||||
due_delta = datetime.timedelta(hours=24)
|
||||
|
||||
_invoice = TestingInvoiceController.manual_invoice(
|
||||
self.USER_1, due_delta, description_price_pairs
|
||||
)
|
||||
inv = TestingInvoiceController(_invoice)
|
||||
|
||||
self.assertEquals(
|
||||
inv.invoice.value,
|
||||
sum(i[1] for i in description_price_pairs)
|
||||
)
|
||||
|
||||
self.assertEquals(
|
||||
len(inv.invoice.lineitem_set.all()),
|
||||
len(description_price_pairs)
|
||||
)
|
||||
|
||||
inv.pay("Demo payment", inv.invoice.value)
|
||||
|
||||
def test_sends_email_on_invoice_creation(self):
|
||||
invoice = self._invoice_containing_prod_1(1)
|
||||
self.assertEquals(1, len(self.emails))
|
||||
|
|
Loading…
Reference in a new issue