invoicing: add number on invoice (#73608)
gitea-wip/lingo/pipeline/pr-main This commit looks good
Details
gitea-wip/lingo/pipeline/pr-main This commit looks good
Details
This commit is contained in:
parent
a20d01203d
commit
0a48dcc92e
|
@ -0,0 +1,48 @@
|
|||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invoicing', '0010_event_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='draftinvoice',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='invoice',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='invoice',
|
||||
name='number',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Counter',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
('name', models.CharField(max_length=128)),
|
||||
('value', models.PositiveIntegerField(default=0)),
|
||||
(
|
||||
'regie',
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.Regie'),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('regie', 'name')},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -222,6 +222,9 @@ class Pool(models.Model):
|
|||
final_invoice.__class__ = Invoice
|
||||
final_invoice.pk = None
|
||||
final_invoice.pool = self
|
||||
final_invoice.number = Counter.get_count(
|
||||
regie=final_invoice.regie, name=final_invoice.created_at.strftime('%y-%m')
|
||||
)
|
||||
final_invoice.save()
|
||||
|
||||
for line in invoice.lines.all().order_by('pk'):
|
||||
|
@ -256,6 +259,7 @@ class AbstractInvoice(models.Model):
|
|||
date_issue = models.DateField(_('Issue date'))
|
||||
regie = models.ForeignKey(Regie, on_delete=models.PROTECT)
|
||||
payer = models.CharField(_('Payer'), max_length=300)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
pool = models.ForeignKey(Pool, on_delete=models.PROTECT)
|
||||
|
||||
|
@ -267,8 +271,33 @@ class DraftInvoice(AbstractInvoice):
|
|||
pass
|
||||
|
||||
|
||||
class Counter(models.Model):
|
||||
regie = models.ForeignKey(Regie, on_delete=models.PROTECT)
|
||||
name = models.CharField(max_length=128)
|
||||
value = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
unique_together = (('regie', 'name'),)
|
||||
|
||||
@classmethod
|
||||
def get_count(cls, regie, name):
|
||||
queryset = cls.objects.select_for_update()
|
||||
with transaction.atomic():
|
||||
counter, dummy = queryset.get_or_create(regie=regie, name=name)
|
||||
counter.value += 1
|
||||
counter.save()
|
||||
return counter.value
|
||||
|
||||
|
||||
class Invoice(AbstractInvoice):
|
||||
pass
|
||||
number = models.PositiveIntegerField(default=0)
|
||||
|
||||
def format_number(self):
|
||||
return 'F-%s-%s-%06d' % (
|
||||
self.regie.slug.upper(),
|
||||
self.created_at.strftime('%y-%m'),
|
||||
int(self.number),
|
||||
)
|
||||
|
||||
|
||||
class InjectedLine(models.Model):
|
||||
|
|
|
@ -30,7 +30,11 @@
|
|||
{% ifchanged line.invoice_id %}
|
||||
{% if not forloop.first %}</ul>{% endif %}
|
||||
<h3 data-invoice-id="{{ line.invoice_id }}">
|
||||
{% blocktrans with number=line.invoice_id payer=line.invoice.payer amount=line.invoice.total_amount %}Invoice #{{ number }} addressed to {{ payer }}, amount {{ amount }}€{% endblocktrans %}
|
||||
{% if pool.draft %}
|
||||
{% blocktrans with number=line.invoice_id payer=line.invoice.payer amount=line.invoice.total_amount %}Invoice TMP-{{ number }} addressed to {{ payer }}, amount {{ amount }}€{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with number=line.invoice.format_number payer=line.invoice.payer amount=line.invoice.total_amount %}Invoice {{ number }} addressed to {{ payer }}, amount {{ amount }}€{% endblocktrans %}
|
||||
{% endif %}
|
||||
</h3>
|
||||
<ul class="objects-list" data-invoice-id="{{ line.invoice_id }}">
|
||||
{% endifchanged %}
|
||||
|
|
|
@ -3,7 +3,7 @@ from unittest import mock
|
|||
|
||||
import pytest
|
||||
|
||||
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, InvoiceLine, Pool, Regie
|
||||
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine, Pool, Regie
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
@ -503,7 +503,11 @@ def test_detail_pool(app, admin_user):
|
|||
assert 'tag-error' not in resp
|
||||
|
||||
|
||||
def test_detail_pool_invoices(app, admin_user):
|
||||
@pytest.mark.parametrize('draft', [True, False])
|
||||
def test_detail_pool_invoices(app, admin_user, draft):
|
||||
invoice_model = DraftInvoice if draft else Invoice
|
||||
line_model = DraftInvoiceLine if draft else InvoiceLine
|
||||
|
||||
campaign = Campaign.objects.create(
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
|
@ -511,18 +515,23 @@ def test_detail_pool_invoices(app, admin_user):
|
|||
)
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=True,
|
||||
draft=draft,
|
||||
status='completed',
|
||||
)
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
invoice1 = DraftInvoice.objects.create(
|
||||
invoice1 = invoice_model.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:1'
|
||||
)
|
||||
invoice2 = DraftInvoice.objects.create(
|
||||
invoice2 = invoice_model.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:2'
|
||||
)
|
||||
if not draft:
|
||||
invoice1.number = 42
|
||||
invoice1.save()
|
||||
invoice2.number = 43
|
||||
invoice2.save()
|
||||
|
||||
line11 = DraftInvoiceLine.objects.create(
|
||||
line11 = line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice1,
|
||||
quantity=1,
|
||||
|
@ -534,7 +543,7 @@ def test_detail_pool_invoices(app, admin_user):
|
|||
user_external_id='user:1',
|
||||
user_name='User1 Name1',
|
||||
)
|
||||
line12 = DraftInvoiceLine.objects.create(
|
||||
line12 = line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 2),
|
||||
invoice=invoice1,
|
||||
quantity=1,
|
||||
|
@ -546,7 +555,7 @@ def test_detail_pool_invoices(app, admin_user):
|
|||
user_external_id='user:2',
|
||||
user_name='User2 Name2',
|
||||
)
|
||||
line13 = DraftInvoiceLine.objects.create(
|
||||
line13 = line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 3),
|
||||
invoice=invoice1,
|
||||
quantity=1,
|
||||
|
@ -559,7 +568,7 @@ def test_detail_pool_invoices(app, admin_user):
|
|||
user_name='User1 Name1',
|
||||
)
|
||||
|
||||
orphan_line = DraftInvoiceLine.objects.create(
|
||||
orphan_line = line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
quantity=1,
|
||||
unit_amount=42,
|
||||
|
@ -571,7 +580,7 @@ def test_detail_pool_invoices(app, admin_user):
|
|||
user_name='User1 Name1',
|
||||
)
|
||||
|
||||
line21 = DraftInvoiceLine.objects.create(
|
||||
line21 = line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice2,
|
||||
quantity=1,
|
||||
|
@ -587,10 +596,17 @@ def test_detail_pool_invoices(app, admin_user):
|
|||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool.pk))
|
||||
assert '#%s' % orphan_line.pk not in resp
|
||||
assert (
|
||||
resp.pyquery('h3[data-invoice-id="%s"]' % invoice1.pk).text()
|
||||
== 'Invoice #%s addressed to payer:1, amount 6.00€' % invoice1.pk
|
||||
)
|
||||
if draft:
|
||||
assert (
|
||||
resp.pyquery('h3[data-invoice-id="%s"]' % invoice1.pk).text()
|
||||
== 'Invoice TMP-%s addressed to payer:1, amount 6.00€' % invoice1.pk
|
||||
)
|
||||
else:
|
||||
assert resp.pyquery(
|
||||
'h3[data-invoice-id="%s"]' % invoice1.pk
|
||||
).text() == 'Invoice F-FOO-%s-000042 addressed to payer:1, amount 6.00€' % invoice1.created_at.strftime(
|
||||
'%y-%m'
|
||||
)
|
||||
assert len(resp.pyquery('ul[data-invoice-id="%s"] li' % invoice1.pk)) == 3
|
||||
assert (
|
||||
resp.pyquery('ul[data-invoice-id="%s"] li:nth-child(1)' % invoice1.pk).text()
|
||||
|
@ -604,10 +620,17 @@ def test_detail_pool_invoices(app, admin_user):
|
|||
resp.pyquery('ul[data-invoice-id="%s"] li:nth-child(3)' % invoice1.pk).text()
|
||||
== '#%s User2 Name2 - 02/09/2022 - Label 12 (2.00)' % line12.pk
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('h3[data-invoice-id="%s"]' % invoice2.pk).text()
|
||||
== 'Invoice #%s addressed to payer:2, amount 1.00€' % invoice2.pk
|
||||
)
|
||||
if draft:
|
||||
assert (
|
||||
resp.pyquery('h3[data-invoice-id="%s"]' % invoice2.pk).text()
|
||||
== 'Invoice TMP-%s addressed to payer:2, amount 1.00€' % invoice2.pk
|
||||
)
|
||||
else:
|
||||
assert resp.pyquery(
|
||||
'h3[data-invoice-id="%s"]' % invoice2.pk
|
||||
).text() == 'Invoice F-FOO-%s-000043 addressed to payer:2, amount 1.00€' % invoice2.created_at.strftime(
|
||||
'%y-%m'
|
||||
)
|
||||
assert len(resp.pyquery('ul[data-invoice-id="%s"] li' % invoice2.pk)) == 1
|
||||
assert (
|
||||
resp.pyquery('ul[data-invoice-id="%s"] li:nth-child(1)' % invoice2.pk).text()
|
||||
|
|
|
@ -13,6 +13,7 @@ from lingo.agendas.models import Agenda
|
|||
from lingo.invoicing import utils
|
||||
from lingo.invoicing.models import (
|
||||
Campaign,
|
||||
Counter,
|
||||
DraftInvoice,
|
||||
DraftInvoiceLine,
|
||||
InjectedLine,
|
||||
|
@ -1400,7 +1401,9 @@ def test_generate_invoices_cmd():
|
|||
|
||||
|
||||
def test_promote_pool():
|
||||
regie = Regie.objects.create(label='Regie')
|
||||
today = datetime.date.today()
|
||||
regie1 = Regie.objects.create(label='Regie1')
|
||||
regie2 = Regie.objects.create(label='Regie2')
|
||||
campaign = Campaign.objects.create(
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
|
@ -1427,7 +1430,7 @@ def test_promote_pool():
|
|||
)
|
||||
|
||||
invoice1 = DraftInvoice.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:1'
|
||||
date_issue=datetime.date.today(), regie=regie1, pool=pool, payer='payer:1'
|
||||
)
|
||||
line11 = DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1454,7 +1457,7 @@ def test_promote_pool():
|
|||
total_amount=2,
|
||||
user_external_id='user:2',
|
||||
payer_external_id='payer:1',
|
||||
regie=regie,
|
||||
regie=regie1,
|
||||
)
|
||||
line12 = DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1472,7 +1475,7 @@ def test_promote_pool():
|
|||
pool=pool,
|
||||
)
|
||||
invoice2 = DraftInvoice.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:2'
|
||||
date_issue=datetime.date.today(), regie=regie1, pool=pool, payer='payer:2'
|
||||
)
|
||||
injected_line21 = InjectedLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1483,7 +1486,7 @@ def test_promote_pool():
|
|||
total_amount=1,
|
||||
user_external_id='user:2',
|
||||
payer_external_id='payer:2',
|
||||
regie=regie,
|
||||
regie=regie1,
|
||||
)
|
||||
line21 = DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1541,8 +1544,12 @@ def test_promote_pool():
|
|||
pool=pool,
|
||||
)
|
||||
|
||||
invoice3 = DraftInvoice.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie2, pool=pool, payer='payer:1'
|
||||
)
|
||||
|
||||
old_invoice = DraftInvoice.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie, pool=old_pool, payer='payer:1'
|
||||
date_issue=datetime.date.today(), regie=regie1, pool=old_pool, payer='payer:1'
|
||||
)
|
||||
DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1567,7 +1574,7 @@ def test_promote_pool():
|
|||
total_amount=2,
|
||||
user_external_id='user:2',
|
||||
payer_external_id='payer:1',
|
||||
regie=regie,
|
||||
regie=regie1,
|
||||
)
|
||||
DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1586,7 +1593,7 @@ def test_promote_pool():
|
|||
)
|
||||
|
||||
other_invoice = DraftInvoice.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie, pool=other_pool, payer='payer:1'
|
||||
date_issue=datetime.date.today(), regie=regie1, pool=other_pool, payer='payer:1'
|
||||
)
|
||||
DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1611,7 +1618,7 @@ def test_promote_pool():
|
|||
total_amount=2,
|
||||
user_external_id='user:2',
|
||||
payer_external_id='payer:1',
|
||||
regie=regie,
|
||||
regie=regie1,
|
||||
)
|
||||
DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -1635,7 +1642,7 @@ def test_promote_pool():
|
|||
assert Campaign.objects.count() == 2
|
||||
assert Pool.objects.count() == 3
|
||||
assert Pool.objects.filter(draft=False).count() == 0
|
||||
assert DraftInvoice.objects.count() == 4
|
||||
assert DraftInvoice.objects.count() == 5
|
||||
assert DraftInvoiceLine.objects.count() == 10
|
||||
assert Invoice.objects.count() == 0
|
||||
assert InvoiceLine.objects.count() == 0
|
||||
|
@ -1647,11 +1654,13 @@ def test_promote_pool():
|
|||
assert Campaign.objects.count() == 2
|
||||
assert Pool.objects.count() == 4
|
||||
assert Pool.objects.filter(draft=False).count() == 1
|
||||
assert DraftInvoice.objects.count() == 4
|
||||
assert DraftInvoice.objects.count() == 5
|
||||
assert DraftInvoiceLine.objects.count() == 10
|
||||
assert Invoice.objects.count() == 2
|
||||
assert Invoice.objects.count() == 3
|
||||
assert InvoiceLine.objects.count() == 6
|
||||
assert InjectedLine.objects.count() == 4
|
||||
assert Counter.objects.get(regie=regie1, name=today.strftime('%y-%m')).value == 2
|
||||
assert Counter.objects.get(regie=regie2, name=today.strftime('%y-%m')).value == 1
|
||||
|
||||
test_counts()
|
||||
|
||||
|
@ -1664,10 +1673,12 @@ def test_promote_pool():
|
|||
|
||||
final_invoice1 = Invoice.objects.order_by('pk')[0]
|
||||
assert final_invoice1.date_issue == invoice1.date_issue
|
||||
assert final_invoice1.regie == regie
|
||||
assert final_invoice1.regie == regie1
|
||||
assert final_invoice1.pool == final_pool
|
||||
assert final_invoice1.payer == invoice1.payer
|
||||
assert final_invoice1.total_amount == invoice1.total_amount == 3
|
||||
assert final_invoice1.number == 1
|
||||
assert final_invoice1.format_number() == 'F-REGIE1-%s-000001' % today.strftime('%y-%m')
|
||||
|
||||
final_line11 = InvoiceLine.objects.order_by('pk')[0]
|
||||
assert final_line11.event_date == line11.event_date
|
||||
|
@ -1705,10 +1716,12 @@ def test_promote_pool():
|
|||
|
||||
final_invoice2 = Invoice.objects.order_by('pk')[1]
|
||||
assert final_invoice2.date_issue == invoice2.date_issue
|
||||
assert final_invoice2.regie == regie
|
||||
assert final_invoice2.regie == regie1
|
||||
assert final_invoice2.pool == final_pool
|
||||
assert final_invoice2.payer == invoice2.payer
|
||||
assert final_invoice2.total_amount == invoice2.total_amount == 3
|
||||
assert final_invoice2.number == 2
|
||||
assert final_invoice2.format_number() == 'F-REGIE1-%s-000002' % today.strftime('%y-%m')
|
||||
|
||||
final_line21 = InvoiceLine.objects.order_by('pk')[2]
|
||||
assert final_line21.event_date == line21.event_date
|
||||
|
@ -1776,6 +1789,15 @@ def test_promote_pool():
|
|||
assert final_orphan_line2.pool == final_pool
|
||||
assert final_orphan_line2.from_injected_line is None
|
||||
|
||||
final_invoice3 = Invoice.objects.order_by('pk')[2]
|
||||
assert final_invoice3.date_issue == invoice3.date_issue
|
||||
assert final_invoice3.regie == regie2
|
||||
assert final_invoice3.pool == final_pool
|
||||
assert final_invoice3.payer == invoice3.payer
|
||||
assert final_invoice3.total_amount == invoice3.total_amount == 0
|
||||
assert final_invoice3.number == 1
|
||||
assert final_invoice3.format_number() == 'F-REGIE2-%s-000001' % today.strftime('%y-%m')
|
||||
|
||||
with pytest.raises(PoolPromotionError) as excinfo:
|
||||
old_pool.promote()
|
||||
assert '%s' % excinfo.value == 'Pool too old'
|
||||
|
|
|
@ -2,7 +2,16 @@ import datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine, Pool, Regie
|
||||
from lingo.invoicing.models import (
|
||||
Campaign,
|
||||
Counter,
|
||||
DraftInvoice,
|
||||
DraftInvoiceLine,
|
||||
Invoice,
|
||||
InvoiceLine,
|
||||
Pool,
|
||||
Regie,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -151,3 +160,37 @@ def test_invoice_total_amount(draft):
|
|||
assert invoice.total_amount == 12
|
||||
invoice2.refresh_from_db()
|
||||
assert invoice2.total_amount == 0
|
||||
|
||||
|
||||
def test_counter():
|
||||
regie1 = Regie.objects.create()
|
||||
regie2 = Regie.objects.create()
|
||||
|
||||
assert Counter.get_count(regie=regie1, name='foo') == 1
|
||||
assert Counter.objects.count() == 1
|
||||
counter1 = Counter.objects.get(regie=regie1, name='foo')
|
||||
assert counter1.value == 1
|
||||
|
||||
assert Counter.get_count(regie=regie1, name='foo') == 2
|
||||
counter1.refresh_from_db()
|
||||
assert counter1.value == 2
|
||||
|
||||
assert Counter.get_count(regie=regie1, name='foo') == 3
|
||||
counter1.refresh_from_db()
|
||||
assert counter1.value == 3
|
||||
|
||||
assert Counter.get_count(regie=regie2, name='foo') == 1
|
||||
assert Counter.objects.count() == 2
|
||||
counter1.refresh_from_db()
|
||||
assert counter1.value == 3
|
||||
counter2 = Counter.objects.get(regie=regie2, name='foo')
|
||||
assert counter2.value == 1
|
||||
|
||||
assert Counter.get_count(regie=regie2, name='bar') == 1
|
||||
assert Counter.objects.count() == 3
|
||||
counter1.refresh_from_db()
|
||||
assert counter1.value == 3
|
||||
counter2.refresh_from_db()
|
||||
assert counter2.value == 1
|
||||
counter3 = Counter.objects.get(regie=regie2, name='bar')
|
||||
assert counter3.value == 1
|
||||
|
|
Loading…
Reference in New Issue