wip/71910-invoicing-train-models (#71910) #2

Closed
lguerin wants to merge 1 commits from wip/71910-invoicing-train-models into wip/71528-generate-draft-invoices
6 changed files with 395 additions and 176 deletions

View File

@ -18,6 +18,7 @@ import datetime
from django.core.management.base import BaseCommand, CommandError
from lingo.invoicing.models import Campaign
from lingo.invoicing.utils import generate_invoices
@ -27,6 +28,8 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('date_start')
parser.add_argument('date_end')
parser.add_argument('--date-issue')
parser.add_argument('--draft', action='store_true')
def handle(self, *args, **options):
try:
@ -37,8 +40,26 @@ class Command(BaseCommand):
date_end = datetime.datetime.fromisoformat(options['date_end']).date()
except ValueError:
raise CommandError('Bad value "%s" for date_end' % options['date_end'])
if options.get('date_issue'):
try:
date_issue = datetime.datetime.fromisoformat(options['date_issue']).date()
except ValueError:
raise CommandError('Bad value "%s" for date_issue' % options['date_issue'])
else:
date_issue = date_end
generate_invoices(date_start=date_start, date_end=date_end)
try:
campaign = Campaign.objects.get(date_start=date_start, date_end=date_end)
except Campaign.DoesNotExist:
campaigns = Campaign.objects.extra(
where=["(date_start, date_end) OVERLAPS (%s, %s)"], params=[date_start, date_end]
)
if campaigns.exists():
raise CommandError('Overlapping campaigns already exist')
campaign = Campaign.objects.create(
date_start=date_start, date_end=date_end, date_issue=date_issue
)
generate_invoices(campaign=campaign, draft=options['draft'])
self.stdout.write(
self.style.SUCCESS('Invoicing generation OK (start: %s, end: %s)' % (date_start, date_end))

View File

@ -0,0 +1,65 @@
# Generated by Django 2.2.26 on 2022-12-01 10:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('invoicing', '0003_invoice'),
]
operations = [
migrations.CreateModel(
name='Campaign',
fields=[
(
'id',
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
('date_start', models.DateField(verbose_name='Start date')),
('date_end', models.DateField(verbose_name='End date')),
('date_issue', models.DateField(verbose_name='Issue date')),
],
),
migrations.CreateModel(
name='Pool',
fields=[
(
'id',
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
('draft', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
(
'campaign',
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.Campaign'),
),
],
),
migrations.AddField(
model_name='draftinvoice',
name='pool',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.Pool'),
preserve_default=False,
),
migrations.AddField(
model_name='draftinvoiceline',
name='pool',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.Pool'),
preserve_default=False,
),
migrations.AddField(
model_name='invoice',
name='pool',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.Pool'),
preserve_default=False,
),
migrations.AddField(
model_name='invoiceline',
name='pool',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.Pool'),
preserve_default=False,
),
]

View File

@ -87,6 +87,18 @@ class Regie(models.Model):
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'))
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)
@ -94,6 +106,8 @@ class AbstractInvoice(models.Model):
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
@ -125,6 +139,8 @@ class AbstractInvoiceLine(models.Model):
],
)
pool = models.ForeignKey(Pool, on_delete=models.PROTECT)
class Meta:
abstract = True

View File

@ -26,14 +26,15 @@ from lingo.invoicing.models import DraftInvoice, DraftInvoiceLine
from lingo.pricing.models import AgendaPricing, AgendaPricingNotFound, PricingError
def get_agendas(date_start, date_end):
def get_agendas(pool):
agendas_pricings = AgendaPricing.objects.filter(flat_fee_schedule=False).extra(
where=["(date_start, date_end) OVERLAPS (%s, %s)"], params=[date_start, date_end]
where=["(date_start, date_end) OVERLAPS (%s, %s)"],
params=[pool.campaign.date_start, pool.campaign.date_end],
)
return Agenda.objects.filter(pk__in=agendas_pricings.values('agendas')).order_by('pk')
def get_all_subscriptions(agendas, date_start, date_end):
def get_all_subscriptions(agendas, pool):
# result:
# {
# 'user_id': {
@ -44,7 +45,11 @@ def get_all_subscriptions(agendas, date_start, date_end):
# }
all_subscriptions = {}
for agenda in agendas:
subscriptions = get_subscriptions(agenda_slug=agenda.slug, date_start=date_start, date_end=date_end)
subscriptions = get_subscriptions(
agenda_slug=agenda.slug,
date_start=pool.campaign.date_start,
date_end=pool.campaign.date_end,
)
for subscription in subscriptions:
if subscription['user_external_id'] not in all_subscriptions:
all_subscriptions[subscription['user_external_id']] = {}
@ -55,9 +60,7 @@ def get_all_subscriptions(agendas, date_start, date_end):
return all_subscriptions
def get_invoice_lines_for_user(
agendas, agendas_pricings, user_external_id, subscriptions, date_start, date_end
):
def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, subscriptions, pool):
def get_agenda_pricing(agendas_pricings_for_agenda, date_event):
# same logic as AgendaPricing.get_agenda_pricing
for agenda_pricing in agendas_pricings_for_agenda:
@ -93,8 +96,8 @@ def get_invoice_lines_for_user(
check_status_list = get_check_status(
agenda_slugs=list(subscriptions.keys()),
user_external_id=user_external_id,
date_start=date_start,
date_end=date_end,
date_start=pool.campaign.date_start,
date_end=pool.campaign.date_end,
)
request = RequestFactory().get('/') # XXX
agendas_by_slug = {a.slug: a for a in agendas}
@ -150,6 +153,7 @@ def get_invoice_lines_for_user(
event=serialized_event,
pricing_data=pricing_error,
status='error',
pool=pool,
)
)
else:
@ -166,16 +170,20 @@ def get_invoice_lines_for_user(
event=serialized_event,
pricing_data=pricing_data,
status='success',
pool=pool,
)
)
DraftInvoiceLine.objects.bulk_create(lines)
return lines
def get_all_invoice_lines(agendas, subscriptions, date_start, date_end):
def get_all_invoice_lines(agendas, subscriptions, pool):
agendas_pricings = (
AgendaPricing.objects.filter(flat_fee_schedule=False)
.extra(where=["(date_start, date_end) OVERLAPS (%s, %s)"], params=[date_start, date_end])
.extra(
where=["(date_start, date_end) OVERLAPS (%s, %s)"],
params=[pool.campaign.date_start, pool.campaign.date_end],
)
.prefetch_related('agendas', 'pricing__criterias', 'pricing__categories')
)
@ -187,13 +195,12 @@ def get_all_invoice_lines(agendas, subscriptions, date_start, date_end):
agendas_pricings=agendas_pricings,
user_external_id=user_external_id,
subscriptions=user_subs,
date_start=date_start,
date_end=date_end,
pool=pool,
)
return lines
def generate_invoices_from_lines(agendas, all_lines, date_start, date_end):
def generate_invoices_from_lines(agendas, all_lines, pool):
agendas_by_slug = {a.slug: a for a in agendas}
# regroup lines by regie, and by payer_external_id (payer)
@ -222,10 +229,15 @@ def generate_invoices_from_lines(agendas, all_lines, date_start, date_end):
for regie_id, regie_subs in lines_by_regie.items():
for payer_external_id, adult_lines in regie_subs.items():
invoice = DraftInvoice.objects.create(
label=_('Invoice from %s to %s') % (date_start, date_end - datetime.timedelta(days=1)),
date_issue=date_end, # XXX
label=_('Invoice from %s to %s')
% (
pool.campaign.date_start,
pool.campaign.date_end - datetime.timedelta(days=1),
),
date_issue=pool.campaign.date_issue,
regie_id=regie_id,
payer=payer_external_id,
pool=pool,
)
DraftInvoiceLine.objects.filter(pk__in=[line.pk for line in adult_lines]).update(invoice=invoice)
invoices.append(invoice)
@ -233,14 +245,13 @@ def generate_invoices_from_lines(agendas, all_lines, date_start, date_end):
return invoices
def generate_invoices(date_start, date_end):
def generate_invoices(campaign, draft=True):
pool = campaign.pool_set.create(draft=draft)
# get agendas with pricing corresponding to the period
agendas = get_agendas(date_start=date_start, date_end=date_end)
agendas = get_agendas(pool=pool)
# get subscriptions for each agenda, for the period
subscriptions = get_all_subscriptions(agendas=agendas, date_start=date_start, date_end=date_end)
subscriptions = get_all_subscriptions(agendas=agendas, pool=pool)
# get invoice lines for all subscribed users, for each agenda in the corresponding period
lines = get_all_invoice_lines(
agendas=agendas, subscriptions=subscriptions, date_start=date_start, date_end=date_end
)
lines = get_all_invoice_lines(agendas=agendas, subscriptions=subscriptions, pool=pool)
# and generate invoices
generate_invoices_from_lines(agendas=agendas, all_lines=lines, date_start=date_start, date_end=date_end)
generate_invoices_from_lines(agendas=agendas, all_lines=lines, pool=pool)

View File

@ -9,7 +9,7 @@ from django.test.utils import CaptureQueriesContext
from lingo.agendas.chrono import ChronoError
from lingo.agendas.models import Agenda
from lingo.invoicing import utils
from lingo.invoicing.models import DraftInvoice, DraftInvoiceLine, Regie
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Pool, Regie
from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingError
pytestmark = pytest.mark.django_db
@ -21,12 +21,18 @@ def test_get_agendas():
agenda3 = Agenda.objects.create(label='Agenda 3')
Agenda.objects.create(label='Agenda 4')
pricing = Pricing.objects.create(label='Foo bar 1')
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
# no agenda pricing defined
assert (
list(utils.get_agendas(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)))
== []
)
assert list(utils.get_agendas(pool=pool)) == []
# agenda pricing, but for flat_fee_schedule
agenda_pricing = AgendaPricing.objects.create(
@ -36,10 +42,7 @@ def test_get_agendas():
flat_fee_schedule=True, # wrong config
)
agenda_pricing.agendas.add(agenda1)
assert (
list(utils.get_agendas(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)))
== []
)
assert list(utils.get_agendas(pool=pool)) == []
# create some agenda pricing
agenda_pricing1 = AgendaPricing.objects.create(
@ -52,74 +55,77 @@ def test_get_agendas():
date_start=datetime.date(year=2022, month=9, day=1),
date_end=datetime.date(year=2022, month=10, day=1),
)
assert (
list(utils.get_agendas(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)))
== []
)
assert list(utils.get_agendas(pool=pool)) == []
# link agendas to agenda pricing
agenda_pricing1.agendas.add(agenda1, agenda2)
agenda_pricing2.agendas.add(agenda3)
assert list(
utils.get_agendas(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1))
) == [agenda3]
assert list(
utils.get_agendas(date_start=datetime.date(2021, 9, 1), date_end=datetime.date(2021, 10, 1))
) == [agenda1, agenda2]
assert (
list(utils.get_agendas(date_start=datetime.date(2022, 8, 31), date_end=datetime.date(2022, 9, 1)))
== []
)
assert list(
utils.get_agendas(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 9, 2))
) == [agenda3]
assert list(
utils.get_agendas(date_start=datetime.date(2022, 9, 30), date_end=datetime.date(2022, 10, 1))
) == [agenda3]
assert (
list(utils.get_agendas(date_start=datetime.date(2022, 10, 1), date_end=datetime.date(2022, 10, 2)))
== []
)
assert list(
utils.get_agendas(date_start=datetime.date(2021, 9, 15), date_end=datetime.date(2022, 9, 15))
) == [agenda1, agenda2, agenda3]
assert list(utils.get_agendas(pool=pool)) == [agenda3]
campaign.date_start = datetime.date(2021, 9, 1)
campaign.date_end = datetime.date(2021, 10, 1)
campaign.save()
assert list(utils.get_agendas(pool=pool)) == [agenda1, agenda2]
campaign.date_start = datetime.date(2022, 8, 31)
campaign.date_end = datetime.date(2022, 9, 1)
campaign.save()
assert list(utils.get_agendas(pool=pool)) == []
campaign.date_start = datetime.date(2022, 9, 1)
campaign.date_end = datetime.date(2022, 9, 2)
campaign.save()
assert list(utils.get_agendas(pool=pool)) == [agenda3]
campaign.date_start = datetime.date(2022, 9, 30)
campaign.date_end = datetime.date(2022, 10, 1)
campaign.save()
assert list(utils.get_agendas(pool=pool)) == [agenda3]
campaign.date_start = datetime.date(2022, 10, 1)
campaign.date_end = datetime.date(2022, 10, 2)
campaign.save()
assert list(utils.get_agendas(pool=pool)) == []
campaign.date_start = datetime.date(2021, 9, 15)
campaign.date_end = datetime.date(2022, 9, 15)
campaign.save()
assert list(utils.get_agendas(pool=pool)) == [agenda1, agenda2, agenda3]
@mock.patch('lingo.invoicing.utils.get_subscriptions')
def test_get_all_subscriptions_error(mock_subscriptions):
agenda = Agenda.objects.create(label='Agenda')
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
mock_subscriptions.side_effect = ChronoError('foo baz')
with pytest.raises(ChronoError):
utils.get_all_subscriptions(
agendas=[agenda], date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)
)
utils.get_all_subscriptions(agendas=[agenda], pool=pool)
@mock.patch('lingo.invoicing.utils.get_subscriptions')
def test_get_all_subscriptions(mock_subscriptions):
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
# no agendas
assert (
utils.get_all_subscriptions(
agendas=[], date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)
)
== {}
)
assert utils.get_all_subscriptions(agendas=[], pool=pool) == {}
assert mock_subscriptions.call_args_list == []
# no subscriptions
mock_subscriptions.return_value = []
assert (
utils.get_all_subscriptions(
agendas=[agenda1, agenda2],
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
)
== {}
)
assert utils.get_all_subscriptions(agendas=[agenda1, agenda2], pool=pool) == {}
assert mock_subscriptions.call_args_list == [
mock.call(
agenda_slug='agenda-1', date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)
@ -157,9 +163,7 @@ def test_get_all_subscriptions(mock_subscriptions):
},
],
]
assert utils.get_all_subscriptions(
agendas=[agenda1, agenda2], date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)
) == {
assert utils.get_all_subscriptions(agendas=[agenda1, agenda2], pool=pool) == {
'user:1': {
'agenda-1': [
{
@ -203,6 +207,15 @@ def test_get_all_subscriptions(mock_subscriptions):
@mock.patch('lingo.invoicing.utils.get_check_status')
def test_get_invoice_lines_for_user_check_status_error(mock_status):
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
mock_status.side_effect = ChronoError('foo baz')
with pytest.raises(ChronoError):
utils.get_invoice_lines_for_user(
@ -210,8 +223,7 @@ def test_get_invoice_lines_for_user_check_status_error(mock_status):
agendas_pricings=[],
user_external_id='user:1',
subscriptions={'foo': 'bar'},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
@ -227,6 +239,15 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
date_end=datetime.date(year=2022, month=10, day=1),
)
agenda_pricing.agendas.add(agenda1, agenda2)
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
# no agendas
assert (
@ -243,8 +264,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
== []
)
@ -258,8 +278,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
== []
)
@ -282,8 +301,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
== []
)
@ -323,8 +341,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
== []
)
@ -411,8 +428,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
assert mock_pricing_data_event.call_args_list == [
mock.call(
@ -495,6 +511,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1}
assert line1.status == 'success'
assert line1.pool == pool
assert isinstance(line2, DraftInvoiceLine)
assert line2.invoice is None
assert line2.slug == 'agenda-1@event-2'
@ -512,6 +529,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
}
assert line2.pricing_data == {'foo2': 'bar2', 'pricing': 2}
assert line2.status == 'success'
assert line2.pool == pool
assert isinstance(line3, DraftInvoiceLine)
assert line3.invoice is None
assert line3.slug == 'agenda-2@eveeent-1'
@ -529,6 +547,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
}
assert line3.pricing_data == {'foo3': 'bar3', 'pricing': 3}
assert line3.status == 'success'
assert line3.pool == pool
assert isinstance(line4, DraftInvoiceLine)
assert line4.invoice is None
assert line4.slug == 'agenda-2@eveeent-2'
@ -546,6 +565,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
}
assert line4.pricing_data == {'foo4': 'bar4', 'pricing': 4}
assert line4.status == 'success'
assert line4.pool == pool
@mock.patch('lingo.invoicing.utils.get_check_status')
@ -559,6 +579,15 @@ def test_get_invoice_lines_for_user_check_status_subscriptions_dates(mock_pricin
date_end=datetime.date(year=2022, month=10, day=1),
)
agenda_pricing.agendas.add(agenda)
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
mock_pricing_data_event.return_value = {'foo': 'bar', 'pricing': 42}
mock_status.return_value = [
@ -589,8 +618,7 @@ def test_get_invoice_lines_for_user_check_status_subscriptions_dates(mock_pricin
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
== []
)
@ -611,8 +639,7 @@ def test_get_invoice_lines_for_user_check_status_subscriptions_dates(mock_pricin
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
== []
)
@ -644,8 +671,7 @@ def test_get_invoice_lines_for_user_check_status_subscriptions_dates(mock_pricin
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
assert len(lines) == 1
assert mock_pricing_data_event.call_args_list == [
@ -690,6 +716,15 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu
date_end=datetime.date(year=2022, month=11, day=1),
)
agenda_pricing3.agendas.add(agenda)
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2023, 1, 1),
date_issue=datetime.date(2023, 1, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
pricing_data_event_patch = mock.patch.object(AgendaPricing, 'get_pricing_data_for_event', autospec=True)
@ -722,8 +757,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu
},
],
},
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2023, 1, 1),
pool=pool,
)
assert len(lines) == 1
assert mock_pricing_data_event.call_args_list[0][0][0] == agenda_pricing1
@ -757,8 +791,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu
},
],
},
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2023, 1, 1),
pool=pool,
)
assert len(lines) == 1
assert mock_pricing_data_event.call_args_list[0][0][0] == agenda_pricing3
@ -791,8 +824,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu
},
],
},
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2023, 1, 1),
pool=pool,
)
assert len(lines) == 0
assert mock_pricing_data_event.call_args_list == []
@ -809,6 +841,15 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data
date_end=datetime.date(year=2022, month=10, day=1),
)
agenda_pricing.agendas.add(agenda)
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 1},
@ -860,8 +901,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data
},
],
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
assert len(lines) == 3
line1, line2, line3 = lines
@ -882,6 +922,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data
}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1}
assert line1.status == 'success'
assert line1.pool == pool
assert isinstance(line2, DraftInvoiceLine)
assert line2.invoice is None
assert line2.slug == 'agenda@event-2'
@ -899,6 +940,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data
}
assert line2.pricing_data == {'error': {}}
assert line2.status == 'error'
assert line2.pool == pool
assert isinstance(line3, DraftInvoiceLine)
assert line3.invoice is None
assert line3.slug == 'agenda@event-3'
@ -916,6 +958,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data
}
assert line3.pricing_data == {'foo3': 'bar3', 'pricing': 3}
assert line3.status == 'success'
assert line3.pool == pool
@mock.patch('lingo.invoicing.utils.get_invoice_lines_for_user')
@ -923,21 +966,33 @@ def test_get_all_invoice_lines(mock_user_lines):
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
Agenda.objects.create(label='Agenda 3')
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
line1 = DraftInvoiceLine.objects.create(
quantity=0,
unit_amount=0,
total_amount=0,
pool=pool,
)
line2 = DraftInvoiceLine.objects.create(
quantity=0,
unit_amount=0,
total_amount=0,
pool=pool,
)
line3 = DraftInvoiceLine.objects.create(
quantity=0,
unit_amount=0,
total_amount=0,
pool=pool,
)
mock_user_lines.side_effect = [[line1, line2], [line3]]
@ -947,8 +1002,7 @@ def test_get_all_invoice_lines(mock_user_lines):
utils.get_all_invoice_lines(
agendas=[agenda1, agenda2],
subscriptions={},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
== []
)
@ -961,8 +1015,7 @@ def test_get_all_invoice_lines(mock_user_lines):
'user:1': {'foo': 'bar1'},
'user:2': {'foo': 'bar2'},
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
) == [line1, line2, line3]
assert mock_user_lines.call_args_list == [
mock.call(
@ -970,16 +1023,14 @@ def test_get_all_invoice_lines(mock_user_lines):
agendas_pricings=mock.ANY,
user_external_id='user:1',
subscriptions={'foo': 'bar1'},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
),
mock.call(
agendas=[agenda1, agenda2],
agendas_pricings=mock.ANY,
user_external_id='user:2',
subscriptions={'foo': 'bar2'},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
),
]
@ -1029,6 +1080,16 @@ def test_get_all_invoice_lines_queryset(mock_status):
)
agenda_pricing22.agendas.add(agenda2)
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 11, 1),
date_issue=datetime.date(2022, 11, 30),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
mock_status.return_value = [
{
'event': {
@ -1109,8 +1170,7 @@ def test_get_all_invoice_lines_queryset(mock_status):
],
},
},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 11, 1),
pool=pool,
)
assert lines
assert len(ctx.captured_queries) == 7
@ -1124,6 +1184,16 @@ def test_generate_invoices_from_lines():
agenda3 = Agenda.objects.create(label='Agenda 3', regie=regie1)
agenda4 = Agenda.objects.create(label='Agenda 4') # regie not configured
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=True,
)
line_error = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-1'},
quantity=0,
@ -1132,6 +1202,7 @@ def test_generate_invoices_from_lines():
user_external_id='user:1',
payer_external_id='user:1',
status='error',
pool=pool,
)
line1 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-1'},
@ -1141,6 +1212,7 @@ def test_generate_invoices_from_lines():
user_external_id='user:1',
payer_external_id='user:1',
status='success',
pool=pool,
)
line2 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-1'},
@ -1150,6 +1222,7 @@ def test_generate_invoices_from_lines():
user_external_id='user:1',
payer_external_id='user:1',
status='success',
pool=pool,
)
line3 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-2'},
@ -1159,6 +1232,7 @@ def test_generate_invoices_from_lines():
user_external_id='user:1',
payer_external_id='user:1',
status='success',
pool=pool,
)
line4 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-2'},
@ -1168,6 +1242,7 @@ def test_generate_invoices_from_lines():
user_external_id='user:1',
payer_external_id='user:2',
status='success',
pool=pool,
)
line5 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-3'},
@ -1177,6 +1252,7 @@ def test_generate_invoices_from_lines():
user_external_id='user:2',
payer_external_id='user:1',
status='success',
pool=pool,
)
DraftInvoiceLine.objects.create( # not used for generation
event={'agenda': 'agenda-3'},
@ -1186,6 +1262,7 @@ def test_generate_invoices_from_lines():
user_external_id='user:2',
payer_external_id='user:1',
status='success',
pool=pool,
)
line6 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-4'}, # regie not configured
@ -1195,21 +1272,20 @@ def test_generate_invoices_from_lines():
user_external_id='user:1',
payer_external_id='user:1',
status='success',
pool=pool,
)
invoices = utils.generate_invoices_from_lines(
agendas=[],
all_lines=[line_error, line1, line2, line3, line4, line5, line6],
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
assert len(invoices) == 0
invoices = utils.generate_invoices_from_lines(
agendas=[agenda1, agenda2, agenda3, agenda4],
all_lines=[line_error, line1, line2, line3, line4, line5, line6],
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
assert len(invoices) == 3
invoice1, invoice2, invoice3 = invoices
@ -1220,23 +1296,26 @@ def test_generate_invoices_from_lines():
assert isinstance(invoice1, DraftInvoice)
assert invoice1.label == 'Invoice from 2022-09-01 to 2022-09-30'
assert invoice1.total_amount == 8
assert invoice1.date_issue == datetime.date(2022, 10, 1)
assert invoice1.date_issue == datetime.date(2022, 10, 31)
assert invoice1.regie == regie1
assert invoice1.payer == 'user:1'
assert invoice1.pool == pool
assert list(invoice1.lines.order_by('pk')) == [line1, line2, line5]
assert isinstance(invoice2, DraftInvoice)
assert invoice2.label == 'Invoice from 2022-09-01 to 2022-09-30'
assert invoice2.total_amount == 3
assert invoice2.date_issue == datetime.date(2022, 10, 1)
assert invoice2.date_issue == datetime.date(2022, 10, 31)
assert invoice2.regie == regie2
assert invoice2.payer == 'user:1'
assert invoice2.pool == pool
assert list(invoice2.lines.order_by('pk')) == [line3]
assert isinstance(invoice3, DraftInvoice)
assert invoice3.label == 'Invoice from 2022-09-01 to 2022-09-30'
assert invoice3.total_amount == 4
assert invoice3.date_issue == datetime.date(2022, 10, 1)
assert invoice3.date_issue == datetime.date(2022, 10, 31)
assert invoice3.regie == regie2
assert invoice3.payer == 'user:2'
assert invoice3.pool == pool
assert list(invoice3.lines.order_by('pk')) == [line4]
@ -1248,73 +1327,40 @@ def test_generate_invoices(mock_generate, mock_lines, mock_subscriptions, mock_a
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
Agenda.objects.create(label='Agenda 3')
line1 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-1'},
quantity=1,
unit_amount=1,
total_amount=1,
user_external_id='user:1',
payer_external_id='user:1',
status='success',
)
line2 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-1'},
quantity=1,
unit_amount=2,
total_amount=2,
user_external_id='user:1',
payer_external_id='user:1',
status='success',
)
line3 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-2'},
quantity=1,
unit_amount=3,
total_amount=3,
user_external_id='user:1',
payer_external_id='user:1',
status='success',
)
DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-1'},
quantity=1,
unit_amount=5,
total_amount=5,
user_external_id='user:2',
payer_external_id='user:1',
status='success',
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
mock_agendas.return_value = [agenda1, agenda2]
mock_subscriptions.return_value = {'foo': 'bar'}
mock_lines.return_value = [line1, line2, line3]
mock_lines.return_value = ['foo', 'bar']
# check only calls between functions
utils.generate_invoices(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1))
assert mock_agendas.call_args_list == [
mock.call(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1))
]
utils.generate_invoices(campaign=campaign, draft=True)
pool = Pool.objects.latest('pk')
assert pool.campaign == campaign
assert pool.draft is True
assert mock_agendas.call_args_list == [mock.call(pool=pool)]
assert mock_subscriptions.call_args_list == [
mock.call(
agendas=[agenda1, agenda2],
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
]
assert mock_lines.call_args_list == [
mock.call(
agendas=[agenda1, agenda2],
subscriptions={'foo': 'bar'},
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
pool=pool,
)
]
assert mock_generate.call_args_list == [
mock.call(
agendas=[agenda1, agenda2],
all_lines=[line1, line2, line3],
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
all_lines=['foo', 'bar'],
pool=pool,
)
]
@ -1338,7 +1384,54 @@ def test_generate_invoices_cmd():
assert '%s' % excinfo.value == 'Bad value "bad-value" for date_end'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-09-01', '2022-10-01', '--date-issue=bad-value')
assert '%s' % excinfo.value == 'Bad value "bad-value" for date_issue'
assert mock_generate.call_args_list == []
assert Campaign.objects.count() == 0
call_command('generate_invoices', '2022-09-01', '2022-10-01')
assert mock_generate.call_args_list == [
mock.call(date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1))
]
assert Campaign.objects.count() == 1
campaign = Campaign.objects.latest('pk')
assert campaign.date_start == datetime.date(2022, 9, 1)
assert campaign.date_end == datetime.date(2022, 10, 1)
assert campaign.date_issue == campaign.date_end
assert mock_generate.call_args_list == [mock.call(campaign=campaign, draft=False)]
mock_generate.reset_mock()
# again
call_command('generate_invoices', '2022-09-01', '2022-10-01')
assert Campaign.objects.count() == 1
assert mock_generate.call_args_list == [mock.call(campaign=campaign, draft=False)]
mock_generate.reset_mock()
call_command('generate_invoices', '2022-09-01', '2022-10-01', '--draft')
assert Campaign.objects.count() == 1
assert mock_generate.call_args_list == [mock.call(campaign=campaign, draft=True)]
mock_generate.reset_mock()
# with overlapping
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-08-31', '2022-09-02')
assert '%s' % excinfo.value == 'Overlapping campaigns already exist'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-09-30', '2022-10-02')
assert '%s' % excinfo.value == 'Overlapping campaigns already exist'
assert mock_generate.call_args_list == []
# no overlapping
call_command('generate_invoices', '2022-08-01', '2022-09-01', '--date-issue=2022-10-31')
assert Campaign.objects.count() == 2
campaign = Campaign.objects.latest('pk')
assert mock_generate.call_args_list == [mock.call(campaign=campaign, draft=False)]
assert campaign.date_start == datetime.date(2022, 8, 1)
assert campaign.date_end == datetime.date(2022, 9, 1)
assert campaign.date_issue == datetime.date(2022, 10, 31)
mock_generate.reset_mock()
call_command('generate_invoices', '2022-10-01', '2022-11-01')
assert Campaign.objects.count() == 3
campaign = Campaign.objects.latest('pk')
assert mock_generate.call_args_list == [mock.call(campaign=campaign, draft=False)]
assert campaign.date_start == datetime.date(2022, 10, 1)
assert campaign.date_end == datetime.date(2022, 11, 1)
assert campaign.date_issue == campaign.date_end

View File

@ -2,7 +2,7 @@ import datetime
import pytest
from lingo.invoicing.models import DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine, Regie
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine, Pool, Regie
pytestmark = pytest.mark.django_db
@ -13,9 +13,19 @@ def test_invoice_total_amount(draft):
invoice_model = DraftInvoice if draft else Invoice
line_model = DraftInvoiceLine if draft else InvoiceLine
invoice = invoice_model.objects.create(date_issue=datetime.date.today(), regie=regie)
campaign = Campaign.objects.create(
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
pool = Pool.objects.create(
campaign=campaign,
draft=draft,
)
invoice = invoice_model.objects.create(date_issue=datetime.date.today(), regie=regie, pool=pool)
assert invoice.total_amount == 0
invoice2 = invoice_model.objects.create(date_issue=datetime.date.today(), regie=regie)
invoice2 = invoice_model.objects.create(date_issue=datetime.date.today(), regie=regie, pool=pool)
assert invoice2.total_amount == 0
# line with error status, ignored
@ -25,6 +35,7 @@ def test_invoice_total_amount(draft):
unit_amount=0,
total_amount=0,
status='error',
pool=pool,
)
invoice.refresh_from_db()
assert invoice.total_amount == 0
@ -73,6 +84,7 @@ def test_invoice_total_amount(draft):
unit_amount=20,
total_amount=20,
status='success',
pool=pool,
)
invoice.refresh_from_db()
assert invoice.total_amount == 32
@ -100,6 +112,7 @@ def test_invoice_total_amount(draft):
unit_amount=20,
total_amount=20,
status='success',
pool=pool,
)
invoice.refresh_from_db()
assert invoice.total_amount == 12