Skip to content

Commit 2c5873a

Browse files
committed
Merge PR #4679 into 16.0
Signed-off-by pedrobaeza
2 parents d10f01d + 41ebd18 commit 2c5873a

4 files changed

Lines changed: 341 additions & 157 deletions

File tree

l10n_es_ticketbai_pos/models/pos_order.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ def tbai_prepare_invoice_values(self, pos_order=None):
9393
if pos_order is None:
9494
vals["previous_tbai_invoice_id"] = self.config_id.tbai_last_invoice_id.id
9595
else:
96-
previous_order_pos_reference = pos_order.get(
96+
order_data = pos_order["data"]
97+
previous_order_pos_reference = order_data.get(
9798
"tbai_previous_order_pos_reference", False
9899
)
99100
if previous_order_pos_reference:
@@ -103,15 +104,15 @@ def tbai_prepare_invoice_values(self, pos_order=None):
103104
vals[
104105
"previous_tbai_invoice_id"
105106
] = tbai_previous_order.tbai_invoice_id.id
106-
datas = base64.b64encode(pos_order["data"]["tbai_datas"].encode("utf-8"))
107+
datas = base64.b64encode(order_data["tbai_datas"].encode("utf-8"))
107108
vals.update(
108109
{
109110
"datas": datas,
110111
"datas_fname": "{}.xsig".format(
111112
self.l10n_es_unique_id.replace("/", "-")
112113
),
113114
"file_size": len(datas),
114-
"signature_value": pos_order["data"]["tbai_signature_value"],
115+
"signature_value": order_data["tbai_signature_value"],
115116
}
116117
)
117118
gipuzkoa_tax_agency = self.env.ref(
@@ -184,10 +185,10 @@ def _tbai_build_invoice(self):
184185
def _process_order(self, pos_order, draft, existing_order):
185186
if draft:
186187
return super()._process_order(pos_order, draft, existing_order)
187-
188-
tbai_vat_regime_key = pos_order["data"].get("tbai_vat_regime_key")
188+
order_data = pos_order["data"]
189+
tbai_vat_regime_key = order_data.get("tbai_vat_regime_key")
189190
if tbai_vat_regime_key and isinstance(tbai_vat_regime_key, str):
190-
pos_order["data"]["tbai_vat_regime_key"] = (
191+
order_data["tbai_vat_regime_key"] = (
191192
self.env["tbai.vat.regime.key"]
192193
.search([("code", "=", tbai_vat_regime_key)], limit=1)
193194
.id
@@ -196,7 +197,7 @@ def _process_order(self, pos_order, draft, existing_order):
196197
order_id = super()._process_order(pos_order, draft, existing_order)
197198
order = self.browse(order_id)
198199

199-
if order.config_id.tbai_enabled and not pos_order.get("to_invoice", False):
200+
if order.config_id.tbai_enabled and not order_data.get("to_invoice", False):
200201
vals = order.tbai_prepare_invoice_values(pos_order)
201202
tbai_invoice = self.env["tbai.invoice"].sudo().create(vals)
202203
order.tbai_invoice_id = tbai_invoice

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)