invoicing: compute and set accounting code on journal lines (#89025)

This commit is contained in:
Lauréline Guérin 2024-04-09 14:50:21 +02:00 committed by Lauréline Guérin
parent cfc2ff7b70
commit f7b4199846
6 changed files with 307 additions and 86 deletions

View File

@ -767,6 +767,7 @@ class AbstractJournalLine(models.Model):
'Effort rate target is not a %(wanted)s: %(effort_rate_target)s'
),
'PricingEffortRateTargetValueError': _('Effort rate target bad value: %(effort_rate_target)s'),
'PricingAccountingCodeError': _('Impossible to determine an accounting code'),
'PricingUnknownCheckStatusError': _('Unknown check status: %(status)s'),
'PricingEventNotCheckedError': _('Event is not checked'),
'PricingBookingNotCheckedError': _('Booking is not checked'),

View File

@ -177,6 +177,7 @@ def get_invoice_lines_for_user(
'booking': serialized_booking,
'status': 'success',
'pool': pool,
'accounting_code': pricing_data.get('accounting_code') or '',
}
booking_details = pricing_data.get('booking_details') or {}
if agenda.partial_bookings and booking_details.get('status') in ['absence', 'presence']:

View File

@ -80,6 +80,10 @@ class PricingEffortRateTargetValueError(PricingError):
pass
class PricingAccountingCodeError(PricingError):
pass
class PricingUnknownCheckStatusError(PricingError):
pass
@ -476,6 +480,12 @@ class Pricing(WithApplicationMixin, models.Model):
user_external_id=user_external_id,
payer_external_id=payer_external_id,
)
accounting_code = self.compute_accounting_code(
request=request,
original_context=context,
user_external_id=user_external_id,
payer_external_id=payer_external_id,
)
return {
'pricing': pricing,
'calculation_details': {
@ -485,6 +495,7 @@ class Pricing(WithApplicationMixin, models.Model):
'effort_rate': effort_rate,
'context': context,
},
'accounting_code': accounting_code,
}
def get_pricing_data_for_event(
@ -518,6 +529,12 @@ class Pricing(WithApplicationMixin, models.Model):
payer_external_id=payer_external_id,
)
modifier = self.get_booking_modifier(agenda=agenda, check_status=check_status)
accounting_code = self.compute_accounting_code(
request=request,
original_context=context,
user_external_id=user_external_id,
payer_external_id=payer_external_id,
)
return self.aggregate_pricing_data(
pricing=pricing,
criterias=criterias,
@ -525,9 +542,12 @@ class Pricing(WithApplicationMixin, models.Model):
effort_rate=effort_rate,
context=context,
modifier=modifier,
accounting_code=accounting_code,
)
def aggregate_pricing_data(self, pricing, criterias, reduction_rate, effort_rate, context, modifier):
def aggregate_pricing_data(
self, pricing, criterias, reduction_rate, effort_rate, context, modifier, accounting_code
):
if modifier['modifier_type'] == 'fixed':
pricing_amount = modifier['modifier_fixed']
else:
@ -542,6 +562,7 @@ class Pricing(WithApplicationMixin, models.Model):
'context': context,
},
'booking_details': modifier,
'accounting_code': accounting_code,
}
@staticmethod
@ -633,7 +654,7 @@ class Pricing(WithApplicationMixin, models.Model):
return round(pricing, 4), criterias
return round(pricing, 2), criterias
def compute_reduction_rate(self, request, original_context, user_external_id, payer_external_id):
def _compute_template(self, request, original_context, user_external_id, payer_external_id, template):
context = RequestContext(request)
context.push(original_context)
context.push({'user_external_id': user_external_id, 'payer_external_id': payer_external_id})
@ -641,8 +662,13 @@ class Pricing(WithApplicationMixin, models.Model):
context['user_external_raw_id'] = user_external_id.split(':')[1]
if ':' in payer_external_id:
context['payer_external_raw_id'] = payer_external_id.split(':')[1]
return Template(template).render(context)
def compute_reduction_rate(self, request, original_context, user_external_id, payer_external_id):
try:
reduction_rate = Template(self.reduction_rate).render(context)
reduction_rate = self._compute_template(
request, original_context, user_external_id, payer_external_id, self.reduction_rate
)
except (TemplateSyntaxError, VariableDoesNotExist):
raise PricingReductionRateError()
@ -683,15 +709,10 @@ class Pricing(WithApplicationMixin, models.Model):
}
def compute_effort_rate_target(self, request, original_context, user_external_id, payer_external_id):
context = RequestContext(request)
context.push(original_context)
context.push({'user_external_id': user_external_id, 'payer_external_id': payer_external_id})
if ':' in user_external_id:
context['user_external_raw_id'] = user_external_id.split(':')[1]
if ':' in payer_external_id:
context['payer_external_raw_id'] = payer_external_id.split(':')[1]
try:
effort_rate_target = Template(self.effort_rate_target).render(context)
effort_rate_target = self._compute_template(
request, original_context, user_external_id, payer_external_id, self.effort_rate_target
)
except (TemplateSyntaxError, VariableDoesNotExist):
raise PricingEffortRateTargetError()
@ -734,6 +755,14 @@ class Pricing(WithApplicationMixin, models.Model):
'bounded_pricing': adjusted_pricing,
}
def compute_accounting_code(self, request, original_context, user_external_id, payer_external_id):
try:
return self._compute_template(
request, original_context, user_external_id, payer_external_id, self.accounting_code
)
except (TemplateSyntaxError, VariableDoesNotExist):
raise PricingAccountingCodeError()
def get_booking_modifier(self, agenda, check_status):
status = check_status['status']
if status not in ['error', 'not-booked', 'cancelled', 'presence', 'absence']:

View File

@ -2524,6 +2524,7 @@ def test_journal_pool_lines(app, admin_user, draft):
('PricingEffortRateTargetError', {}),
('PricingEffortRateTargetFormatError', {'effort_rate_target': 'foo', 'wanted': 'decimal'}),
('PricingEffortRateTargetValueError', {'effort_rate_target': 42}),
('PricingAccountingCodeError', {}),
('PricingUnknownCheckStatusError', {'status': 'unknown'}),
('PricingEventNotCheckedError', {}),
('PricingBookingNotCheckedError', {}),
@ -2642,7 +2643,7 @@ def test_journal_pool_lines(app, admin_user, draft):
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk)
)
assert len(resp.pyquery('td.status')) == 29
assert len(resp.pyquery('td.status')) == 30
assert format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[0].pk).text()) == 'Success'
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[0].pk).text().strip()
@ -2749,156 +2750,165 @@ def test_journal_pool_lines(app, admin_user, draft):
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[12].pk).text())
== 'Error (Unknown check status: unknown) ignore - mark as fixed'
== 'Error (Impossible to determine an accounting code) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[12].pk).text().strip()
== "{'error': 'PricingUnknownCheckStatusError', 'error_details': {'status': 'unknown'}} "
== "{'error': 'PricingAccountingCodeError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[13].pk).text())
== 'Error (Event is not checked) ignore - mark as fixed'
== 'Error (Unknown check status: unknown) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[13].pk).text().strip()
== "{'error': 'PricingEventNotCheckedError', 'error_details': {}} "
== "{'error': 'PricingUnknownCheckStatusError', 'error_details': {'status': 'unknown'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[14].pk).text())
== 'Error (Booking is not checked) ignore - mark as fixed'
== 'Error (Event is not checked) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[14].pk).text().strip()
== "{'error': 'PricingBookingNotCheckedError', 'error_details': {}} "
== "{'error': 'PricingEventNotCheckedError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[15].pk).text())
== 'Error (Multiple booking found) ignore - mark as fixed'
== 'Error (Booking is not checked) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[15].pk).text().strip()
== "{'error': 'PricingMultipleBookingError', 'error_details': {}} "
== "{'error': 'PricingBookingNotCheckedError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[16].pk).text())
== 'Error (Check type error: not found) ignore - mark as fixed'
== 'Error (Multiple booking found) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[16].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'reason': 'not-found'}} "
== "{'error': 'PricingMultipleBookingError', 'error_details': {}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[17].pk).text())
== 'Error (Check type error: pricing not configured (group: foo-bar, check type: foo-reason)) ignore - mark as fixed'
== 'Error (Check type error: not found) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[17].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'reason': 'not-found'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[18].pk).text())
== 'Error (Check type error: pricing not configured (group: foo-bar, check type: foo-reason)) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[18].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'check_type': 'foo-reason', "
"'check_type_group': 'foo-bar', 'reason': 'not-configured'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[18].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[19].pk).text())
== 'Error (Check type error: wrong kind (group: foo-bar, check type: foo-reason)) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[18].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[19].pk).text().strip()
== "{'error': 'PricingBookingCheckTypeError', 'error_details': {'check_type': 'foo-reason', "
"'check_type_group': 'foo-bar', 'reason': 'wrong-kind'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[19].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[20].pk).text())
== 'Error (Impossible to determine payer: template is empty) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[19].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[20].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'empty-template'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[20].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[21].pk).text())
== 'Error (Impossible to determine payer: result is empty) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[20].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[21].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'empty-result'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[21].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[22].pk).text())
== 'Error (Impossible to determine payer: syntax error) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[21].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[22].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'syntax-error'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[22].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[23].pk).text())
== 'Error (Impossible to determine payer: variable error) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[22].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[23].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'variable-error'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[23].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[24].pk).text())
== 'Error (Impossible to determine payer: card model is not configured) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[23].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[24].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'missing-card-model'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[24].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[25].pk).text())
== 'Error (Impossible to determine payer: payer is not configured on regie) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[24].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[25].pk).text().strip()
== "{'error': 'PayerError', 'error_details': {'reason': 'missing-payer'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[25].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[26].pk).text())
== 'Error (Impossible to get payer foo: mapping not defined) ignore - mark as fixed'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[25].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[26].pk).text().strip()
== "{'error': 'PayerDataError', 'error_details': {'key': 'foo', 'reason': 'not-defined'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[26].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[27].pk).text())
== 'Fixed (Impossible to get payer foo: result is empty) reset'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[26].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[27].pk).text().strip()
== "{'error': 'PayerDataError', 'error_details': {'key': 'foo', 'reason': 'empty-result'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[27].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[28].pk).text())
== 'Ignored (Impossible to get payer foo: result is not a boolean) reset'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[27].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[28].pk).text().strip()
== "{'error': 'PayerDataError', 'error_details': {'key': 'foo', 'reason': 'not-a-boolean'}} "
"{'agenda': 'agenda-a', 'primary_event': 'event-aa', 'slug': 'event-aa--date'} {}"
)
assert (
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[28].pk).text())
format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[29].pk).text())
== 'Success (Injected)'
)
assert (
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[28].pk).text().strip()
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[29].pk).text().strip()
== "{'foo': 'bar'} {} {}"
)
@ -2934,12 +2944,12 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_external_id': 'payer:2'},
)
assert len(resp.pyquery('tr td.status')) == 28
assert len(resp.pyquery('tr td.status')) == 29
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_first_name': 'first'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 30
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_first_name': 'first1'},
@ -2949,7 +2959,7 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_last_name': 'last'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 30
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_last_name': 'last1'},
@ -2964,12 +2974,12 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'payer_direct_debit': True},
)
assert len(resp.pyquery('tr td.status')) == 28
assert len(resp.pyquery('tr td.status')) == 29
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_external_id': 'user:1'},
)
assert len(resp.pyquery('tr td.status')) == 28
assert len(resp.pyquery('tr td.status')) == 29
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_external_id': 'user:2'},
@ -2979,27 +2989,27 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_first_name': 'first'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 30
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_first_name': 'first1'},
)
assert len(resp.pyquery('tr td.status')) == 28
assert len(resp.pyquery('tr td.status')) == 29
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_last_name': 'last'},
)
assert len(resp.pyquery('tr td.status')) == 29
assert len(resp.pyquery('tr td.status')) == 30
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'user_last_name': 'last1'},
)
assert len(resp.pyquery('tr td.status')) == 28
assert len(resp.pyquery('tr td.status')) == 29
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'agenda': 'agenda-a'},
)
assert len(resp.pyquery('tr td.status')) == 27
assert len(resp.pyquery('tr td.status')) == 28
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'agenda': 'agenda-b'},
@ -3014,7 +3024,7 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'event': 'agenda-a@event-aa'},
)
assert len(resp.pyquery('tr td.status')) == 26
assert len(resp.pyquery('tr td.status')) == 27
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'event': 'agenda-b@event-b'},
@ -3039,12 +3049,12 @@ def test_journal_pool_lines(app, admin_user, draft):
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'status': 'error'},
)
assert len(resp.pyquery('tr td.status')) == 26
assert len(resp.pyquery('tr td.status')) == 27
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'status': 'error_todo'},
)
assert len(resp.pyquery('tr td.status')) == 24
assert len(resp.pyquery('tr td.status')) == 25
resp = app.get(
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/' % (regie.pk, campaign.pk, pool.pk),
params={'status': 'error_ignored'},

View File

@ -437,10 +437,10 @@ def test_get_invoice_lines_for_user_check_status(
# correct data
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 1},
{'foo2': 'bar2', 'pricing': 2},
{'foo3': 'bar3', 'pricing': 3},
{'foo4': 'bar4', 'pricing': 4},
{'foo1': 'bar1', 'pricing': 1, 'accounting_code': '414141'},
{'foo2': 'bar2', 'pricing': 2, 'accounting_code': '424242'},
{'foo3': 'bar3', 'pricing': 3, 'accounting_code': '434343'},
{'foo4': 'bar4', 'pricing': 4, 'accounting_code': '444444'},
]
mock_payer.return_value = 'payer:1'
mock_status.return_value = [
@ -591,7 +591,8 @@ def test_get_invoice_lines_for_user_check_status(
'label': 'Event 1',
}
assert line1.booking == {'foo': 'baz1'}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'accounting_code': '414141'}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
assert line1.pool == pool
assert line1.from_injected_line is None
@ -617,7 +618,8 @@ def test_get_invoice_lines_for_user_check_status(
'label': 'Event 2',
}
assert line2.booking == {'foo': 'baz2'}
assert line2.pricing_data == {'foo2': 'bar2', 'pricing': 2}
assert line2.pricing_data == {'foo2': 'bar2', 'pricing': 2, 'accounting_code': '424242'}
assert line2.accounting_code == '424242'
assert line2.status == 'success'
assert line2.pool == pool
assert line2.from_injected_line is None
@ -643,7 +645,8 @@ def test_get_invoice_lines_for_user_check_status(
'label': 'Eveeent 1',
}
assert line3.booking == {'foo': 'bazzz1'}
assert line3.pricing_data == {'foo3': 'bar3', 'pricing': 3}
assert line3.pricing_data == {'foo3': 'bar3', 'pricing': 3, 'accounting_code': '434343'}
assert line3.accounting_code == '434343'
assert line3.status == 'success'
assert line3.pool == pool
assert line3.from_injected_line is None
@ -669,7 +672,8 @@ def test_get_invoice_lines_for_user_check_status(
'label': 'Eveeent 2',
}
assert line4.booking == {'foo': 'bazzz2'}
assert line4.pricing_data == {'foo4': 'bar4', 'pricing': 4}
assert line4.pricing_data == {'foo4': 'bar4', 'pricing': 4, 'accounting_code': '444444'}
assert line4.accounting_code == '444444'
assert line4.status == 'success'
assert line4.pool == pool
assert line4.from_injected_line is None
@ -693,6 +697,7 @@ def test_get_invoice_lines_for_user_check_status(
assert line5.event == {}
assert line5.booking == {}
assert line5.pricing_data == {}
assert line5.accounting_code == ''
assert line5.status == 'success'
assert line5.pool == pool
assert line5.from_injected_line == injected_line1
@ -714,6 +719,7 @@ def test_get_invoice_lines_for_user_check_status(
assert line6.event == {}
assert line6.booking == {}
assert line6.pricing_data == {}
assert line6.accounting_code == ''
assert line6.status == 'success'
assert line6.pool == pool
assert line6.from_injected_line == injected_line2
@ -735,6 +741,7 @@ def test_get_invoice_lines_for_user_check_status(
assert line7.event == {}
assert line7.booking == {}
assert line7.pricing_data == {}
assert line7.accounting_code == ''
assert line7.status == 'success'
assert line7.pool == pool
assert line7.from_injected_line == injected_line4
@ -768,9 +775,9 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings(
)
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 1},
{'foo2': 'bar2', 'pricing': 2},
{'foo3': 'bar3', 'pricing': 3},
{'foo1': 'bar1', 'pricing': 1, 'accounting_code': '414141'},
{'foo2': 'bar2', 'pricing': 2, 'accounting_code': '424242'},
{'foo3': 'bar3', 'pricing': 3, 'accounting_code': '434343'},
]
mock_payer.return_value = 'payer:1'
mock_status.return_value = [
@ -888,7 +895,8 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings(
'label': 'Event 1',
}
assert line1.booking == {'foo': 'baz1'}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'accounting_code': '414141'}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
assert line1.pool == pool
assert line1.from_injected_line is None
@ -915,7 +923,8 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings(
'label': 'Event 2',
}
assert line2.booking == {'foo': 'baz2', 'computed_duration': 45}
assert line2.pricing_data == {'foo2': 'bar2', 'pricing': 2}
assert line2.pricing_data == {'foo2': 'bar2', 'pricing': 2, 'accounting_code': '424242'}
assert line2.accounting_code == '424242'
assert line2.status == 'success'
assert line2.pool == pool
assert line2.from_injected_line is None
@ -942,7 +951,8 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings(
'label': 'Event 2',
}
assert line3.booking == {'foo': 'baz3', 'computed_duration': 70}
assert line3.pricing_data == {'foo3': 'bar3', 'pricing': 3}
assert line3.pricing_data == {'foo3': 'bar3', 'pricing': 3, 'accounting_code': '434343'}
assert line3.accounting_code == '434343'
assert line3.status == 'success'
assert line3.pool == pool
assert line3.from_injected_line is None
@ -977,7 +987,12 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_without_bookin
# presence but no booking, and no check_type
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}},
{
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
},
]
mock_payer.return_value = 'payer:1'
mock_status.return_value = [
@ -1036,7 +1051,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_without_bookin
assert line1.quantity == 120
assert line1.quantity_type == 'minutes'
assert line1.booking == {'foo': 'baz1', 'computed_duration': 120}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}}
assert line1.pricing_data == {
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
# presence but no booking, and check_type
@ -1047,11 +1068,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_without_bookin
'foo1': 'bar1',
'pricing': 2,
'booking_details': {'status': 'presence', 'check_type': 'foo', 'check_type_group': 'foobar'},
'accounting_code': '414141',
},
{
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}, # second call to get normal pricing
]
mock_pricing_data_event.reset_mock()
@ -1116,7 +1139,9 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_without_bookin
'foo1': 'bar1',
'pricing': 2,
'booking_details': {'status': 'presence', 'check_type': 'foo', 'check_type_group': 'foobar'},
'accounting_code': '414141',
}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
@ -1149,7 +1174,12 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
# presence, and no check_type, no overtaking
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}},
{
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
},
]
mock_payer.return_value = 'payer:1'
mock_status.return_value = [
@ -1207,12 +1237,23 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
assert line1.quantity == 120
assert line1.quantity_type == 'minutes'
assert line1.booking == {'foo': 'baz1', 'computed_duration': 120, 'adjusted_duration': 120}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}}
assert line1.pricing_data == {
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
# presence, and no check_type, with overtaking
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}},
{
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
},
]
mock_pricing_data_event.reset_mock()
mock_status.return_value = [
@ -1273,7 +1314,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
assert line1.quantity_type == 'minutes'
assert line1.event['primary_event'] == 'event-1'
assert line1.booking == {'foo': 'baz1', 'computed_duration': 120, 'adjusted_duration': 90}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}}
assert line1.pricing_data == {
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
assert line2.event_date == datetime.date(2022, 9, 1)
assert line2.slug == 'agenda@event-1'
@ -1284,16 +1331,28 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
assert line2.quantity_type == 'minutes'
assert line2.event['primary_event'] == 'event-1::overtaking'
assert line2.booking == {'foo': 'baz1', 'computed_duration': 120, 'adjusted_duration': 90}
assert line2.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}}
assert line2.pricing_data == {
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}
assert line2.accounting_code == '414141'
assert line2.status == 'success'
# absence, and no check_type
mock_pricing_data_event.side_effect = [
{'foo1': 'bar1', 'pricing': 0, 'booking_details': {'status': 'absence'}},
{
'foo1': 'bar1',
'pricing': 0,
'booking_details': {'status': 'absence'},
'accounting_code': '414141',
},
{
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}, # second call to get normal pricing
]
mock_pricing_data_event.reset_mock()
@ -1370,7 +1429,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
assert line1.quantity_type == 'minutes'
assert line1.event['primary_event'] == 'event-1'
assert line1.booking == {'foo': 'baz1', 'computed_duration': 120, 'adjusted_duration': 120}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}}
assert line1.pricing_data == {
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
assert line2.event_date == datetime.date(2022, 9, 1)
assert line2.slug == 'agenda@event-1'
@ -1381,7 +1446,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
assert line2.quantity_type == 'minutes'
assert line2.event['primary_event'] == 'event-1:absence:'
assert line2.booking == {'foo': 'baz1', 'computed_duration': 120, 'adjusted_duration': 120}
assert line2.pricing_data == {'foo1': 'bar1', 'pricing': 0, 'booking_details': {'status': 'absence'}}
assert line2.pricing_data == {
'foo1': 'bar1',
'pricing': 0,
'booking_details': {'status': 'absence'},
'accounting_code': '414141',
}
assert line2.accounting_code == '414141'
assert line2.status == 'success'
# presence, and check_type, with overtaking
@ -1392,11 +1463,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
'foo1': 'bar1',
'pricing': 1.5,
'booking_details': {'status': 'presence', 'check_type': 'foo', 'check_type_group': 'foobar'},
'accounting_code': '414141',
},
{
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}, # second call to get normal pricing
]
mock_pricing_data_event.reset_mock()
@ -1473,7 +1546,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
assert line1.quantity_type == 'minutes'
assert line1.event['primary_event'] == 'event-1'
assert line1.booking == {'foo': 'baz1', 'computed_duration': 120, 'adjusted_duration': 90}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}}
assert line1.pricing_data == {
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}
assert line1.accounting_code == '414141'
assert line1.status == 'success'
assert line2.event_date == datetime.date(2022, 9, 1)
assert line2.slug == 'agenda@event-1'
@ -1484,7 +1563,13 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
assert line2.quantity_type == 'minutes'
assert line2.event['primary_event'] == 'event-1::overtaking'
assert line2.booking == {'foo': 'baz1', 'computed_duration': 120, 'adjusted_duration': 90}
assert line2.pricing_data == {'foo1': 'bar1', 'pricing': 1, 'booking_details': {'status': 'presence'}}
assert line2.pricing_data == {
'foo1': 'bar1',
'pricing': 1,
'booking_details': {'status': 'presence'},
'accounting_code': '414141',
}
assert line2.accounting_code == '414141'
assert line2.status == 'success'
assert line3.event_date == datetime.date(2022, 9, 1)
assert line3.slug == 'agenda@event-1'
@ -1499,7 +1584,9 @@ def test_get_invoice_lines_for_user_check_status_partial_bookings_with_booking(
'foo1': 'bar1',
'pricing': 1.5,
'booking_details': {'status': 'presence', 'check_type': 'foo', 'check_type_group': 'foobar'},
'accounting_code': '414141',
}
assert line3.accounting_code == '414141'
assert line3.status == 'success'
@ -1731,6 +1818,7 @@ def test_get_invoice_lines_for_user_get_payer_id_error(mock_payer, mock_status):
}
assert line.booking == {'foo': 'baz'}
assert line.pricing_data == {'error': 'PayerError', 'error_details': {'foo': 'bar'}}
assert line.accounting_code == ''
assert line.status == 'error'
assert line.pool == pool
assert line.from_injected_line is None
@ -2332,6 +2420,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(
}
assert line1.booking == {'foo': 'baz'}
assert line1.pricing_data == {'foo1': 'bar1', 'pricing': '1'}
assert line1.accounting_code == ''
assert line1.status == 'success'
assert line1.pool == pool
assert line2.event_date == datetime.date(2022, 9, 2)
@ -2355,6 +2444,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(
}
assert line2.booking == {'foo': 'baz'}
assert line2.pricing_data == {'error': 'PricingError', 'error_details': {'foo': 'bar'}}
assert line2.accounting_code == ''
assert line2.status == 'error'
assert line2.pool == pool
assert line3.event_date == datetime.date(2022, 9, 3)
@ -2378,6 +2468,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(
}
assert line3.booking == {'foo': 'baz'}
assert line3.pricing_data == {'foo3': 'bar3', 'pricing': '3'}
assert line3.accounting_code == ''
assert line3.status == 'success'
assert line3.pool == pool

View File

@ -16,6 +16,7 @@ from lingo.pricing.models import (
CriteriaConditionNotFound,
MultipleDefaultCriteriaCondition,
Pricing,
PricingAccountingCodeError,
PricingBookingCheckTypeError,
PricingBookingNotCheckedError,
PricingCriteriaCategory,
@ -58,8 +59,14 @@ class MockedRequestResponse(mock.Mock):
def mocked_requests_send(request, **kwargs):
data = [
{'id': 1, 'fields': {'foo': 'bar', 'bar': False, 'rate': 42, 'target': 2000}},
{'id': 2, 'fields': {'foo': 'baz', 'bar': True, 'rate': 35, 'target': 3000}},
{
'id': 1,
'fields': {'foo': 'bar', 'bar': False, 'rate': 42, 'target': 2000, 'accounting_code': '424242'},
},
{
'id': 2,
'fields': {'foo': 'baz', 'bar': True, 'rate': 35, 'target': 3000, 'accounting_code': '353535'},
},
] # fake result
return MockedRequestResponse(content=json.dumps({'data': data}))
@ -1101,6 +1108,82 @@ def test_apply_effort_rate_target(context):
)
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
def test_compute_accounting_code(mock_send, context, nocache):
pricing = Pricing.objects.create(
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
# empty template
assert pricing.accounting_code == ''
assert (
pricing.compute_accounting_code(
request=context['request'],
original_context={},
user_external_id='child:42',
payer_external_id='parent:35',
)
== ''
)
for value in ['{% for %}', '{{ "foo"|add:user.email }}']:
pricing.accounting_code = value
pricing.save()
with pytest.raises(PricingAccountingCodeError):
pricing.compute_accounting_code(
request=context['request'],
original_context={},
user_external_id='child:42',
payer_external_id='parent:35',
)
for value in [
'424242',
'{{ foo }}',
'{{ cards|objects:"foo"|first|get:"fields"|get:"accounting_code" }}',
]:
pricing.accounting_code = value
pricing.save()
assert (
pricing.compute_accounting_code(
request=context['request'],
original_context={'foo': '424242'},
user_external_id='child:42',
payer_external_id='parent:35',
)
== '424242'
)
# user_external_id and payer_external_id can be used
pricing.accounting_code = {
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_id|filter_by:"bar"|filter_value:payer_external_id|list }}',
}
pricing.save()
mock_send.reset_mock()
pricing.compute_accounting_code(
request=context['request'],
original_context={},
user_external_id='child:42',
payer_external_id='parent:35',
)
assert 'filter-foo=child%3A42&' in mock_send.call_args_list[0][0][0].url
assert 'filter-bar=parent%3A35&' in mock_send.call_args_list[0][0][0].url
pricing.accounting_code = {
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_raw_id|filter_by:"bar"|filter_value:payer_external_raw_id|list }}',
}
pricing.save()
mock_send.reset_mock()
pricing.compute_accounting_code(
request=context['request'],
original_context={},
user_external_id='child:42',
payer_external_id='parent:35',
)
assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url
assert 'filter-bar=35&' in mock_send.call_args_list[0][0][0].url
def test_format_pricing_data():
agenda = Agenda.objects.create(label='Foo bar')
pricing = Pricing.objects.create(
@ -1435,6 +1518,7 @@ def test_get_pricing_data(context):
pricing_data={
'foo:bar': 42,
},
accounting_code='{{ 424240|add:2 }}',
)
pricing.criterias.add(criteria)
pricing.categories.add(category, through_defaults={'order': 1})
@ -1453,6 +1537,7 @@ def test_get_pricing_data(context):
'effort_rate': {},
'context': {'domicile': 'commune', 'qf': '2'},
},
'accounting_code': '424242',
}
@ -1470,6 +1555,7 @@ def test_get_pricing_data_for_event(context):
pricing_data={
'foo:bar': 42,
},
accounting_code='424242',
)
pricing.criterias.add(criteria)
pricing.categories.add(category, through_defaults={'order': 1})
@ -1495,6 +1581,7 @@ def test_get_pricing_data_for_event(context):
'modifier_type': 'rate',
'modifier_rate': 0,
},
'accounting_code': '424242',
}
@ -1638,6 +1725,7 @@ def test_aggregate_pricing_data(modifier, pricing_amount):
effort_rate={'foo': 'bazz'},
context={'domicile': 'commune', 'qf': 2},
modifier=modifier,
accounting_code='424242',
) == {
'pricing': pricing_amount,
'calculation_details': {
@ -1648,6 +1736,7 @@ def test_aggregate_pricing_data(modifier, pricing_amount):
'context': {'domicile': 'commune', 'qf': 2},
},
'booking_details': modifier,
'accounting_code': '424242',
}