Open WhyNotHugo opened 2 years ago
Buenas! Cómo te imaginabas la implementación de esto? Una que se me ocurre es agregarle un tercer "estado" RESULT_PENDING
a ReceiptValidation
, y que el procedimiento para validar un Receipt
sea:
ReceiptValidation
con result=RESULT_PENDING
Receipt
result=RESULT_APPROVED
o result=RESULT_REJECTED
al ReceiptValidation
existente, según corresponda.Luego la app al arrancar busca todos los ReceiptValidation
en pending y llama revalidate
para ellos.
Otra opción más barata podría ser un write ahead log. Te ahorras las llamadas extra a la DB, pero es más laburo de implementación y probablemente requiera persistent storage.
PD: excelente laburo este proyecto
Ah, estuve mirando más en detalle el código y veo que sería más simple, por las garantías que ya da la implementación actual: buscar los Receipt
sin un ReceiptValidation
pero con un receipt_number
. Esos son los candidatos a validaciones interrumpidas.
Creo que esto podría funcionar:
for receipt in Receipt.objects.filter(
receipt_number__isnull=False,
validation__isnull=True,
):
receipt.revalidate()
Corrección: ese ejemplo saltearía comprobantes con alguna validación fallida.
Capaz así?
for receipt in Receipt.objects.filter(
receipt_number__isnull=False,
).exclude(
validation__result=ReceiptValidation.RESULT_APPROVED,
):
receipt.revalidate()
Suena bien :+1: . Salvo que me esté perdiendo algo, el código actual nunca crea un ReceiptValidation
con ReceiptValidation.RESULT_REJECTED
ante errores, solamente ReceiptValidation.RESULT_APPROVED
:
En ese caso, tu versión anterior también debería funcionar.
Pero no sé si en algún momento se usó el RESULT_REJECTED
, y la DB podría tener alguno con result = RESULT_REJECTED
.
Tema relacionado: que opinás de que el revalidate
setee receipt_number=None
en caso de que no haya sido validado? Así como hace validate
para los fallidos:
Si un Receipt no validado queda con receipt_number != None
, al llamar Receipt.validate()
se va a intentar usar ese receipt_number
ya seteado, que probablemente sea viejo.
Edit: y ahora que lo pienso, no setearlo en None hace que se intente revalidar para siempre.
Puede que valga la pena eliminar ReceiptValidation.RESULT_REJECTED
completamente. Por ahora dejo un issue como recordatorio: https://github.com/WhyNotHugo/django-afip/issues/214
que opinás de que el revalidate setee receipt_number=None en caso de que no haya sido validado?
Puede haber problemas de concurrencia:
validate
setea el receipt_number
y empieza a comunicarse con el AFIP.revalidate
encuentra el comprobante a medio validar, consulta al AFIP el estado.revalidate
recibe su respuesta antes que validate
. revalidate
elimina el receipt_number
.receipt_number
queda perdido.Es poco probable pero posible.
Creo que habría que lockear las fila durante la validación para evitar este problema.
Buen catch. Estuve pensando en esto de setear el receipt_number
en null, acá lo que pensé:
Te dejo el walltext oculto, pero TLDR: creo que se pueden lockear para asegurar la integridad de datos, pero me surgen algunos caveats que hacen medio mala la UX.
Esta se me ocurrió después, mucho más simple: que el revalidate
compare el Receipt.receipt_number
con FECompUltimoAutorizado
, probablemente dentro de una transacción por si cambia el point of sales y receipt type. Si ya fue usado, borrarlo.
Si no fue usado y se lo deja seteado, todavía podría haber problemas: podría asignarsele el mismo receipt_number
a otro Receipt
, y si se intentan validar juntos, qs.validate()
va a throwear. Lo que podemos hacer es que el validate
haga un except <El numero o fecha del comprobante no se corresponde con el proximo a autorizar. ..>
, y setee todos los receipt_number = None
si tal cosa pasa. Así, el próximo intento debería funcionar. Esto podría hacerse siempre de hecho, más allá del contexto de este problema.
PD: tal vez esté dandole mucha vuelta a un caso bastante anómalo :sweat_smile: . La verdad es que la implementación actual no tiene ningún problema de integridad de datos. Para clarificar, lo que estoy queriendo resolver sería: (1) que no se apilen los Receipts
no validados por crashes, y (2) que Receipts.validate()
para un receipt no validado por crashes no throwee.
Me parece que la opción 1 va bien :+1:
Acá hay un primer draft con la opción 1: https://github.com/WhyNotHugo/django-afip/pull/217
Find receipts that seem to have failed a previous submission and call
revalidate
for them.