# 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 . 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')