toulouse-maelis : gérer l'expiration lors du paiement (#76395) #224

Merged
nroche merged 8 commits from wip/76395-parsifal-cancel-invoice into main 2023-04-21 14:10:00 +02:00
7 changed files with 516 additions and 25 deletions

View File

@ -0,0 +1,30 @@
# Generated by Django 3.2.18 on 2023-04-17 16:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('toulouse_maelis', '0008_auto_20230413_1757'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='basket_generation_date',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='invoice',
name='maelis_cancel_notification_date',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='toulousemaelis',
name='cancel_invoice_delay',
field=models.PositiveIntegerField(
default='30',
verbose_name="Délais de conservation des factures issues d'un panier (en minutes)",
),
),
]

View File

@ -81,6 +81,10 @@ class ToulouseMaelis(BaseResource, HTTPResource):
default='P,L,S,' + ','.join(str(x) for x in range(1, 10)),
verbose_name='Codes des natures des activités loisirs, séparés par des virgules',
)
cancel_invoice_delay = models.PositiveIntegerField(
default='30',
verbose_name="Délais de conservation des factures issues d'un panier (en minutes)",
)
category = 'Connecteurs métiers'
_category_ordering = ['Famille', 'Activités']
@ -287,8 +291,19 @@ class ToulouseMaelis(BaseResource, HTTPResource):
for invoice in invoices:
invoice.notify()
def cancel_basket_invoices(self):

Je mettrai un @atomic et un select_for_update(), pendant qu'on fait ça on a pas trop envie qu'il se passe autre chose.

Je mettrai un @atomic et un select_for_update(), pendant qu'on fait ça on a pas trop envie qu'il se passe autre chose.
invoices = self.invoice_set.filter(
lingo_notification_date__isnull=True,
basket_generation_date__isnull=False,
maelis_cancel_notification_date__isnull=True,
created__lte=now() - datetime.timedelta(minutes=self.cancel_invoice_delay + 20),
)
for invoice in invoices:
invoice.cancel()
def hourly(self):
self.notify_invoices_paid()
self.cancel_basket_invoices()

Un léger doute sur le fait d'avoir un délai par défaut de 30 minutes et un cron toutes les heures :)

Un léger doute sur le fait d'avoir un délai par défaut de 30 minutes et un cron toutes les heures :)
def get_referential(self, referential_name, id=None, q=None, limit=None, distinct=True):
if id is not None:
@ -3628,6 +3643,10 @@ class ToulouseMaelis(BaseResource, HTTPResource):
baskets = self.get_baskets_raw(family_id)
for item in baskets:
item['text'] = self.get_referential_value('Regie', item['codeRegie'])
# send invoice cancellation order more often than hourly
self.cancel_basket_invoices()
return {'data': baskets}
@endpoint(
@ -3724,6 +3743,19 @@ class ToulouseMaelis(BaseResource, HTTPResource):
'idBasket': post_data['basket_id'],
},
)
# only one invoice should be returned, create it now to manage cancellation
if response:
for item in response.get('factureLst') or []:
invoice = self.invoice_set.create(
regie_id=item['regie']['code'],
invoice_id=item['numInvoice'],
family_id=family_id,
basket_generation_date=now(),
maelis_data=item,
maelis_data_update_date=now(),
)
self.logger.info("Ajout de %s sur la famille '%s'", repr(invoice), family_id)
return {'data': response}
@endpoint(
@ -4003,7 +4035,10 @@ class ToulouseMaelis(BaseResource, HTTPResource):
},
)
def invoice(self, request, regie_id, invoice_id, **kwargs):
return {'data': self.get_invoice(regie_id, invoice_id).format_content()}
invoice = self.get_invoice(regie_id, invoice_id)
if invoice.status() == 'cancelled':
raise APIError('Invoice cancelled')
return {'data': invoice.format_content()}
@endpoint(
display_category='Facture',
@ -4026,8 +4061,10 @@ class ToulouseMaelis(BaseResource, HTTPResource):
)
def pay_invoice(self, request, regie_id, invoice_id, post_data, **kwargs):
invoice = self.get_invoice(regie_id, invoice_id)
if invoice.status() != 'created':
if invoice.status() in ['paid', 'notified']:
raise APIError('Invoice already paid')
if invoice.maelis_cancel_notification_date is not None:
raise APIError('Invoice cancelled')
invoice.lingo_data = post_data
invoice.lingo_notification_date = now()
@ -4139,6 +4176,8 @@ class Invoice(models.Model):
maelis_data_update_date = models.DateTimeField(null=True)
lingo_notification_date = models.DateTimeField(null=True)
maelis_notification_date = models.DateTimeField(null=True)
basket_generation_date = models.DateTimeField(null=True)
maelis_cancel_notification_date = models.DateTimeField(null=True)
def __repr__(self):
return '<Invoice "%s/%s">' % (self.regie_id, self.invoice_id)
@ -4151,6 +4190,12 @@ class Invoice(models.Model):
return 'notified'
if self.lingo_notification_date is not None:
return 'paid'
if (
self.lingo_notification_date is None
and self.basket_generation_date is not None
and self.created <= now() - datetime.timedelta(minutes=self.resource.cancel_invoice_delay)
):
return 'cancelled'
return 'created'
def format_content(self):
@ -4204,6 +4249,24 @@ class Invoice(models.Model):
obj.save()
return True
@transaction.atomic
def cancel(self):
obj = Invoice.objects.select_for_update().get(pk=self.pk)
if obj.lingo_notification_date is not None or obj.basket_generation_date is None:
return False
try:
obj.resource.call(
'Activity',
'cancelInvoiceAndDeleteSubscribeList',
idInvoice=obj.maelis_data['idInvoice'],
)
except SOAPServiceUnreachable:
return False
obj.maelis_cancel_notification_date = now()
obj.save()
obj.resource.logger.info("Annulation de %s sur la famille '%s'", repr(obj), obj.family_id)
return True
class Meta:
ordering = ('resource', 'regie_id', 'invoice_id')
unique_together = [['resource', 'regie_id', 'invoice_id']]

View File

@ -0,0 +1,5 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:cancelInvoiceAndDeleteSubscribeListResponse xmlns:ns2="activity.ws.maelis.sigec.com" xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com" xmlns:ns4="bean.persistence.school.ws.maelis.sigec.com"/>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,19 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>Une erreur est survenue : java.sql.SQLException: ORA-01403: aucune donnée trouvée
ORA-06512: à "MAELIS_T2.FACTURE_SUPPRIMER", ligne 144
ORA-06512: à ligne 1
</faultstring>
<detail>
<ns1:MaelisActivityException xmlns:ns1="activity.ws.maelis.sigec.com">
<message xmlns:ns4="bean.persistence.school.ws.maelis.sigec.com" xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com" xmlns:ns2="activity.ws.maelis.sigec.com">Une erreur est survenue : java.sql.SQLException: ORA-01403: aucune donnée trouvée
ORA-06512: à "MAELIS_T2.FACTURE_SUPPRIMER", ligne 144
ORA-06512: à ligne 1
</message>
</ns1:MaelisActivityException>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,56 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:readInvoicesResponse xmlns:ns2="ws.maelis.sigec.com">
<invoiceList>
<numInvoice>18</numInvoice>
<idInvoice>F10055641671</idInvoice>
<libelleTTF>DSBL TEST</libelleTTF>
<regie>
<code>109</code>
<libelle>DSBL</libelle>
</regie>
<numFamily>1312</numFamily>
<name>EO_NICOLAS MARGE</name>
<amountInvoice>300</amountInvoice>
<amountPaid>0</amountPaid>
<amountPaidTG>0</amountPaidTG>
<dateInvoice>2023-04-20T00:00:00+02:00</dateInvoice>
<dateDeadline>2023-12-31T00:00:00+01:00</dateDeadline>
<payer>
<num>266143</num>
<lastname>EO_NICOLAS</lastname>
<firstname>MARGE</firstname>
<civility>MME</civility>
</payer>
<lineInvoiceList>
<numLine>1</numLine>
<numPers>266148</numPers>
<idActivity>A10053179798</idActivity>
<idUnit>A10053179809</idUnit>
<idIns>S10055641665</idIns>
<libelleLine>PORTAIL MERCREDI - 15h30/17h - 8/15Ans</libelleLine>
<name>EO_NICOLAS LISA</name>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
<quantity>1.0</quantity>
<unitPrice>150.0</unitPrice>
<amountLine>150</amountLine>
</lineInvoiceList>
<lineInvoiceList>
<numLine>2</numLine>
<numPers>266145</numPers>
<idActivity>A10053179798</idActivity>
<idUnit>A10053179809</idUnit>
<idIns>S10055641658</idIns>
<libelleLine>PORTAIL MERCREDI - 15h30/17h - 8/15Ans</libelleLine>
<name>EO_NICOLAS BART</name>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
<quantity>1.0</quantity>
<unitPrice>150.0</unitPrice>
<amountLine>150</amountLine>
</lineInvoiceList>
</invoiceList>
</ns2:readInvoicesResponse>
</soap:Body>
</soap:Envelope>

View File

@ -11,7 +11,7 @@
<code>109</code>
<libelle>DSBL</libelle>
</regie>
<numFamily>322802</numFamily>
<numFamily>1312</numFamily>
<name>EO_NICOLAS MARGE</name>
<amountInvoice>300</amountInvoice>
<amountPaid>0</amountPaid>

View File

@ -8522,7 +8522,7 @@ def test_get_baskets_not_linked_error(con, app):
def test_get_baskets_no_basket(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
url = get_endpoint('get-baskets')
resp = app.get(url + '?family_id=311352')
resp = app.get(url + '?family_id=1312')
assert resp.json['err'] == 0
assert resp.json['data'] == []
@ -8534,9 +8534,9 @@ def test_update_basket_time(activity_service, con, app):
url = get_endpoint('update-basket-time')
params = {'basket_id': 'S10055641661'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 0
Link.objects.create(resource=con, family_id='311352', name_id='local')
Link.objects.create(resource=con, family_id='1312', name_id='local')
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
@ -8555,7 +8555,7 @@ def test_update_basket_time_no_basket(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
url = get_endpoint('update-basket-time')
params = {'basket_id': 'S10055641661'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'S10055641661' basket on family"
@ -8564,7 +8564,7 @@ def test_update_basket_time_basket_not_found(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
url = get_endpoint('update-basket-time')
params = {'basket_id': 'plop'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'plop' basket on family"
@ -8577,9 +8577,9 @@ def test_delete_basket_line(activity_service, con, app):
url = get_endpoint('delete-basket-line')
params = {'basket_id': 'S10055641661', 'line_id': 'S10055641668'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 0
Link.objects.create(resource=con, family_id='311352', name_id='local')
Link.objects.create(resource=con, family_id='1312', name_id='local')
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
@ -8599,7 +8599,7 @@ def test_delete_basket_line_no_basket(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
url = get_endpoint('delete-basket-line')
params = {'basket_id': 'S10055641661', 'line_id': 'S10055641668'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'S10055641661' basket on family"
@ -8608,7 +8608,7 @@ def test_delete_basket_line_basket_not_found(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
url = get_endpoint('delete-basket-line')
params = {'basket_id': 'plop', 'line_id': 'S10055641668'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'plop' basket on family"
@ -8617,7 +8617,7 @@ def test_delete_basket_line_line_not_found(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
url = get_endpoint('delete-basket-line')
params = {'basket_id': 'S10055641661', 'line_id': 'plop'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'plop' basket line on basket"
@ -8635,9 +8635,9 @@ def test_delete_basket(activity_service, con, app):
url = get_endpoint('delete-basket')
params = {'basket_id': 'S10055641661'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 0
Link.objects.create(resource=con, family_id='311352', name_id='local')
Link.objects.create(resource=con, family_id='1312', name_id='local')
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
@ -8656,7 +8656,7 @@ def test_delete_basket_no_basket(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
url = get_endpoint('delete-basket')
params = {'basket_id': 'S10055641661'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'S10055641661' basket on family"
@ -8665,20 +8665,17 @@ def test_delete_basket_not_found(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
url = get_endpoint('delete-basket')
params = {'basket_id': 'plop'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'plop' basket on family"
def test_validate_basket(activity_service, con, app):
def test_validate_basket(activity_service, con, app, caplog):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
url = get_endpoint('validate-basket')
params = {'basket_id': 'S10055641661'}
resp = app.post_json(url + '?family_id=311352', params=params)
assert resp.json['err'] == 0
Link.objects.create(resource=con, family_id='311352', name_id='local')
Link.objects.create(resource=con, family_id='1312', name_id='local')
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
@ -8690,7 +8687,7 @@ def test_validate_basket(activity_service, con, app):
'idInvoice': 'F10055641671',
'libelleTTF': 'DSBL TEST',
'regie': {'code': 109, 'libelle': 'DSBL'},
'numFamily': 322802,
'numFamily': 1312,
'name': 'EO_NICOLAS MARGE',
'refTIPI': None,
'amountInvoice': '300',
@ -8747,6 +8744,10 @@ def test_validate_basket(activity_service, con, app):
'idInsLst': ['S10055641665', 'S10055641658'],
}
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
assert invoice.basket_generation_date is not None
def test_validate_basket_not_linked_error(con, app):
url = get_endpoint('validate-basket')
@ -8760,7 +8761,7 @@ def test_validate_basket_no_basket(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
url = get_endpoint('validate-basket')
params = {'basket_id': 'S10055641661'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'S10055641661' basket on family"
@ -8769,11 +8770,233 @@ def test_validate_basket_not_found(activity_service, con, app):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
url = get_endpoint('validate-basket')
params = {'basket_id': 'plop'}
resp = app.post_json(url + '?family_id=311352', params=params)
resp = app.post_json(url + '?family_id=1312', params=params)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == "no 'plop' basket on family"
def test_cancel_basket_invoice_cron(activity_service, invoice_service, con, app, freezer, caplog):
def request_check(request):
assert request == 'F10055641671'
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.xml'))
activity_service.add_soap_response(
'cancelInvoiceAndDeleteSubscribeList',
get_xml_file('R_cancel_invoice_and_delete_subscribe_list.xml'),
request_check=request_check,
)
url = get_endpoint('validate-basket')
params = {'basket_id': 'S10055641661'}
Link.objects.create(resource=con, family_id='1312', name_id='local')
assert con.cancel_invoice_delay == 30
# invoice created on validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
data = resp.json['data']['factureLst'][0]
assert data['regie']['code'] == 109
assert data['numInvoice'] == '18'
assert data['idInvoice'] == 'F10055641671'
assert caplog.records[-1].levelno == logging.INFO
assert caplog.records[-1].message == 'Ajout de <Invoice "109/18"> sur la famille \'1312\''
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
assert invoice.basket_generation_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 18:30:00'
assert invoice.maelis_cancel_notification_date is None
# invoice is not yet cancelled
con.cancel_basket_invoices()
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' in [x['id'] for x in resp.json['data']]
# invoice is no more display but cancellation order is not sent to maelis yet
freezer.move_to('2023-03-03 19:00:00')
con.cancel_basket_invoices()
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
assert invoice.maelis_cancel_notification_date is None
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' not in [x['id'] for x in resp.json['data']]
# cancellation order is now sent to maelis
freezer.move_to('2023-03-03 19:20:00')
con.cancel_basket_invoices()
assert caplog.records[-1].levelno == logging.INFO
assert caplog.records[-1].message == 'Annulation de <Invoice "109/18"> sur la famille \'1312\''
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.maelis_cancel_notification_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 19:20:00'
def test_cancel_basket_invoice_cron_keep_paid_invoices(
activity_service, invoice_service, con, app, freezer, caplog
):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.xml'))
assert con.cancel_invoice_delay == 30
# get a basket invoice
freezer.move_to('2023-03-03 18:30:00')
url = get_endpoint('validate-basket')
params = {'basket_id': 'S10055641661'}
Link.objects.create(resource=con, family_id='1312', name_id='local')
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
assert caplog.records[-1].levelno == logging.INFO
assert caplog.records[-1].message == 'Ajout de <Invoice "109/18"> sur la famille \'1312\''
assert [(str(repr(x)), x.status()) for x in con.invoice_set.filter(regie_id=109)] == [
('<Invoice "109/18">', 'created')
]
# get family invoices
freezer.move_to('2023-03-03 18:35:00')
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert '1312-18' in [x['id'] for x in resp.json['data']]
assert 'Mise à jour de <Invoice "109/18">' not in caplog.records[-1].message
assert [(str(repr(x)), x.status()) for x in con.invoice_set.filter(regie_id=109)] == [
('<Invoice "109/18">', 'created'),
]
resp = app.get(get_endpoint('regie/109/invoices/history') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' not in [x['id'] for x in resp.json['data']]
# pay invoice
freezer.move_to('2023-03-03 18:38:00')
url = get_endpoint('regie/109/invoice/1312-18/pay/')
data = {
'transaction_date': '2023-03-03T18:38:00',
'transaction_id': 'xxx',
}
resp = app.post_json(url + '?NameID=ignored', params=data)
assert resp.json['err'] == 0
assert [(str(repr(x)), x.status()) for x in con.invoice_set.filter(regie_id=109)] == [
('<Invoice "109/18">', 'paid'),
]
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' not in [x['id'] for x in resp.json['data']]
resp = app.get(get_endpoint('regie/109/invoices/history') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' in [x['id'] for x in resp.json['data']]
# invoice is not cancelled and no cancellation order is sent to maelis
freezer.move_to('2023-03-03 19:20:00')
con.cancel_basket_invoices()
assert [(str(repr(x)), x.status()) for x in con.invoice_set.filter(regie_id=109)] == [
('<Invoice "109/18">', 'paid'),
]
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.maelis_cancel_notification_date is None
resp = app.get(get_endpoint('regie/109/invoices/history') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' in [x['id'] for x in resp.json['data']]
def test_cancel_basket_invoice_cron_maelis_error(activity_service, invoice_service, con, app, freezer):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.xml'))
activity_service.add_soap_response(
'cancelInvoiceAndDeleteSubscribeList',
get_xml_file('R_cancel_invoice_and_delete_subscribe_list_error.xml'),
)
url = get_endpoint('validate-basket')
params = {'basket_id': 'S10055641661'}
Link.objects.create(resource=con, family_id='1312', name_id='local')
assert con.cancel_invoice_delay == 30
# invoice created on validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
data = resp.json['data']['factureLst'][0]
assert data['regie']['code'] == 109
assert data['numInvoice'] == '18'
assert data['idInvoice'] == 'F10055641671'
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
assert invoice.basket_generation_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 18:30:00'
assert invoice.maelis_cancel_notification_date is None
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' in [x['id'] for x in resp.json['data']]
# error on cancellation order
freezer.move_to('2023-03-03 19:20:00')
try:
con.cancel_basket_invoices()
except SOAPError:
pass
else:
assert False, 'cron should raise an exception'
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
assert invoice.maelis_cancel_notification_date is None
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert resp.json['err'] == 0
assert '1312-18' not in [x['id'] for x in resp.json['data']]
def test_cancel_basket_invoice_on_get_baskets(activity_service, con, app, freezer, caplog):
def request_check(request):
assert request == 'F10055641671'
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
activity_service.add_soap_response(
'cancelInvoiceAndDeleteSubscribeList',
get_xml_file('R_cancel_invoice_and_delete_subscribe_list.xml'),
request_check=request_check,
)
Link.objects.create(resource=con, family_id='1312', name_id='local')
url = get_endpoint('get-baskets')
assert con.cancel_invoice_delay == 30
# invoice created on validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?NameID=local', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
# invoice is not yet cancelled
resp = app.get(url + '?family_id=1312')
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
# invoice is no more display but cancellation order is not sent to maelis yet
freezer.move_to('2023-03-03 19:00:00')
resp = app.get(url + '?family_id=1312')
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
assert invoice.maelis_cancel_notification_date is None
# cancellation order is now sent to maelis
freezer.move_to('2023-03-03 19:20:00')
resp = app.get(url + '?family_id=1312')
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.maelis_cancel_notification_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 19:20:00'
def test_read_nursery_list(con, app):
url = get_endpoint('read-nursery-list')
resp = app.get(url)
@ -9050,6 +9273,15 @@ def test_invoices(invoice_service, con, app, caplog, freezer):
assert invoice.maelis_data_update_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 18:20:00'
assert invoice.status() == 'created'
# Cancelled basket invoices are not displayed
freezer.move_to('2023-03-03 18:30:00')
invoice.basket_generation_date = invoice.created
invoice.save()
assert invoice.status() == 'cancelled'
resp = app.get(url + '?NameID=local')
assert resp.json['err'] == 0
assert resp.json['data'] == []
@mock.patch('passerelle.utils.Request.get')
def test_invoices_cache(mocked_get, con, app):
@ -9201,6 +9433,33 @@ def test_invoice(invoice_service, con, app):
assert resp.json['data']['label'] == 'CLAE JANVIER 2023'
def test_invoice_if_cancelled(activity_service, invoice_service, con, app, freezer):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.xml'))
url = get_endpoint('regie/109/invoice/1312-18')
# invoice created on validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?family_id=1312', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
resp = app.get(url + '?NameID=ignored')
assert resp.json['err'] == 0
assert resp.json['data']['display_id'] == '18'
assert resp.json['data']['label'] == 'DSBL TEST'
# cancelled basket invoice is no more returned
freezer.move_to('2023-03-03 19:00:00')
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
resp = app.get(url + '?NameID=local')
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'Invoice cancelled'
def test_invoice_wrong_referential_key_error(con, app):
url = get_endpoint('regie/plop/invoice/1312-8')
resp = app.get(url)
@ -9388,6 +9647,65 @@ def test_pay_historical_invoice(invoice_service, con, app):
assert resp.json['err_desc'] == 'Invoice already paid'
def test_pay_not_yet_cancelled_basket_invoice(activity_service, invoice_service, con, app, freezer):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.xml'))
url = get_endpoint('regie/109/invoice/1312-18/pay/')
data = {
'transaction_date': '2023-03-03T19:20:00',
'transaction_id': 'xxx',
}
# invoice created on validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?family_id=1312', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
# cancellation order was not sent to maelis
freezer.move_to('2023-03-03 19:20:00')
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
assert invoice.maelis_cancel_notification_date is None
resp = app.post_json(url + '?NameID=ignored', params=data)
assert resp.json['err'] == 0
assert resp.json['data'] == 'ok'
def test_pay_cancelled_basket_invoice(activity_service, invoice_service, con, app, freezer):
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
activity_service.add_soap_response(
'cancelInvoiceAndDeleteSubscribeList',
get_xml_file('R_cancel_invoice_and_delete_subscribe_list.xml'),
)
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.xml'))
url = get_endpoint('regie/109/invoice/1312-18/pay/')
data = {
'transaction_date': '2023-03-03T19:20:00',
'transaction_id': 'xxx',
}
# invoice created on validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?family_id=1312', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
# cancellation order sent to maelis
freezer.move_to('2023-03-03 19:20:00')
con.cancel_basket_invoices()
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
assert invoice.maelis_cancel_notification_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 19:20:00'
resp = app.post_json(url + '?NameID=ignored', params=data)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'Invoice cancelled'
def test_invoice_pdf(invoice_service, con, app):
def request_check(request):
assert request.codeRegie == 102