lingo/lingo/invoicing/models.py

161 lines
5.0 KiB
Python

# lingo - payment and billing system
# Copyright (C) 2022 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import copy
from django.contrib.auth.models import Group
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.utils.formats import date_format
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from lingo.utils.misc import generate_slug
class RegieImportError(Exception):
pass
class Regie(models.Model):
label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
description = models.TextField(
_('Description'), null=True, blank=True, help_text=_('Optional regie description.')
)
cashier_role = models.ForeignKey(
Group,
blank=True,
null=True,
default=None,
related_name='+',
verbose_name=_('Cashier Role'),
on_delete=models.SET_NULL,
)
class Meta:
ordering = ['label']
def __str__(self):
return self.label
def save(self, *args, **kwargs):
if not self.slug:
self.slug = generate_slug(self)
super().save(*args, **kwargs)
@property
def base_slug(self):
return slugify(self.label)
def export_json(self):
return {
'label': self.label,
'slug': self.slug,
'description': self.description,
'permissions': {
'cashier': self.cashier_role.name if self.cashier_role else None,
},
}
@classmethod
def import_json(cls, data):
data = copy.deepcopy(data)
permissions = data.pop('permissions') or {}
role_name = permissions.get('cashier')
if role_name:
try:
data['cashier_role'] = Group.objects.get(name=role_name)
except Group.DoesNotExists:
raise RegieImportError('Missing role: %s' % role_name)
except Group.MultipleObjectsReturned:
raise RegieImportError('Multiple role exist with the name: %s' % role_name)
regie, created = cls.objects.update_or_create(slug=data['slug'], defaults=data)
return created, regie
class Campaign(models.Model):
date_start = models.DateField(_('Start date'))
date_end = models.DateField(_('End date'))
date_issue = models.DateField(_('Issue date'))
def __str__(self):
return _('From %(start)s to %(end)s') % {
'start': date_format(self.date_start, 'd/m/Y'),
'end': date_format(self.date_end, 'd/m/Y'),
}
class Pool(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT)
draft = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class AbstractInvoice(models.Model):
label = models.CharField(_('Label'), max_length=300)
total_amount = models.DecimalField(max_digits=9, decimal_places=2, default=0)
date_issue = models.DateField(_('Issue date'))
regie = models.ForeignKey(Regie, on_delete=models.PROTECT)
payer = models.CharField(_('Payer'), max_length=300)
pool = models.ForeignKey(Pool, on_delete=models.PROTECT)
class Meta:
abstract = True
class DraftInvoice(AbstractInvoice):
pass
class Invoice(AbstractInvoice):
pass
class AbstractInvoiceLine(models.Model):
slug = models.SlugField(max_length=250)
label = models.CharField(max_length=260)
quantity = models.FloatField()
unit_amount = models.DecimalField(max_digits=9, decimal_places=2)
total_amount = models.DecimalField(max_digits=9, decimal_places=2)
user_external_id = models.CharField(max_length=250)
payer_external_id = models.CharField(max_length=250)
event = JSONField(default=dict)
pricing_data = JSONField(default=dict)
status = models.CharField(
max_length=10,
choices=[
('success', _('Success')),
('error', _('Error')),
],
)
pool = models.ForeignKey(Pool, on_delete=models.PROTECT)
class Meta:
abstract = True
class DraftInvoiceLine(AbstractInvoiceLine):
invoice = models.ForeignKey(DraftInvoice, on_delete=models.PROTECT, null=True, related_name='lines')
class InvoiceLine(AbstractInvoiceLine):
invoice = models.ForeignKey(Invoice, on_delete=models.PROTECT, null=True, related_name='lines')