Skip to content

Commit 41ebd18

Browse files
committed
[IMP] l10n_es_ticketbai_pos: refactor simplified tbai invoice generation to avoid rare race conditions and async/await issues
1 parent f17bb05 commit 41ebd18

3 files changed

Lines changed: 333 additions & 150 deletions

File tree

l10n_es_ticketbai_pos/static/src/js/Screens/PaymentScreen/PaymentScreen.js

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ odoo.define("l10n_es_ticketbai_pos.PaymentScreen", function (require) {
5757
error_msgs.push(
5858
_t("TicketBAI Invoice not built yet. Please try again.")
5959
);
60+
} else if (!order.is_tbai_invoice_ready()) {
61+
error_msgs.push(
62+
_t("TicketBAI Invoice is incomplete. Please try again.")
63+
);
6064
}
6165
if (error_msgs.length) {
6266
Gui.showPopup("ErrorPopup", {
@@ -73,44 +77,66 @@ odoo.define("l10n_es_ticketbai_pos.PaymentScreen", function (require) {
7377
const order = this.currentOrder;
7478

7579
if (this.env.pos.company.tbai_enabled && !order.is_to_invoice()) {
76-
if (!order.tbai_simplified_invoice) {
77-
try {
78-
// Start/continue building
79-
await order.tbai_build_invoice();
80-
const tbai_inv = await order.tbai_current_invoice;
80+
console.info("[TicketBAI] Starting order validation...");
81+
try {
82+
// Start/continue building
83+
console.info("[TicketBAI] Building/rebuilding invoice...");
84+
await order.tbai_build_invoice();
85+
const tbai_inv = await order.tbai_current_invoice;
8186

82-
if (!tbai_inv) {
83-
// Validation failed → BLOCK order and inform user
84-
this.showPopup("ErrorPopup", {
85-
title: this.env._t("TicketBAI"),
86-
body: this.env._t(
87-
"Cannot generate TicketBAI simplified invoice.\n\n" +
88-
"Check:\n" +
89-
"• Company VAT is set\n" +
90-
"• Customer has VAT (or foreign ID)\n" +
91-
"• All products have exactly one tax\n" +
92-
"• Fiscal position has VAT Regime Key"
93-
),
94-
});
95-
// ← BLOCK validation
96-
return;
97-
}
98-
99-
order.tbai_simplified_invoice = tbai_inv;
100-
} catch (err) {
101-
console.error("TicketBAI invoice generation failed:", err);
87+
if (!tbai_inv) {
88+
// Validation failed → BLOCK order and inform user
89+
console.error("[TicketBAI] Invoice build returned null");
10290
this.showPopup("ErrorPopup", {
103-
title: this.env._t("TicketBAI - Critical Error"),
91+
title: this.env._t("TicketBAI"),
10492
body: this.env._t(
105-
"Failed to generate or sign the TicketBAI invoice.\n\n" +
106-
"Error: %s\n\n" +
107-
"The order cannot be validated without a valid signed ticket.",
108-
err.message || err
93+
"Cannot generate TicketBAI simplified invoice.\n\n" +
94+
"Check:\n" +
95+
"• Company VAT is set\n" +
96+
"• Customer has VAT (or foreign ID)\n" +
97+
"• All products have exactly one tax\n" +
98+
"• Fiscal position has VAT Regime Key"
10999
),
110100
});
111-
// ← BLOCK validation (required by law)
112101
return;
113102
}
103+
// ← BLOCK validation
104+
if (!tbai_inv.datas || !tbai_inv.signature_value) {
105+
console.error("[TicketBAI] Invoice incomplete:", {
106+
has_datas: Boolean(tbai_inv.datas),
107+
has_signature: Boolean(tbai_inv.signature_value),
108+
});
109+
this.showPopup("ErrorPopup", {
110+
title: this.env._t("TicketBAI - Incomplete Invoice"),
111+
body: this.env._t(
112+
"The TicketBAI invoice was generated but is missing critical data.\n\n" +
113+
"This may be due to:\n" +
114+
"• Certificate signing failure\n" +
115+
"• Network connectivity issues\n" +
116+
"• Corrupt certificate\n\n" +
117+
"Please try again or contact support."
118+
),
119+
});
120+
return;
121+
}
122+
123+
order.tbai_simplified_invoice = tbai_inv;
124+
console.info(
125+
"[TicketBAI] Invoice validated and assigned successfully"
126+
);
127+
} catch (err) {
128+
console.error("[TicketBAI] Invoice generation failed:", err);
129+
this.showPopup("ErrorPopup", {
130+
title: this.env._t("TicketBAI - Critical Error"),
131+
body: this.env._t(
132+
"Failed to generate or sign the TicketBAI invoice.\n\n" +
133+
"Error: %s\n\n" +
134+
"The order cannot be validated without a valid signed ticket.",
135+
err.message || err
136+
),
137+
});
138+
// ← BLOCK validation (required by law)
139+
return;
114140
}
115141
}
116142

l10n_es_ticketbai_pos/static/src/js/models.js

Lines changed: 94 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ odoo.define("l10n_es_ticketbai_pos.models", function (require) {
7575

7676
return order.tbai_current_invoice
7777
.then((tbai_inv) => {
78+
// Check if export failed
79+
const exported = order.export_as_JSON();
80+
if (exported.tbai_export_failed) {
81+
Gui.showPopup("Error", {
82+
title: _t("TicketBAI"),
83+
body: _t(
84+
"The TicketBAI invoice could not be signed. " +
85+
"Please refresh and try again."
86+
),
87+
});
88+
return Promise.reject(new Error("TicketBAI export failed"));
89+
}
90+
7891
if (tbai_inv) {
7992
const tbai_last_invoice_data = {
8093
order: {
@@ -88,15 +101,15 @@ odoo.define("l10n_es_ticketbai_pos.models", function (require) {
88101
};
89102
this.set_tbai_last_invoice_data(tbai_last_invoice_data);
90103
}
91-
// Even if null → still push the order (it's already validated)
92104
return super.push_single_order(...arguments);
93105
})
94106
.catch((err) => {
95-
console.error(
96-
"push_single_order: TicketBAI processing failed",
97-
err
98-
);
99-
// Do NOT block order sync — but log it
107+
console.error("push_single_order: TicketBAI failed", err);
108+
// ← BLOCK the push on TicketBAI errors
109+
if (err.message === "TicketBAI export failed") {
110+
// Don't push
111+
return Promise.reject(err);
112+
}
100113
return super.push_single_order(...arguments);
101114
});
102115
}
@@ -179,6 +192,8 @@ odoo.define("l10n_es_ticketbai_pos.models", function (require) {
179192
super(...arguments);
180193
this.tbai_simplified_invoice = null;
181194
this.tbai_current_invoice = $.when();
195+
// NEW: Prevent concurrent builds
196+
this._tbai_build_lock = false;
182197
if (this.pos.company.tbai_enabled && "json" in arguments[1]) {
183198
this.tbai_simplified_invoice =
184199
new tbai_models.TicketBAISimplifiedInvoice(
@@ -265,6 +280,7 @@ odoo.define("l10n_es_ticketbai_pos.models", function (require) {
265280
if (tbai_inv !== null) {
266281
const datas = tbai_inv.datas;
267282
const signature_value = tbai_inv.signature_value;
283+
268284
if (datas !== null && signature_value !== null) {
269285
json.tbai_signature_value = signature_value;
270286
json.tbai_datas = datas;
@@ -275,7 +291,18 @@ odoo.define("l10n_es_ticketbai_pos.models", function (require) {
275291
json.tbai_previous_order_pos_reference =
276292
tbai_inv.previous_tbai_invoice.order.simplified_invoice;
277293
}
294+
} else {
295+
// ← MARK the order as invalid
296+
json.tbai_export_failed = true;
297+
json.tbai_datas = null;
298+
json.tbai_signature_value = null;
278299
}
300+
} else if (!this.to_invoice) {
301+
// No invoice AND not creating full invoice = REQUIRED but MISSING
302+
// This is the error case: TicketBAI simplified invoice required but not built
303+
json.tbai_export_failed = true;
304+
json.tbai_datas = null;
305+
json.tbai_signature_value = null;
279306
}
280307
}
281308
return json;
@@ -293,43 +320,75 @@ odoo.define("l10n_es_ticketbai_pos.models", function (require) {
293320
}
294321

295322
async tbai_build_invoice() {
296-
/*
297-
* Reset a previously rejected promise to a safe resolved state.
298-
* Works with both native Promises and jQuery Deferreds (still used in Odoo 16 POS).
299-
*/
300-
var current = this.tbai_current_invoice;
301-
if (current && typeof current.catch === "function") {
302-
try {
303-
await current;
304-
} catch (e) {
305-
/* Absorb */
306-
}
307-
if (
308-
(current.state && current.state() === "rejected") ||
309-
(current.isRejected && current.isRejected())
310-
) {
311-
this.tbai_current_invoice = Promise.resolve(null);
312-
}
323+
if (this._tbai_build_lock) {
324+
console.warn("[TicketBAI] Build already in progress, waiting...");
325+
return this.tbai_current_invoice;
313326
}
314327

315-
this.tbai_current_invoice = this.tbai_current_invoice.then(async () => {
316-
if (!this.check_tbai_conf() || this.to_invoice) {
317-
return null;
328+
this._tbai_build_lock = true;
329+
330+
try {
331+
var current = this.tbai_current_invoice;
332+
if (current && typeof current.catch === "function") {
333+
try {
334+
await current;
335+
} catch (e) {
336+
/* Absorb */
337+
console.warn("[TicketBAI] Previous build failed:", e);
338+
}
339+
if (
340+
(current.state && current.state() === "rejected") ||
341+
(current.isRejected && current.isRejected())
342+
) {
343+
this.tbai_current_invoice = Promise.resolve(null);
344+
}
318345
}
319346

320-
const tbai_inv = new tbai_models.TicketBAISimplifiedInvoice(
321-
{},
322-
{
323-
pos: this.pos,
324-
order: this,
347+
this.tbai_current_invoice = this.tbai_current_invoice.then(
348+
async () => {
349+
if (!this.check_tbai_conf() || this.to_invoice) {
350+
console.info(
351+
"[TicketBAI] Skipping invoice build (conf check failed or to_invoice)"
352+
);
353+
return null;
354+
}
355+
356+
console.info("[TicketBAI] Starting invoice build...");
357+
const tbai_inv = new tbai_models.TicketBAISimplifiedInvoice(
358+
{},
359+
{
360+
pos: this.pos,
361+
order: this,
362+
}
363+
);
364+
365+
await tbai_inv.build_invoice();
366+
console.info(
367+
"[TicketBAI] Invoice build completed successfully"
368+
);
369+
return tbai_inv;
325370
}
326371
);
327372

328-
await tbai_inv.build_invoice();
329-
return tbai_inv;
330-
});
373+
return this.tbai_current_invoice;
374+
} finally {
375+
this._tbai_build_lock = false;
376+
}
377+
}
378+
379+
// NEW: Validation method to check invoice state
380+
is_tbai_invoice_ready() {
381+
if (!this.pos.company.tbai_enabled || this.to_invoice) {
382+
// Not required
383+
return true;
384+
}
385+
386+
const inv = this.tbai_simplified_invoice;
387+
if (!inv) {
388+
return false;
389+
}
331390

332-
return this.tbai_current_invoice;
391+
return Boolean(inv.datas && inv.signature_value && inv.tbai_identifier);
333392
}
334393
};
335394
Registries.Model.extend(Order, L10nEsTicketBAIPosOrder);

0 commit comments

Comments
 (0)