toulouse-maelis: add a for_payment parameter to reduce cancellation delay (#76856) #227
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 3.2.18 on 2023-04-21 13:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('toulouse_maelis', '0010_toulousemaelis_max_payment_delay'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invoice',
|
||||
name='start_payment_date',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
|
@ -300,11 +300,15 @@ class ToulouseMaelis(BaseResource, HTTPResource):
|
|||
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 + self.max_payment_delay)),
|
||||
|
||||
)
|
||||
for invoice in invoices:
|
||||
invoice.cancel()
|
||||
if (
|
||||
bdauvergne
commented
La condition ne sera plus nécessaire. La condition ne sera plus nécessaire.
nroche
commented
Merci de m'avoir donné la formule (que j'avais demandé), je la comprends, mais en fait je ne souhaite pas optimiser dès à présent (pour moi c'est encore trop embrouillé). Merci de m'avoir donné la formule (que j'avais demandé), je la comprends, mais en fait je ne souhaite pas optimiser dès à présent (pour moi c'est encore trop embrouillé).
|
||||
invoice.start_payment_date is not None
|
||||
and invoice.start_payment_date <= now() - datetime.timedelta(minutes=self.max_payment_delay)
|
||||
) or invoice.created <= now() - datetime.timedelta(
|
||||
minutes=(self.cancel_invoice_delay + self.max_payment_delay)
|
||||
):
|
||||
invoice.cancel()
|
||||
|
||||
def hourly(self):
|
||||
self.notify_invoices_paid()
|
||||
|
@ -3992,9 +3996,11 @@ class ToulouseMaelis(BaseResource, HTTPResource):
|
|||
def invoices(self, request, regie_id, NameID=None, family_id=None):
|
||||
family_id = family_id or self.get_link(NameID).family_id
|
||||
invoices = [
|
||||
i.format_content() for i in self.get_invoices(family_id, regie_id) if i.status() == 'created'
|
||||
i.format_content()
|
||||
for i in self.get_invoices(family_id, regie_id)
|
||||
if i.status() in ['created', 'for_payment']
|
||||
]
|
||||
return {'data': invoices}
|
||||
return {'has_invoice_for_payment': True, 'data': invoices}
|
||||
|
||||
@endpoint(
|
||||
display_category='Facture',
|
||||
|
@ -4037,13 +4043,23 @@ class ToulouseMaelis(BaseResource, HTTPResource):
|
|||
parameters={
|
||||
'regie_id': {'description': 'Identifiant de la régie', 'example_value': '102'},
|
||||
'invoice_id': {'description': 'Identifiant de facture', 'example_value': 'IDFAM-42'},
|
||||
'for_payment': {
|
||||
'description': "Si présent, annuler la facture panier à l'expiration du delai maximum de paiement depuis la date de l'appel"
|
||||
},
|
||||
},
|
||||
)
|
||||
def invoice(self, request, regie_id, invoice_id, **kwargs):
|
||||
def invoice(self, request, regie_id, invoice_id, for_payment=None, **kwargs):
|
||||
invoice = self.get_invoice(regie_id, invoice_id)
|
||||
if invoice.status() == 'cancelled':
|
||||
raise APIError('Invoice cancelled')
|
||||
bdauvergne
commented
Je pense que tu peux mettre à jour start_payment_date sur n'importe quel type de facture ça ne mange pas de pain et ça nous donne une information dans tous les cas. Je pense que tu peux mettre à jour start_payment_date sur n'importe quel type de facture ça ne mange pas de pain et ça nous donne une information dans tous les cas.
nroche
commented
Fait. Fait.
|
||||
return {'data': invoice.format_content()}
|
||||
if for_payment is not None:
|
||||
invoice.start_payment_date = now()
|
||||
invoice.save()
|
||||
if invoice.status() == 'cancelling':
|
||||
bdauvergne
commented
Non c'est sur invoices qu'il faut ajouter has_invoice_for_payment et c'est toujours True. Non c'est sur invoices qu'il faut ajouter has_invoice_for_payment et c'est toujours True.
bdauvergne
commented
Ce point est important, sans ça ça ne marchera pas. Ce point est important, sans ça ça ne marchera pas.
nroche
commented
Oups, j'ai compris tout de travers. J'ai corrigé : je le renvoie toujours à True sur Oups, j'ai compris tout de travers.
Il s'agit ici d'ajouter une indication comme quoi le connecteur gère bien le paramètre '?for_payment'.
J'ai corrigé : je le renvoie toujours à True sur `/invoies` et il n'est pas renvoyé sur `invoices/history`.
|
||||
raise APIError('Invoice cancelling')
|
||||
return {
|
||||
'data': invoice.format_content(),
|
||||
}
|
||||
|
||||
@endpoint(
|
||||
display_category='Facture',
|
||||
|
@ -4068,7 +4084,7 @@ class ToulouseMaelis(BaseResource, HTTPResource):
|
|||
invoice = self.get_invoice(regie_id, invoice_id)
|
||||
if invoice.status() in ['paid', 'notified']:
|
||||
raise APIError('Invoice already paid')
|
||||
if invoice.maelis_cancel_notification_date is not None:
|
||||
if invoice.status() == 'cancelled':
|
||||
raise APIError('Invoice cancelled')
|
||||
|
||||
invoice.lingo_data = post_data
|
||||
|
@ -4179,6 +4195,7 @@ class Invoice(models.Model):
|
|||
created = models.DateTimeField('Created', auto_now_add=True)
|
||||
updated = models.DateTimeField('Updated', auto_now=True)
|
||||
maelis_data_update_date = models.DateTimeField(null=True)
|
||||
start_payment_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)
|
||||
|
@ -4192,15 +4209,26 @@ class Invoice(models.Model):
|
|||
self.maelis_data['amountInvoice'] == self.maelis_data['amountPaid']
|
||||
or self.maelis_notification_date is not None
|
||||
):
|
||||
# payInvoice was sent to Maelis
|
||||
return 'notified'
|
||||
if self.lingo_notification_date is not None:
|
||||
# pay order received from Lingo
|
||||
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'
|
||||
|
||||
# basket invoice only
|
||||
if self.basket_generation_date is not None:
|
||||
if self.maelis_cancel_notification_date is not None:
|
||||
# cancelInvoiceAndDeleteSubscribeList was sent to Maelis
|
||||
return 'cancelled'
|
||||
if self.lingo_notification_date is None:
|
||||
if self.start_payment_date is not None:
|
||||
# displayed into Lingo but no more payable
|
||||
return 'for_payment'
|
||||
if self.created <= now() - datetime.timedelta(minutes=self.resource.cancel_invoice_delay):
|
||||
# hide invoice to Lingo
|
||||
return 'cancelling'
|
||||
|
||||
# new invoice
|
||||
return 'created'
|
||||
|
||||
def format_content(self):
|
||||
|
@ -4224,8 +4252,10 @@ class Invoice(models.Model):
|
|||
'reference_id': item['numInvoice'],
|
||||
'maelis_item': item,
|
||||
}
|
||||
if paid:
|
||||
if paid or self.status() == 'for_payment':
|
||||
invoice.update({'pay_limit_date': '', 'online_payment': False})
|
||||
if self.status() == 'for_payment':
|
||||
invoice['no_online_payment_reason'] = 'Transation de payement en cours'
|
||||
return invoice
|
||||
|
||||
@transaction.atomic
|
||||
|
|
|
@ -8821,7 +8821,7 @@ def test_cancel_basket_invoice_cron(activity_service, invoice_service, con, app,
|
|||
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.status() == 'cancelling'
|
||||
assert invoice.maelis_cancel_notification_date is None
|
||||
|
||||
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
|
||||
|
@ -8834,9 +8834,67 @@ def test_cancel_basket_invoice_cron(activity_service, invoice_service, con, app,
|
|||
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.status() == 'cancelled'
|
||||
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_having_for_payment_date(
|
||||
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,
|
||||
)
|
||||
Link.objects.create(resource=con, family_id='1312', name_id='local')
|
||||
assert con.cancel_invoice_delay == 30
|
||||
assert con.max_payment_delay == 20
|
||||
|
||||
# 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'
|
||||
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
|
||||
|
||||
# notificate payment starts
|
||||
freezer.move_to('2023-03-03 18:40:00')
|
||||
resp = app.get(get_endpoint('regie/109/invoice/1312-18') + '?for_payment')
|
||||
assert resp.json['err'] == 0
|
||||
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
|
||||
assert invoice.start_payment_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 18:40:00'
|
||||
|
||||
# invoice is still displayed before cancellation order is sent to maelis
|
||||
# (but no more payable)
|
||||
con.cancel_basket_invoices()
|
||||
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
|
||||
assert invoice.status() == 'for_payment'
|
||||
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']]
|
||||
|
||||
# cancellation order is now sent to maelis
|
||||
freezer.move_to('2023-03-03 19:10: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.status() == 'cancelled'
|
||||
assert invoice.maelis_cancel_notification_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 19:10:00'
|
||||
|
||||
|
||||
def test_cancel_basket_invoice_cron_keep_paid_invoices(
|
||||
activity_service, invoice_service, con, app, freezer, caplog
|
||||
):
|
||||
|
@ -8946,7 +9004,7 @@ def test_cancel_basket_invoice_cron_maelis_error(activity_service, invoice_servi
|
|||
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.status() == 'cancelling'
|
||||
assert invoice.maelis_cancel_notification_date is None
|
||||
|
||||
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
|
||||
|
@ -8990,7 +9048,7 @@ def test_cancel_basket_invoice_on_get_baskets(activity_service, con, app, freeze
|
|||
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.status() == 'cancelling'
|
||||
assert invoice.maelis_cancel_notification_date is None
|
||||
|
||||
# cancellation order is now sent to maelis
|
||||
|
@ -9216,6 +9274,7 @@ def test_invoices(invoice_service, con, app, caplog, freezer):
|
|||
Link.objects.create(resource=con, family_id='1312', name_id='local')
|
||||
resp = app.get(url + '?NameID=local')
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['has_invoice_for_payment'] is True
|
||||
assert len(resp.json['data'])
|
||||
for invoice in resp.json['data']:
|
||||
assert invoice['display_id']
|
||||
|
@ -9281,7 +9340,7 @@ def test_invoices(invoice_service, con, app, caplog, freezer):
|
|||
freezer.move_to('2023-03-03 18:30:00')
|
||||
invoice.basket_generation_date = invoice.created
|
||||
invoice.save()
|
||||
assert invoice.status() == 'cancelled'
|
||||
assert invoice.status() == 'cancelling'
|
||||
resp = app.get(url + '?NameID=local')
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
@ -9441,6 +9500,10 @@ def test_invoice_if_cancelled(activity_service, invoice_service, con, app, freez
|
|||
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'),
|
||||
)
|
||||
url = get_endpoint('regie/109/invoice/1312-18')
|
||||
assert con.cancel_invoice_delay == 30
|
||||
|
||||
|
@ -9456,9 +9519,62 @@ def test_invoice_if_cancelled(activity_service, invoice_service, con, app, freez
|
|||
assert resp.json['data']['display_id'] == '18'
|
||||
assert resp.json['data']['label'] == 'DSBL TEST'
|
||||
|
||||
# cancelled basket invoice is no more returned
|
||||
# cancelling 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() == 'cancelling'
|
||||
resp = app.get(url + '?NameID=local')
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'Invoice cancelling'
|
||||
|
||||
# cancelled basket invoice is no more returned
|
||||
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'
|
||||
resp = app.get(url + '?NameID=local')
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'Invoice cancelled'
|
||||
|
||||
|
||||
def test_invoice_for_payment(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')
|
||||
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') + '?family_id=1312', params={'basket_id': 'S10055641661'}
|
||||
)
|
||||
assert resp.json['err'] == 0
|
||||
|
||||
resp = app.get(url + '?NameID=ignored&for_payment')
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data']['display_id'] == '18'
|
||||
assert resp.json['data']['label'] == 'DSBL TEST'
|
||||
|
||||
# basket invoice is still returned but is no more payable
|
||||
freezer.move_to('2023-03-03 18:50:00')
|
||||
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
|
||||
assert invoice.start_payment_date is not None
|
||||
assert invoice.status() == 'for_payment'
|
||||
resp = app.get(url + '?NameID=local')
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data']['display_id'] == '18'
|
||||
assert resp.json['data']['pay_limit_date'] == ''
|
||||
assert resp.json['data']['online_payment'] is False
|
||||
assert resp.json['data']['no_online_payment_reason'] == 'Transation de payement en cours'
|
||||
|
||||
# basket invoice is no more returned since cancellation order sent to maelis
|
||||
con.cancel_basket_invoices()
|
||||
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
|
||||
|
@ -9674,7 +9790,7 @@ def test_pay_not_yet_cancelled_basket_invoice(activity_service, invoice_service,
|
|||
# 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.status() == 'cancelling'
|
||||
assert invoice.maelis_cancel_notification_date is None
|
||||
resp = app.post_json(url + '?NameID=ignored', params=data)
|
||||
assert resp.json['err'] == 0
|
||||
|
|
Garde
created__lte=now() - datetime.timedelta(minutes=self.cancel_invoice_delay)
mais à ajoute :faut prendre le max (my bad) pas le min. Désolé.
oui (si j'ai bien interprété), donc on change rien ici.
---8<---
Actuellement on a :
Le but de ce ticket c'est :
Ce qui est résumé dans la description du ticket par :
delais avant annulation = min(T+30, T'+20)
Si l'on réduit le delais avant annulation (pas d'affichage lingo) à min(T+30, T'+20),
alors on doit annuler (ordre qui est passé à maélis) les factures qui sont dans ces deux cas (OU logique)
soit (T <= t - (30+20) ) OU (T' <= t - 20).