# 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 collections import datetime import json from django.contrib import messages from django.db import transaction from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext from django.views.generic import ( CreateView, DeleteView, DetailView, FormView, ListView, TemplateView, UpdateView, ) from lingo.agendas.models import Agenda from lingo.invoicing.forms import CampaignForm, PoolForm from lingo.invoicing.models import ( Campaign, DraftInvoice, DraftInvoiceLine, InvoiceLine, Pool, Regie, RegieImportError, ) from lingo.invoicing.utils import generate_invoices from lingo.pricing.forms import ImportForm def import_regies(data): results = collections.defaultdict(list) with transaction.atomic(): regies = data.get('regies', []) for regie in regies: created, regie_obj = Regie.import_json(regie) if created: results['created'].append(regie_obj) else: results['updated'].append(regie_obj) return results class HomeView(TemplateView): template_name = 'lingo/invoicing/manager_home.html' home = HomeView.as_view() class RegiesListView(ListView): template_name = 'lingo/invoicing/manager_regie_list.html' model = Regie regies_list = RegiesListView.as_view() class RegieAddView(CreateView): template_name = 'lingo/invoicing/manager_regie_form.html' model = Regie fields = ['label', 'description', 'cashier_role'] def get_success_url(self): return reverse('lingo-manager-invoicing-regie-detail', args=[self.object.pk]) regie_add = RegieAddView.as_view() class RegieDetailView(DetailView): template_name = 'lingo/invoicing/manager_regie_detail.html' model = Regie def get_context_data(self, **kwargs): kwargs['regie'] = self.object kwargs['agendas'] = Agenda.objects.filter(regie=self.object) return super().get_context_data(**kwargs) regie_detail = RegieDetailView.as_view() class RegieEditView(UpdateView): template_name = 'lingo/invoicing/manager_regie_form.html' model = Regie fields = ['label', 'description', 'cashier_role'] def get_success_url(self): return reverse('lingo-manager-invoicing-regie-detail', args=[self.object.pk]) regie_edit = RegieEditView.as_view() class RegieDeleteView(DeleteView): template_name = 'lingo/manager_confirm_delete.html' model = Regie def get_success_url(self): return reverse('lingo-manager-invoicing-regie-list') regie_delete = RegieDeleteView.as_view() class RegiesExportView(ListView): model = Regie def get(self, request, *args, **kwargs): response = HttpResponse(content_type='application/json') today = datetime.date.today() attachment = 'attachment; filename="export_regies_{}.json"'.format(today.strftime('%Y%m%d')) response['Content-Disposition'] = attachment json.dump({'regies': [regie.export_json() for regie in self.get_queryset()]}, response, indent=2) return response regies_export = RegiesExportView.as_view() class RegiesImportView(FormView): form_class = ImportForm template_name = 'lingo/invoicing/manager_import.html' success_url = reverse_lazy('lingo-manager-invoicing-regie-list') def form_valid(self, form): try: config_json = json.loads(self.request.FILES['config_json'].read()) except ValueError: form.add_error('config_json', _('File is not in the expected JSON format.')) return self.form_invalid(form) try: results = import_regies(config_json) except RegieImportError as exc: form.add_error('config_json', '%s' % exc) return self.form_invalid(form) import_messages = { 'create': lambda x: ungettext( 'A regie was created.', '%(count)d regies were created.', x, ), 'update': lambda x: ungettext( 'A regie was updated.', '%(count)d regie were updated.', x, ), } create_message = _('No regie created.') update_message = _('No regie updated.') created = len(results.get('created', [])) updated = len(results.get('updated', [])) if created: create_message = import_messages.get('create')(created) % {'count': created} if updated: update_message = import_messages.get('update')(updated) % {'count': updated} message = "%s %s" % (create_message, update_message) messages.info(self.request, message) return super().form_valid(form) regies_import = RegiesImportView.as_view() class CampaignListView(ListView): template_name = 'lingo/invoicing/manager_campaign_list.html' model = Campaign campaign_list = CampaignListView.as_view() class CampaignAddView(CreateView): template_name = 'lingo/invoicing/manager_campaign_form.html' model = Campaign form_class = CampaignForm def get_success_url(self): return reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk]) campaign_add = CampaignAddView.as_view() class CampaignDetailView(DetailView): template_name = 'lingo/invoicing/manager_campaign_detail.html' model = Campaign def get_context_data(self, **kwargs): kwargs['pools'] = self.object.pool_set.order_by('created_at') kwargs['has_real_pool'] = any(not p.draft for p in kwargs['pools']) return super().get_context_data(**kwargs) campaign_detail = CampaignDetailView.as_view() class CampaignEditView(UpdateView): template_name = 'lingo/invoicing/manager_campaign_form.html' model = Campaign form_class = CampaignForm def get_queryset(self): return super().get_queryset().exclude(pool__draft=False) def get_success_url(self): return reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk]) campaign_edit = CampaignEditView.as_view() class CampaignDeleteView(DeleteView): template_name = 'lingo/manager_confirm_delete.html' model = Campaign def get_queryset(self): return super().get_queryset().exclude(pool__draft=False) def delete(self, request, *args, **kwargs): self.object = self.get_object() DraftInvoiceLine.objects.filter(pool__campaign=self.object).delete() DraftInvoice.objects.filter(pool__campaign=self.object).delete() Pool.objects.filter(campaign=self.object).delete() return super().delete(request, *args, **kwargs) def get_success_url(self): return reverse('lingo-manager-invoicing-campaign-list') campaign_delete = CampaignDeleteView.as_view() class PoolDetailView(DetailView): template_name = 'lingo/invoicing/manager_pool_detail.html' model = Pool pk_url_kwarg = 'pool_pk' def dispatch(self, request, *args, **kwargs): self.campaign = get_object_or_404(Campaign, pk=kwargs['pk']) return super().dispatch(request, *args, **kwargs) def get_queryset(self): return self.campaign.pool_set.all() def get_context_data(self, **kwargs): kwargs['object'] = self.campaign kwargs['pool'] = self.object line_model = InvoiceLine if self.object.draft: line_model = DraftInvoiceLine kwargs['lines'] = line_model.objects.filter(pool=self.object) return super().get_context_data(**kwargs) pool_detail = PoolDetailView.as_view() class PoolAddView(FormView): template_name = 'lingo/invoicing/manager_pool_form.html' form_class = PoolForm def dispatch(self, request, *args, **kwargs): self.object = get_object_or_404(Campaign.objects.exclude(pool__draft=False), pk=kwargs['pk']) return super().dispatch(request, *args, **kwargs) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['campaign'] = self.object return kwargs def get_context_data(self, **kwargs): kwargs['object'] = self.object return super().get_context_data(**kwargs) def form_valid(self, form): generate_invoices(campaign=self.object, draft=form.cleaned_data['draft']) return super().form_valid(form) def get_success_url(self): return '%s#open:pools' % reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk]) pool_add = PoolAddView.as_view() class PoolDeleteView(DeleteView): template_name = 'lingo/manager_confirm_delete.html' model = Pool pk_url_kwarg = 'pool_pk' def dispatch(self, request, *args, **kwargs): self.campaign = get_object_or_404(Campaign.objects.exclude(pool__draft=False), pk=kwargs['pk']) return super().dispatch(request, *args, **kwargs) def get_queryset(self): return self.campaign.pool_set.filter(draft=True) def delete(self, request, *args, **kwargs): self.object = self.get_object() DraftInvoiceLine.objects.filter(pool=self.object).delete() DraftInvoice.objects.filter(pool=self.object).delete() return super().delete(request, *args, **kwargs) def get_success_url(self): return '%s#open:pools' % reverse('lingo-manager-invoicing-campaign-detail', args=[self.campaign.pk]) pool_delete = PoolDeleteView.as_view()