aboutsummaryrefslogtreecommitdiffstats
path: root/servo/models/invoices.py
diff options
context:
space:
mode:
authorFilipp Lepalaan <filipp@mac.com>2015-08-04 10:11:24 +0300
committerFilipp Lepalaan <filipp@mac.com>2015-08-04 10:11:24 +0300
commit63b0fc6269b38edf7234b9f151b80d81f614c0a3 (patch)
tree555de3068f33f8dddb4619349bbea7d9b7c822fd /servo/models/invoices.py
downloadServo-63b0fc6269b38edf7234b9f151b80d81f614c0a3.tar.gz
Servo-63b0fc6269b38edf7234b9f151b80d81f614c0a3.tar.bz2
Servo-63b0fc6269b38edf7234b9f151b80d81f614c0a3.zip
Initial commit
First public commit
Diffstat (limited to 'servo/models/invoices.py')
-rw-r--r--servo/models/invoices.py226
1 files changed, 226 insertions, 0 deletions
diff --git a/servo/models/invoices.py b/servo/models/invoices.py
new file mode 100644
index 0000000..301753a
--- /dev/null
+++ b/servo/models/invoices.py
@@ -0,0 +1,226 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, First Party Software
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+from django.db import models
+from django.conf import settings
+from django.utils import timezone
+from django.contrib.sites.models import Site
+from django.utils.translation import ugettext_lazy as _
+
+from django.dispatch import receiver
+from django.db.models.signals import post_save
+
+from django.contrib.sites.managers import CurrentSiteManager
+
+from servo import defaults
+from servo.models import User, Customer, Order, ServiceOrderItem, AbstractOrderItem
+
+
+class Invoice(models.Model):
+ site = models.ForeignKey(
+ Site,
+ editable=False,
+ default=defaults.site_id
+ )
+ created_at = models.DateTimeField(editable=False, auto_now_add=True)
+ created_by = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False)
+
+ PAYMENT_METHODS = (
+ (0, _("No Charge")),
+ (1, _("Cash")),
+ (2, _("Invoice")),
+ (3, _("Credit Card")),
+ (4, _("Mail payment")),
+ (5, _("Online payment"))
+ )
+
+ payment_method = models.IntegerField(
+ editable=False,
+ choices=PAYMENT_METHODS,
+ default=PAYMENT_METHODS[0][0],
+ verbose_name=_("Payment Method")
+ )
+
+ is_paid = models.BooleanField(default=False, verbose_name=_("paid"))
+ paid_at = models.DateTimeField(null=True, editable=False)
+ order = models.ForeignKey(Order, editable=False)
+ customer = models.ForeignKey(
+ Customer,
+ null=True,
+ editable=False,
+ on_delete=models.SET_NULL
+ )
+
+ # We remember the following the following so that the customer info
+ # on the invoice doesn't change if the customer is modified or deleted
+ customer_name = models.CharField(
+ max_length=255,
+ default=_("Walk-in"),
+ verbose_name=_("Name")
+ )
+ customer_phone = models.CharField(
+ null=True,
+ blank=True,
+ max_length=128,
+ verbose_name=_("Phone")
+ )
+ customer_email = models.CharField(
+ null=True,
+ blank=True,
+ max_length=128,
+ verbose_name=_("Email")
+ )
+ customer_address = models.CharField(
+ null=True,
+ blank=True,
+ max_length=255,
+ verbose_name=_("Address")
+ )
+ reference = models.CharField(
+ null=True,
+ blank=True,
+ max_length=255,
+ verbose_name=_("Reference")
+ )
+
+ total_net = models.DecimalField(max_digits=8, decimal_places=2) # total w/o taxes
+ total_tax = models.DecimalField(max_digits=8, decimal_places=2) # total taxes
+ total_gross = models.DecimalField(max_digits=8, decimal_places=2) # total with taxes
+
+ total_margin = models.DecimalField(
+ max_digits=8,
+ decimal_places=2,
+ editable=False
+ )
+
+ objects = CurrentSiteManager()
+
+ def get_payment_total(self):
+ from django.db.models import Sum
+ result = self.payment_set.all().aggregate(Sum('amount'))
+ return result['amount__sum']
+
+ def get_payment_methods(self):
+ """
+ Returns the different payment methods used in this invoice
+ """
+ payments = self.payment_set.all()
+ return [x.get_method_display() for x in payments]
+
+ def dispatch(self, products):
+ for p in products:
+ soi = ServiceOrderItem.objects.get(pk=p)
+ InvoiceItem.from_soi(soi, self)
+
+ soi.product.sell(soi.amount, self.order.location)
+ soi.dispatched = True
+ soi.save()
+
+ def get_absolute_url(self):
+ from django.core.urlresolvers import reverse
+ return reverse("invoices-view_invoice", args=[self.pk])
+
+ class Meta:
+ ordering = ('-id', )
+ app_label = 'servo'
+ get_latest_by = "id"
+
+
+class InvoiceItem(AbstractOrderItem):
+ invoice = models.ForeignKey(Invoice)
+ price = models.DecimalField(
+ max_digits=8,
+ decimal_places=2,
+ verbose_name=_("Sales Price")
+ )
+
+ @classmethod
+ def from_soi(cls, soi, invoice, invoice_item=None):
+ """
+ Copies SalesOrderItem into an InvoiceItem
+ """
+ if invoice_item:
+ i = invoice_item
+ else:
+ i = cls(invoice=invoice)
+
+ i.sn = soi.sn
+ i.code = soi.code
+ i.title = soi.title
+ i.price = soi.price
+ i.amount = soi.amount
+ i.product = soi.product
+ i.description = soi.description
+ i.created_by = invoice.created_by
+ i.save()
+ return i
+
+ class Meta:
+ app_label = "servo"
+
+
+class Payment(models.Model):
+ invoice = models.ForeignKey(Invoice)
+ METHODS = (
+ (0, _("No Charge")),
+ (1, _("Cash")),
+ (2, _("Invoice")),
+ (3, _("Credit Card")),
+ (4, _("Mail payment")),
+ (5, _("Online payment"))
+ )
+ method = models.IntegerField(
+ choices=METHODS,
+ default=METHODS[0][0],
+ verbose_name=_("Payment Method")
+ )
+ created_by = models.ForeignKey(User)
+ created_at = models.DateTimeField(auto_now_add=True)
+ amount = models.DecimalField(max_digits=8, decimal_places=2)
+
+ class Meta:
+ app_label = "servo"
+
+
+@receiver(post_save, sender=Invoice)
+def trigger_order_dispatched(sender, instance, created, **kwargs):
+ if created:
+ description = _(u'Order %s dispatched') % instance.order.code
+ instance.order.notify('dispatched', description, instance.created_by)
+
+@receiver(post_save, sender=Payment)
+def trigger_payment_received(sender, instance, created, **kwargs):
+ if created:
+ invoice = instance.invoice
+
+ if instance.method > 0:
+ description = _(u'Payment for %0.2f received') % instance.amount
+ invoice.order.notify('paid', description, instance.created_by)
+
+ if invoice.paid_at is None:
+ if invoice.get_payment_total() == invoice.total_gross:
+ invoice.paid_at = timezone.now()
+ invoice.save()