Tower: upload laundry_management 19.0.19.0.4 (via marketplace)
This commit is contained in:
158
addons/laundry_management/tools/laundry_pos_link_healthcheck.py
Normal file
158
addons/laundry_management/tools/laundry_pos_link_healthcheck.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
"""POS ↔ laundry.order link healthcheck.
|
||||||
|
|
||||||
|
Run via:
|
||||||
|
docker exec -i odoo-laundry-system-odoo19-1 odoo shell \\
|
||||||
|
-c /etc/odoo/odoo.conf -d dev --no-http \\
|
||||||
|
< extra-addons/laundry_management/tools/laundry_pos_link_healthcheck.py
|
||||||
|
|
||||||
|
Read-only by default. To heal missing links + repair the tracking-code
|
||||||
|
sequence, set HEAL=True at the top.
|
||||||
|
|
||||||
|
What it does
|
||||||
|
────────────
|
||||||
|
1. Reports the laundry tracking-code sequence vs MAX(tracking_code) —
|
||||||
|
this is the most common cause of the silent "no laundry.order
|
||||||
|
created" symptom (UniqueViolation swallowed by the savepoint).
|
||||||
|
2. Lists the latest 10 POS orders with linkage status + line scan.
|
||||||
|
3. Lists the latest 10 laundry.order rows.
|
||||||
|
4. Counts unlinked POS orders that SHOULD have a laundry.order
|
||||||
|
(have ≥1 laundry-service line, no settlement-only).
|
||||||
|
5. Verifies that the laundry-side hand-off is callable as a Cashier-
|
||||||
|
group user (permission smoke).
|
||||||
|
6. Optional heal: backfills missing links + repairs the sequence.
|
||||||
|
"""
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
env = self.env # noqa: F821 — provided by odoo shell
|
||||||
|
|
||||||
|
# Toggle to True to backfill missing links + repair the sequence.
|
||||||
|
HEAL = False
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print("POS ↔ LAUNDRY LINK HEALTHCHECK HEAL =", HEAL)
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
# ── 1. Sequence sanity check ──────────────────────────────────────────
|
||||||
|
SEQ_CODE = "laundry.order.line.tracking"
|
||||||
|
seq = env["ir.sequence"].sudo().search([("code", "=", SEQ_CODE)], limit=1)
|
||||||
|
if seq:
|
||||||
|
env.cr.execute(
|
||||||
|
"""SELECT COALESCE(MAX(
|
||||||
|
CAST(NULLIF(REGEXP_REPLACE(tracking_code,'[^0-9]','','g'),'')
|
||||||
|
AS INTEGER)
|
||||||
|
), 0) FROM laundry_order_line WHERE tracking_code IS NOT NULL"""
|
||||||
|
)
|
||||||
|
max_existing = env.cr.fetchone()[0] or 0
|
||||||
|
drift = seq.number_next - (max_existing + 1)
|
||||||
|
status = "OK" if drift >= 0 else f"BEHIND by {-drift} (collisions imminent)"
|
||||||
|
print(f"\n[1] sequence {SEQ_CODE}")
|
||||||
|
print(f" number_next = {seq.number_next} "
|
||||||
|
f"max_existing = {max_existing} drift = {drift} {status}")
|
||||||
|
else:
|
||||||
|
print(f"\n[1] !!! sequence {SEQ_CODE} not found")
|
||||||
|
|
||||||
|
# ── 2. Latest 10 POS orders with linkage ─────────────────────────────
|
||||||
|
print("\n[2] last 10 pos.order with linkage")
|
||||||
|
env.cr.execute("""
|
||||||
|
SELECT id, name, pos_reference, partner_id, amount_total, state,
|
||||||
|
create_date, laundry_order_id
|
||||||
|
FROM pos_order
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 10
|
||||||
|
""")
|
||||||
|
for row in env.cr.dictfetchall():
|
||||||
|
link = row["laundry_order_id"] or "-"
|
||||||
|
print(f" pos.id={row['id']:>3} name={(row['name'] or '')[:24]:<24} "
|
||||||
|
f"ref={(row['pos_reference'] or '-'):<22} "
|
||||||
|
f"link={link:>4} state={row['state']}")
|
||||||
|
|
||||||
|
# ── 3. Latest 10 laundry.order ────────────────────────────────────────
|
||||||
|
print("\n[3] last 10 laundry.order")
|
||||||
|
env.cr.execute("""
|
||||||
|
SELECT id, name, pos_order_id, partner_id, amount_total, amount_due, state
|
||||||
|
FROM laundry_order
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 10
|
||||||
|
""")
|
||||||
|
for row in env.cr.dictfetchall():
|
||||||
|
print(f" lo.id={row['id']:>3} name={row['name']:<22} "
|
||||||
|
f"pos={row['pos_order_id'] or '-':>4} "
|
||||||
|
f"total={row['amount_total']:>7} due={row['amount_due']:>5} "
|
||||||
|
f"state={row['state']}")
|
||||||
|
|
||||||
|
# ── 4. Unlinked POS orders that SHOULD have a laundry.order ──────────
|
||||||
|
print("\n[4] unlinked POS orders that have laundry-service lines")
|
||||||
|
unlinked = env["pos.order"].search(
|
||||||
|
[("laundry_order_id", "=", False)], order="id desc",
|
||||||
|
)
|
||||||
|
should_have = []
|
||||||
|
no_laundry_lines = []
|
||||||
|
for o in unlinked:
|
||||||
|
has_laundry = any(
|
||||||
|
line.product_id
|
||||||
|
and line.product_id.product_tmpl_id.is_laundry_service
|
||||||
|
and not line.product_id.product_tmpl_id.is_laundry_settlement
|
||||||
|
for line in o.lines
|
||||||
|
)
|
||||||
|
if has_laundry:
|
||||||
|
should_have.append(o)
|
||||||
|
else:
|
||||||
|
no_laundry_lines.append(o)
|
||||||
|
print(f" should-have-link (skipped): {len(should_have)}")
|
||||||
|
print(f" no-laundry-lines (correct skip): {len(no_laundry_lines)}")
|
||||||
|
for o in should_have[:10]:
|
||||||
|
print(f" pos.id={o.id:>3} name={o.name!r} partner={o.partner_id.name!r}")
|
||||||
|
|
||||||
|
# ── 5. Permission smoke — Cashier can run the hand-off ───────────────
|
||||||
|
print("\n[5] permission smoke — call _maybe_create_laundry_order as Cashier")
|
||||||
|
cashier_group = env.ref(
|
||||||
|
"laundry_management.group_laundry_cashier", raise_if_not_found=False,
|
||||||
|
)
|
||||||
|
# Odoo 19: res.groups.users → res.groups.user_ids
|
||||||
|
group_users = (
|
||||||
|
getattr(cashier_group, "user_ids", None)
|
||||||
|
or getattr(cashier_group, "users", None)
|
||||||
|
) if cashier_group else None
|
||||||
|
if group_users:
|
||||||
|
cashier = group_users[0]
|
||||||
|
print(f" using existing cashier: {cashier.login}")
|
||||||
|
if should_have:
|
||||||
|
target = should_have[0]
|
||||||
|
try:
|
||||||
|
with env.cr.savepoint():
|
||||||
|
target.with_user(cashier).sudo()._maybe_create_laundry_order()
|
||||||
|
print(f" [OK] handoff callable as Cashier on POS {target.id}")
|
||||||
|
except Exception:
|
||||||
|
print(f" !!! permission failure:")
|
||||||
|
traceback.print_exc()
|
||||||
|
else:
|
||||||
|
print(" no cashier user available — skipping (admin-only env)")
|
||||||
|
|
||||||
|
# ── 6. Optional heal ─────────────────────────────────────────────────
|
||||||
|
if HEAL:
|
||||||
|
print("\n[6] HEALING")
|
||||||
|
if seq:
|
||||||
|
env["laundry.order.line"].sudo()._repair_tracking_sequence()
|
||||||
|
seq_after = env["ir.sequence"].sudo().search(
|
||||||
|
[("code", "=", SEQ_CODE)], limit=1,
|
||||||
|
)
|
||||||
|
print(f" sequence repaired → number_next = {seq_after.number_next}")
|
||||||
|
healed = 0
|
||||||
|
for o in should_have:
|
||||||
|
try:
|
||||||
|
with env.cr.savepoint():
|
||||||
|
o.sudo()._maybe_create_laundry_order()
|
||||||
|
if o.laundry_order_id:
|
||||||
|
healed += 1
|
||||||
|
except Exception:
|
||||||
|
print(f" heal failed on POS {o.id}:")
|
||||||
|
traceback.print_exc()
|
||||||
|
print(f" healed {healed} of {len(should_have)} missing links")
|
||||||
|
env.cr.commit()
|
||||||
|
print(" committed.")
|
||||||
|
else:
|
||||||
|
print("\n[6] heal SKIPPED — set HEAL=True at top of script to backfill")
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("HEALTHCHECK DONE")
|
||||||
|
print("=" * 70)
|
||||||
Reference in New Issue
Block a user