Compare commits

..

80 Commits

Author SHA1 Message Date
gsinghpal
6a60a55cd8 sync: register fusion-expenses + fusion_configurator as module repos
Adds the two new module repos to repos.txt (so sync scripts + fresh clones
include them) and to .gitignore (so the parent ignores them like the rest).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:51:02 -04:00
gsinghpal
eddec0bb6e sync scripts: self-heal gitea remote if a fresh clone dropped it
Re-clones from GitHub leave repos with only origin; pull/push now re-add the
gitea mirror remote automatically so the mirror cannot silently drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:41:12 -04:00
gsinghpal
4830613701 Add cross-machine sync tooling (clone/pull/push all module repos)
repos.txt + sync-*.sh let any machine (Mac or Windows/Git Bash) hydrate all 49
module repos and keep them in sync via GitHub+gitea. Pulls are fast-forward only
(never clobber local work); pushes send commits only and flag uncommitted repos.
See SYNC.md for the two-machine workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:34:32 -04:00
gsinghpal
14cd6a666b Track .claude/ settings and .superpowers/ so they sync across clones
.claude/settings.local.json is forced past the global ~/.config/git/ignore
rule (**/.claude/settings.local.json); .superpowers/ is no longer gitignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:12:16 -04:00
gsinghpal
a66cdefc01 Split 49 modules/suites into independent git repos; untrack from monorepo
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled
Each top-level module/suite folder is now its own private repo on GitHub
(gsinghpal/<name>) and gitea (admin/<name>), with a fresh single initial
commit. The monorepo no longer tracks them (added to .gitignore + git rm
--cached); working-tree files are retained on disk and managed in their
own repos. The monorepo keeps shared root files (CLAUDE.md, docs/, scripts/,
tools/, AGENTS.md, WIP/obsolete dirs) and full history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 01:54:34 -04:00
gsinghpal
2a7b315e98 Changes 2026-06-05 10:04:20 -04:00
gsinghpal
44bd03a96a Merge branch 'main' of https://github.com/gsinghpal/Odoo-Modules 2026-06-05 10:04:08 -04:00
gsinghpal
108c76d347 Merge: WO header button label tweaks (fusion_plating_jobs 19.0.12.6.1; deployed to entech) 2026-06-05 09:05:54 -04:00
gsinghpal
3b33e80ee9 fix(plating): WO header buttons - 'Workspace' label + icon-only Process Tree (fusion_plating_jobs 19.0.12.6.1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:05:54 -04:00
gsinghpal
04abb1520a Merge: self-heal WOs missing recipe/steps (fusion_plating_jobs 19.0.12.6.0; deployed to entech) 2026-06-05 01:15:18 -04:00
gsinghpal
a6186120b2 fix(plating): self-heal work orders missing recipe/steps + repair existing
Root cause: jobs auto-created at SO confirm resolve the recipe once; if the
SO line's process variant is not set at that instant (new parts, or the
copy-from-quote path), the WO is created with no recipe and no steps, and
setting the recipe on the line afterward never propagates (idempotency
guard + no line-to-job sync).

Fix (fusion_plating_jobs 19.0.12.6.0):
- fp.job._fp_resync_recipe_from_so(): re-resolve recipe from the SO line(s)
  and build steps (mirrors action_confirm: generate, promote pending to
  ready, express overrides). Acts only on not-started jobs; idempotent.
- action_fp_resync_recipe_from_so(): "Re-sync Recipe from SO" header button.
- sale.order.line.write: when x_fc_process_variant_id changes, auto-heal the
  linked not-yet-started WO.

Verified on entech: WO-30071-01/02 healed (recipe + 8 steps each); auto-
propagation confirmed in a rolled-back transaction. Deployed in place
(fusion_plating_jobs is ahead of git); same change recorded here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 01:14:57 -04:00
gsinghpal
73a59cad0b Merge: de-dash plating code (em-dash to hyphen) + intake-neutral customer emails (deployed to entech; branch strange-margulis-3c9718; disjoint from main fusion_clock work) 2026-06-05 00:42:44 -04:00
gsinghpal
88e1e5e9bb chore(plating): de-dash fusion_plating_iot too
Same em-dash -> hyphen sweep applied to fusion_plating_iot (lives under
fusion_iot/ so the main commit missed it). Comments/strings only; no
functional dashes in this module. Keeps git in sync with the in-place
de-dash already applied to entech.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 00:35:44 -04:00
gsinghpal
8c76a16366 chore(plating): de-dash shipped code + intake-neutral customer emails
Replace em-dashes and en-dashes with hyphens across 789 shipped source
files (py/xml/js/scss) so the delivered module reads as human-written;
em-dashes had become a recognizable AI-generated tell. Internal .md dev
notes are excluded. The WO-sticker mojibake strippers keep their dash
search targets (now written — / –). No logic changes: comments
and display strings only; validated with py_compile + lxml parse.

Rewrite the 7 customer notification emails to be intake-neutral
(ship-in / drop-off / pickup) and repair-aware, and fix the Shipped
email documents line (packing slip vs bill of lading; certificate only
when issued). Subjects use a hyphen separator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 00:16:19 -04:00
gsinghpal
dcaa7dc1fe Merge: fusion_clock 19.0.5.0.1 code-review hardening + planning module uninstalled on entech
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:41:10 -04:00
gsinghpal
19d484680d fix(fusion_clock): code-review hardening [19.0.5.0.1]
- _cron_generate: per-rule savepoint isolation (one bad rule can't abort the
  whole daily batch)
- fclk_attach_recurrence: clear an existing recurrence first (no orphaned rule
  generating forever)
- fclk_apply_planner_cell: collapse split rows (search was limit=1 after the
  UNIQUE drop, orphaning extras)
- fclk_release_shift: reject non-posted/open shifts (raw-POST guard)
- delete_open_shift: report success=false when nothing was deleted + JS surfaces it
- _generate: log before removing an empty recurrence
Tests added for collapse, re-attach, draft-release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:32:44 -04:00
gsinghpal
23da01fcc1 Merge: remove Odoo Planning dependency from fusion_clock
Native roles + recurrence + publish/notify + open shifts/self-assign; portal
Schedule folded in; fusion_planning retired. Deployed to entech (admin) as
fusion_clock 19.0.5.0.0; 8 planning.slot + 1 planning.role migrated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:59:22 -04:00
gsinghpal
53c292083f test(fusion_clock): update tests for dropped unique + overnight; fix leave reason
test_unique_employee_date_schedule -> test_multiple_shifts_per_day_allowed;
test_invalid_same_day_range_is_rejected -> test_overnight_range_is_accepted;
add required reason to the recurrence leave-skip test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:38:44 -04:00
gsinghpal
1630a2025f fix(fusion_clock): planning port defers when planning ORM not loaded during -u
fusion_clock doesn't depend on planning, so planning's models load AFTER it
during -u and the port saw no data. Now detect planning tables via SQL,
defer (no marker) when the ORM isn't loaded, and finish the port from the
deploy odoo-shell step (full registry). Marker now owned by the method.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:28:34 -04:00
gsinghpal
498963e83a fix(fusion_clock): pre-migrate re-links orphaned config-param external ids
Settings saved via set_param() have no ir_model_data; the noupdate config XML
then collides on UNIQUE(key) during -u. Pre-migrate links existing params to
their XML external id (value-preserving) so upgrades are robust. Found on the
Entech clone-verify; affects prod (35 params vs 32 xmlids).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:24:08 -04:00
gsinghpal
0cb30f256d feat(fusion_clock): settings UI for generation horizon + self-unassign; open-shift in backend views [C1]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:14:03 -04:00
gsinghpal
2ad94070c7 feat(fusion_clock): open shifts + self-assign + bulk apply [B4-B5]
Model: fclk_create_open_shifts/claim_open_shift/release_shift (days-before
cutoff + role eligibility)/bulk_apply. Planner: Open Shift… panel, open-shifts
strip with delete, Apply-to-dept; load includes open shifts. Portal: claim
open shifts + release own upcoming shifts with feedback banners. Tests for
claim/role-gate/release/bulk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:12:10 -04:00
gsinghpal
80d06ff77f feat(fusion_claims): service-booking wizard live client search + address autocomplete
The wizard's dynamic fields were non-functional: the "Existing customer" box had
no search (no endpoint, no handler — the typed string was only sent on submit),
and address autocomplete never attached (google_address_autocomplete.js patches
FormController, which a client action is not).

Live client search:
- new jsonrpc endpoint /fusion_claims/service_booking/search_customers — searches
  res.partner (name/phone/email) and resolves a typed SO number to its partner.
- JS: debounced (250ms) onCustSearch -> .sb-cust-results dropdown; pickCustomer()
  sets state.partnerId + fills the contact, which action_book_from_wizard already
  consumes for cust_mode='existing'.
- FIELD-SAFE domain: res.partner has NO `mobile` field in Odoo 19 — referencing it
  raises ValueError and the swallowed exception made the search silently return
  nothing. Build the OR domain only over fields in Partner._fields. Smoke-tested on
  prod data ('25450'->1, '1 905-'->8).

Address autocomplete (wizard-local):
- component loads Google Places (key = ICP fusion_claims.google_maps_api_key, which
  IS configured on westin), attaches via useRef('root')+onMounted/onPatched to every
  input.sb-addr-input, writes street/city/lat/lng into reactive state. Fully guarded
  (per-input _sbAc, _addrStarted/_addrNoKey gate, .catch on both hooks) so a missing
  key degrades to manual entry and can never break render.

Verified: pyflakes clean, JS node --check, SCSS compiles, XML well-formed, dropdown
UI rendered against Bootstrap+compiled CSS. Documented in fusion_claims/CLAUDE.md §48.
Bump fusion_claims 19.0.9.6.0 -> 19.0.9.7.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:07:47 -04:00
gsinghpal
68aaa132ee feat(fusion_clock): schedule parity — overnight, split shifts, open shifts [B1-B3]
is_open + crosses_midnight fields; employee_id optional (open shifts);
company_id computed w/ env.company fallback; drop hard one-per-day UNIQUE
(allow split + open). Overnight math in planned_hours/_check_schedule_times/
scheduled_times. _get_fclk_day_plan resolves multiple posted rows into ONE
work-window so penalties/overtime/absence stay correct. Migration drops the
old constraint defensively. Tests for overnight, window, open shifts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:04:58 -04:00
gsinghpal
d35d5f4b34 feat(fusion_clock): planning -> native data migration [A8]
post-migrate(19.0.5.0.0) -> fusion.clock.schedule._fclk_port_planning_data:
planning.role -> fusion.clock.role, employee default/allowed roles, and
planning.slot -> fusion.clock.schedule (local date+float, role map, posted
if published, open if unassigned). Guarded (no-op on Community), idempotent
(marker), per-row savepoints. Integration test runs on Enterprise clones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:58:13 -04:00
gsinghpal
3376a32143 feat(fusion_clock): Publish & Notify range + portal Schedule fold-in [A6-A7]
Generalise post_week into fclk_publish_range/fclk_email_posted_range +
planner Publish… panel + publish_range endpoint. Fold the /my/clock/schedule
controller+template+css from fusion_planning into fusion_clock (native
schedule only, role colour); inline Schedule nav across all portal pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:54:59 -04:00
gsinghpal
734b3b94fd feat(fusion_clock): native recurring shifts engine [A4-A5]
fusion.clock.schedule.recurrence (repeat every N day/week/month/year;
forever/until/N-times) re-fit from planning.recurrency onto per-day rows;
daily generation cron; _fclk_on_leave skip; planner Repeat…/Stop-repeat
UI + endpoints; recurrence + role indicators on cells.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:49:26 -04:00
gsinghpal
b4ca85e291 feat(fusion_clock): native shift roles (fusion.clock.role) [A1-A3]
Replaces Odoo Planning's planning.role: name+colour model with the same
1-11 palette, employee default/allowed role fields, Employee Roles editor,
role_id on shift template + schedule with default resolution, ACLs, menus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:42:04 -04:00
gsinghpal
53fe13344d fix(fusion_claims): service-booking wizard responsive — namespace Bootstrap classes + reorder media queries
The 19.0.9.5.0 CSS pass (padding/scroll) did not fix "fields sitting on each
other" on small screens. Deep dive against the live web.assets_backend bundle
found two compounding root causes (both measured, not guessed):

1. Dead media query. The @media(max-width:560px) collapse for the inner field
   grids (.two/.three) and .timepick was nested BEFORE the base .two/.three/
   .timepick rules. Equal specificity → the later base rule wins → the media
   query never applied. matchMedia matched at 320/390px yet grids stayed 2–3
   columns and crammed/truncated. Moved all @media overrides to the END of the
   .o_service_booking block so they win the cascade.

2. Bootstrap class collision. The wizard reused row/card/grid/btn; Odoo's
   backend Bootstrap applies .row{display:flex;margin:0 -16px}, .card{display:flex},
   etc. globally even under the scoped parent (they win for properties the scoped
   rule doesn't set). Measured: every .row computed display:flex + margin-left/
   right:-16px. Renamed all layout classes to sb-* (sb-row/sb-card/sb-grid/sb-btn)
   in service_booking.xml + service_booking.scss.

Verified at 320/390/768/1280 against the real prod bundle (computed
grid-template-columns now 1fr at <=560px; .sb-row margin 0 / display block;
2-col desktop intact). Documented both gotchas in fusion_claims/CLAUDE.md §47.

Bump fusion_claims 19.0.9.5.0 -> 19.0.9.6.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:38:52 -04:00
gsinghpal
c9eb61ee0c feat(plating): hide quality smart buttons at zero + order-scope NCR/CAPA counts
On the sale.order and fp.job forms, the Holds/Checks/NCRs/CAPAs/RMAs quality
smart buttons (and the SO WO button) now hide when their count is 0, so the row
shows only quality work that exists. NCR and CAPA counts are re-scoped from
customer-wide to order/job via a shared _fp_quality_ncr_ids() helper (NCRs
reached through the order's RMAs + the order/job's holds), so each badge and the
list its button opens always agree. Also aligned the job RMA button's list
domain to its (already SO-scoped) count.

Reverts the Sub-12 Phase D "always-visible (zero is OK)" choice back to the
module's documented hide-at-zero convention.

- fusion_plating_quality 19.0.8.1.0 -> 19.0.8.2.0
- fusion_plating_jobs    19.0.12.4.0 -> 19.0.12.5.0

Deployed + verified on entech (badge == helper across sampled SOs/jobs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:36:54 -04:00
gsinghpal
023fc95acd docs(fusion_clock): implementation plan — remove Planning dependency
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:34:52 -04:00
gsinghpal
9574fa0ae4 docs(fusion_clock): design spec — remove Odoo Planning dependency
Re-fit Planning's role + recurrence + send onto the native per-day
fusion.clock.schedule model; retire fusion_planning into fusion_clock;
make the family Community-installable. Full feature-parity matrix,
data migration, and gated Entech rollout included.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:29:27 -04:00
gsinghpal
423f288507 feat(fusion_shipping): Shipping Label + Commercial Invoice smart buttons on delivery order
Carriers post the shipping label and (for international) the commercial invoice
to the delivery order's chatter, where they were hard to find. Add two smart
buttons on the stock.picking form that open the latest of each via the PDF
preview dialog (fusion_pdf_preview when installed, else open in a new tab).

Document discovery is carrier-agnostic (computed from the picking's attachments):
labels match 'Label*'; invoices match '*CommercialInvoice*' (UPS/Canada Post) or
'ShippingDoc-*' (FedEx/DHL, _get_delivery_doc_prefix). Buttons hide when absent.

Verified on entech: real FedEx picking resolved its label (invoice correctly
none for a domestic ship); synthetic UPS names resolved label+invoice and the
invoice button fired fusion_pdf_preview.open_attachment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:24:26 -04:00
gsinghpal
a86f20017d feat(fusion_shipping): UPS bill-receiver clarity + controllable commercial invoice
- Relabel UPS 'Bill My Account' -> "Bill recipient's UPS account" with clear
  help (it bills the customer's own UPS account; $0 shipping line; falls back to
  Bill Shipper when the customer has no account on file).
- Improve the customer 'UPS Account Number' field help (stored per-customer,
  auto-recalled at ship time for Bill Receiver).
- Add ups_rest_documentation_type setting (No / UPS commercial invoice) on the
  UPS REST carrier, mirroring FedEx. Default 'invoice' preserves the existing
  auto-generate-on-international behaviour; gate require_invoice on it so it can
  be turned off. Surfaced on the UPS REST config page.

Validated live on entech (UPS production): CA->US shipment generated the label
+ a 60KB commercial invoice PDF (country of origin auto = CA, HS code applied),
then voided. Bill Receiver request confirmed accepted by UPS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:12:10 -04:00
gsinghpal
7426501555 fix(fusion_shipping): UPS REST ship request sends malformed ReferenceNumber
The UPS REST ship request wrapped the reference as
{'Value': [{'Code':'BM','Value': picking.name}]}, but _ups_rest_prepare_shipping_data
already builds reference_number as a list of {Code, Value} dicts. UPS expects
ReferenceNumber to be such an object (or array) with a STRING Value and rejects
the double-wrapped form on the ship call. This branch fires for every non-US/US
(e.g. CA->CA, CA->US) shipment, so rating worked but label creation failed.

Pass the list directly. Validated end-to-end against UPS production from a
Canadian origin: rate + real label (tracking 1Z6W...6355, then voided).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:47:05 -04:00
gsinghpal
3e787a1b24 Merge branch 'main' of https://github.com/gsinghpal/Odoo-Modules 2026-06-04 18:24:34 -04:00
gsinghpal
6f006e24ad feat(certificates): cert Customer Contact is multi-contact, auto-filled, sends to all
fp.certificate.contact_partner_id (single 'Customer Contact') becomes
contact_partner_ids (Many2many) — same shape as the partner's Default CoC
Contacts, as requested.

- Auto-populate: at job creation (fp.job cert resolution) + lazy-fill at issue,
  contact_partner_ids = the customer's x_fc_default_coc_contact_ids (ALL).
- Send: action_send_to_customer pre-fills the composer with exactly the cert's
  contact_partner_ids, so the CoC goes to all the defined clients (fallback:
  company).
- Primary: the FIRST contact prints on the CoC + is gated for email; report
  uses contact_partner_ids[:1].
- Gate: requires >=1 Customer Contact + the primary has an email.
- View: many2many_tags.
- Migration 19.0.10.3.0: copies each cert's old single contact into the new M2m,
  drops the orphaned column.

Deployed + verified on entech: migration copied 16 certs, old column dropped,
field is M2m, send pre-fills the cert contacts, CoC report renders. entech-only
part_line_ids preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:09:36 -04:00
gsinghpal
ba6aeaaca9 feat(certificates): multiple Default CoC Contacts per customer (M2o -> M2m)
res.partner.x_fc_default_coc_contact_id (single Many2one) becomes
x_fc_default_coc_contact_ids (self-referential Many2many 'Default CoC
Contacts') so a customer can list several contacts who need the CoC.

- res.partner: M2m field (rel fp_default_coc_contact_rel) + many2many_tags.
- Cert: contact_partner_id (primary addressee printed on the cert) is set to
  the FIRST CoC contact at job creation + lazy-filled at issue.
- Send: action_send_to_customer pre-fills the email composer with ALL the
  customer's CoC contacts (primary + the rest), falling back to the company.
- fp.job cert-default resolution + the action_issue gate wording updated.
- Migration 19.0.10.2.0: copies each partner's old single value into the new
  M2m, then drops the orphaned column.

Deployed + verified on entech: migration copied 2 existing values, old column
dropped, field is M2m, send pre-fills all contacts. entech-only part_line_ids
/ multi-part resolver preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:31:17 -04:00
gsinghpal
27577dd51a Merge branch 'claude/service-booking-css-fix' into main
Service-booking wizard CSS: scroll on small screens (height:100% so overflow
engages), padded fields (!important vs Odoo input normalisation), narrow-screen
sub-grid collapse. Also hardens scripts/verify_service_booking.sh with an
asset-bundle compile gate. Clone-verified GREEN (assets compile) + deployed to
westin-v19 (fusion_claims 19.0.9.5.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:02:51 -04:00
gsinghpal
a10b7425f7 fix(scripts): asset-compile gate — odoo shell needs --no-http (port 8069 held by live app)
The compile gate's 'odoo shell' tried to bind 8069 (the running app holds it) and
died with 'Address already in use' before compiling, false-failing the gate. Add
--no-http --http-port=0 --gevent-port=0 (same as the test run) so the shell loads
the registry and force-compiles the bundles without binding a port.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:57:38 -04:00
gsinghpal
dcd4955bb7 feat(jobs+receiving): confirm->receive flow, lock recipe, reset step, lock steps, fix bake gate
- Confirm->Receive (A): after a single interactive SO confirm, receiving's
  action_confirm returns action_view_receiving() so the user lands straight
  on the Receive Parts screen (opt-out via fp_no_receiving_redirect context).
- Lock recipe (1): recipe_id readonly on the WO form — stick to the
  order-entry recipe.
- Hide spec (2): customer_spec_id invisible on the WO form.
- Reset step (3): new fp.job.step.button_reset (operator-usable, audited) +
  an undo button next to Start. Resets to Ready, clears finish + sign-off,
  closes open timelogs, keeps start audit + move/CoC history.
- Lock steps (4): steps list create=false delete=false (no Add a line / no
  trash) — steps come from the recipe, only skippable, never deleted.
- Bake gate fix (5): _fp_missing_required_step_inputs now honours the node's
  collect_measurements master switch, matching the Record-Inputs wizard.
  collect_measurements=False + required prompts no longer blocks finish
  (wizard shows 0 rows, so the gate must too). Unblocks WO-30098 + 63 other
  affected nodes (bake steps).

Deployed + verified on entech (-u jobs; bake finishes, reset done->ready,
recipe readonly, spec hidden, steps locked, receiving redirect target OK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:55:34 -04:00
gsinghpal
a2277b481c fix(fusion_claims): service-booking wizard scrolls + responsive + padded fields
Reported on the live wizard: no scroll on small screens, not responsive, fields
look unpadded.
- .o_service_booking: min-height:100% -> height:100% so the root is capped to the
  action area and overflow:auto scrolls INTERNALLY (min-height let it grow to
  content height, so the clipping action container never scrolled).
- input/select/textarea.f: padding 10px 12px !important + line-height 1.4 so
  Odoo's backend input normalisation can't strip the field padding.
- add a <=560px media query collapsing the .two/.three sub-grids, wrapping the
  time picker, and tightening margins (the main .grid already collapses at 780px).
- bump version 19.0.9.4.0 -> 19.0.9.5.0 (asset cache-bust).

Also harden scripts/verify_service_booking.sh: force-compile web.assets_backend +
web.assets_web_dark on the clone after tests, so a broken SCSS fails the deploy
gate BEFORE prod (a bad stylesheet would break the whole backend bundle; -u does
not compile assets — Odoo compiles them lazily).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:51:44 -04:00
gsinghpal
197030a188 feat(reports): packing slip in Print menu of SO, Work Order, Receiving
Generalize the delivery packing slip template to be model-agnostic
(branches on doc._name to resolve the sale order + ship-to for
sale.order / fp.job / fp.receiving / fusion.plating.delivery) and add
three report actions bound to sale.order, fp.job and fp.receiving so the
packing slip appears in each one's Print menu (delivery already had it).
Uses _scheduled / _notes so it never AttributeErrors on models without
scheduled_date / notes. Declare the fusion_plating_receiving dep on
reports (already transitive via logistics) for the fp.receiving binding.

Verified on entech: real content for SO-30102, WO-30102, RCV-30103; all
four Print-menu bindings live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:25:52 -04:00
gsinghpal
c97a0d985c feat(reports): packing slip for local deliveries (fusion.plating.delivery)
The packing slip report only existed for stock.picking (Delivery Orders),
but this shop ships via fusion.plating.delivery and has no pickings — so
packing slips never rendered for their flow, and the prior auto-generate +
email-notification paths pointed the stock.picking report at a delivery
(wrong model -> blank PDF).

Add a delivery-native variant: report_fp_packing_slip_delivery_portrait +
action_report_fp_packing_slip_delivery_portrait (bound to
fusion.plating.delivery -> shows in the delivery Print menu), resolving the
SO + lines from the delivery job_ref (same pattern as the BoL report) and
reusing the shared styles / address / signoff bits + a sale.order.line
items table. Repoint _fp_generate_packing_slip (dispatch auto-gen) and the
notification attachment to the new report.

Verified on entech: real content (customer, PO, items, PS#) for DLV-30102 —
142KB PDF vs prior blank 12.8KB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:12:50 -04:00
gsinghpal
e6bbf566ca feat(logistics): auto-generate packing slip on local delivery dispatch
fusion.plating.delivery already had packing_list_attachment_id + a viewer
action, but nothing populated it — shipping a local delivery produced no
packing slip. Add _fp_generate_packing_slip(): renders
fusion_plating_reports.action_report_fp_packing_slip_portrait and stores
it on packing_list_attachment_id. Hooked into action_start_route (the
dispatch / loaded-on-vehicle moment, so it travels with the goods) and as
a generate-if-missing catch-all on action_mark_delivered. Idempotent
(skips deliveries that already have one unless force=True) and best-effort
(a report glitch logs + continues, never blocks shipping). Report action
resolved at runtime so logistics keeps no hard dep on
fusion_plating_reports. Deployed + verified on entech (12.8KB PDF for
DLV-30097, rolled back).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:49:30 -04:00
gsinghpal
86e9fdead8 fix(jobs): guard res.partner.mobile read in delivery defaults (Odoo 19)
res.partner has no `mobile` field in this Odoo 19 build, so
_fp_resolve_delivery_defaults crashed with AttributeError when a job's
last shop step finished and auto-created a delivery
(button_finish -> _fp_check_advance_post_shop -> _fp_create_delivery).
This blocked operators from finishing the step at all.

Guard the read with the codebase's 'x' in obj._fields pattern so it
falls back to phone, and still picks up mobile on instances that define
it. Deployed + verified on entech (restart, no -u; pure Python change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:29:25 -04:00
gsinghpal
c80ffa1b2c feat(fusion_plating): internal sticker = external layout w/ internal notes + Receiving print buttons
- Internal Job Sticker is no longer a separate Layout A: it's now a COPY of the
  External sticker (Layout B, one per box, logo + WO + BOX + QR + rail fields +
  prominent PLATING THICKNESS banner) but feeds the INTERNAL description and
  labels its notes "INTERNAL NOTES" so the shop copy can't be confused with the
  customer copy. The old Layout-A body template is deleted.
- Removed the Internal sticker from the fp.job Print menu (binding_model_id ->
  False); it now prints from the Receiving screen instead.
- Added "External Sticker" + "Internal Sticker" print buttons to the fp.receiving
  form header (shown once a WO exists). Each renders one label per tracked box
  for the receiving's work order (passes a single WO so the SO-scoped box loop
  doesn't reprint each label per job).
Verified on entech (WO-30094 / RCV-30096): internal renders Layout B with the
internal description + INTERNAL NOTES; external unchanged; both receiving buttons
return the right report actions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:16:14 -04:00
gsinghpal
97880765b5 docs(fusion_plating): note .article UTF-8 fix breaks the dpi=96 mm job stickers
Caveat on the existing "custom-header reports need .article for UTF-8" rule:
the dpi=96 mm-based job stickers are the exception — adding .article re-routes
through Odoo's standard report CSS and blows up the mm/dpi layout. For those,
strip the non-ASCII glyph to ASCII in _clean() instead. (Learned fixing the
'375ºF' bake-text mojibake on the external sticker.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:59:32 -04:00
gsinghpal
587988bb06 feat(fusion_plating_jobs): external sticker — prominent thickness banner + strip degree mojibake
- Relocate plating thickness out of the cramped rail (where it shared a row
  with Due and wrapped) to a big 21pt "PLATING THICKNESS" banner at the top of
  the main panel — the team's most-watched spec, now hard to miss. Gated on a
  new has_thk flag (real value with a digit; skips empty/'N/A'). Due takes the
  full rail row.
- Fix the bake-text degree mojibake: operators type 'º' (U+00BA) for "375ºF";
  through this sticker's lightweight html_container path (no .article UTF-8
  wrapper) it renders "375°F". Adding a .article wrapper fixes encoding but
  blows up the dpi=96 mm layout (tested), so _clean() now strips º/°/˚ to clean
  ASCII -> "375F".
Verified on entech (WO-30094).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:57:33 -04:00
gsinghpal
a209648ed9 fix(fusion_plating_jobs): external sticker — stop the rail clipping the Due/Thk row
The left rail (overflow:hidden, fixed height) was ~8mm over its budget, so the
last grid row (Due | Thk) fell off the bottom and rendered as an empty band.
Reclaimed the height: r-logo 11→9mm, r-wo 14→13mm, r-qrflags 36→32mm (+ qfwrap-full
33→31mm / qffull line-height 36→32mm to match), r-fld padding 1→0.7mm. Due/Thk
now render fully. Verified on entech (WO-30094, PO 980933709).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:42:58 -04:00
gsinghpal
ea6b3fe2e9 fix(fusion_plating_jobs): internal sticker row 3 — tighten label/value gap, widen Thk
- Drop the <br/> between label and value (.lbl is display:block, so the
  value already sits beneath it; the <br/> added a blank-line gap).
- Rebalance widths: PO# 34→25%, Qty 16→13%, Due 30→26%, Thk 20→36% + nowrap,
  so a thickness RANGE (e.g. 0.0025" - 0.0030") stays on ONE line instead of
  wrapping. Verified on entech (WO-30096).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:07:16 -04:00
gsinghpal
b23eaa5695 feat(fusion_plating_jobs): internal sticker — add factory logo, customer→row 2, bigger 4-field row 3
Internal Job Sticker (Layout A) redesign per operator feedback:
- Factory (ENTECH) logo added to the black header band on a white chip
  (mirrors the external sticker; visible on the dark band + thermal print).
- Customer moved out of row 3 up next to Part# in row 2. Part# keeps the
  dominant width (stripped customer name is short + consistent), MASK/BAKE
  flags still float at the Part# edge.
- Row 3 now PO# / Qty / Due / Thk (4 fields, customer removed) with bigger
  values (13/14/12/11pt) spread across the full width.
- Internal header QR trimmed 30→27mm so the QR-driven band is shorter; the
  freed height flows to the NOTES block.
Rendered + verified on entech (WO-30072 / AMP-CANA).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:19:17 -04:00
gsinghpal
489312365e fix(certificates): honour recipe thickness suppression at cert issue
The recipe-cert-toggles feature (fb6cccc8) taught
fp.job._resolve_required_cert_types to suppress thickness for recipes
with requires_thickness_report=False (passivation, chemical conversion,
anodize seal-only). But the actual thickness-data ENFORCEMENT never got
the memo: both fp.certificate.action_issue's hard gate AND the
Issue-Certs wizard's readiness hint re-derived 'needs thickness' from
partner flags only and ignored the recipe. Result: a passivation CoC for
a thickness/strict customer could never be issued — the gate demanded
Fischerscope data the process physically cannot produce.

Consolidate the partner-flag + recipe-suppression logic into one
fp.certificate._fp_needs_thickness_data() helper and route both the gate
and the wizard through it, so the cert-type resolver and the issue-time
gate can never drift again. Add regression tests: passivation recipe
suppresses the issue gate even for strict-thickness customers; a normal
recipe still enforces (control, guards aerospace).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 10:43:05 -04:00
gsinghpal
6728197570 Update .DS_Store 2026-06-04 10:36:45 -04:00
gsinghpal
eea4dad048 Merge branch 'claude/technician-service-booking' into main
Technician Service Booking & Auto-Quote: OWL 'Book a Service' wizard,
editable fusion.service.rate rate-card table, auto draft repair Sale Order
(call-out + per-km), and the fusion_tasks datetime-inverse tz fix. Clone-verified
GREEN and deployed to westin-v19 (fusion_claims 19.0.9.4.0) on 2026-06-04.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:53:10 -04:00
gsinghpal
63694eccb1 fix(scripts): verify_service_booking — general orphan-FK sweep + test port fix + scoped tags
Hardened after the first real clone-verify on odoo-westin:
- Cleanup now generates an orphan-delete for EVERY single-column FK from PROD's
  pg_constraint and applies it to the clone (was tax-tables-only). westin-v19 also
  has deleted-company (payslip_tags_table, account_account_res_company_rel) and
  deleted-journal (account_payment_method_line) orphans that broke the clone -u.
- run_odoo passes --http-port=0 --gevent-port=0 so --test-enable (which forces
  http_spawn even with --no-http in Odoo 19) doesn't die on 'Address already in use'.
- TEST_TAGS scoped to this feature's classes (the broad tag also runs pre-existing
  dashboard/wizard tests that fail in this prod-config runner, unrelated to this work).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 06:10:25 -04:00
gsinghpal
252716156c test(fusion_tasks): tz test task needs description (NOT NULL) + is_in_store
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 06:07:36 -04:00
gsinghpal
dfa266d691 test(fusion_claims,fusion_tasks): fix clone-test failures (future dates + seed-aware asserts)
Real install verified on the Westin clone; these were test-only bugs:
- Task-create tests hardcoded scheduled_date 2026-06-03, now in the past, which
  the base _check_no_overlap rejects ('Cannot schedule tasks in the past'). Use
  future dates (tz test pins a future July date so Toronto stays EDT for the
  9:00->13:00 UTC assertion).
- Service-rate resolver tests created rows with seeded codes (callout_standard_normal,
  per_km) -> UNIQUE(code) violation post-install. Assert against the seed instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 06:04:11 -04:00
gsinghpal
7b8364eb58 fix(fusion_claims): seed service products as product.product (direct variant ref)
The <template>_product_variant auto external-ID is not reliably created in this
Odoo 19 (only 5 exist on westin-v19; none for these products or product_labor_hourly),
so the rate rows' product_id refs failed at install: 'External ID not found:
..._product_variant'. Seed each product as model=product.product (the xmlid IS the
variant; name/price/uom/etc. delegate via _inherits) and reference it directly.
In-shop labour now uses a dedicated product_labour_inshop ($75) rather than reusing
product_labor_hourly, whose variant xmlid likewise does not exist. Caught on the
Westin clone install.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 05:55:00 -04:00
gsinghpal
4e5e9f4c91 fix(fusion_claims): drop uom_po_id from seed labour products
product.template lost the separate purchase-UoM field uom_po_id in Odoo 19
(only uom_id remains). The plan's seed carried uom_po_id, which ParseErrors at
install: 'Invalid field uom_po_id in product.template'. Caught on first real
clone-install on the Westin Enterprise clone. The existing product_labor_hourly
uses uom_id only — match that.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 05:46:58 -04:00
gsinghpal
f84c22c743 feat(fusion_claims): Book a Service entry point + version bump
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 01:31:46 -04:00
gsinghpal
46d19fd581 fix(fusion_claims): OWL wizard review fixes (statement handler, scss borders, tech guard)
- Move the call-type select handler into onCallType() — OWL cannot compile a
  multi-statement inline t-on body (was a render-breaking crash on mount).
- Replace color-mix() inside border shorthands with var(--sb-border) (Odoo-19
  SCSS drops color-mix in a border shorthand).
- Technician placeholder option value '' (not 'false') so the required-tech
  guard isn't bypassed.
- Remove dead setTiming(); null-coalesce the refdata onWillStart load.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 01:29:55 -04:00
gsinghpal
56ca82c611 feat(fusion_claims): OWL service-booking wizard + dark/light SCSS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 01:16:29 -04:00
gsinghpal
d457b86eaa fix(fusion_claims): default booking description + isolate order-less task test
Review follow-up: the base fusion.technician.task.description is required=True and
non-in-store tasks require an address (_check_address_required). So:
- action_book_from_wizard now defaults description to 'Service booking' when the
  payload carries neither description nor issue (avoids a required-field failure).
- test_task_without_order_is_allowed now sets description + is_in_store=True so it
  exercises only the relaxed _check_order_link, not those unrelated base constraints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 01:09:14 -04:00
gsinghpal
92e8a18fcb feat(fusion_claims): action_book_from_wizard + jsonrpc booking routes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 01:00:53 -04:00
gsinghpal
245e551c68 feat(fusion_claims): service pricing resolver + draft-SO builder from rate table
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:54:39 -04:00
gsinghpal
a022eaaabe feat(fusion_claims): allow order-less tasks + service-repair SO flag
Relaxes _check_order_link to a no-op (service bookings auto-create their SO;
in-shop/walk-in tasks may have none) and adds x_fc_is_service_repair on
sale.order. The 'Service Repair' crm.tag from the plan is intentionally
omitted: fusion_claims does not depend on crm and sale.order has no tag_ids;
the boolean flag is the repair-SO identity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:51:10 -04:00
gsinghpal
0e6bb7b676 fix(fusion_tasks): make datetime inverses use the same tz resolver as compute
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:47:48 -04:00
gsinghpal
d5d410f6d0 chore(fusion_claims): bump version for service-rate foundation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:42:50 -04:00
gsinghpal
41141a75e8 feat(fusion_claims): Service Rates menu, list (inline-edit) + form + ACL
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:42:42 -04:00
gsinghpal
d512dfccf0 feat(fusion_claims): seed service-rate rows from the rate card
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:41:44 -04:00
gsinghpal
5e9576ed8f feat(fusion_claims): seed service-rate products
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:39:03 -04:00
gsinghpal
80d9a960e7 feat(fusion_claims): add fusion.service.rate model + resolvers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:38:27 -04:00
gsinghpal
3fe5d5c17c test(fusion_plating_shopfloor): sign-off tests use the authenticated admin + a recipe link
Clone-verify fixes: the HTTP request runs as base.user_admin, so set/read
x_fc_signature_image on that user (not self.env.user / uid 1); give the step a
recipe_node_id so button_finish passes the S21 no-recipe-link gate (also fixes
the pre-existing test_sign_off_finishes_step). 5/5 pass on an entech clone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:37:01 -04:00
gsinghpal
190b394001 feat(fusion_plating_shopfloor): workspace sign-off confirms saved signature, draws only when absent
onFinishStep: if the user has a saved Plating Signature, show FpSignatureConfirm
(one-tap, preview); otherwise open the draw-pad. Factored _openSignaturePad +
_commitSignOff (sends null data URI when using the saved signature).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:22:42 -04:00
gsinghpal
b5a300f439 feat(fusion_plating_shopfloor): FpSignatureConfirm dialog + asset registration
Confirm-with-preview dialog (saved-signature preview + Sign & Finish + Use a
different signature). Registered after the signature_pad assets; version bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:21:36 -04:00
gsinghpal
f0400114f9 docs(service-booking): add spec, plans, mockup, and clone-verify script
Kickoff brief, design spec, both implementation plans (rates foundation +
booking wizard), the UI mockup, and the hands-off Westin clone-verify/deploy
script for the Technician Service Booking feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:20:36 -04:00
gsinghpal
25ef7832f5 feat(fusion_plating_shopfloor): sign_off reuses+persists Plating Signature; load exposes it
/fp/workspace/sign_off: signature_data_uri now optional; a supplied drawing
persists to res.users.x_fc_signature_image (SELF_WRITEABLE) and the wasted
per-step ir.attachment is dropped; no drawing + a saved signature just finishes.
/fp/workspace/load exposes user_has_plating_signature + user_plating_signature.
Merged 3 new tests into the existing TestWorkspaceSignOff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:20:35 -04:00
gsinghpal
600e11fabb docs(fusion_plating_shopfloor): implementation plan - reuse saved Plating Signature
4 tasks: backend (load payload + sign_off persist/drop-attachment + HttpCase
tests) -> FpSignatureConfirm component + manifest -> job_workspace confirm-vs-draw
wiring -> entech clone-verify. Isolated worktree off main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:14:14 -04:00
gsinghpal
5e3e6b5319 docs(fusion_plating_shopfloor): spec - reuse saved Plating Signature on sign-off
Shop-floor sign-off currently makes operators redraw a signature every
time, and the drawing is discarded (reports read x_fc_signature_image).
Spec: use the saved Plating Signature (one-tap confirm-with-preview);
draw once when absent and persist it to x_fc_signature_image so future
sign-offs + reports reuse it. Tablet-workspace scope; no model/migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 00:06:10 -04:00
6715 changed files with 356 additions and 1273308 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"WebFetch(domain:docs.clover.com)"
]
},
"hooks": {
"UserPromptSubmit": [],
"Stop": [],
"Notification": []
}
}

54
.gitignore vendored
View File

@@ -15,4 +15,56 @@ __pycache__/
# Local-only diagnostic logs from test runs
_test_*.log
.superpowers/
# --- Split-out module repos (now independent git repos; managed separately) ---
/disable_iap_calls/
/disable_odoo_online/
/disable_publisher_warranty/
/fusion_accounts/
/fusion_api/
/fusion_canada_post/
/fusion_centralize_billing/
/fusion_chatter_enhance/
/fusion_claims/
/fusion_clock/
/fusion_clock_ai/
/fusion_clover/
/fusion_digitize/
/fusion_faxes/
/fusion_helpdesk/
/fusion_helpdesk_central/
/fusion_inventory/
/fusion_loaners_management/
/fusion_login_audit/
/fusion_ltc_management/
/fusion_notes/
/fusion_odoo_fixes/
/fusion_payroll/
/fusion_pdf_preview/
/fusion_planning/
/fusion_portal/
/fusion_poynt/
/fusion_rental/
/fusion_repairs/
/fusion_reports_templates/
/fusion_ringcentral/
/fusion_schedule/
/fusion_service_charges/
/fusion_shipping/
/fusion_so_to_po/
/fusion_tasks/
/fusion_templates/
/fusion_theme_switcher/
/fusion_voip_ringcentral/
/fusion_whitelabels/
/network_logger/
/nexa_coa_setup/
/fusion_plating/
/fusion_accounting/
/fusion_iot/
/fusion_labels/
/fusion_projects/
/fusion-statements/
/fusion-woo-odoo/
/fusion-expenses/
/fusion_configurator/

58
SYNC.md Normal file
View File

@@ -0,0 +1,58 @@
# Syncing Odoo-Modules across machines (Mac + Windows)
Each module/suite folder here is its **own git repo** (private on GitHub at
`gsinghpal/<name>`, mirrored to gitea `admin/<name>`). This parent folder is a
separate repo that holds the shared files (CLAUDE.md, docs, scripts, these sync
helpers). The cloud (GitHub) is the hub: both machines push to it and pull from it.
Nothing here ever deletes your work. Pulls are fast-forward only, so local changes
are never overwritten; pushes only send commits.
## First-time setup on a new machine (e.g. the Windows PC)
1. Install **Git** (Git for Windows includes "Git Bash", which runs these scripts).
2. Sign in to GitHub once so git can push/pull:
- easiest: `gh auth login` (or let Git Credential Manager prompt on first pull)
3. Get everything:
```
git clone https://github.com/gsinghpal/Odoo-Modules.git
cd Odoo-Modules
bash sync-clone-all.sh
```
That clones the parent, then all 49 module repos into place.
(gitea is an optional second mirror. The first push to it will ask for your
`git.nexasystems.ca` login. If you only use GitHub, those gitea lines just fail
quietly and GitHub stays the source of truth.)
## Daily workflow (same on Mac and Windows)
- **Before you start:** `bash sync-pull-all.sh` - pulls the latest for the parent
and every module. Anything with local changes or a diverged history is skipped and
listed, so you can handle it yourself.
- **Do your work**, then **commit inside the module(s) you changed**:
```
cd fusion_clock
git add -A
git commit -m "..."
cd ..
```
- **When done:** `bash sync-push-all.sh` - pushes every committed change to GitHub
+ gitea, and flags any repo that still has uncommitted changes (so nothing is
silently left behind).
## Golden rule for two machines
Push from the machine you worked on **before** you switch to the other one, and run
`sync-pull-all.sh` on the other machine **before** you start. That keeps both in sync
and avoids diverged histories.
## Helper scripts
| Script | What it does |
|--------|--------------|
| `sync-clone-all.sh` | Clone any module repo listed in `repos.txt` that isn't here yet. |
| `sync-pull-all.sh` | Fast-forward pull the parent + all modules (safe, never clobbers). |
| `sync-push-all.sh` | Push committed work for the parent + all modules to GitHub + gitea. |
| `sync-refresh-list.sh` | Rebuild `repos.txt` from the repos present here (after adding/removing a module). |
| `repos.txt` | The list of module repo names the scripts act on. |

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Disable IAP Calls',
'version': '19.0.1.0.0',
'category': 'Tools',
'summary': 'Disables all IAP (In-App Purchase) external API calls',
'description': """
This module completely disables:
- IAP service calls to Odoo servers
- OCR/Extract API calls
- Lead enrichment API calls
- Any other external Odoo API communication
For local development use only.
""",
'author': 'Development',
'depends': ['iap'],
'data': [],
'installable': True,
'auto_install': True,
'license': 'LGPL-3',
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Disable IAP Calls',
'version': '19.0.1.0.0',
'category': 'Tools',
'summary': 'Disables all IAP (In-App Purchase) external API calls',
'description': """
This module completely disables:
- IAP service calls to Odoo servers
- OCR/Extract API calls
- Lead enrichment API calls
- Any other external Odoo API communication
For local development use only.
""",
'author': 'Development',
'depends': ['iap'],
'data': [],
'installable': True,
'auto_install': True,
'license': 'LGPL-3',
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import iap_account

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Disable all IAP external API calls for local development
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class IapAccountDisabled(models.Model):
_inherit = 'iap.account'
@api.model
def get_credits(self, service_name):
"""
DISABLED: Return fake unlimited credits
"""
_logger.info("IAP get_credits DISABLED - returning unlimited credits for %s", service_name)
return 999999

View File

@@ -1,86 +0,0 @@
# Graph Report - /Users/gurpreet/Github/Odoo-Modules/disable_iap_calls (2026-04-22)
## Corpus Check
- 8 files · ~284 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 11 nodes · 8 edges · 8 communities detected
- Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
## God Nodes (most connected - your core abstractions)
1. `IapAccountDisabled` - 2 edges
2. `get_credits()` - 2 edges
3. `DISABLED: Return fake unlimited credits` - 0 edges
## Surprising Connections (you probably didn't know these)
- None detected - all connections are within the same source files.
## Communities
### Community 0 - "Community 0"
Cohesion: 0.67
Nodes (2): get_credits(), IapAccountDisabled
### Community 1 - "Community 1"
Cohesion: 1.0
Nodes (0):
### Community 2 - "Community 2"
Cohesion: 1.0
Nodes (0):
### Community 3 - "Community 3"
Cohesion: 1.0
Nodes (0):
### Community 4 - "Community 4"
Cohesion: 1.0
Nodes (0):
### Community 5 - "Community 5"
Cohesion: 1.0
Nodes (0):
### Community 6 - "Community 6"
Cohesion: 1.0
Nodes (1): DISABLED: Return fake unlimited credits
### Community 7 - "Community 7"
Cohesion: 1.0
Nodes (0):
## Knowledge Gaps
- **1 isolated node(s):** `DISABLED: Return fake unlimited credits`
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 1`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 2`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 3`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 4`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 5`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 6`** (1 nodes): `DISABLED: Return fake unlimited credits`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 7`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **What connects `DISABLED: Return fake unlimited credits` to the rest of the system?**
_1 weakly-connected nodes found - possible documentation gaps or missing edges._

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py", "target": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py", "target": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py", "label": "iap_account.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L1"}, {"id": "iap_account_iapaccountdisabled", "label": "IapAccountDisabled", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L10"}, {"id": "iap_account_get_credits", "label": "get_credits()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L14"}, {"id": "iap_account_rationale_15", "label": "DISABLED: Return fake unlimited credits", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L15"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py", "target": "logging", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L4", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L5", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py", "target": "iap_account_iapaccountdisabled", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L10", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py", "target": "iap_account_get_credits", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L14", "weight": 1.0}, {"source": "iap_account_rationale_15", "target": "iap_account_iapaccountdisabled_get_credits", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L15", "weight": 1.0}], "raw_calls": [{"caller_nid": "iap_account_get_credits", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py", "source_location": "L18"}]}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py", "label": "iap_account.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L1"}, {"id": "iap_account_iapaccountdisabled", "label": "IapAccountDisabled", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L10"}, {"id": "iap_account_get_credits", "label": "get_credits()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L14"}, {"id": "iap_account_rationale_15", "label": "DISABLED: Return fake unlimited credits", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L15"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py", "target": "logging", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L4", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L5", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py", "target": "iap_account_iapaccountdisabled", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L10", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py", "target": "iap_account_get_credits", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L14", "weight": 1.0}, {"source": "iap_account_rationale_15", "target": "iap_account_iapaccountdisabled_get_credits", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L15", "weight": 1.0}], "raw_calls": [{"caller_nid": "iap_account_get_credits", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py", "source_location": "L18"}]}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_manifest_py", "label": "__manifest__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/__manifest__.py", "source_location": "L1"}], "edges": [], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_manifest_py", "label": "__manifest__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/__manifest__.py", "source_location": "L1"}], "edges": [], "raw_calls": []}

File diff suppressed because one or more lines are too long

View File

@@ -1,205 +0,0 @@
{
"directed": false,
"multigraph": false,
"graph": {},
"nodes": [
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py",
"community": 1,
"norm_label": "__init__.py"
},
{
"label": "__manifest__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/__manifest__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_manifest_py",
"community": 5,
"norm_label": "__manifest__.py"
},
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py",
"community": 2,
"norm_label": "__init__.py"
},
{
"label": "iap_account.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py",
"community": 0,
"norm_label": "iap_account.py"
},
{
"label": "IapAccountDisabled",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py",
"source_location": "L10",
"id": "iap_account_iapaccountdisabled",
"community": 0,
"norm_label": "iapaccountdisabled"
},
{
"label": "get_credits()",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py",
"source_location": "L14",
"id": "iap_account_get_credits",
"community": 0,
"norm_label": "get_credits()"
},
{
"label": "DISABLED: Return fake unlimited credits",
"file_type": "rationale",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py",
"source_location": "L15",
"id": "iap_account_rationale_15",
"community": 6,
"norm_label": "disabled: return fake unlimited credits"
},
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py",
"community": 3,
"norm_label": "__init__.py"
},
{
"label": "__manifest__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/__manifest__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_manifest_py",
"community": 7,
"norm_label": "__manifest__.py"
},
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py",
"community": 4,
"norm_label": "__init__.py"
},
{
"label": "iap_account.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py",
"community": 0,
"norm_label": "iap_account.py"
}
],
"links": [
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_iap_calls_init_py",
"confidence_score": 1.0
},
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_init_py",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py",
"source_location": "L10",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py",
"_tgt": "iap_account_iapaccountdisabled",
"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py",
"target": "iap_account_iapaccountdisabled",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/models/iap_account.py",
"source_location": "L14",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py",
"_tgt": "iap_account_get_credits",
"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_models_iap_account_py",
"target": "iap_account_get_credits",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py",
"source_location": "L10",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py",
"_tgt": "iap_account_iapaccountdisabled",
"source": "iap_account_iapaccountdisabled",
"target": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/iap_account.py",
"source_location": "L14",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py",
"_tgt": "iap_account_get_credits",
"source": "iap_account_get_credits",
"target": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_iap_account_py",
"confidence_score": 1.0
},
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_init_py",
"confidence_score": 1.0
},
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_iap_calls/disable_iap_calls/models/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_iap_calls_disable_iap_calls_models_init_py",
"confidence_score": 1.0
}
],
"hyperedges": []
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import iap_account

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Disable all IAP external API calls for local development
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class IapAccountDisabled(models.Model):
_inherit = 'iap.account'
@api.model
def get_credits(self, service_name):
"""
DISABLED: Return fake unlimited credits
"""
_logger.info("IAP get_credits DISABLED - returning unlimited credits for %s", service_name)
return 999999

View File

@@ -1,143 +0,0 @@
# Disable Odoo Online Services
**Version:** 18.0.1.0.0
**License:** LGPL-3
**Odoo Version:** 18.0
## Overview
This module comprehensively disables all external communications between your Odoo instance and Odoo's servers. It prevents:
- License/subscription checks
- User count reporting
- IAP (In-App Purchase) credit checks
- Publisher warranty communications
- Partner autocomplete/enrichment
- Expiration warnings in the UI
## Features
### 1. IAP JSON-RPC Blocking
Patches the core `iap_jsonrpc` function to prevent all IAP API calls:
- Returns fake successful responses
- Logs all blocked calls
- Provides unlimited credits for services that check
### 2. License Parameter Protection
Protects critical `ir.config_parameter` values:
- `database.expiration_date` → Always returns `2099-12-31 23:59:59`
- `database.expiration_reason` → Always returns `renewal`
- `database.enterprise_code` → Always returns `PERMANENT_LOCAL`
### 3. Session Info Patching
Modifies `session_info()` to prevent frontend warnings:
- Sets expiration date to 2099
- Sets `warning` to `False`
- Removes "already linked" subscription prompts
### 4. User Creation Protection
Logs user creation without triggering subscription checks:
- Blocks any external validation
- Logs permission changes
### 5. Publisher Warranty Block
Disables all warranty-related server communication:
- `_get_sys_logs()` → Returns empty response
- `update_notification()` → Returns success without calling server
### 6. Cron Job Blocking
Blocks scheduled actions that contact Odoo:
- Publisher Warranty Check
- Database Auto-Expiration Check
- Various IAP-related crons
## Installation
1. Copy the module to your Odoo addons directory
2. Restart Odoo
3. Go to Apps → Update Apps List
4. Search for "Disable Odoo Online Services"
5. Click Install
## Verification
Check that blocking is active:
```bash
docker logs odoo-container 2>&1 | grep -i "BLOCKED\|DISABLED"
```
Expected output:
```
IAP JSON-RPC calls have been DISABLED globally
Module update_list: Scanning local addons only (Odoo Apps store disabled)
Publisher warranty update_notification BLOCKED
Creating 1 user(s) - subscription check DISABLED
```
## Configuration
No configuration required. The module automatically:
- Sets permanent expiration values on install (via `_post_init_hook`)
- Patches all necessary functions when loaded
- Protects values from being changed
## Technical Details
### Files
| File | Purpose |
|------|---------|
| `models/disable_iap_tools.py` | Patches `iap_jsonrpc` globally |
| `models/disable_online_services.py` | Blocks publisher warranty, cron jobs |
| `models/disable_database_expiration.py` | Protects `ir.config_parameter` |
| `models/disable_session_leaks.py` | Patches session info, user creation |
| `models/disable_partner_autocomplete.py` | Blocks partner enrichment |
| `models/disable_all_external.py` | Additional external call blocks |
### Blocked Endpoints
All redirected to `http://localhost:65535`:
- `iap.endpoint`
- `publisher_warranty_url`
- `partner_autocomplete.endpoint`
- `iap_extract_endpoint`
- `olg.endpoint`
- `mail.media_library_endpoint`
- `sms.endpoint`
- `crm.iap_lead_mining.endpoint`
- And many more...
## Dependencies
- `base`
- `web`
- `iap`
- `mail`
- `base_setup`
## Compatibility
- Odoo 18.0 Community Edition
- Odoo 18.0 Enterprise Edition
## Disclaimer
This module is intended for legitimate use cases such as:
- Air-gapped environments
- Development/testing instances
- Self-hosted deployments with proper licensing
Ensure you comply with Odoo's licensing terms for your use case.
## Changelog
### 1.0.0 (2025-12-29)
- Initial release
- IAP blocking
- Publisher warranty blocking
- Session info patching
- User creation protection
- Config parameter protection

View File

@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
from . import models
def _post_init_hook(env):
"""
Set all configuration parameters to disable external Odoo services.
This runs after module installation.
"""
import logging
_logger = logging.getLogger(__name__)
set_param = env['ir.config_parameter'].sudo().set_param
# Set permanent database expiration
params_to_set = {
# Database license parameters
'database.expiration_date': '2099-12-31 23:59:59',
'database.expiration_reason': 'renewal',
'database.enterprise_code': 'PERMANENT_LOCAL',
# Clear "already linked" parameters
'database.already_linked_subscription_url': '',
'database.already_linked_email': '',
'database.already_linked_send_mail_url': '',
# Redirect all IAP endpoints to localhost
'iap.endpoint': 'http://localhost:65535',
'partner_autocomplete.endpoint': 'http://localhost:65535',
'iap_extract_endpoint': 'http://localhost:65535',
'olg.endpoint': 'http://localhost:65535',
'mail.media_library_endpoint': 'http://localhost:65535',
'website.api_endpoint': 'http://localhost:65535',
'sms.endpoint': 'http://localhost:65535',
'crm.iap_lead_mining.endpoint': 'http://localhost:65535',
'reveal.endpoint': 'http://localhost:65535',
'publisher_warranty_url': 'http://localhost:65535',
# OCN (Odoo Cloud Notification) - blocks push notifications to Odoo
'odoo_ocn.endpoint': 'http://localhost:65535', # Main OCN endpoint
'mail_mobile.enable_ocn': 'False', # Disable OCN push notifications
'odoo_ocn.project_id': '', # Clear any registered project
'ocn.uuid': '', # Clear OCN UUID to prevent registration
# Snailmail (physical mail service)
'snailmail.endpoint': 'http://localhost:65535',
# Social media IAP
'social.facebook_endpoint': 'http://localhost:65535',
'social.twitter_endpoint': 'http://localhost:65535',
'social.linkedin_endpoint': 'http://localhost:65535',
}
_logger.info("=" * 60)
_logger.info("DISABLE ODOO ONLINE: Setting configuration parameters")
_logger.info("=" * 60)
for key, value in params_to_set.items():
try:
set_param(key, value)
_logger.info("Set %s = %s", key, value if len(str(value)) < 30 else value[:30] + "...")
except Exception as e:
_logger.warning("Could not set %s: %s", key, e)
_logger.info("=" * 60)
_logger.info("DISABLE ODOO ONLINE: Configuration complete")
_logger.info("=" * 60)

View File

@@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Disable Odoo Online Services',
'version': '19.0.1.0.0',
'category': 'Tools',
'summary': 'Blocks ALL external Odoo server communications',
'description': """
Comprehensive Module to Disable ALL Odoo Online Services
=========================================================
This module completely blocks all external communications from Odoo to Odoo's servers.
**Blocked Services:**
- Publisher Warranty checks (license validation)
- IAP (In-App Purchase) - All services
- Partner Autocomplete API
- Company Enrichment API
- VAT Lookup API
- SMS API
- Invoice/Expense OCR Extract
- Media Library (Stock Images)
- Currency Rate Live Updates
- CRM Lead Mining
- CRM Reveal (Website visitor identification)
- Google Calendar Sync
- AI/OLG Content Generation
- Database Registration
- Module Update checks from Odoo Store
- Session-based license detection
- Frontend expiration panel warnings
**Use Cases:**
- Air-gapped installations
- Local development without internet
- Enterprise deployments that don't want telemetry
- Testing environments
**WARNING:** This module disables legitimate Odoo services.
Only use if you understand the implications.
""",
'author': 'Fusion Development',
'website': 'https://fusiondevelopment.com',
'license': 'LGPL-3',
'depends': ['base', 'mail', 'web'],
'data': [
'data/disable_external_services.xml',
],
'assets': {
'web.assets_backend': [
'disable_odoo_online/static/src/js/disable_external_links.js',
],
},
'installable': True,
'auto_install': False,
'application': False,
}

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- All config parameters are set via post_init_hook in __init__.py -->
<!-- This file is kept for future data records if needed -->
</odoo>

View File

@@ -1,143 +0,0 @@
# Disable Odoo Online Services
**Version:** 18.0.1.0.0
**License:** LGPL-3
**Odoo Version:** 18.0
## Overview
This module comprehensively disables all external communications between your Odoo instance and Odoo's servers. It prevents:
- License/subscription checks
- User count reporting
- IAP (In-App Purchase) credit checks
- Publisher warranty communications
- Partner autocomplete/enrichment
- Expiration warnings in the UI
## Features
### 1. IAP JSON-RPC Blocking
Patches the core `iap_jsonrpc` function to prevent all IAP API calls:
- Returns fake successful responses
- Logs all blocked calls
- Provides unlimited credits for services that check
### 2. License Parameter Protection
Protects critical `ir.config_parameter` values:
- `database.expiration_date` → Always returns `2099-12-31 23:59:59`
- `database.expiration_reason` → Always returns `renewal`
- `database.enterprise_code` → Always returns `PERMANENT_LOCAL`
### 3. Session Info Patching
Modifies `session_info()` to prevent frontend warnings:
- Sets expiration date to 2099
- Sets `warning` to `False`
- Removes "already linked" subscription prompts
### 4. User Creation Protection
Logs user creation without triggering subscription checks:
- Blocks any external validation
- Logs permission changes
### 5. Publisher Warranty Block
Disables all warranty-related server communication:
- `_get_sys_logs()` → Returns empty response
- `update_notification()` → Returns success without calling server
### 6. Cron Job Blocking
Blocks scheduled actions that contact Odoo:
- Publisher Warranty Check
- Database Auto-Expiration Check
- Various IAP-related crons
## Installation
1. Copy the module to your Odoo addons directory
2. Restart Odoo
3. Go to Apps → Update Apps List
4. Search for "Disable Odoo Online Services"
5. Click Install
## Verification
Check that blocking is active:
```bash
docker logs odoo-container 2>&1 | grep -i "BLOCKED\|DISABLED"
```
Expected output:
```
IAP JSON-RPC calls have been DISABLED globally
Module update_list: Scanning local addons only (Odoo Apps store disabled)
Publisher warranty update_notification BLOCKED
Creating 1 user(s) - subscription check DISABLED
```
## Configuration
No configuration required. The module automatically:
- Sets permanent expiration values on install (via `_post_init_hook`)
- Patches all necessary functions when loaded
- Protects values from being changed
## Technical Details
### Files
| File | Purpose |
|------|---------|
| `models/disable_iap_tools.py` | Patches `iap_jsonrpc` globally |
| `models/disable_online_services.py` | Blocks publisher warranty, cron jobs |
| `models/disable_database_expiration.py` | Protects `ir.config_parameter` |
| `models/disable_session_leaks.py` | Patches session info, user creation |
| `models/disable_partner_autocomplete.py` | Blocks partner enrichment |
| `models/disable_all_external.py` | Additional external call blocks |
### Blocked Endpoints
All redirected to `http://localhost:65535`:
- `iap.endpoint`
- `publisher_warranty_url`
- `partner_autocomplete.endpoint`
- `iap_extract_endpoint`
- `olg.endpoint`
- `mail.media_library_endpoint`
- `sms.endpoint`
- `crm.iap_lead_mining.endpoint`
- And many more...
## Dependencies
- `base`
- `web`
- `iap`
- `mail`
- `base_setup`
## Compatibility
- Odoo 18.0 Community Edition
- Odoo 18.0 Enterprise Edition
## Disclaimer
This module is intended for legitimate use cases such as:
- Air-gapped environments
- Development/testing instances
- Self-hosted deployments with proper licensing
Ensure you comply with Odoo's licensing terms for your use case.
## Changelog
### 1.0.0 (2025-12-29)
- Initial release
- IAP blocking
- Publisher warranty blocking
- Session info patching
- User creation protection
- Config parameter protection

View File

@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
from . import models
def _post_init_hook(env):
"""
Set all configuration parameters to disable external Odoo services.
This runs after module installation.
"""
import logging
_logger = logging.getLogger(__name__)
set_param = env['ir.config_parameter'].sudo().set_param
# Set permanent database expiration
params_to_set = {
# Database license parameters
'database.expiration_date': '2099-12-31 23:59:59',
'database.expiration_reason': 'renewal',
'database.enterprise_code': 'PERMANENT_LOCAL',
# Clear "already linked" parameters
'database.already_linked_subscription_url': '',
'database.already_linked_email': '',
'database.already_linked_send_mail_url': '',
# Redirect all IAP endpoints to localhost
'iap.endpoint': 'http://localhost:65535',
'partner_autocomplete.endpoint': 'http://localhost:65535',
'iap_extract_endpoint': 'http://localhost:65535',
'olg.endpoint': 'http://localhost:65535',
'mail.media_library_endpoint': 'http://localhost:65535',
'website.api_endpoint': 'http://localhost:65535',
'sms.endpoint': 'http://localhost:65535',
'crm.iap_lead_mining.endpoint': 'http://localhost:65535',
'reveal.endpoint': 'http://localhost:65535',
'publisher_warranty_url': 'http://localhost:65535',
# OCN (Odoo Cloud Notification) - blocks push notifications to Odoo
'odoo_ocn.endpoint': 'http://localhost:65535', # Main OCN endpoint
'mail_mobile.enable_ocn': 'False', # Disable OCN push notifications
'odoo_ocn.project_id': '', # Clear any registered project
'ocn.uuid': '', # Clear OCN UUID to prevent registration
# Snailmail (physical mail service)
'snailmail.endpoint': 'http://localhost:65535',
# Social media IAP
'social.facebook_endpoint': 'http://localhost:65535',
'social.twitter_endpoint': 'http://localhost:65535',
'social.linkedin_endpoint': 'http://localhost:65535',
}
_logger.info("=" * 60)
_logger.info("DISABLE ODOO ONLINE: Setting configuration parameters")
_logger.info("=" * 60)
for key, value in params_to_set.items():
try:
set_param(key, value)
_logger.info("Set %s = %s", key, value if len(str(value)) < 30 else value[:30] + "...")
except Exception as e:
_logger.warning("Could not set %s: %s", key, e)
_logger.info("=" * 60)
_logger.info("DISABLE ODOO ONLINE: Configuration complete")
_logger.info("=" * 60)

View File

@@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Disable Odoo Online Services',
'version': '19.0.1.0.0',
'category': 'Tools',
'summary': 'Blocks ALL external Odoo server communications',
'description': """
Comprehensive Module to Disable ALL Odoo Online Services
=========================================================
This module completely blocks all external communications from Odoo to Odoo's servers.
**Blocked Services:**
- Publisher Warranty checks (license validation)
- IAP (In-App Purchase) - All services
- Partner Autocomplete API
- Company Enrichment API
- VAT Lookup API
- SMS API
- Invoice/Expense OCR Extract
- Media Library (Stock Images)
- Currency Rate Live Updates
- CRM Lead Mining
- CRM Reveal (Website visitor identification)
- Google Calendar Sync
- AI/OLG Content Generation
- Database Registration
- Module Update checks from Odoo Store
- Session-based license detection
- Frontend expiration panel warnings
**Use Cases:**
- Air-gapped installations
- Local development without internet
- Enterprise deployments that don't want telemetry
- Testing environments
**WARNING:** This module disables legitimate Odoo services.
Only use if you understand the implications.
""",
'author': 'Fusion Development',
'website': 'https://fusiondevelopment.com',
'license': 'LGPL-3',
'depends': ['base', 'mail', 'web'],
'data': [
'data/disable_external_services.xml',
],
'assets': {
'web.assets_backend': [
'disable_odoo_online/static/src/js/disable_external_links.js',
],
},
'installable': True,
'auto_install': False,
'application': False,
}

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- All config parameters are set via post_init_hook in __init__.py -->
<!-- This file is kept for future data records if needed -->
</odoo>

View File

@@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
from . import disable_iap_tools # Patches iap_jsonrpc globally - MUST be first
from . import disable_http_requests # Patches requests library to block Odoo domains
from . import disable_online_services
from . import disable_partner_autocomplete
from . import disable_database_expiration
from . import disable_all_external
from . import disable_session_leaks

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
"""
Comprehensive blocking of ALL external Odoo service calls.
Only inherits from models that are guaranteed to exist in base Odoo.
"""
import logging
from odoo import api, models, fields
_logger = logging.getLogger(__name__)
# ============================================================
# Block Currency Rate Live Updates - Uses res.currency which always exists
# ============================================================
class ResCurrencyDisabled(models.Model):
_inherit = 'res.currency'
@api.model
def _get_rates_from_provider(self, provider, date):
"""DISABLED: Return empty rates."""
_logger.debug("Currency rate provider BLOCKED: provider=%s", provider)
return {}
# ============================================================
# Block Gravatar - Uses res.partner which always exists
# ============================================================
class ResPartnerDisabled(models.Model):
_inherit = 'res.partner'
@api.model
def _get_gravatar_image(self, email):
"""DISABLED: Return False to skip gravatar lookup."""
_logger.debug("Gravatar lookup BLOCKED for email=%s", email)
return False

View File

@@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
"""
Disable database expiration checks and registration.
Consolidates all ir.config_parameter overrides.
"""
import logging
from datetime import datetime
from odoo import api, models, fields
_logger = logging.getLogger(__name__)
class IrConfigParameter(models.Model):
"""Override config parameters to prevent expiration and protect license values."""
_inherit = 'ir.config_parameter'
PROTECTED_PARAMS = {
'database.expiration_date': '2099-12-31 23:59:59',
'database.expiration_reason': 'renewal',
'database.enterprise_code': 'PERMANENT_LOCAL',
}
CLEAR_PARAMS = [
'database.already_linked_subscription_url',
'database.already_linked_email',
'database.already_linked_send_mail_url',
]
def init(self, force=False):
"""Set permanent valid subscription on module init."""
super().init(force=force)
self._set_permanent_subscription()
@api.model
def _set_permanent_subscription(self):
"""Set database to never expire."""
_logger.info("Setting permanent subscription values...")
for key, value in self.PROTECTED_PARAMS.items():
try:
self.env.cr.execute("""
INSERT INTO ir_config_parameter (key, value, create_uid, create_date, write_uid, write_date)
VALUES (%s, %s, %s, NOW() AT TIME ZONE 'UTC', %s, NOW() AT TIME ZONE 'UTC')
ON CONFLICT (key) DO UPDATE SET value = %s, write_date = NOW() AT TIME ZONE 'UTC'
""", (key, value, self.env.uid, self.env.uid, value))
except Exception as e:
_logger.debug("Could not set param %s: %s", key, e)
for key in self.CLEAR_PARAMS:
try:
self.env.cr.execute("""
INSERT INTO ir_config_parameter (key, value, create_uid, create_date, write_uid, write_date)
VALUES (%s, '', %s, NOW() AT TIME ZONE 'UTC', %s, NOW() AT TIME ZONE 'UTC')
ON CONFLICT (key) DO UPDATE SET value = '', write_date = NOW() AT TIME ZONE 'UTC'
""", (key, self.env.uid, self.env.uid))
except Exception as e:
_logger.debug("Could not clear param %s: %s", key, e)
@api.model
def get_param(self, key, default=False):
"""Override get_param to return permanent values for protected params."""
if key in self.PROTECTED_PARAMS:
return self.PROTECTED_PARAMS[key]
if key in self.CLEAR_PARAMS:
return ''
return super().get_param(key, default)
def set_param(self, key, value):
"""Override set_param to prevent external processes from changing protected values."""
if key in self.PROTECTED_PARAMS:
if value != self.PROTECTED_PARAMS[key]:
_logger.warning("Blocked attempt to change protected param %s to %s", key, value)
return True
if key in self.CLEAR_PARAMS:
value = ''
return super().set_param(key, value)
class DatabaseExpirationCheck(models.AbstractModel):
_name = 'disable.odoo.online.expiration'
_description = 'Database Expiration Blocker'
@api.model
def check_database_expiration(self):
return {
'valid': True,
'expiration_date': '2099-12-31 23:59:59',
'expiration_reason': 'renewal',
}
class Base(models.AbstractModel):
_inherit = 'base'
@api.model
def _get_database_expiration_date(self):
return datetime(2099, 12, 31, 23, 59, 59)
@api.model
def _check_database_enterprise_expiration(self):
return True

View File

@@ -1,129 +0,0 @@
# -*- coding: utf-8 -*-
"""
Block ALL outgoing HTTP requests to Odoo-related domains.
This patches the requests library to intercept and block external calls.
"""
import logging
import requests
from functools import wraps
from urllib.parse import urlparse
_logger = logging.getLogger(__name__)
# Domains to block - all Odoo external services
BLOCKED_DOMAINS = [
'odoo.com',
'odoofin.com',
'odoo.sh',
'iap.odoo.com',
'iap-services.odoo.com',
'partner-autocomplete.odoo.com',
'iap-extract.odoo.com',
'iap-sms.odoo.com',
'upgrade.odoo.com',
'apps.odoo.com',
'production.odoofin.com',
'plaid.com',
'yodlee.com',
'gravatar.com',
'www.gravatar.com',
'secure.gravatar.com',
]
# Store original functions
_original_request = None
_original_get = None
_original_post = None
def _is_blocked_url(url):
"""Check if the URL should be blocked."""
if not url:
return False
try:
parsed = urlparse(url)
domain = parsed.netloc.lower()
for blocked in BLOCKED_DOMAINS:
if blocked in domain:
return True
except Exception:
pass
return False
def _blocked_request(method, url, **kwargs):
"""Intercept and block requests to Odoo domains."""
if _is_blocked_url(url):
_logger.warning("HTTP REQUEST BLOCKED: %s %s", method.upper(), url)
# Return a mock response
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
return response
return _original_request(method, url, **kwargs)
def _blocked_get(url, **kwargs):
"""Intercept and block GET requests."""
if _is_blocked_url(url):
_logger.warning("HTTP GET BLOCKED: %s", url)
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
return response
return _original_get(url, **kwargs)
def _blocked_post(url, **kwargs):
"""Intercept and block POST requests."""
if _is_blocked_url(url):
_logger.warning("HTTP POST BLOCKED: %s", url)
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
return response
return _original_post(url, **kwargs)
def patch_requests():
"""Monkey-patch requests library to block Odoo domains."""
global _original_request, _original_get, _original_post
try:
if _original_request is None:
_original_request = requests.Session.request
_original_get = requests.get
_original_post = requests.post
# Patch Session.request (catches most calls)
def patched_session_request(self, method, url, **kwargs):
if _is_blocked_url(url):
_logger.warning("HTTP SESSION REQUEST BLOCKED: %s %s", method.upper(), url)
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
response.request = requests.models.PreparedRequest()
response.request.url = url
response.request.method = method
return response
return _original_request(self, method, url, **kwargs)
requests.Session.request = patched_session_request
requests.get = _blocked_get
requests.post = _blocked_post
_logger.info("HTTP requests to Odoo domains have been BLOCKED")
_logger.info("Blocked domains: %s", ', '.join(BLOCKED_DOMAINS))
except Exception as e:
_logger.warning("Could not patch requests library: %s", e)
# Apply patch when module is imported
patch_requests()

View File

@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
"""
Override the core IAP tools to block ALL external API calls.
This is the master switch that blocks ALL Odoo external communications.
"""
import logging
from odoo import exceptions, _
_logger = logging.getLogger(__name__)
# Store original function reference
_original_iap_jsonrpc = None
def _disabled_iap_jsonrpc(url, method='call', params=None, timeout=15):
"""
DISABLED: Block all IAP JSON-RPC calls.
Returns empty/success response instead of making external calls.
"""
_logger.info("IAP JSONRPC BLOCKED: %s (method=%s)", url, method)
# Return appropriate empty responses based on the endpoint
if '/authorize' in url:
return 'fake_transaction_token_disabled'
elif '/capture' in url or '/cancel' in url:
return True
elif '/credits' in url:
return 999999
elif 'partner-autocomplete' in url:
return []
elif 'enrich' in url:
return {}
elif 'sms' in url:
_logger.warning("SMS API call blocked - SMS will not be sent")
return {'state': 'success', 'credits': 999999}
elif 'extract' in url:
return {'status': 'success', 'credits': 999999}
else:
return {}
def patch_iap_tools():
"""
Monkey-patch the iap_jsonrpc function to block external calls.
This is called when the module loads.
"""
global _original_iap_jsonrpc
try:
from odoo.addons.iap.tools import iap_tools
if _original_iap_jsonrpc is None:
_original_iap_jsonrpc = iap_tools.iap_jsonrpc
iap_tools.iap_jsonrpc = _disabled_iap_jsonrpc
_logger.info("IAP JSON-RPC calls have been DISABLED globally")
except ImportError:
_logger.debug("IAP module not installed, skipping patch")
except Exception as e:
_logger.warning("Could not patch IAP tools: %s", e)
# Apply patch when module is imported
patch_iap_tools()

View File

@@ -1,153 +0,0 @@
# -*- coding: utf-8 -*-
"""
Disable various Odoo online services and external API calls.
"""
import logging
from odoo import api, models, fields
_logger = logging.getLogger(__name__)
class IrModuleModule(models.Model):
"""Disable module update checks from Odoo store."""
_inherit = 'ir.module.module'
@api.model
def update_list(self):
"""
Override to prevent fetching from Odoo Apps store.
Only scan local addons paths.
"""
_logger.info("Module update_list: Scanning local addons only (Odoo Apps store disabled)")
return super().update_list()
def button_immediate_upgrade(self):
"""Prevent upgrade attempts that might contact Odoo."""
_logger.info("Module upgrade: Processing locally only")
return super().button_immediate_upgrade()
class IrCron(models.Model):
"""Disable scheduled actions that contact Odoo servers."""
_inherit = 'ir.cron'
def _callback(self, cron_name, server_action_id):
"""
Override to block certain cron jobs that contact Odoo.
Odoo 19 signature: _callback(self, cron_name, server_action_id)
"""
blocked_crons = [
'publisher',
'warranty',
'update_notification',
'database_expiration',
'iap_enrich',
'ocr',
'Invoice OCR',
'enrich leads',
'fetchmail',
'online sync',
]
cron_lower = (cron_name or '').lower()
for blocked in blocked_crons:
if blocked.lower() in cron_lower:
_logger.info("Cron BLOCKED (external call): %s", cron_name)
return False
return super()._callback(cron_name, server_action_id)
class ResConfigSettings(models.TransientModel):
"""Override config settings to prevent external service configuration."""
_inherit = 'res.config.settings'
def set_values(self):
"""Ensure certain settings stay disabled."""
res = super().set_values()
# Disable any auto-update settings and set permanent expiration
params = self.env['ir.config_parameter'].sudo()
params.set_param('database.expiration_date', '2099-12-31 23:59:59')
params.set_param('database.expiration_reason', 'renewal')
params.set_param('database.enterprise_code', 'PERMANENT_LOCAL')
# Disable IAP endpoint (redirect to nowhere)
params.set_param('iap.endpoint', 'http://localhost:65535')
# Disable various external services
params.set_param('partner_autocomplete.endpoint', 'http://localhost:65535')
params.set_param('iap_extract_endpoint', 'http://localhost:65535')
params.set_param('olg.endpoint', 'http://localhost:65535')
params.set_param('mail.media_library_endpoint', 'http://localhost:65535')
return res
class PublisherWarrantyContract(models.AbstractModel):
"""Completely disable publisher warranty checks."""
_inherit = 'publisher_warranty.contract'
@api.model
def _get_sys_logs(self):
"""
DISABLED: Do not contact Odoo servers.
Returns fake successful response.
"""
_logger.info("Publisher warranty _get_sys_logs BLOCKED")
return {
'messages': [],
'enterprise_info': {
'expiration_date': '2099-12-31 23:59:59',
'expiration_reason': 'renewal',
'enterprise_code': 'PERMANENT_LOCAL',
}
}
@api.model
def _get_message(self):
"""DISABLED: Return empty message."""
_logger.info("Publisher warranty _get_message BLOCKED")
return {}
def update_notification(self, cron_mode=True):
"""
DISABLED: Do not send any data to Odoo servers.
Just update local parameters with permanent values.
"""
_logger.info("Publisher warranty update_notification BLOCKED")
# Set permanent valid subscription parameters
params = self.env['ir.config_parameter'].sudo()
params.set_param('database.expiration_date', '2099-12-31 23:59:59')
params.set_param('database.expiration_reason', 'renewal')
params.set_param('database.enterprise_code', 'PERMANENT_LOCAL')
# Clear any "already linked" parameters
params.set_param('database.already_linked_subscription_url', '')
params.set_param('database.already_linked_email', '')
params.set_param('database.already_linked_send_mail_url', '')
return True
class IrHttp(models.AbstractModel):
"""Block certain routes that call external services."""
_inherit = 'ir.http'
@classmethod
def _pre_dispatch(cls, rule, arguments):
"""Log and potentially block external service routes."""
# List of route patterns that should be blocked
blocked_routes = [
'/iap/',
'/partner_autocomplete/',
'/google_',
'/ocr/',
'/sms/',
]
# Note: We don't actually block here as it might break functionality
# The actual blocking happens at the API/model level
return super()._pre_dispatch(rule, arguments)

View File

@@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
"""
Disable Partner Autocomplete external API calls.
"""
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class ResPartner(models.Model):
"""Disable partner autocomplete from Odoo API."""
_inherit = 'res.partner'
@api.model
def autocomplete(self, query, timeout=15):
"""
DISABLED: Return empty results instead of calling Odoo's partner API.
"""
_logger.debug("Partner autocomplete DISABLED - returning empty results for: %s", query)
return []
@api.model
def enrich_company(self, company_domain, partner_gid, vat, timeout=15):
"""
DISABLED: Return empty data instead of calling Odoo's enrichment API.
"""
_logger.debug("Partner enrichment DISABLED - returning empty for domain: %s", company_domain)
return {}
@api.model
def read_by_vat(self, vat, timeout=15):
"""
DISABLED: Return empty data instead of calling Odoo's VAT lookup API.
"""
_logger.debug("Partner VAT lookup DISABLED - returning empty for VAT: %s", vat)
return {}
class ResCompany(models.Model):
"""Disable company autocomplete features."""
_inherit = 'res.company'
@api.model
def autocomplete(self, query, timeout=15):
"""
DISABLED: Return empty results for company autocomplete.
"""
_logger.debug("Company autocomplete DISABLED - returning empty results")
return []

View File

@@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
"""
Block session-based information leaks and frontend detection mechanisms.
Specifically targets the web_enterprise module's subscription checks.
"""
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class IrHttp(models.AbstractModel):
"""
Override session info to prevent frontend from detecting license status.
This specifically blocks web_enterprise's ExpirationPanel from showing.
"""
_inherit = 'ir.http'
def session_info(self):
"""
Override session info to set permanent valid subscription data.
This prevents the frontend ExpirationPanel from showing warnings.
Key overrides:
- expiration_date: Set to far future (2099)
- expiration_reason: Set to 'renewal' (valid subscription)
- warning: Set to False to hide all warning banners
"""
result = super().session_info()
# Override expiration-related session data
# These are read by enterprise_subscription_service.js
result['expiration_date'] = '2099-12-31 23:59:59'
result['expiration_reason'] = 'renewal'
result['warning'] = False # Critical: prevents warning banners
# Remove any "already linked" subscription info
# These could trigger redirect prompts
result.pop('already_linked_subscription_url', None)
result.pop('already_linked_email', None)
result.pop('already_linked_send_mail_url', None)
_logger.debug("Session info patched - expiration set to 2099, warnings disabled")
return result
class ResUsers(models.Model):
"""
Override user creation/modification to prevent subscription checks.
When users are created, Odoo Enterprise normally contacts Odoo servers
to verify the subscription allows that many users.
"""
_inherit = 'res.users'
@api.model_create_multi
def create(self, vals_list):
"""
Override create to ensure no external subscription check is triggered.
The actual check happens in publisher_warranty.contract which we've
already blocked, but this is an extra safety measure.
"""
_logger.info("Creating %d user(s) - subscription check DISABLED", len(vals_list))
# Create users normally - no external checks will happen
# because publisher_warranty.contract.update_notification is blocked
users = super().create(vals_list)
# Don't trigger any warranty checks
return users
def write(self, vals):
"""
Override write to log user modifications.
"""
result = super().write(vals)
# If internal user status changed, log it
if 'share' in vals or 'groups_id' in vals:
_logger.info("User permissions updated - subscription check DISABLED")
return result

View File

@@ -1,38 +0,0 @@
/** @odoo-module **/
/**
* This module intercepts clicks on external Odoo links to prevent
* referrer leakage when users click help/documentation/upgrade links.
*/
import { browser } from "@web/core/browser/browser";
// Store original window.open
const originalOpen = browser.open;
// Override browser.open to add referrer protection
browser.open = function(url, target, features) {
if (url && typeof url === 'string') {
const urlLower = url.toLowerCase();
// Check if it's an Odoo external link
const odooPatterns = [
'odoo.com',
'odoo.sh',
'accounts.odoo',
];
const isOdooLink = odooPatterns.some(pattern => urlLower.includes(pattern));
if (isOdooLink) {
// For Odoo links, open with noreferrer to prevent leaking your domain
const newWindow = originalOpen.call(this, url, target || '_blank', 'noopener,noreferrer');
return newWindow;
}
}
return originalOpen.call(this, url, target, features);
};
console.log('[disable_odoo_online] External link protection loaded');

View File

@@ -1,220 +0,0 @@
# Graph Report - /Users/gurpreet/Github/Odoo-Modules/disable_odoo_online (2026-04-22)
## Corpus Check
- 22 files · ~5,870 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 106 nodes · 119 edges · 27 communities detected
- Extraction: 97% EXTRACTED · 3% INFERRED · 0% AMBIGUOUS · INFERRED: 3 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 8|Community 8]]
- [[_COMMUNITY_Community 9|Community 9]]
- [[_COMMUNITY_Community 10|Community 10]]
- [[_COMMUNITY_Community 11|Community 11]]
- [[_COMMUNITY_Community 12|Community 12]]
- [[_COMMUNITY_Community 13|Community 13]]
- [[_COMMUNITY_Community 14|Community 14]]
- [[_COMMUNITY_Community 15|Community 15]]
- [[_COMMUNITY_Community 16|Community 16]]
- [[_COMMUNITY_Community 17|Community 17]]
- [[_COMMUNITY_Community 18|Community 18]]
- [[_COMMUNITY_Community 19|Community 19]]
- [[_COMMUNITY_Community 20|Community 20]]
- [[_COMMUNITY_Community 21|Community 21]]
- [[_COMMUNITY_Community 22|Community 22]]
- [[_COMMUNITY_Community 23|Community 23]]
- [[_COMMUNITY_Community 24|Community 24]]
- [[_COMMUNITY_Community 25|Community 25]]
- [[_COMMUNITY_Community 26|Community 26]]
## God Nodes (most connected - your core abstractions)
1. `_is_blocked_url()` - 6 edges
2. `IrConfigParameter` - 5 edges
3. `_post_init_hook()` - 4 edges
4. `IrModuleModule` - 4 edges
5. `IrCron` - 4 edges
6. `ResConfigSettings` - 4 edges
7. `PublisherWarrantyContract` - 4 edges
8. `_blocked_request()` - 4 edges
9. `_blocked_get()` - 4 edges
10. `_blocked_post()` - 4 edges
## Surprising Connections (you probably didn't know these)
- None detected - all connections are within the same source files.
## Communities
### Community 0 - "Community 0"
Cohesion: 0.14
Nodes (16): _get_message(), _get_sys_logs(), IrCron, IrHttp, IrModuleModule, _pre_dispatch(), PublisherWarrantyContract, Disable module update checks from Odoo store. (+8 more)
### Community 1 - "Community 1"
Cohesion: 0.26
Nodes (10): Base, _check_database_enterprise_expiration(), check_database_expiration(), DatabaseExpirationCheck, _get_database_expiration_date(), get_param(), IrConfigParameter, Override config parameters to prevent expiration and protect license values. (+2 more)
### Community 2 - "Community 2"
Cohesion: 0.27
Nodes (10): _blocked_get(), _blocked_post(), _blocked_request(), _is_blocked_url(), patch_requests(), Check if the URL should be blocked., Intercept and block requests to Odoo domains., Intercept and block GET requests. (+2 more)
### Community 3 - "Community 3"
Cohesion: 0.22
Nodes (7): create(), IrHttp, Override session info to prevent frontend from detecting license status. Thi, Override session info to set permanent valid subscription data. This pre, Override user creation/modification to prevent subscription checks. When use, Override write to log user modifications., ResUsers
### Community 4 - "Community 4"
Cohesion: 0.24
Nodes (5): Override set_param to prevent external processes from changing protected values., DISABLED: Do not send any data to Odoo servers. Just update local parame, Ensure certain settings stay disabled., _post_init_hook(), Set all configuration parameters to disable external Odoo services. This run
### Community 5 - "Community 5"
Cohesion: 0.33
Nodes (7): autocomplete(), enrich_company(), Disable partner autocomplete from Odoo API., Disable company autocomplete features., read_by_vat(), ResCompany, ResPartner
### Community 6 - "Community 6"
Cohesion: 0.4
Nodes (4): _disabled_iap_jsonrpc(), patch_iap_tools(), DISABLED: Block all IAP JSON-RPC calls. Returns empty/success response inste, Monkey-patch the iap_jsonrpc function to block external calls. This is calle
### Community 7 - "Community 7"
Cohesion: 0.53
Nodes (4): _get_gravatar_image(), _get_rates_from_provider(), ResCurrencyDisabled, ResPartnerDisabled
### Community 8 - "Community 8"
Cohesion: 1.0
Nodes (0):
### Community 9 - "Community 9"
Cohesion: 1.0
Nodes (0):
### Community 10 - "Community 10"
Cohesion: 1.0
Nodes (0):
### Community 11 - "Community 11"
Cohesion: 1.0
Nodes (0):
### Community 12 - "Community 12"
Cohesion: 1.0
Nodes (1): Set database to never expire.
### Community 13 - "Community 13"
Cohesion: 1.0
Nodes (1): Override get_param to return permanent values for protected params.
### Community 14 - "Community 14"
Cohesion: 1.0
Nodes (1): Override to prevent fetching from Odoo Apps store. Only scan local addon
### Community 15 - "Community 15"
Cohesion: 1.0
Nodes (1): DISABLED: Do not contact Odoo servers. Returns fake successful response.
### Community 16 - "Community 16"
Cohesion: 1.0
Nodes (1): DISABLED: Return empty message.
### Community 17 - "Community 17"
Cohesion: 1.0
Nodes (1): Log and potentially block external service routes.
### Community 18 - "Community 18"
Cohesion: 1.0
Nodes (1): DISABLED: Return empty rates.
### Community 19 - "Community 19"
Cohesion: 1.0
Nodes (1): DISABLED: Return False to skip gravatar lookup.
### Community 20 - "Community 20"
Cohesion: 1.0
Nodes (1): DISABLED: Return empty results instead of calling Odoo's partner API.
### Community 21 - "Community 21"
Cohesion: 1.0
Nodes (1): DISABLED: Return empty data instead of calling Odoo's enrichment API.
### Community 22 - "Community 22"
Cohesion: 1.0
Nodes (1): DISABLED: Return empty data instead of calling Odoo's VAT lookup API.
### Community 23 - "Community 23"
Cohesion: 1.0
Nodes (1): DISABLED: Return empty results for company autocomplete.
### Community 24 - "Community 24"
Cohesion: 1.0
Nodes (1): Override create to ensure no external subscription check is triggered. T
### Community 25 - "Community 25"
Cohesion: 1.0
Nodes (0):
### Community 26 - "Community 26"
Cohesion: 1.0
Nodes (0):
## Knowledge Gaps
- **39 isolated node(s):** `Set all configuration parameters to disable external Odoo services. This run`, `Override config parameters to prevent expiration and protect license values.`, `Set permanent valid subscription on module init.`, `Set database to never expire.`, `Override get_param to return permanent values for protected params.` (+34 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 8`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 9`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 10`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 11`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 12`** (1 nodes): `Set database to never expire.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 13`** (1 nodes): `Override get_param to return permanent values for protected params.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 14`** (1 nodes): `Override to prevent fetching from Odoo Apps store. Only scan local addon`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 15`** (1 nodes): `DISABLED: Do not contact Odoo servers. Returns fake successful response.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 16`** (1 nodes): `DISABLED: Return empty message.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 17`** (1 nodes): `Log and potentially block external service routes.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 18`** (1 nodes): `DISABLED: Return empty rates.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 19`** (1 nodes): `DISABLED: Return False to skip gravatar lookup.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 20`** (1 nodes): `DISABLED: Return empty results instead of calling Odoo's partner API.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 21`** (1 nodes): `DISABLED: Return empty data instead of calling Odoo's enrichment API.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 22`** (1 nodes): `DISABLED: Return empty data instead of calling Odoo's VAT lookup API.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 23`** (1 nodes): `DISABLED: Return empty results for company autocomplete.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 24`** (1 nodes): `Override create to ensure no external subscription check is triggered. T`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 25`** (1 nodes): `disable_external_links.js`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 26`** (1 nodes): `disable_external_links.js`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `IrConfigParameter` connect `Community 1` to `Community 4`?**
_High betweenness centrality (0.069) - this node is a cross-community bridge._
- **Why does `ResConfigSettings` connect `Community 0` to `Community 4`?**
_High betweenness centrality (0.042) - this node is a cross-community bridge._
- **Why does `PublisherWarrantyContract` connect `Community 0` to `Community 4`?**
_High betweenness centrality (0.042) - this node is a cross-community bridge._
- **What connects `Set all configuration parameters to disable external Odoo services. This run`, `Override config parameters to prevent expiration and protect license values.`, `Set permanent valid subscription on module init.` to the rest of the system?**
_39 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.14 - nodes in this community are weakly interconnected._

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_all_external_py", "label": "disable_all_external.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L1"}, {"id": "disable_all_external_rescurrencydisabled", "label": "ResCurrencyDisabled", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L17"}, {"id": "disable_all_external_get_rates_from_provider", "label": "_get_rates_from_provider()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L21"}, {"id": "disable_all_external_respartnerdisabled", "label": "ResPartnerDisabled", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L31"}, {"id": "disable_all_external_get_gravatar_image", "label": "_get_gravatar_image()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L35"}, {"id": "disable_all_external_rationale_22", "label": "DISABLED: Return empty rates.", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L22"}, {"id": "disable_all_external_rationale_36", "label": "DISABLED: Return False to skip gravatar lookup.", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L36"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_all_external_py", "target": "logging", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L7", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_all_external_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L8", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_all_external_py", "target": "disable_all_external_rescurrencydisabled", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L17", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_all_external_py", "target": "disable_all_external_get_rates_from_provider", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L21", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_all_external_py", "target": "disable_all_external_respartnerdisabled", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L31", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_all_external_py", "target": "disable_all_external_get_gravatar_image", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L35", "weight": 1.0}, {"source": "disable_all_external_rationale_22", "target": "disable_all_external_rescurrencydisabled_get_rates_from_provider", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L22", "weight": 1.0}, {"source": "disable_all_external_rationale_36", "target": "disable_all_external_respartnerdisabled_get_gravatar_image", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L36", "weight": 1.0}], "raw_calls": [{"caller_nid": "disable_all_external_get_rates_from_provider", "callee": "debug", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L23"}, {"caller_nid": "disable_all_external_get_gravatar_image", "callee": "debug", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_all_external.py", "source_location": "L37"}]}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_manifest_py", "label": "__manifest__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__manifest__.py", "source_location": "L1"}], "edges": [], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_static_src_js_disable_external_links_js", "label": "disable_external_links.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/static/src/js/disable_external_links.js", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_static_src_js_disable_external_links_js", "target": "browser", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/static/src/js/disable_external_links.js", "source_location": "L8", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L4", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L5", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L6", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L7", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/__init__.py", "source_location": "L8", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_static_src_js_disable_external_links_js", "label": "disable_external_links.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/static/src/js/disable_external_links.js", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_static_src_js_disable_external_links_js", "target": "browser", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/static/src/js/disable_external_links.js", "source_location": "L8", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L4", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L5", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L6", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L7", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/__init__.py", "source_location": "L8", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_disable_iap_tools_py", "label": "disable_iap_tools.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L1"}, {"id": "disable_iap_tools_disabled_iap_jsonrpc", "label": "_disabled_iap_jsonrpc()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L16"}, {"id": "disable_iap_tools_patch_iap_tools", "label": "patch_iap_tools()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L43"}, {"id": "disable_iap_tools_rationale_17", "label": "DISABLED: Block all IAP JSON-RPC calls. Returns empty/success response inste", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L17"}, {"id": "disable_iap_tools_rationale_44", "label": "Monkey-patch the iap_jsonrpc function to block external calls. This is calle", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L44"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_disable_iap_tools_py", "target": "logging", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L7", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_disable_iap_tools_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L8", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_disable_iap_tools_py", "target": "disable_iap_tools_disabled_iap_jsonrpc", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L16", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_models_disable_iap_tools_py", "target": "disable_iap_tools_patch_iap_tools", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L43", "weight": 1.0}, {"source": "disable_iap_tools_rationale_17", "target": "disable_iap_tools_disabled_iap_jsonrpc", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L17", "weight": 1.0}, {"source": "disable_iap_tools_rationale_44", "target": "disable_iap_tools_patch_iap_tools", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L44", "weight": 1.0}], "raw_calls": [{"caller_nid": "disable_iap_tools_disabled_iap_jsonrpc", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L21"}, {"caller_nid": "disable_iap_tools_disabled_iap_jsonrpc", "callee": "warning", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L35"}, {"caller_nid": "disable_iap_tools_patch_iap_tools", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L57"}, {"caller_nid": "disable_iap_tools_patch_iap_tools", "callee": "debug", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L60"}, {"caller_nid": "disable_iap_tools_patch_iap_tools", "callee": "warning", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L62"}]}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L1"}, {"id": "init_post_init_hook", "label": "_post_init_hook()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L5"}, {"id": "init_rationale_6", "label": "Set all configuration parameters to disable external Odoo services. This run", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L6"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_init_py", "target": "init_post_init_hook", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L5", "weight": 1.0}, {"source": "init_rationale_6", "target": "init_post_init_hook", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L6", "weight": 1.0}], "raw_calls": [{"caller_nid": "init_post_init_hook", "callee": "getLogger", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L11"}, {"caller_nid": "init_post_init_hook", "callee": "sudo", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L13"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L54"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L55"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L56"}, {"caller_nid": "init_post_init_hook", "callee": "items", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L58"}, {"caller_nid": "init_post_init_hook", "callee": "set_param", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L60"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L61"}, {"caller_nid": "init_post_init_hook", "callee": "len", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L61"}, {"caller_nid": "init_post_init_hook", "callee": "str", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L61"}, {"caller_nid": "init_post_init_hook", "callee": "warning", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L63"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L65"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L66"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__init__.py", "source_location": "L67"}]}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L1"}, {"id": "init_post_init_hook", "label": "_post_init_hook()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L5"}, {"id": "init_rationale_6", "label": "Set all configuration parameters to disable external Odoo services. This run", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L6"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_init_py", "target": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_disable_odoo_online_init_py", "target": "init_post_init_hook", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L5", "weight": 1.0}, {"source": "init_rationale_6", "target": "init_post_init_hook", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L6", "weight": 1.0}], "raw_calls": [{"caller_nid": "init_post_init_hook", "callee": "getLogger", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L11"}, {"caller_nid": "init_post_init_hook", "callee": "sudo", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L13"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L54"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L55"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L56"}, {"caller_nid": "init_post_init_hook", "callee": "items", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L58"}, {"caller_nid": "init_post_init_hook", "callee": "set_param", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L60"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L61"}, {"caller_nid": "init_post_init_hook", "callee": "len", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L61"}, {"caller_nid": "init_post_init_hook", "callee": "str", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L61"}, {"caller_nid": "init_post_init_hook", "callee": "warning", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L63"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L65"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L66"}, {"caller_nid": "init_post_init_hook", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/disable_odoo_online/__init__.py", "source_location": "L67"}]}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_manifest_py", "label": "__manifest__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/__manifest__.py", "source_location": "L1"}], "edges": [], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_iap_tools_py", "label": "disable_iap_tools.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L1"}, {"id": "disable_iap_tools_disabled_iap_jsonrpc", "label": "_disabled_iap_jsonrpc()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L16"}, {"id": "disable_iap_tools_patch_iap_tools", "label": "patch_iap_tools()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L43"}, {"id": "disable_iap_tools_rationale_17", "label": "DISABLED: Block all IAP JSON-RPC calls. Returns empty/success response inste", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L17"}, {"id": "disable_iap_tools_rationale_44", "label": "Monkey-patch the iap_jsonrpc function to block external calls. This is calle", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L44"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_iap_tools_py", "target": "logging", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L7", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_iap_tools_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L8", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_iap_tools_py", "target": "disable_iap_tools_disabled_iap_jsonrpc", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L16", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_disable_odoo_online_models_disable_iap_tools_py", "target": "disable_iap_tools_patch_iap_tools", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L43", "weight": 1.0}, {"source": "disable_iap_tools_rationale_17", "target": "disable_iap_tools_disabled_iap_jsonrpc", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L17", "weight": 1.0}, {"source": "disable_iap_tools_rationale_44", "target": "disable_iap_tools_patch_iap_tools", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L44", "weight": 1.0}], "raw_calls": [{"caller_nid": "disable_iap_tools_disabled_iap_jsonrpc", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L21"}, {"caller_nid": "disable_iap_tools_disabled_iap_jsonrpc", "callee": "warning", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L35"}, {"caller_nid": "disable_iap_tools_patch_iap_tools", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L57"}, {"caller_nid": "disable_iap_tools_patch_iap_tools", "callee": "debug", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L60"}, {"caller_nid": "disable_iap_tools_patch_iap_tools", "callee": "warning", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_odoo_online/models/disable_iap_tools.py", "source_location": "L62"}]}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
from . import disable_iap_tools # Patches iap_jsonrpc globally - MUST be first
from . import disable_http_requests # Patches requests library to block Odoo domains
from . import disable_online_services
from . import disable_partner_autocomplete
from . import disable_database_expiration
from . import disable_all_external
from . import disable_session_leaks

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
"""
Comprehensive blocking of ALL external Odoo service calls.
Only inherits from models that are guaranteed to exist in base Odoo.
"""
import logging
from odoo import api, models, fields
_logger = logging.getLogger(__name__)
# ============================================================
# Block Currency Rate Live Updates - Uses res.currency which always exists
# ============================================================
class ResCurrencyDisabled(models.Model):
_inherit = 'res.currency'
@api.model
def _get_rates_from_provider(self, provider, date):
"""DISABLED: Return empty rates."""
_logger.debug("Currency rate provider BLOCKED: provider=%s", provider)
return {}
# ============================================================
# Block Gravatar - Uses res.partner which always exists
# ============================================================
class ResPartnerDisabled(models.Model):
_inherit = 'res.partner'
@api.model
def _get_gravatar_image(self, email):
"""DISABLED: Return False to skip gravatar lookup."""
_logger.debug("Gravatar lookup BLOCKED for email=%s", email)
return False

View File

@@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
"""
Disable database expiration checks and registration.
Consolidates all ir.config_parameter overrides.
"""
import logging
from datetime import datetime
from odoo import api, models, fields
_logger = logging.getLogger(__name__)
class IrConfigParameter(models.Model):
"""Override config parameters to prevent expiration and protect license values."""
_inherit = 'ir.config_parameter'
PROTECTED_PARAMS = {
'database.expiration_date': '2099-12-31 23:59:59',
'database.expiration_reason': 'renewal',
'database.enterprise_code': 'PERMANENT_LOCAL',
}
CLEAR_PARAMS = [
'database.already_linked_subscription_url',
'database.already_linked_email',
'database.already_linked_send_mail_url',
]
def init(self, force=False):
"""Set permanent valid subscription on module init."""
super().init(force=force)
self._set_permanent_subscription()
@api.model
def _set_permanent_subscription(self):
"""Set database to never expire."""
_logger.info("Setting permanent subscription values...")
for key, value in self.PROTECTED_PARAMS.items():
try:
self.env.cr.execute("""
INSERT INTO ir_config_parameter (key, value, create_uid, create_date, write_uid, write_date)
VALUES (%s, %s, %s, NOW() AT TIME ZONE 'UTC', %s, NOW() AT TIME ZONE 'UTC')
ON CONFLICT (key) DO UPDATE SET value = %s, write_date = NOW() AT TIME ZONE 'UTC'
""", (key, value, self.env.uid, self.env.uid, value))
except Exception as e:
_logger.debug("Could not set param %s: %s", key, e)
for key in self.CLEAR_PARAMS:
try:
self.env.cr.execute("""
INSERT INTO ir_config_parameter (key, value, create_uid, create_date, write_uid, write_date)
VALUES (%s, '', %s, NOW() AT TIME ZONE 'UTC', %s, NOW() AT TIME ZONE 'UTC')
ON CONFLICT (key) DO UPDATE SET value = '', write_date = NOW() AT TIME ZONE 'UTC'
""", (key, self.env.uid, self.env.uid))
except Exception as e:
_logger.debug("Could not clear param %s: %s", key, e)
@api.model
def get_param(self, key, default=False):
"""Override get_param to return permanent values for protected params."""
if key in self.PROTECTED_PARAMS:
return self.PROTECTED_PARAMS[key]
if key in self.CLEAR_PARAMS:
return ''
return super().get_param(key, default)
def set_param(self, key, value):
"""Override set_param to prevent external processes from changing protected values."""
if key in self.PROTECTED_PARAMS:
if value != self.PROTECTED_PARAMS[key]:
_logger.warning("Blocked attempt to change protected param %s to %s", key, value)
return True
if key in self.CLEAR_PARAMS:
value = ''
return super().set_param(key, value)
class DatabaseExpirationCheck(models.AbstractModel):
_name = 'disable.odoo.online.expiration'
_description = 'Database Expiration Blocker'
@api.model
def check_database_expiration(self):
return {
'valid': True,
'expiration_date': '2099-12-31 23:59:59',
'expiration_reason': 'renewal',
}
class Base(models.AbstractModel):
_inherit = 'base'
@api.model
def _get_database_expiration_date(self):
return datetime(2099, 12, 31, 23, 59, 59)
@api.model
def _check_database_enterprise_expiration(self):
return True

View File

@@ -1,129 +0,0 @@
# -*- coding: utf-8 -*-
"""
Block ALL outgoing HTTP requests to Odoo-related domains.
This patches the requests library to intercept and block external calls.
"""
import logging
import requests
from functools import wraps
from urllib.parse import urlparse
_logger = logging.getLogger(__name__)
# Domains to block - all Odoo external services
BLOCKED_DOMAINS = [
'odoo.com',
'odoofin.com',
'odoo.sh',
'iap.odoo.com',
'iap-services.odoo.com',
'partner-autocomplete.odoo.com',
'iap-extract.odoo.com',
'iap-sms.odoo.com',
'upgrade.odoo.com',
'apps.odoo.com',
'production.odoofin.com',
'plaid.com',
'yodlee.com',
'gravatar.com',
'www.gravatar.com',
'secure.gravatar.com',
]
# Store original functions
_original_request = None
_original_get = None
_original_post = None
def _is_blocked_url(url):
"""Check if the URL should be blocked."""
if not url:
return False
try:
parsed = urlparse(url)
domain = parsed.netloc.lower()
for blocked in BLOCKED_DOMAINS:
if blocked in domain:
return True
except Exception:
pass
return False
def _blocked_request(method, url, **kwargs):
"""Intercept and block requests to Odoo domains."""
if _is_blocked_url(url):
_logger.warning("HTTP REQUEST BLOCKED: %s %s", method.upper(), url)
# Return a mock response
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
return response
return _original_request(method, url, **kwargs)
def _blocked_get(url, **kwargs):
"""Intercept and block GET requests."""
if _is_blocked_url(url):
_logger.warning("HTTP GET BLOCKED: %s", url)
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
return response
return _original_get(url, **kwargs)
def _blocked_post(url, **kwargs):
"""Intercept and block POST requests."""
if _is_blocked_url(url):
_logger.warning("HTTP POST BLOCKED: %s", url)
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
return response
return _original_post(url, **kwargs)
def patch_requests():
"""Monkey-patch requests library to block Odoo domains."""
global _original_request, _original_get, _original_post
try:
if _original_request is None:
_original_request = requests.Session.request
_original_get = requests.get
_original_post = requests.post
# Patch Session.request (catches most calls)
def patched_session_request(self, method, url, **kwargs):
if _is_blocked_url(url):
_logger.warning("HTTP SESSION REQUEST BLOCKED: %s %s", method.upper(), url)
response = requests.models.Response()
response.status_code = 200
response._content = b'{}'
response.headers['Content-Type'] = 'application/json'
response.request = requests.models.PreparedRequest()
response.request.url = url
response.request.method = method
return response
return _original_request(self, method, url, **kwargs)
requests.Session.request = patched_session_request
requests.get = _blocked_get
requests.post = _blocked_post
_logger.info("HTTP requests to Odoo domains have been BLOCKED")
_logger.info("Blocked domains: %s", ', '.join(BLOCKED_DOMAINS))
except Exception as e:
_logger.warning("Could not patch requests library: %s", e)
# Apply patch when module is imported
patch_requests()

View File

@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
"""
Override the core IAP tools to block ALL external API calls.
This is the master switch that blocks ALL Odoo external communications.
"""
import logging
from odoo import exceptions, _
_logger = logging.getLogger(__name__)
# Store original function reference
_original_iap_jsonrpc = None
def _disabled_iap_jsonrpc(url, method='call', params=None, timeout=15):
"""
DISABLED: Block all IAP JSON-RPC calls.
Returns empty/success response instead of making external calls.
"""
_logger.info("IAP JSONRPC BLOCKED: %s (method=%s)", url, method)
# Return appropriate empty responses based on the endpoint
if '/authorize' in url:
return 'fake_transaction_token_disabled'
elif '/capture' in url or '/cancel' in url:
return True
elif '/credits' in url:
return 999999
elif 'partner-autocomplete' in url:
return []
elif 'enrich' in url:
return {}
elif 'sms' in url:
_logger.warning("SMS API call blocked - SMS will not be sent")
return {'state': 'success', 'credits': 999999}
elif 'extract' in url:
return {'status': 'success', 'credits': 999999}
else:
return {}
def patch_iap_tools():
"""
Monkey-patch the iap_jsonrpc function to block external calls.
This is called when the module loads.
"""
global _original_iap_jsonrpc
try:
from odoo.addons.iap.tools import iap_tools
if _original_iap_jsonrpc is None:
_original_iap_jsonrpc = iap_tools.iap_jsonrpc
iap_tools.iap_jsonrpc = _disabled_iap_jsonrpc
_logger.info("IAP JSON-RPC calls have been DISABLED globally")
except ImportError:
_logger.debug("IAP module not installed, skipping patch")
except Exception as e:
_logger.warning("Could not patch IAP tools: %s", e)
# Apply patch when module is imported
patch_iap_tools()

View File

@@ -1,153 +0,0 @@
# -*- coding: utf-8 -*-
"""
Disable various Odoo online services and external API calls.
"""
import logging
from odoo import api, models, fields
_logger = logging.getLogger(__name__)
class IrModuleModule(models.Model):
"""Disable module update checks from Odoo store."""
_inherit = 'ir.module.module'
@api.model
def update_list(self):
"""
Override to prevent fetching from Odoo Apps store.
Only scan local addons paths.
"""
_logger.info("Module update_list: Scanning local addons only (Odoo Apps store disabled)")
return super().update_list()
def button_immediate_upgrade(self):
"""Prevent upgrade attempts that might contact Odoo."""
_logger.info("Module upgrade: Processing locally only")
return super().button_immediate_upgrade()
class IrCron(models.Model):
"""Disable scheduled actions that contact Odoo servers."""
_inherit = 'ir.cron'
def _callback(self, cron_name, server_action_id):
"""
Override to block certain cron jobs that contact Odoo.
Odoo 19 signature: _callback(self, cron_name, server_action_id)
"""
blocked_crons = [
'publisher',
'warranty',
'update_notification',
'database_expiration',
'iap_enrich',
'ocr',
'Invoice OCR',
'enrich leads',
'fetchmail',
'online sync',
]
cron_lower = (cron_name or '').lower()
for blocked in blocked_crons:
if blocked.lower() in cron_lower:
_logger.info("Cron BLOCKED (external call): %s", cron_name)
return False
return super()._callback(cron_name, server_action_id)
class ResConfigSettings(models.TransientModel):
"""Override config settings to prevent external service configuration."""
_inherit = 'res.config.settings'
def set_values(self):
"""Ensure certain settings stay disabled."""
res = super().set_values()
# Disable any auto-update settings and set permanent expiration
params = self.env['ir.config_parameter'].sudo()
params.set_param('database.expiration_date', '2099-12-31 23:59:59')
params.set_param('database.expiration_reason', 'renewal')
params.set_param('database.enterprise_code', 'PERMANENT_LOCAL')
# Disable IAP endpoint (redirect to nowhere)
params.set_param('iap.endpoint', 'http://localhost:65535')
# Disable various external services
params.set_param('partner_autocomplete.endpoint', 'http://localhost:65535')
params.set_param('iap_extract_endpoint', 'http://localhost:65535')
params.set_param('olg.endpoint', 'http://localhost:65535')
params.set_param('mail.media_library_endpoint', 'http://localhost:65535')
return res
class PublisherWarrantyContract(models.AbstractModel):
"""Completely disable publisher warranty checks."""
_inherit = 'publisher_warranty.contract'
@api.model
def _get_sys_logs(self):
"""
DISABLED: Do not contact Odoo servers.
Returns fake successful response.
"""
_logger.info("Publisher warranty _get_sys_logs BLOCKED")
return {
'messages': [],
'enterprise_info': {
'expiration_date': '2099-12-31 23:59:59',
'expiration_reason': 'renewal',
'enterprise_code': 'PERMANENT_LOCAL',
}
}
@api.model
def _get_message(self):
"""DISABLED: Return empty message."""
_logger.info("Publisher warranty _get_message BLOCKED")
return {}
def update_notification(self, cron_mode=True):
"""
DISABLED: Do not send any data to Odoo servers.
Just update local parameters with permanent values.
"""
_logger.info("Publisher warranty update_notification BLOCKED")
# Set permanent valid subscription parameters
params = self.env['ir.config_parameter'].sudo()
params.set_param('database.expiration_date', '2099-12-31 23:59:59')
params.set_param('database.expiration_reason', 'renewal')
params.set_param('database.enterprise_code', 'PERMANENT_LOCAL')
# Clear any "already linked" parameters
params.set_param('database.already_linked_subscription_url', '')
params.set_param('database.already_linked_email', '')
params.set_param('database.already_linked_send_mail_url', '')
return True
class IrHttp(models.AbstractModel):
"""Block certain routes that call external services."""
_inherit = 'ir.http'
@classmethod
def _pre_dispatch(cls, rule, arguments):
"""Log and potentially block external service routes."""
# List of route patterns that should be blocked
blocked_routes = [
'/iap/',
'/partner_autocomplete/',
'/google_',
'/ocr/',
'/sms/',
]
# Note: We don't actually block here as it might break functionality
# The actual blocking happens at the API/model level
return super()._pre_dispatch(rule, arguments)

View File

@@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
"""
Disable Partner Autocomplete external API calls.
"""
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class ResPartner(models.Model):
"""Disable partner autocomplete from Odoo API."""
_inherit = 'res.partner'
@api.model
def autocomplete(self, query, timeout=15):
"""
DISABLED: Return empty results instead of calling Odoo's partner API.
"""
_logger.debug("Partner autocomplete DISABLED - returning empty results for: %s", query)
return []
@api.model
def enrich_company(self, company_domain, partner_gid, vat, timeout=15):
"""
DISABLED: Return empty data instead of calling Odoo's enrichment API.
"""
_logger.debug("Partner enrichment DISABLED - returning empty for domain: %s", company_domain)
return {}
@api.model
def read_by_vat(self, vat, timeout=15):
"""
DISABLED: Return empty data instead of calling Odoo's VAT lookup API.
"""
_logger.debug("Partner VAT lookup DISABLED - returning empty for VAT: %s", vat)
return {}
class ResCompany(models.Model):
"""Disable company autocomplete features."""
_inherit = 'res.company'
@api.model
def autocomplete(self, query, timeout=15):
"""
DISABLED: Return empty results for company autocomplete.
"""
_logger.debug("Company autocomplete DISABLED - returning empty results")
return []

View File

@@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
"""
Block session-based information leaks and frontend detection mechanisms.
Specifically targets the web_enterprise module's subscription checks.
"""
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class IrHttp(models.AbstractModel):
"""
Override session info to prevent frontend from detecting license status.
This specifically blocks web_enterprise's ExpirationPanel from showing.
"""
_inherit = 'ir.http'
def session_info(self):
"""
Override session info to set permanent valid subscription data.
This prevents the frontend ExpirationPanel from showing warnings.
Key overrides:
- expiration_date: Set to far future (2099)
- expiration_reason: Set to 'renewal' (valid subscription)
- warning: Set to False to hide all warning banners
"""
result = super().session_info()
# Override expiration-related session data
# These are read by enterprise_subscription_service.js
result['expiration_date'] = '2099-12-31 23:59:59'
result['expiration_reason'] = 'renewal'
result['warning'] = False # Critical: prevents warning banners
# Remove any "already linked" subscription info
# These could trigger redirect prompts
result.pop('already_linked_subscription_url', None)
result.pop('already_linked_email', None)
result.pop('already_linked_send_mail_url', None)
_logger.debug("Session info patched - expiration set to 2099, warnings disabled")
return result
class ResUsers(models.Model):
"""
Override user creation/modification to prevent subscription checks.
When users are created, Odoo Enterprise normally contacts Odoo servers
to verify the subscription allows that many users.
"""
_inherit = 'res.users'
@api.model_create_multi
def create(self, vals_list):
"""
Override create to ensure no external subscription check is triggered.
The actual check happens in publisher_warranty.contract which we've
already blocked, but this is an extra safety measure.
"""
_logger.info("Creating %d user(s) - subscription check DISABLED", len(vals_list))
# Create users normally - no external checks will happen
# because publisher_warranty.contract.update_notification is blocked
users = super().create(vals_list)
# Don't trigger any warranty checks
return users
def write(self, vals):
"""
Override write to log user modifications.
"""
result = super().write(vals)
# If internal user status changed, log it
if 'share' in vals or 'groups_id' in vals:
_logger.info("User permissions updated - subscription check DISABLED")
return result

View File

@@ -1,38 +0,0 @@
/** @odoo-module **/
/**
* This module intercepts clicks on external Odoo links to prevent
* referrer leakage when users click help/documentation/upgrade links.
*/
import { browser } from "@web/core/browser/browser";
// Store original window.open
const originalOpen = browser.open;
// Override browser.open to add referrer protection
browser.open = function(url, target, features) {
if (url && typeof url === 'string') {
const urlLower = url.toLowerCase();
// Check if it's an Odoo external link
const odooPatterns = [
'odoo.com',
'odoo.sh',
'accounts.odoo',
];
const isOdooLink = odooPatterns.some(pattern => urlLower.includes(pattern));
if (isOdooLink) {
// For Odoo links, open with noreferrer to prevent leaking your domain
const newWindow = originalOpen.call(this, url, target || '_blank', 'noopener,noreferrer');
return newWindow;
}
}
return originalOpen.call(this, url, target, features);
};
console.log('[disable_odoo_online] External link protection loaded');

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Disable Publisher Warranty',
'version': '19.0.1.0.0',
'category': 'Tools',
'summary': 'Disables all communication with Odoo publisher warranty servers',
'description': """
This module completely disables:
- Publisher warranty server communication
- Subscription expiration checks
- Automatic license updates
For local development use only.
""",
'author': 'Development',
'depends': ['mail'],
'data': [],
'installable': True,
'auto_install': True,
'license': 'LGPL-3',
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Disable Publisher Warranty',
'version': '19.0.1.0.0',
'category': 'Tools',
'summary': 'Disables all communication with Odoo publisher warranty servers',
'description': """
This module completely disables:
- Publisher warranty server communication
- Subscription expiration checks
- Automatic license updates
For local development use only.
""",
'author': 'Development',
'depends': ['mail'],
'data': [],
'installable': True,
'auto_install': True,
'license': 'LGPL-3',
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import publisher_warranty

View File

@@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
# Disable all publisher warranty / subscription checks for local development
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class PublisherWarrantyContractDisabled(models.AbstractModel):
_inherit = "publisher_warranty.contract"
@api.model
def _get_sys_logs(self):
"""
DISABLED: Do not contact Odoo servers.
Returns fake successful response.
"""
_logger.info("Publisher warranty check DISABLED - not contacting Odoo servers")
return {
"messages": [],
"enterprise_info": {
"expiration_date": "2099-12-31 23:59:59",
"expiration_reason": "renewal",
"enterprise_code": self.env['ir.config_parameter'].sudo().get_param('database.enterprise_code', ''),
}
}
def update_notification(self, cron_mode=True):
"""
DISABLED: Do not send any data to Odoo servers.
Just update local parameters with permanent values.
"""
_logger.info("Publisher warranty update_notification DISABLED - no server contact")
# Set permanent valid subscription parameters
set_param = self.env['ir.config_parameter'].sudo().set_param
set_param('database.expiration_date', '2099-12-31 23:59:59')
set_param('database.expiration_reason', 'renewal')
# Clear any "already linked" parameters
set_param('database.already_linked_subscription_url', False)
set_param('database.already_linked_email', False)
set_param('database.already_linked_send_mail_url', False)
return True

View File

@@ -1,96 +0,0 @@
# Graph Report - /Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty (2026-04-22)
## Corpus Check
- 8 files · ~414 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 13 nodes · 10 edges · 9 communities detected
- Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 8|Community 8]]
## God Nodes (most connected - your core abstractions)
1. `PublisherWarrantyContractDisabled` - 3 edges
2. `_get_sys_logs()` - 2 edges
3. `DISABLED: Do not send any data to Odoo servers. Just update local parame` - 1 edges
4. `DISABLED: Do not contact Odoo servers. Returns fake successful response.` - 0 edges
## Surprising Connections (you probably didn't know these)
- None detected - all connections are within the same source files.
## Communities
### Community 0 - "Community 0"
Cohesion: 0.67
Nodes (2): _get_sys_logs(), PublisherWarrantyContractDisabled
### Community 1 - "Community 1"
Cohesion: 1.0
Nodes (1): DISABLED: Do not send any data to Odoo servers. Just update local parame
### Community 2 - "Community 2"
Cohesion: 1.0
Nodes (0):
### Community 3 - "Community 3"
Cohesion: 1.0
Nodes (0):
### Community 4 - "Community 4"
Cohesion: 1.0
Nodes (0):
### Community 5 - "Community 5"
Cohesion: 1.0
Nodes (0):
### Community 6 - "Community 6"
Cohesion: 1.0
Nodes (0):
### Community 7 - "Community 7"
Cohesion: 1.0
Nodes (0):
### Community 8 - "Community 8"
Cohesion: 1.0
Nodes (1): DISABLED: Do not contact Odoo servers. Returns fake successful response.
## Knowledge Gaps
- **2 isolated node(s):** `DISABLED: Do not contact Odoo servers. Returns fake successful response.`, `DISABLED: Do not send any data to Odoo servers. Just update local parame`
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 1`** (2 nodes): `.update_notification()`, `DISABLED: Do not send any data to Odoo servers. Just update local parame`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 2`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 3`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 4`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 5`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 6`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 7`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 8`** (1 nodes): `DISABLED: Do not contact Odoo servers. Returns fake successful response.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `PublisherWarrantyContractDisabled` connect `Community 0` to `Community 1`?**
_High betweenness centrality (0.098) - this node is a cross-community bridge._
- **What connects `DISABLED: Do not contact Odoo servers. Returns fake successful response.`, `DISABLED: Do not send any data to Odoo servers. Just update local parame` to the rest of the system?**
_2 weakly-connected nodes found - possible documentation gaps or missing edges._

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py", "target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py", "target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/models/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_manifest_py", "label": "__manifest__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/__manifest__.py", "source_location": "L1"}], "edges": [], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py", "target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/__init__.py", "source_location": "L2", "weight": 1.0}], "raw_calls": []}

View File

@@ -1 +0,0 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_manifest_py", "label": "__manifest__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/__manifest__.py", "source_location": "L1"}], "edges": [], "raw_calls": []}

File diff suppressed because one or more lines are too long

View File

@@ -1,247 +0,0 @@
{
"directed": false,
"multigraph": false,
"graph": {},
"nodes": [
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py",
"community": 2,
"norm_label": "__init__.py"
},
{
"label": "__manifest__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/__manifest__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_manifest_py",
"community": 6,
"norm_label": "__manifest__.py"
},
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py",
"community": 3,
"norm_label": "__init__.py"
},
{
"label": "__manifest__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/__manifest__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_manifest_py",
"community": 7,
"norm_label": "__manifest__.py"
},
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/models/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py",
"community": 4,
"norm_label": "__init__.py"
},
{
"label": "publisher_warranty.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_publisher_warranty_py",
"community": 0,
"norm_label": "publisher_warranty.py"
},
{
"label": "PublisherWarrantyContractDisabled",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L10",
"id": "publisher_warranty_publisherwarrantycontractdisabled",
"community": 0,
"norm_label": "publisherwarrantycontractdisabled"
},
{
"label": "_get_sys_logs()",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L14",
"id": "publisher_warranty_get_sys_logs",
"community": 0,
"norm_label": "_get_sys_logs()"
},
{
"label": ".update_notification()",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L29",
"id": "publisher_warranty_publisherwarrantycontractdisabled_update_notification",
"community": 1,
"norm_label": ".update_notification()"
},
{
"label": "DISABLED: Do not contact Odoo servers. Returns fake successful response.",
"file_type": "rationale",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L15",
"id": "publisher_warranty_rationale_15",
"community": 8,
"norm_label": "disabled: do not contact odoo servers. returns fake successful response."
},
{
"label": "DISABLED: Do not send any data to Odoo servers. Just update local parame",
"file_type": "rationale",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L30",
"id": "publisher_warranty_rationale_30",
"community": 1,
"norm_label": "disabled: do not send any data to odoo servers. just update local parame"
},
{
"label": "__init__.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/__init__.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py",
"community": 5,
"norm_label": "__init__.py"
},
{
"label": "publisher_warranty.py",
"file_type": "code",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L1",
"id": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_publisher_warranty_py",
"community": 0,
"norm_label": "publisher_warranty.py"
}
],
"links": [
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_init_py",
"confidence_score": 1.0
},
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_init_py",
"confidence_score": 1.0
},
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/models/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_init_py",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L10",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_publisher_warranty_py",
"_tgt": "publisher_warranty_publisherwarrantycontractdisabled",
"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_publisher_warranty_py",
"target": "publisher_warranty_publisherwarrantycontractdisabled",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L14",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_publisher_warranty_py",
"_tgt": "publisher_warranty_get_sys_logs",
"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_disable_publisher_warranty_models_publisher_warranty_py",
"target": "publisher_warranty_get_sys_logs",
"confidence_score": 1.0
},
{
"relation": "method",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L29",
"weight": 1.0,
"_src": "publisher_warranty_publisherwarrantycontractdisabled",
"_tgt": "publisher_warranty_publisherwarrantycontractdisabled_update_notification",
"source": "publisher_warranty_publisherwarrantycontractdisabled",
"target": "publisher_warranty_publisherwarrantycontractdisabled_update_notification",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L10",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_publisher_warranty_py",
"_tgt": "publisher_warranty_publisherwarrantycontractdisabled",
"source": "publisher_warranty_publisherwarrantycontractdisabled",
"target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_publisher_warranty_py",
"confidence_score": 1.0
},
{
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L14",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_publisher_warranty_py",
"_tgt": "publisher_warranty_get_sys_logs",
"source": "publisher_warranty_get_sys_logs",
"target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_publisher_warranty_py",
"confidence_score": 1.0
},
{
"relation": "rationale_for",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/publisher_warranty.py",
"source_location": "L30",
"weight": 1.0,
"_src": "publisher_warranty_rationale_30",
"_tgt": "publisher_warranty_publisherwarrantycontractdisabled_update_notification",
"source": "publisher_warranty_publisherwarrantycontractdisabled_update_notification",
"target": "publisher_warranty_rationale_30",
"confidence_score": 1.0
},
{
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": "/Users/gurpreet/Github/Odoo-Modules/disable_publisher_warranty/models/__init__.py",
"source_location": "L2",
"weight": 1.0,
"_src": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py",
"_tgt": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py",
"source": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py",
"target": "users_gurpreet_github_odoo_modules_disable_publisher_warranty_models_init_py",
"confidence_score": 1.0
}
],
"hyperedges": []
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import publisher_warranty

View File

@@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
# Disable all publisher warranty / subscription checks for local development
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
class PublisherWarrantyContractDisabled(models.AbstractModel):
_inherit = "publisher_warranty.contract"
@api.model
def _get_sys_logs(self):
"""
DISABLED: Do not contact Odoo servers.
Returns fake successful response.
"""
_logger.info("Publisher warranty check DISABLED - not contacting Odoo servers")
return {
"messages": [],
"enterprise_info": {
"expiration_date": "2099-12-31 23:59:59",
"expiration_reason": "renewal",
"enterprise_code": self.env['ir.config_parameter'].sudo().get_param('database.enterprise_code', ''),
}
}
def update_notification(self, cron_mode=True):
"""
DISABLED: Do not send any data to Odoo servers.
Just update local parameters with permanent values.
"""
_logger.info("Publisher warranty update_notification DISABLED - no server contact")
# Set permanent valid subscription parameters
set_param = self.env['ir.config_parameter'].sudo().set_param
set_param('database.expiration_date', '2099-12-31 23:59:59')
set_param('database.expiration_reason', 'renewal')
# Clear any "already linked" parameters
set_param('database.already_linked_subscription_url', False)
set_param('database.already_linked_email', False)
set_param('database.already_linked_send_mail_url', False)
return True

View File

@@ -1,2 +0,0 @@
from . import models
from . import wizard

View File

@@ -1,22 +0,0 @@
{
'name': 'Fusion Bank Statements',
'version': '19.0.1.0.0',
'category': 'Accounting',
'summary': 'Import OFX/QFX bank statements with automatic duplicate detection',
'description': 'Upload OFX, QFX, or QBO files exported from your bank '
'(ScotiaConnect, TD, RBC, etc.) and import them as bank '
'statement lines. Smart duplicate detection using the bank\'s '
'transaction ID (fitid). No external server communication.',
'author': 'Fusion Central',
'website': 'https://fusionsoft.ca',
'license': 'LGPL-3',
'depends': ['account'],
'data': [
'security/ir.model.access.csv',
'wizard/import_statement_views.xml',
'views/account_journal_views.xml',
],
'external_dependencies': {'python': ['ofxparse']},
'installable': True,
'auto_install': False,
}

View File

@@ -1,2 +0,0 @@
from . import import_log
from . import account_journal

View File

@@ -1,16 +0,0 @@
from odoo import models
class AccountJournal(models.Model):
_inherit = 'account.journal'
def action_open_statement_import(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': 'Import Bank Statement',
'res_model': 'fusion.statement.import',
'view_mode': 'form',
'target': 'new',
'context': {'default_journal_id': self.id},
}

View File

@@ -1,26 +0,0 @@
from odoo import fields, models
class FusionStatementImportLog(models.Model):
_name = 'fusion.statement.import.log'
_description = 'Imported Bank Transaction Log'
_order = 'date desc, id desc'
_rec_name = 'fitid'
journal_id = fields.Many2one(
'account.journal', required=True, ondelete='cascade', index=True,
)
fitid = fields.Char(string='Bank Transaction ID', required=True, index=True)
date = fields.Date()
amount = fields.Float(digits=(16, 2))
payment_ref = fields.Char(string='Description')
import_date = fields.Datetime(default=fields.Datetime.now, readonly=True)
statement_line_id = fields.Many2one('account.bank.statement.line', ondelete='set null')
company_id = fields.Many2one(
'res.company', required=True, default=lambda self: self.env.company,
)
_sql_constraints = [
('journal_fitid_unique', 'UNIQUE(journal_id, fitid)',
'This transaction has already been imported for this journal.'),
]

View File

@@ -1,5 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fusion_import_log_accountant,fusion.statement.import.log accountant,model_fusion_statement_import_log,account.group_account_invoice,1,1,1,0
access_fusion_import_log_manager,fusion.statement.import.log manager,model_fusion_statement_import_log,account.group_account_manager,1,1,1,1
access_fusion_import_wizard,fusion.statement.import wizard,model_fusion_statement_import,account.group_account_invoice,1,1,1,1
access_fusion_import_line,fusion.statement.import.line wizard,model_fusion_statement_import_line,account.group_account_invoice,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fusion_import_log_accountant fusion.statement.import.log accountant model_fusion_statement_import_log account.group_account_invoice 1 1 1 0
3 access_fusion_import_log_manager fusion.statement.import.log manager model_fusion_statement_import_log account.group_account_manager 1 1 1 1
4 access_fusion_import_wizard fusion.statement.import wizard model_fusion_statement_import account.group_account_invoice 1 1 1 1
5 access_fusion_import_line fusion.statement.import.line wizard model_fusion_statement_import_line account.group_account_invoice 1 1 1 1

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add "Import Statement" button to bank journal form view -->
<record id="view_account_journal_form_inherit_fusion" model="ir.ui.view">
<field name="name">account.journal.form.fusion.statements</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_open_statement_import"
type="object"
class="oe_stat_button"
icon="fa-upload"
invisible="type != 'bank'">
<span class="o_stat_text">Import Statement</span>
</button>
</xpath>
</field>
</record>
</odoo>

Some files were not shown because too many files have changed in this diff Show More