Compare commits
3 Commits
449f29fc7f
...
7a02382623
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a02382623 | ||
|
|
169e97af02 | ||
|
|
3c959771ae |
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
{
|
||||
'name': 'Fusion Plating — Native Jobs',
|
||||
'version': '19.0.8.22.9',
|
||||
'version': '19.0.8.22.10',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.',
|
||||
'author': 'Nexa Systems Inc.',
|
||||
|
||||
@@ -10,7 +10,15 @@ from odoo import api, models
|
||||
|
||||
|
||||
class ReportFpJobMargin(models.AbstractModel):
|
||||
_name = 'report.fusion_plating_jobs.report_fp_job_margin'
|
||||
# Odoo looks up the report's data model via report.<report_name>.
|
||||
# The action's report_name is `fusion_plating_jobs.report_fp_job_margin_template`,
|
||||
# so this MUST be `report.fusion_plating_jobs.report_fp_job_margin_template`.
|
||||
# Pre-2026-05-12 the model name was missing the `_template` suffix,
|
||||
# which silently caused _get_report_values to never fire and the
|
||||
# template rendered with no `rows` -> blank PDF. The t-field error
|
||||
# was masking this because it crashed earlier; once t-field was
|
||||
# swapped to t-esc the blank-render surfaced.
|
||||
_name = 'report.fusion_plating_jobs.report_fp_job_margin_template'
|
||||
_description = 'Work Order Margin Report'
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from . import models
|
||||
from .hooks import post_init_hook
|
||||
from .hooks import pre_init_hook, post_init_hook
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"data/09_res_partner.xml",
|
||||
"data/10_account_reconcile_model.xml",
|
||||
],
|
||||
"pre_init_hook": "pre_init_hook",
|
||||
"post_init_hook": "post_init_hook",
|
||||
"installable": True,
|
||||
"application": False,
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<!-- Named 'Customer Project' to avoid collision with the project module's
|
||||
auto-created 'Project' plan. This is where customer-engagement
|
||||
analytic accounts (PRJ-YYYY-CUST-NAME) live. -->
|
||||
<record id="plan_project" model="account.analytic.plan">
|
||||
<field name="name">Customer Project</field>
|
||||
<field name="default_applicability">mandatory</field>
|
||||
</record>
|
||||
|
||||
<record id="plan_department" model="account.analytic.plan">
|
||||
<field name="name">Department</field>
|
||||
<field name="default_applicability">mandatory</field>
|
||||
</record>
|
||||
|
||||
<record id="plan_sred_tag" model="account.analytic.plan">
|
||||
<field name="name">SR&ED Tag</field>
|
||||
<field name="default_applicability">optional</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<!-- Department analytic accounts -->
|
||||
<record id="aa_dept_dev" model="account.analytic.account">
|
||||
<field name="name">Development</field>
|
||||
<field name="code">DEPT-DEV</field>
|
||||
<field name="plan_id" ref="plan_department"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_dept_sales" model="account.analytic.account">
|
||||
<field name="name">Sales & Marketing</field>
|
||||
<field name="code">DEPT-SALES</field>
|
||||
<field name="plan_id" ref="plan_department"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_dept_admin" model="account.analytic.account">
|
||||
<field name="name">Admin & Operations</field>
|
||||
<field name="code">DEPT-ADMIN</field>
|
||||
<field name="plan_id" ref="plan_department"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_dept_hosting" model="account.analytic.account">
|
||||
<field name="name">Hosting Operations</field>
|
||||
<field name="code">DEPT-HOSTING</field>
|
||||
<field name="plan_id" ref="plan_department"/>
|
||||
</record>
|
||||
|
||||
<!-- SR&ED Tag analytic accounts -->
|
||||
<record id="aa_sred_t4_dev" model="account.analytic.account">
|
||||
<field name="name">T4 Dev Salary — full proxy</field>
|
||||
<field name="code">SRED-T4-DEV-SALARY</field>
|
||||
<field name="plan_id" ref="plan_sred_tag"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_sred_specified" model="account.analytic.account">
|
||||
<field name="name">Specified Employee Salary — 75% cap</field>
|
||||
<field name="code">SRED-SPECIFIED-EMPLOYEE</field>
|
||||
<field name="plan_id" ref="plan_sred_tag"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_sred_contr_ca_arm" model="account.analytic.account">
|
||||
<field name="name">Contractor CA Arm's Length — 80% eligible</field>
|
||||
<field name="code">SRED-CONTRACTOR-CA-ARM-LENGTH</field>
|
||||
<field name="plan_id" ref="plan_sred_tag"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_sred_contr_ca_naf" model="account.analytic.account">
|
||||
<field name="name">Contractor CA Non-Arm's Length</field>
|
||||
<field name="code">SRED-CONTRACTOR-CA-NON-ARM-LENGTH</field>
|
||||
<field name="plan_id" ref="plan_sred_tag"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_sred_materials" model="account.analytic.account">
|
||||
<field name="name">Materials Consumed in R&D</field>
|
||||
<field name="code">SRED-MATERIALS-CONSUMED</field>
|
||||
<field name="plan_id" ref="plan_sred_tag"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_sred_overhead_basis" model="account.analytic.account">
|
||||
<field name="name">Overhead Proxy Basis (direct labour basis)</field>
|
||||
<field name="code">SRED-OVERHEAD-PROXY-BASIS</field>
|
||||
<field name="plan_id" ref="plan_sred_tag"/>
|
||||
</record>
|
||||
|
||||
<record id="aa_sred_not_eligible" model="account.analytic.account">
|
||||
<field name="name">Not Eligible (default)</field>
|
||||
<field name="code">NOT-ELIGIBLE</field>
|
||||
<field name="plan_id" ref="plan_sred_tag"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -4,6 +4,72 @@ import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# l10n_ca account codes that collide with the Nexa CoA design and that
|
||||
# l10n_ca pre-loads with 'income_other'/'expense'/etc. types we don't want.
|
||||
# Each of these is checked at pre_init: if it has zero postings we suffix
|
||||
# its code with '.OLD' and archive it so our XML can claim the code.
|
||||
# Codes with postings are LEFT ALONE — we renumbered the Nexa code instead
|
||||
# (115100 stays as l10n_ca 'Customers Account' AR; Nexa shareholder receivable
|
||||
# moved to 119100. 511100 stays as l10n_ca 'Inside Purchases'; Nexa Cloud
|
||||
# Infrastructure moved to 511105).
|
||||
_L10N_CA_COLLISION_CODES = [
|
||||
"118100", "118200", "118300",
|
||||
"213100", "214100",
|
||||
"221200",
|
||||
"311100", "311200", "311300",
|
||||
"411100", "411200", "411300",
|
||||
"413100", "413200", "413300",
|
||||
"511110", "511120", "511130", "511140", "511200", "511210",
|
||||
"512100", "512110", "512200",
|
||||
"611100", "611200", "611300",
|
||||
"612100", "612200",
|
||||
]
|
||||
|
||||
|
||||
def pre_init_hook(env):
|
||||
"""Run BEFORE XML data is loaded. Clear l10n_ca account codes that would
|
||||
collide with Nexa's chart of accounts."""
|
||||
_logger.info("nexa_coa_setup: pre_init_hook starting")
|
||||
_clear_l10n_ca_collisions(env)
|
||||
_logger.info("nexa_coa_setup: pre_init_hook complete")
|
||||
|
||||
|
||||
def _clear_l10n_ca_collisions(env):
|
||||
"""For each colliding code: if it has zero postings, rename to NNNNNN.OLD
|
||||
and set inactive. If it has postings, leave alone (Nexa code was renumbered
|
||||
in the XML to avoid the conflict)."""
|
||||
cleared = 0
|
||||
kept_with_postings = 0
|
||||
not_found = 0
|
||||
for code in _L10N_CA_COLLISION_CODES:
|
||||
acc = env["account.account"].search([("code", "=", code)], limit=1)
|
||||
if not acc:
|
||||
not_found += 1
|
||||
continue
|
||||
usage = env["account.move.line"].search_count([("account_id", "=", acc.id)])
|
||||
if usage > 0:
|
||||
_logger.info(
|
||||
"nexa_coa_setup: keeping l10n_ca account %s (%s) — %d postings exist",
|
||||
code, acc.name, usage,
|
||||
)
|
||||
kept_with_postings += 1
|
||||
continue
|
||||
new_code = f"{code}.OLD"
|
||||
# Skip if already suffixed (idempotency)
|
||||
if acc.code.endswith(".OLD"):
|
||||
continue
|
||||
acc.write({
|
||||
"code": new_code,
|
||||
"name": f"(l10n_ca LEGACY) {acc.name or acc.display_name}",
|
||||
"active": False,
|
||||
})
|
||||
cleared += 1
|
||||
_logger.info(
|
||||
"nexa_coa_setup: collision sweep — cleared %d, kept-with-postings %d, not-found %d",
|
||||
cleared, kept_with_postings, not_found,
|
||||
)
|
||||
|
||||
|
||||
def post_init_hook(env):
|
||||
"""Imperative one-shot operations after module data is loaded.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user