lingo/tests/invoicing/test_invoice_generation.py

1438 lines
49 KiB
Python

import datetime
from unittest import mock
import pytest
from django.core.management import CommandError, call_command
from django.db import connection
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 Campaign, DraftInvoice, DraftInvoiceLine, Pool, Regie
from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingError
pytestmark = pytest.mark.django_db
def test_get_agendas():
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
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(pool=pool)) == []
# agenda pricing, but for flat_fee_schedule
agenda_pricing = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2022, month=9, day=1),
date_end=datetime.date(year=2022, month=10, day=1),
flat_fee_schedule=True, # wrong config
)
agenda_pricing.agendas.add(agenda1)
assert list(utils.get_agendas(pool=pool)) == []
# create some agenda pricing
agenda_pricing1 = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
agenda_pricing2 = AgendaPricing.objects.create(
pricing=pricing,
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(pool=pool)) == []
# link agendas to agenda pricing
agenda_pricing1.agendas.add(agenda1, agenda2)
agenda_pricing2.agendas.add(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], 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=[], pool=pool) == {}
assert mock_subscriptions.call_args_list == []
# no subscriptions
mock_subscriptions.return_value = []
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)
),
mock.call(
agenda_slug='agenda-2', date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)
),
]
mock_subscriptions.reset_mock()
# with subscriptions
mock_subscriptions.side_effect = [
[
{
'user_external_id': 'user:1',
'date_start': '2022-08-01',
'date_end': '2022-09-02',
},
{
'user_external_id': 'user:1',
'date_start': '2022-09-02',
'date_end': '2022-09-03',
},
{
'user_external_id': 'user:2',
'date_start': '2022-09-02',
'date_end': '2022-09-03',
},
],
[
{
'user_external_id': 'user:1',
'date_start': '2022-08-01',
'date_end': '2022-10-01',
},
],
]
assert utils.get_all_subscriptions(agendas=[agenda1, agenda2], pool=pool) == {
'user:1': {
'agenda-1': [
{
'user_external_id': 'user:1',
'date_start': '2022-08-01',
'date_end': '2022-09-02',
},
{
'user_external_id': 'user:1',
'date_start': '2022-09-02',
'date_end': '2022-09-03',
},
],
'agenda-2': [
{
'user_external_id': 'user:1',
'date_start': '2022-08-01',
'date_end': '2022-10-01',
},
],
},
'user:2': {
'agenda-1': [
{
'user_external_id': 'user:2',
'date_start': '2022-09-02',
'date_end': '2022-09-03',
},
],
},
}
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)
),
mock.call(
agenda_slug='agenda-2', date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1)
),
]
@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(
agendas=['foo'],
agendas_pricings=[],
user_external_id='user:1',
subscriptions={'foo': 'bar'},
pool=pool,
)
@mock.patch('lingo.invoicing.utils.get_check_status')
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_status):
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
pricing = Pricing.objects.create(label='Foo bar 1')
agenda_pricing = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2022, month=9, day=1),
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 (
utils.get_invoice_lines_for_user(
agendas=[],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
'agenda-1': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-10-01',
},
],
},
pool=pool,
)
== []
)
assert mock_status.call_args_list == []
assert mock_pricing_data_event.call_args_list == []
# no subscriptions
assert (
utils.get_invoice_lines_for_user(
agendas=[agenda1, agenda2],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={},
pool=pool,
)
== []
)
assert mock_status.call_args_list == []
assert mock_pricing_data_event.call_args_list == []
# no status
mock_status.return_value = []
assert (
utils.get_invoice_lines_for_user(
agendas=[agenda1, agenda2],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
'agenda-1': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-10-01',
},
],
},
pool=pool,
)
== []
)
assert mock_status.call_args_list == [
mock.call(
agenda_slugs=['agenda-1'],
user_external_id='user:1',
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
)
]
mock_status.reset_mock()
assert mock_pricing_data_event.call_args_list == []
# agenda and subscriptions not matching
mock_status.return_value = [
{
'event': {
'agenda': 'agenda-1',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event',
'label': 'Event',
}
}
]
assert (
utils.get_invoice_lines_for_user(
agendas=[agenda1, agenda2],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
'unknown': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-10-01',
},
],
},
pool=pool,
)
== []
)
assert mock_status.call_args_list == [
mock.call(
agenda_slugs=['unknown'],
user_external_id='user:1',
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
)
]
mock_status.reset_mock()
assert mock_pricing_data_event.call_args_list == []
# correct data
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 1},
{'foo2': 'bar2', 'pricing': 2},
{'foo3': 'bar3', 'pricing': 3},
{'foo4': 'bar4', 'pricing': 4},
]
mock_status.return_value = [
# many events for agenda-1
{
'event': {
'agenda': 'agenda-1',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event-1',
'label': 'Event 1',
},
'check_status': {'foo': 'bar1'},
'booking': {'foo': 'baz1'},
},
{
'event': {
'agenda': 'agenda-1',
'start_datetime': '2022-09-02T12:00:00+02:00',
'slug': 'event-2',
'label': 'Event 2',
},
'check_status': {'foo': 'bar2'},
'booking': {'foo': 'baz2'},
},
# and for agenda-2
{
'event': {
'agenda': 'agenda-2',
'start_datetime': '2022-09-01T13:00:00+02:00',
'slug': 'eveeent-1',
'label': 'Eveeent 1',
},
'check_status': {'foo': 'barrr1'},
'booking': {'foo': 'bazzz1'},
},
{
'event': {
'agenda': 'agenda-2',
'start_datetime': '2022-09-02T13:00:00+02:00',
'slug': 'eveeent-2',
'label': 'Eveeent 2',
},
'check_status': {'foo': 'barrr2'},
'booking': {'foo': 'bazzz2'},
},
]
lines = utils.get_invoice_lines_for_user(
agendas=[agenda1, agenda2],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
# matching dates
'agenda-1': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-10-01',
},
],
'agenda-2': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-10-01',
},
],
},
pool=pool,
)
assert mock_pricing_data_event.call_args_list == [
mock.call(
request=mock.ANY,
agenda=agenda1,
event={
'agenda': 'agenda-1',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event-1',
'label': 'Event 1',
},
subscription={'user_external_id': 'user:1', 'date_start': '2022-09-01', 'date_end': '2022-10-01'},
check_status={'foo': 'bar1'},
booking={'foo': 'baz1'},
user_external_id='user:1',
adult_external_id='user:1',
),
mock.call(
request=mock.ANY,
agenda=agenda1,
event={
'agenda': 'agenda-1',
'start_datetime': '2022-09-02T12:00:00+02:00',
'slug': 'event-2',
'label': 'Event 2',
},
subscription={'user_external_id': 'user:1', 'date_start': '2022-09-01', 'date_end': '2022-10-01'},
check_status={'foo': 'bar2'},
booking={'foo': 'baz2'},
user_external_id='user:1',
adult_external_id='user:1',
),
mock.call(
request=mock.ANY,
agenda=agenda2,
event={
'agenda': 'agenda-2',
'start_datetime': '2022-09-01T13:00:00+02:00',
'slug': 'eveeent-1',
'label': 'Eveeent 1',
},
subscription={'user_external_id': 'user:1', 'date_start': '2022-09-01', 'date_end': '2022-10-01'},
check_status={'foo': 'barrr1'},
booking={'foo': 'bazzz1'},
user_external_id='user:1',
adult_external_id='user:1',
),
mock.call(
request=mock.ANY,
agenda=agenda2,
event={
'agenda': 'agenda-2',
'start_datetime': '2022-09-02T13:00:00+02:00',
'slug': 'eveeent-2',
'label': 'Eveeent 2',
},
subscription={'user_external_id': 'user:1', 'date_start': '2022-09-01', 'date_end': '2022-10-01'},
check_status={'foo': 'barrr2'},
booking={'foo': 'bazzz2'},
user_external_id='user:1',
adult_external_id='user:1',
),
]
assert len(lines) == 4
line1, line2, line3, line4 = lines
assert isinstance(line1, DraftInvoiceLine)
assert line1.invoice is None
assert line1.slug == 'agenda-1@event-1'
assert line1.label == 'Event 1'
assert line1.quantity == 1
assert line1.unit_amount == 1
assert line1.total_amount == 1
assert line1.user_external_id == 'user:1'
assert line1.payer_external_id == 'user:1'
assert line1.event == {
'agenda': 'agenda-1',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event-1',
'label': 'Event 1',
}
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'
assert line2.label == 'Event 2'
assert line2.quantity == 1
assert line2.unit_amount == 2
assert line2.total_amount == 2
assert line2.user_external_id == 'user:1'
assert line2.payer_external_id == 'user:1'
assert line2.event == {
'agenda': 'agenda-1',
'start_datetime': '2022-09-02T12:00:00+02:00',
'slug': 'event-2',
'label': 'Event 2',
}
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'
assert line3.label == 'Eveeent 1'
assert line3.quantity == 1
assert line3.unit_amount == 3
assert line3.total_amount == 3
assert line3.user_external_id == 'user:1'
assert line3.payer_external_id == 'user:1'
assert line3.event == {
'agenda': 'agenda-2',
'start_datetime': '2022-09-01T13:00:00+02:00',
'slug': 'eveeent-1',
'label': 'Eveeent 1',
}
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'
assert line4.label == 'Eveeent 2'
assert line4.quantity == 1
assert line4.unit_amount == 4
assert line4.total_amount == 4
assert line4.user_external_id == 'user:1'
assert line4.payer_external_id == 'user:1'
assert line4.event == {
'agenda': 'agenda-2',
'start_datetime': '2022-09-02T13:00:00+02:00',
'slug': 'eveeent-2',
'label': 'Eveeent 2',
}
assert line4.pricing_data == {'foo4': 'bar4', 'pricing': 4}
assert line4.status == 'success'
assert line4.pool == pool
@mock.patch('lingo.invoicing.utils.get_check_status')
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
def test_get_invoice_lines_for_user_check_status_subscriptions_dates(mock_pricing_data_event, mock_status):
agenda = Agenda.objects.create(label='Agenda')
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2022, month=9, day=1),
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 = [
{
'event': {
'agenda': 'agenda',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
]
assert (
utils.get_invoice_lines_for_user(
agendas=[agenda],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
'agenda': [
{
'user_external_id': 'user:1',
# event is outside the period
'date_start': '2022-08-31',
'date_end': '2022-09-01',
},
],
},
pool=pool,
)
== []
)
assert mock_pricing_data_event.call_args_list == []
assert (
utils.get_invoice_lines_for_user(
agendas=[agenda],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
'agenda': [
{
'user_external_id': 'user:1',
# event is outside the period
'date_start': '2022-09-02',
'date_end': '2022-09-03',
},
],
},
pool=pool,
)
== []
)
assert mock_pricing_data_event.call_args_list == []
lines = utils.get_invoice_lines_for_user(
agendas=[agenda],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
'agenda': [
{
'user_external_id': 'user:1',
# event is outside the period
'date_start': '2022-08-31',
'date_end': '2022-09-01',
},
{
'user_external_id': 'user:1',
# bingo !
'date_start': '2022-09-01',
'date_end': '2022-09-02',
},
{
'user_external_id': 'user:1',
# event is outside the period
'date_start': '2022-09-02',
'date_end': '2022-09-03',
},
],
},
pool=pool,
)
assert len(lines) == 1
assert mock_pricing_data_event.call_args_list == [
mock.call(
request=mock.ANY,
agenda=agenda,
event={
'agenda': 'agenda',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event',
'label': 'Event',
},
subscription={'user_external_id': 'user:1', 'date_start': '2022-09-01', 'date_end': '2022-09-02'},
check_status={'foo': 'bar'},
booking={'foo': 'baz'},
user_external_id='user:1',
adult_external_id='user:1',
)
]
@mock.patch('lingo.invoicing.utils.get_check_status')
def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_status):
agenda = Agenda.objects.create(label='Agenda')
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing1 = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2022, month=9, day=1),
date_end=datetime.date(year=2022, month=10, day=1),
)
agenda_pricing1.agendas.add(agenda)
agenda_pricing2 = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2022, month=10, day=1),
date_end=datetime.date(year=2022, month=11, day=1),
flat_fee_schedule=True,
)
agenda_pricing2.agendas.add(agenda)
agenda_pricing3 = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2022, month=10, day=1),
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)
# check agenda pricing of september is used
for event_date in ['2022-09-01T12:00:00+02:00', '2022-09-30T12:00:00+02:00']:
mock_status.return_value = [
{
'event': {
'agenda': 'agenda',
'start_datetime': event_date,
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
]
with pricing_data_event_patch as mock_pricing_data_event:
mock_pricing_data_event.return_value = {'foo': 'bar', 'pricing': 42}
lines = utils.get_invoice_lines_for_user(
agendas=[agenda],
agendas_pricings=AgendaPricing.objects.all(),
user_external_id='user:1',
subscriptions={
'agenda': [
{
'user_external_id': 'user:1',
'date_start': '2022-01-01',
'date_end': '2023-01-01',
},
],
},
pool=pool,
)
assert len(lines) == 1
assert mock_pricing_data_event.call_args_list[0][0][0] == agenda_pricing1
# check agenda pricing of october is used
for event_date in ['2022-10-01T12:00:00+02:00', '2022-10-31T12:00:00+02:00']:
mock_status.return_value = [
{
'event': {
'agenda': 'agenda',
'start_datetime': event_date,
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
]
with pricing_data_event_patch as mock_pricing_data_event:
mock_pricing_data_event.return_value = {'foo': 'bar', 'pricing': 42}
lines = utils.get_invoice_lines_for_user(
agendas=[agenda],
agendas_pricings=AgendaPricing.objects.all(),
user_external_id='user:1',
subscriptions={
'agenda': [
{
'user_external_id': 'user:1',
'date_start': '2022-01-01',
'date_end': '2023-01-01',
},
],
},
pool=pool,
)
assert len(lines) == 1
assert mock_pricing_data_event.call_args_list[0][0][0] == agenda_pricing3
# no matching agenda pricing
for event_date in ['2022-08-31T12:00:00+02:00', '2022-11-01T12:00:00+02:00']:
mock_status.return_value = [
{
'event': {
'agenda': 'agenda',
'start_datetime': event_date,
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
]
with pricing_data_event_patch as mock_pricing_data_event:
lines = utils.get_invoice_lines_for_user(
agendas=[agenda],
agendas_pricings=AgendaPricing.objects.all(),
user_external_id='user:1',
subscriptions={
'agenda': [
{
'user_external_id': 'user:1',
'date_start': '2022-01-01',
'date_end': '2023-01-01',
},
],
},
pool=pool,
)
assert len(lines) == 0
assert mock_pricing_data_event.call_args_list == []
@mock.patch('lingo.invoicing.utils.get_check_status')
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data_event, mock_status):
agenda = Agenda.objects.create(label='Agenda')
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing = AgendaPricing.objects.create(
pricing=pricing,
date_start=datetime.date(year=2022, month=9, day=1),
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},
PricingError(),
{'foo3': 'bar3', 'pricing': 3},
]
mock_status.return_value = [
{
'event': {
'agenda': 'agenda',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event-1',
'label': 'Event 1',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
{
'event': {
'agenda': 'agenda',
'start_datetime': '2022-09-02T12:00:00+02:00',
'slug': 'event-2',
'label': 'Event 2',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
{
'event': {
'agenda': 'agenda',
'start_datetime': '2022-09-03T12:00:00+02:00',
'slug': 'event-3',
'label': 'Event 3',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
]
lines = utils.get_invoice_lines_for_user(
agendas=[agenda],
agendas_pricings=[agenda_pricing],
user_external_id='user:1',
subscriptions={
'agenda': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-10-01',
},
],
},
pool=pool,
)
assert len(lines) == 3
line1, line2, line3 = lines
assert isinstance(line1, DraftInvoiceLine)
assert line1.invoice is None
assert line1.slug == 'agenda@event-1'
assert line1.label == 'Event 1'
assert line1.quantity == 1
assert line1.unit_amount == 1
assert line1.total_amount == 1
assert line1.user_external_id == 'user:1'
assert line1.payer_external_id == 'user:1'
assert line1.event == {
'agenda': 'agenda',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event-1',
'label': 'Event 1',
}
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'
assert line2.label == 'Event 2'
assert line2.quantity == 0
assert line2.unit_amount == 0
assert line2.total_amount == 0
assert line2.user_external_id == 'user:1'
assert line2.payer_external_id == 'user:1'
assert line2.event == {
'agenda': 'agenda',
'start_datetime': '2022-09-02T12:00:00+02:00',
'slug': 'event-2',
'label': 'Event 2',
}
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'
assert line3.label == 'Event 3'
assert line3.quantity == 1
assert line3.unit_amount == 3
assert line3.total_amount == 3
assert line3.user_external_id == 'user:1'
assert line3.payer_external_id == 'user:1'
assert line3.event == {
'agenda': 'agenda',
'start_datetime': '2022-09-03T12:00:00+02:00',
'slug': 'event-3',
'label': 'Event 3',
}
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')
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]]
# no subscriptions
assert (
utils.get_all_invoice_lines(
agendas=[agenda1, agenda2],
subscriptions={},
pool=pool,
)
== []
)
assert mock_user_lines.call_args_list == []
# with subscriptions
assert utils.get_all_invoice_lines(
agendas=[agenda1, agenda2],
subscriptions={
'user:1': {'foo': 'bar1'},
'user:2': {'foo': 'bar2'},
},
pool=pool,
) == [line1, line2, line3]
assert mock_user_lines.call_args_list == [
mock.call(
agendas=[agenda1, agenda2],
agendas_pricings=mock.ANY,
user_external_id='user:1',
subscriptions={'foo': 'bar1'},
pool=pool,
),
mock.call(
agendas=[agenda1, agenda2],
agendas_pricings=mock.ANY,
user_external_id='user:2',
subscriptions={'foo': 'bar2'},
pool=pool,
),
]
@mock.patch('lingo.invoicing.utils.get_check_status')
def test_get_all_invoice_lines_queryset(mock_status):
# don't mock get_pricing_data_for_event to check all querysets
category1 = CriteriaCategory.objects.create(label='Foo1', slug='foo1')
criteria1 = Criteria.objects.create(label='Bar1', slug='bar1', condition='True', category=category1)
category2 = CriteriaCategory.objects.create(label='Foo2', slug='foo2')
criteria2 = Criteria.objects.create(label='Bar2', slug='bar2', condition='True', category=category2)
pricing1 = Pricing.objects.create(label='Foo bar 1')
pricing1.criterias.add(criteria1, criteria2)
pricing1.categories.add(category1, through_defaults={'order': 1})
pricing1.categories.add(category2, through_defaults={'order': 2})
pricing2 = Pricing.objects.create(label='Foo bar 2')
pricing2.criterias.add(criteria1, criteria2)
pricing2.categories.add(category1, through_defaults={'order': 1})
pricing2.categories.add(category2, through_defaults={'order': 2})
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda_pricing11 = AgendaPricing.objects.create(
pricing=pricing1,
date_start=datetime.date(year=2022, month=9, day=1),
date_end=datetime.date(year=2022, month=10, day=1),
)
agenda_pricing11.agendas.add(agenda1)
agenda_pricing12 = AgendaPricing.objects.create(
pricing=pricing1,
date_start=datetime.date(year=2022, month=10, day=1),
date_end=datetime.date(year=2022, month=11, day=1),
)
agenda_pricing12.agendas.add(agenda1)
agenda2 = Agenda.objects.create(label='Agenda 2')
agenda_pricing21 = AgendaPricing.objects.create(
pricing=pricing2,
date_start=datetime.date(year=2022, month=9, day=1),
date_end=datetime.date(year=2022, month=10, day=1),
)
agenda_pricing21.agendas.add(agenda2)
agenda_pricing22 = AgendaPricing.objects.create(
pricing=pricing2,
date_start=datetime.date(year=2022, month=10, day=1),
date_end=datetime.date(year=2022, month=11, day=1),
)
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': {
'agenda': 'agenda-1',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
{
'event': {
'agenda': 'agenda-1',
'start_datetime': '2022-10-01T12:00:00+02:00',
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
{
'event': {
'agenda': 'agenda-2',
'start_datetime': '2022-09-01T12:00:00+02:00',
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
{
'event': {
'agenda': 'agenda-2',
'start_datetime': '2022-10-01T12:00:00+02:00',
'slug': 'event',
'label': 'Event',
},
'check_status': {'foo': 'bar'},
'booking': {'foo': 'baz'},
},
]
with CaptureQueriesContext(connection) as ctx:
lines = utils.get_all_invoice_lines(
agendas=[agenda1, agenda2],
subscriptions={
'user:1': {
'agenda-1': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-11-01',
},
],
'agenda-2': [
{
'user_external_id': 'user:1',
'date_start': '2022-09-01',
'date_end': '2022-11-01',
},
],
},
'user:2': {
'agenda-1': [
{
'user_external_id': 'user:2',
'date_start': '2022-09-01',
'date_end': '2022-11-01',
},
],
'agenda-2': [
{
'user_external_id': 'user:2',
'date_start': '2022-09-01',
'date_end': '2022-11-01',
},
],
},
},
pool=pool,
)
assert lines
assert len(ctx.captured_queries) == 7
def test_generate_invoices_from_lines():
regie1 = Regie.objects.create(label='Regie 1')
regie2 = Regie.objects.create(label='Regie 2')
agenda1 = Agenda.objects.create(label='Agenda 1', regie=regie1)
agenda2 = Agenda.objects.create(label='Agenda 2', regie=regie2)
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,
unit_amount=0,
total_amount=0,
user_external_id='user:1',
payer_external_id='user:1',
status='error',
pool=pool,
)
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',
pool=pool,
)
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',
pool=pool,
)
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',
pool=pool,
)
line4 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-2'},
quantity=1,
unit_amount=4,
total_amount=4,
user_external_id='user:1',
payer_external_id='user:2',
status='success',
pool=pool,
)
line5 = DraftInvoiceLine.objects.create(
event={'agenda': 'agenda-3'},
quantity=1,
unit_amount=5,
total_amount=5,
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'},
quantity=1,
unit_amount=5,
total_amount=5,
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
quantity=1,
unit_amount=6,
total_amount=6,
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],
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],
pool=pool,
)
assert len(invoices) == 3
invoice1, invoice2, invoice3 = invoices
# refresh total_amount field (triggered)
invoice1.refresh_from_db()
invoice2.refresh_from_db()
invoice3.refresh_from_db()
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, 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, 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, 31)
assert invoice3.regie == regie2
assert invoice3.payer == 'user:2'
assert invoice3.pool == pool
assert list(invoice3.lines.order_by('pk')) == [line4]
@mock.patch('lingo.invoicing.utils.get_agendas')
@mock.patch('lingo.invoicing.utils.get_all_subscriptions')
@mock.patch('lingo.invoicing.utils.get_all_invoice_lines')
@mock.patch('lingo.invoicing.utils.generate_invoices_from_lines')
def test_generate_invoices(mock_generate, mock_lines, mock_subscriptions, mock_agendas):
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),
)
mock_agendas.return_value = [agenda1, agenda2]
mock_subscriptions.return_value = {'foo': 'bar'}
mock_lines.return_value = ['foo', 'bar']
# check only calls between functions
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],
pool=pool,
)
]
assert mock_lines.call_args_list == [
mock.call(
agendas=[agenda1, agenda2],
subscriptions={'foo': 'bar'},
pool=pool,
)
]
assert mock_generate.call_args_list == [
mock.call(
agendas=[agenda1, agenda2],
all_lines=['foo', 'bar'],
pool=pool,
)
]
def test_generate_invoices_cmd():
with mock.patch(
'lingo.invoicing.management.commands.generate_invoices.generate_invoices'
) as mock_generate:
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices')
assert '%s' % excinfo.value == 'Error: the following arguments are required: date_start, date_end'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', 'bad-value', '2022-10-01')
assert '%s' % excinfo.value == 'Bad value "bad-value" for date_start'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-09-01', 'bad-value')
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 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