diff --git a/passerelle/contrib/toulouse_maelis/migrations/0009_auto_20230417_1836.py b/passerelle/contrib/toulouse_maelis/migrations/0009_auto_20230417_1836.py new file mode 100644 index 00000000..c5e08346 --- /dev/null +++ b/passerelle/contrib/toulouse_maelis/migrations/0009_auto_20230417_1836.py @@ -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)", + ), + ), + ] diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py index d29724dc..30e39aa5 100644 --- a/passerelle/contrib/toulouse_maelis/models.py +++ b/passerelle/contrib/toulouse_maelis/models.py @@ -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): + 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() 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 '' % (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']] diff --git a/tests/data/toulouse_maelis/R_cancel_invoice_and_delete_subscribe_list.xml b/tests/data/toulouse_maelis/R_cancel_invoice_and_delete_subscribe_list.xml new file mode 100644 index 00000000..bd2fd862 --- /dev/null +++ b/tests/data/toulouse_maelis/R_cancel_invoice_and_delete_subscribe_list.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/data/toulouse_maelis/R_cancel_invoice_and_delete_subscribe_list_error.xml b/tests/data/toulouse_maelis/R_cancel_invoice_and_delete_subscribe_list_error.xml new file mode 100644 index 00000000..c085de83 --- /dev/null +++ b/tests/data/toulouse_maelis/R_cancel_invoice_and_delete_subscribe_list_error.xml @@ -0,0 +1,19 @@ + + + + soap:Server + 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 + + + + 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 + + + + + + diff --git a/tests/data/toulouse_maelis/R_read_invoices_regie_109.xml b/tests/data/toulouse_maelis/R_read_invoices_regie_109.xml new file mode 100755 index 00000000..b6e76b78 --- /dev/null +++ b/tests/data/toulouse_maelis/R_read_invoices_regie_109.xml @@ -0,0 +1,56 @@ + + + + + 18 + F10055641671 + DSBL TEST + + 109 + DSBL + + 1312 + EO_NICOLAS MARGE + 300 + 0 + 0 + 2023-04-20T00:00:00+02:00 + 2023-12-31T00:00:00+01:00 + + 266143 + EO_NICOLAS + MARGE + MME + + + 1 + 266148 + A10053179798 + A10053179809 + S10055641665 + PORTAIL MERCREDI - 15h30/17h - 8/15Ans + EO_NICOLAS LISA + 2023-02-01T00:00:00+01:00 + 2023-06-30T00:00:00+02:00 + 1.0 + 150.0 + 150 + + + 2 + 266145 + A10053179798 + A10053179809 + S10055641658 + PORTAIL MERCREDI - 15h30/17h - 8/15Ans + EO_NICOLAS BART + 2023-02-01T00:00:00+01:00 + 2023-06-30T00:00:00+02:00 + 1.0 + 150.0 + 150 + + + + + diff --git a/tests/data/toulouse_maelis/R_validate_basket.xml b/tests/data/toulouse_maelis/R_validate_basket.xml index f603ab87..20aecaf6 100644 --- a/tests/data/toulouse_maelis/R_validate_basket.xml +++ b/tests/data/toulouse_maelis/R_validate_basket.xml @@ -11,7 +11,7 @@ 109 DSBL - 322802 + 1312 EO_NICOLAS MARGE 300 0 diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py index 5c579191..a2cd5dc9 100644 --- a/tests/test_toulouse_maelis.py +++ b/tests/test_toulouse_maelis.py @@ -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 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 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 sur la famille \'1312\'' + assert [(str(repr(x)), x.status()) for x in con.invoice_set.filter(regie_id=109)] == [ + ('', '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 ' not in caplog.records[-1].message + assert [(str(repr(x)), x.status()) for x in con.invoice_set.filter(regie_id=109)] == [ + ('', '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)] == [ + ('', '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)] == [ + ('', '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