Compare commits
3 Commits
fusion_acc
...
8217bb0ff6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8217bb0ff6 | ||
|
|
867b5f71a1 | ||
|
|
bee5ba4d3f |
@@ -1,26 +1,30 @@
|
||||
{
|
||||
'name': 'Fusion Accounting',
|
||||
'version': '19.0.1.0.4',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 25,
|
||||
'summary': 'Meta-module that installs the full Fusion Accounting suite (core, AI, migration; bank rec, reports, etc. as later sub-modules ship).',
|
||||
'summary': 'Meta-module that installs the full Fusion Accounting suite as a Community-edition replacement for Odoo Enterprise accounting.',
|
||||
'description': """
|
||||
Fusion Accounting (Meta-Module)
|
||||
===============================
|
||||
One-click install of the entire Fusion Accounting suite.
|
||||
One-click install of the entire Fusion Accounting suite \u2014 a Community-edition
|
||||
replacement for Odoo Enterprise's accounting modules.
|
||||
|
||||
Currently installs:
|
||||
- fusion_accounting_core Shared schema, security, runtime helpers
|
||||
- fusion_accounting_ai AI Co-Pilot (Claude/GPT)
|
||||
- fusion_accounting_migration Transitional Enterprise->Fusion data migration
|
||||
- fusion_accounting_bank_rec AI-assisted bank reconciliation (Phase 1)
|
||||
- fusion_accounting_reports AI-augmented financial reports (Phase 2)
|
||||
- fusion_accounting_assets AI-augmented asset management (Phase 3)
|
||||
- fusion_accounting_followup AI-augmented customer follow-ups (Phase 4)
|
||||
Sub-modules installed:
|
||||
- fusion_accounting_core Shared schema, security, runtime helpers
|
||||
- fusion_accounting_ai AI Co-Pilot (Claude/GPT/local LLM)
|
||||
- fusion_accounting_migration Transitional Enterprise->Fusion data migration
|
||||
- fusion_accounting_bank_rec AI-assisted bank reconciliation
|
||||
- fusion_accounting_reports AI-augmented financial reports
|
||||
- fusion_accounting_assets AI-augmented asset management
|
||||
- fusion_accounting_followup AI-augmented customer follow-ups
|
||||
- fusion_accounting_l10n_ca Canadian reports + tax return tracking
|
||||
- fusion_accounting_hr_payroll Payroll \u2192 GL bridge (replaces hr_payroll_account)
|
||||
- fusion_accounting_ocr Tesseract + LLM invoice OCR
|
||||
- fusion_accounting_documents Documents app \u2194 invoice bridge
|
||||
|
||||
Future sub-modules (added per the roadmap as each Phase ships):
|
||||
- fusion_accounting_dashboard (Phase 5)
|
||||
- fusion_accounting_budget (Phase 6)
|
||||
Renames the Community "Invoicing" top-level menu to "Accounting" and slots
|
||||
all Fusion sub-features as sub-menus, mirroring the Odoo Enterprise UX.
|
||||
|
||||
Built by Nexa Systems Inc.
|
||||
""",
|
||||
@@ -37,8 +41,14 @@ Built by Nexa Systems Inc.
|
||||
'fusion_accounting_reports',
|
||||
'fusion_accounting_assets',
|
||||
'fusion_accounting_followup',
|
||||
'fusion_accounting_l10n_ca',
|
||||
'fusion_accounting_hr_payroll',
|
||||
'fusion_accounting_ocr',
|
||||
'fusion_accounting_documents',
|
||||
],
|
||||
'data': [
|
||||
'data/menu_overrides.xml',
|
||||
],
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'license': 'OPL-1',
|
||||
|
||||
21
fusion_accounting/data/menu_overrides.xml
Normal file
21
fusion_accounting/data/menu_overrides.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="0">
|
||||
<!--
|
||||
Top-level "Invoicing" menu rename + visual rebrand.
|
||||
|
||||
V19 Community ships this menu as "Invoicing" with the standard
|
||||
accounting icon. Odoo Enterprise's `accountant` module renames it
|
||||
to "Accounting" and swaps the icon. We do the same here so that
|
||||
once Enterprise is uninstalled, the unified menu still presents
|
||||
as "Accounting" (not "Invoicing") to users.
|
||||
|
||||
This file lives in the meta-module so the rename only takes effect
|
||||
when the full Fusion suite is installed; sub-modules installed
|
||||
a-la-carte don't change the menu's name.
|
||||
-->
|
||||
<record id="account.menu_finance" model="ir.ui.menu">
|
||||
<field name="name">Accounting</field>
|
||||
<field name="web_icon">fusion_accounting,static/description/icon.png</field>
|
||||
<field name="sequence">25</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting AI',
|
||||
'version': '19.0.1.0.1',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 26,
|
||||
'summary': 'AI Co-Pilot for Odoo accounting (Claude/GPT) with conversational interface, dashboard, rules.',
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Root menu under Accounting (account_accountant uses accountant.menu_accounting) -->
|
||||
<!-- Lives under Community Accounting "Configuration" sub-menu so the
|
||||
AI co-pilot is reachable regardless of whether Enterprise is
|
||||
installed. (Was previously parented to `accountant.menu_accounting`
|
||||
which doesn't exist after the Enterprise uninstall.) -->
|
||||
<menuitem id="menu_fusion_accounting_root"
|
||||
name="Fusion AI"
|
||||
parent="accountant.menu_accounting"
|
||||
sequence="8"
|
||||
parent="account.menu_finance_configuration"
|
||||
sequence="55"
|
||||
groups="fusion_accounting_core.group_fusion_accounting_user"/>
|
||||
|
||||
<!-- Dashboard -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Assets',
|
||||
'version': '19.0.1.0.36',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||
'description': """
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Top-level menu (visible only when account_asset Enterprise NOT installed) -->
|
||||
<!-- Lives under Community Accounting "Accounting" sub-menu. Only visible
|
||||
when Enterprise's account_asset is absent. -->
|
||||
<menuitem id="menu_fusion_assets_root"
|
||||
name="Asset Management"
|
||||
sequence="60"
|
||||
web_icon="fusion_accounting_assets,static/description/icon.png"
|
||||
parent="account.menu_finance_entries"
|
||||
sequence="25"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Asset list/form -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting — Bank Reconciliation',
|
||||
'version': '19.0.1.0.26',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 28,
|
||||
'summary': 'Native V19 bank reconciliation widget with AI confidence scoring + behavioural learning.',
|
||||
|
||||
@@ -20,11 +20,14 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Top-level menu — only visible when Enterprise's account_accountant is absent -->
|
||||
<!-- Container menu lives under the Community Accounting "Accounting"
|
||||
sub-menu (account.menu_finance_entries). Only visible when
|
||||
Enterprise's account_accountant is absent (Enterprise's reconcile
|
||||
widget owns the same UI surface). -->
|
||||
<menuitem id="menu_fusion_bank_rec_root"
|
||||
name="Bank Reconciliation"
|
||||
sequence="40"
|
||||
web_icon="fusion_accounting_bank_rec,static/description/icon.png"
|
||||
parent="account.menu_finance_entries"
|
||||
sequence="15"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<menuitem id="menu_fusion_bank_rec_main"
|
||||
@@ -34,9 +37,8 @@
|
||||
sequence="10"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Sub-menu for the auto-reconcile wizard -->
|
||||
<menuitem id="menu_fusion_auto_reconcile_wizard"
|
||||
name="Auto-Reconcile…"
|
||||
name="Auto-Reconcile\u2026"
|
||||
parent="menu_fusion_bank_rec_root"
|
||||
action="action_fusion_auto_reconcile_wizard"
|
||||
sequence="20"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Core',
|
||||
'version': '19.0.1.0.2',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 24,
|
||||
'summary': 'Shared base for the Fusion Accounting sub-module suite (security, shared schema, runtime helpers).',
|
||||
|
||||
@@ -8,20 +8,46 @@ class ResUsers(models.Model):
|
||||
|
||||
@api.model
|
||||
def _fusion_recompute_coexistence_group(self):
|
||||
"""Set group membership = all internal users iff Enterprise absent.
|
||||
"""Maintain the two coexistence groups based on Enterprise presence.
|
||||
|
||||
- ``group_fusion_show_when_enterprise_absent``: members = all internal
|
||||
users when NO Enterprise accounting module is installed. Used to
|
||||
unhide Fusion menus that would conflict with Enterprise UIs.
|
||||
|
||||
- ``group_fusion_show_when_enterprise_present``: members = all internal
|
||||
users when AT LEAST ONE Enterprise accounting module IS installed.
|
||||
Used to hide migration/transitional UIs once Enterprise has been
|
||||
uninstalled (so the user doesn't see "Migrate from Enterprise" with
|
||||
nothing to migrate).
|
||||
|
||||
The two groups are mutually exclusive at any moment in time, but a
|
||||
user can transition between them as Enterprise modules are installed
|
||||
or uninstalled. Idempotent; safe to call multiple times.
|
||||
|
||||
Called from ir.module.module.button_immediate_install / uninstall
|
||||
overrides. Idempotent; safe to call multiple times.
|
||||
overrides.
|
||||
"""
|
||||
group = self.env.ref(
|
||||
absent_group = self.env.ref(
|
||||
'fusion_accounting_core.group_fusion_show_when_enterprise_absent',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
if not group:
|
||||
present_group = self.env.ref(
|
||||
'fusion_accounting_core.group_fusion_show_when_enterprise_present',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
if not absent_group and not present_group:
|
||||
return
|
||||
|
||||
enterprise_installed = self.env['ir.module.module']._fusion_is_enterprise_accounting_installed()
|
||||
all_internal = self.sudo().search([('share', '=', False)])
|
||||
|
||||
if enterprise_installed:
|
||||
group.sudo().write({'user_ids': [(5, 0, 0)]})
|
||||
if absent_group:
|
||||
absent_group.sudo().write({'user_ids': [(5, 0, 0)]})
|
||||
if present_group:
|
||||
present_group.sudo().write({'user_ids': [(6, 0, all_internal.ids)]})
|
||||
else:
|
||||
all_internal = self.sudo().search([('share', '=', False)])
|
||||
group.sudo().write({'user_ids': [(6, 0, all_internal.ids)]})
|
||||
if absent_group:
|
||||
absent_group.sudo().write({'user_ids': [(6, 0, all_internal.ids)]})
|
||||
if present_group:
|
||||
present_group.sudo().write({'user_ids': [(5, 0, 0)]})
|
||||
|
||||
@@ -49,4 +49,12 @@
|
||||
<field name="name">Fusion: Show menus when Enterprise absent</field>
|
||||
<field name="comment">Computed group. Membership: all internal users when no Enterprise accounting module is installed. Used to hide fusion sub-module menus that would conflict with Enterprise UIs.</field>
|
||||
</record>
|
||||
|
||||
<!-- Phase 8: inverse coexistence group \u2014 visible only when Enterprise IS present.
|
||||
Used to hide migration/transitional UIs once the migration is complete and
|
||||
Enterprise has been uninstalled. -->
|
||||
<record id="group_fusion_show_when_enterprise_present" model="res.groups">
|
||||
<field name="name">Fusion: Show menus when Enterprise present</field>
|
||||
<field name="comment">Computed group. Membership: all internal users WHEN at least one Enterprise accounting module is installed. Used to hide migration/transitional UIs that are irrelevant once Enterprise has been uninstalled.</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Follow-up',
|
||||
'version': '19.0.1.0.30',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented customer follow-ups (dunning) for unpaid invoices.',
|
||||
'description': """
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Top-level menu (visible only when account_followup Enterprise NOT installed) -->
|
||||
<!-- Lives under Community Accounting "Customers" sub-menu. Only visible
|
||||
when Enterprise's account_followup is absent. -->
|
||||
<menuitem id="menu_fusion_followup_root"
|
||||
name="Customer Follow-ups"
|
||||
sequence="70"
|
||||
web_icon="fusion_accounting_followup,static/description/icon.png"
|
||||
name="Follow-ups"
|
||||
parent="account.menu_finance_receivables"
|
||||
sequence="50"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Partners list (gated to overdue) -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting — Canadian Reports',
|
||||
'version': '19.0.1.0.0',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Localizations/Reporting',
|
||||
'summary': 'Canadian-specific report definitions and tax return templates for Fusion Accounting.',
|
||||
'description': """
|
||||
@@ -21,6 +21,7 @@ Auto-installs when l10n_ca + fusion_accounting_reports are both present.
|
||||
'data/fusion_tax_return_data.xml',
|
||||
'data/report_ca_balance_sheet.xml',
|
||||
'data/report_ca_profit_loss.xml',
|
||||
'views/menu_views.xml',
|
||||
],
|
||||
'auto_install': ['l10n_ca', 'fusion_accounting_reports'],
|
||||
'installable': True,
|
||||
|
||||
56
fusion_accounting_l10n_ca/views/menu_views.xml
Normal file
56
fusion_accounting_l10n_ca/views/menu_views.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!--
|
||||
Canadian-flavored P&L + BS menus. Live alongside the rest of the
|
||||
Fusion reports under Accounting > Reporting. Open the OWL
|
||||
ReportViewer with the Canadian report_code so the line_specs
|
||||
come from the Canadian definitions seeded in this module.
|
||||
-->
|
||||
|
||||
<record id="action_fusion_report_ca_pnl" model="ir.actions.act_window">
|
||||
<field name="name">Profit and Loss (Canada)</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'pnl', 'default_report_code': 'ca_profit_loss', 'default_comparison': 'previous_year'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_ca_bs" model="ir.actions.act_window">
|
||||
<field name="name">Balance Sheet (Canada)</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'balance_sheet', 'default_report_code': 'ca_balance_sheet', 'default_comparison': 'previous_period'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_fusion_report_ca_pnl"
|
||||
name="Profit and Loss (Canada)"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_ca_pnl"
|
||||
sequence="15"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_report_ca_bs"
|
||||
name="Balance Sheet (Canada)"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_ca_bs"
|
||||
sequence="16"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Tax-return tracking list -->
|
||||
<record id="action_fusion_tax_return" model="ir.actions.act_window">
|
||||
<field name="name">Tax Returns</field>
|
||||
<field name="res_model">fusion.tax.return</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">No tax returns recorded yet</p>
|
||||
<p>Track GST/HST/PST/T4/T5018/payroll-remittance filings.
|
||||
Each return covers a (date_from, date_to) window and moves
|
||||
from draft \u2192 to-file \u2192 filed as you submit it.</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_fusion_tax_return"
|
||||
name="Tax Returns (CA)"
|
||||
parent="account.menu_finance_configuration"
|
||||
action="action_fusion_tax_return"
|
||||
sequence="100"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
</odoo>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Migration',
|
||||
'version': '19.0.1.0.0',
|
||||
'version': '19.0.1.1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 27,
|
||||
'summary': 'Transitional module: migrates Odoo Enterprise accounting data to Fusion Accounting tables before Enterprise uninstall.',
|
||||
|
||||
@@ -27,21 +27,29 @@
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Top-level "Fusion Accounting" menu so the UserError guidance
|
||||
("Fusion Accounting -> Migrate from Enterprise") is actually reachable.
|
||||
Placed at top level (no parent) because the migration is a one-time
|
||||
admin task; making it visible during switchover is the point.
|
||||
`groups` hides the menu from non-admins (mirroring the ACL on the wizard).
|
||||
Migration wizard lives under Accounting > Configuration, and is
|
||||
ONLY visible while at least one Enterprise accounting module is
|
||||
still installed. Once the operator has uninstalled Enterprise, the
|
||||
wizard is hidden \u2014 there's nothing left to migrate.
|
||||
Visibility is gated by the intersection of:
|
||||
- group_fusion_accounting_admin (admin-only feature)
|
||||
- group_fusion_show_when_enterprise_present (computed: members
|
||||
iff at least one Enterprise accounting module is installed)
|
||||
-->
|
||||
<!-- Note: gating uses ONLY group_fusion_show_when_enterprise_present.
|
||||
Admin-restriction is enforced via the model ACL
|
||||
(ir.model.access.csv only grants access to group_fusion_accounting_admin).
|
||||
Odoo `groups=` on menuitems uses OR semantics, so listing both groups
|
||||
would let any admin see the menu even after Enterprise is uninstalled. -->
|
||||
<menuitem id="menu_fusion_migration_root"
|
||||
name="Fusion Accounting"
|
||||
sequence="95"
|
||||
web_icon="fusion_accounting_migration,static/description/icon.png"
|
||||
groups="fusion_accounting_core.group_fusion_accounting_admin"/>
|
||||
<menuitem id="menu_fusion_migration_wizard"
|
||||
name="Migrate from Enterprise"
|
||||
parent="account.menu_finance_configuration"
|
||||
sequence="95"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_present"/>
|
||||
<menuitem id="menu_fusion_migration_wizard"
|
||||
name="Run Migration Wizard"
|
||||
parent="menu_fusion_migration_root"
|
||||
action="action_fusion_migration_wizard"
|
||||
sequence="10"
|
||||
groups="fusion_accounting_core.group_fusion_accounting_admin"/>
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_present"/>
|
||||
</odoo>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Reports',
|
||||
'version': '19.0.1.0.38',
|
||||
'version': '19.0.1.1.1',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).',
|
||||
'description': """
|
||||
@@ -47,6 +47,7 @@ menu hides; the engine and AI tools remain available for the chat.
|
||||
'reports/report_pdf_template.xml',
|
||||
'wizards/xlsx_export_wizard_views.xml',
|
||||
'wizards/period_picker_wizard_views.xml',
|
||||
'views/report_actions.xml',
|
||||
'views/menu_views.xml',
|
||||
],
|
||||
'external_dependencies': {
|
||||
|
||||
@@ -15,9 +15,11 @@ export class PeriodFilter extends Component {
|
||||
async onReportTypeChange(ev) {
|
||||
const reportType = ev.target.value;
|
||||
if (reportType && this.state.dateFrom && this.state.dateTo) {
|
||||
// Switching report type clears the report_code (user is picking
|
||||
// a different category, not a variant).
|
||||
await this.reports.runReport(
|
||||
reportType, this.state.dateFrom, this.state.dateTo,
|
||||
this.state.comparison);
|
||||
this.state.comparison, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +29,8 @@ export class PeriodFilter extends Component {
|
||||
await this.reports.runReport(
|
||||
this.state.currentReportType,
|
||||
this.state.dateFrom, this.state.dateTo,
|
||||
this.state.comparison);
|
||||
this.state.comparison,
|
||||
this.state.currentReportCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export class ReportsService {
|
||||
this.state = reactive({
|
||||
availableReports: [],
|
||||
currentReportType: null,
|
||||
currentReportCode: null,
|
||||
currentResult: null,
|
||||
currentAnomalies: [],
|
||||
currentCommentary: null,
|
||||
@@ -41,15 +42,17 @@ export class ReportsService {
|
||||
}
|
||||
}
|
||||
|
||||
async runReport(reportType, dateFrom, dateTo, comparison = 'none') {
|
||||
async runReport(reportType, dateFrom, dateTo, comparison = 'none', reportCode = null) {
|
||||
this.state.isLoading = true;
|
||||
this.state.currentReportType = reportType;
|
||||
this.state.currentReportCode = reportCode;
|
||||
this.state.dateFrom = dateFrom;
|
||||
this.state.dateTo = dateTo;
|
||||
this.state.comparison = comparison;
|
||||
try {
|
||||
this.state.currentResult = await this.rpc(`${ENDPOINT_BASE}/run`, {
|
||||
report_type: reportType,
|
||||
report_code: reportCode,
|
||||
date_from: dateFrom,
|
||||
date_to: dateTo,
|
||||
comparison: comparison,
|
||||
@@ -136,7 +139,8 @@ export class ReportsService {
|
||||
this.state.comparison = mode;
|
||||
if (this.state.currentReportType) {
|
||||
return this.runReport(this.state.currentReportType,
|
||||
this.state.dateFrom, this.state.dateTo, mode);
|
||||
this.state.dateFrom, this.state.dateTo, mode,
|
||||
this.state.currentReportCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@ export class ReportViewer extends Component {
|
||||
|
||||
const ctx = this.props.action?.context || {};
|
||||
const reportType = ctx.default_report_type || 'pnl';
|
||||
// default_report_code lets multiple reports of the same type
|
||||
// (e.g. pnl, cash_flow, executive_summary, annual_statements all
|
||||
// type='pnl') resolve to their own line_specs.
|
||||
const reportCode = ctx.default_report_code || null;
|
||||
const comparison = ctx.default_comparison || 'none';
|
||||
const companyId = this.env.services.user?.context?.allowed_company_ids?.[0];
|
||||
|
||||
onWillStart(async () => {
|
||||
@@ -29,7 +34,8 @@ export class ReportViewer extends Component {
|
||||
const today = new Date();
|
||||
const year = today.getFullYear();
|
||||
await this.reports.runReport(
|
||||
reportType, `${year}-01-01`, `${year}-12-31`, 'none');
|
||||
reportType, `${year}-01-01`, `${year}-12-31`,
|
||||
comparison, reportCode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_fusion_reports_root"
|
||||
name="Financial Reports"
|
||||
sequence="50"
|
||||
web_icon="fusion_accounting_reports,static/description/icon.png"
|
||||
<!--
|
||||
Fusion dynamic financial reports live as direct children of the
|
||||
Community "Reporting" sub-menu (account.menu_finance_reports),
|
||||
sitting alongside Community's PDF-based "Statement Reports" /
|
||||
"Partner Reports" / "Taxes & Fiscal" / "Management" wrappers.
|
||||
|
||||
Each menu opens an act_window with view_mode='fusion_reports'
|
||||
(the OWL ReportViewer). report_actions.xml defines the actions.
|
||||
|
||||
All gated to the coexistence group so they only appear when
|
||||
Enterprise's account_reports is uninstalled.
|
||||
-->
|
||||
|
||||
<!-- Top of the list \u2014 daily-driver statements -->
|
||||
<menuitem id="menu_fusion_reports_pnl"
|
||||
name="Profit and Loss"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_pnl"
|
||||
sequence="10"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_bs"
|
||||
name="Balance Sheet"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_balance_sheet"
|
||||
sequence="11"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_executive_summary"
|
||||
name="Executive Summary"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_executive_summary"
|
||||
sequence="12"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_cash_flow"
|
||||
name="Cash Flow Statement"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_cash_flow"
|
||||
sequence="13"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_annual_statements"
|
||||
name="Annual Statements"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_annual_statements"
|
||||
sequence="14"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Audit / drill-down statements -->
|
||||
<menuitem id="menu_fusion_reports_tb"
|
||||
name="Trial Balance"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_trial_balance"
|
||||
sequence="20"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_gl"
|
||||
name="General Ledger"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_general_ledger"
|
||||
sequence="21"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Partner-grouped -->
|
||||
<menuitem id="menu_fusion_reports_aged_receivable"
|
||||
name="Aged Receivable"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_aged_receivable"
|
||||
sequence="30"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_aged_payable"
|
||||
name="Aged Payable"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_aged_payable"
|
||||
sequence="31"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_partner_ledger"
|
||||
name="Partner Ledger"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_partner_ledger"
|
||||
sequence="32"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Tax -->
|
||||
<menuitem id="menu_fusion_reports_tax"
|
||||
name="Tax Summary"
|
||||
parent="account.menu_finance_reports"
|
||||
action="action_fusion_report_tax_summary"
|
||||
sequence="40"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!--
|
||||
Tools group at the bottom: custom-period picker, XLSX export wizard,
|
||||
anomaly browser. Less-frequently used; bundled together so they
|
||||
don't clutter the report list.
|
||||
-->
|
||||
<menuitem id="menu_fusion_reports_tools_group"
|
||||
name="Tools"
|
||||
parent="account.menu_finance_reports"
|
||||
sequence="90"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
<menuitem id="menu_fusion_reports_open"
|
||||
name="Open Report..."
|
||||
parent="menu_fusion_reports_root"
|
||||
name="Custom Period..."
|
||||
parent="menu_fusion_reports_tools_group"
|
||||
action="action_fusion_period_picker_wizard"
|
||||
sequence="10"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<menuitem id="menu_fusion_reports_xlsx"
|
||||
name="Export to XLSX..."
|
||||
parent="menu_fusion_reports_root"
|
||||
parent="menu_fusion_reports_tools_group"
|
||||
action="action_fusion_xlsx_export_wizard"
|
||||
sequence="20"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
@@ -28,7 +118,7 @@
|
||||
|
||||
<menuitem id="menu_fusion_reports_anomalies"
|
||||
name="Anomalies"
|
||||
parent="menu_fusion_reports_root"
|
||||
parent="menu_fusion_reports_tools_group"
|
||||
action="action_fusion_report_anomaly_list"
|
||||
sequence="30"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
101
fusion_accounting_reports/views/report_actions.xml
Normal file
101
fusion_accounting_reports/views/report_actions.xml
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!--
|
||||
One ir.actions.act_window per built-in report, each opens the OWL
|
||||
ReportViewer (view_mode='fusion_reports'). The viewer reads
|
||||
``default_report_type`` and ``default_report_code`` from the action
|
||||
context to pick which fusion.report to render.
|
||||
|
||||
New menus per-report live below; users no longer need to go through
|
||||
the period-picker wizard for the standard reports.
|
||||
-->
|
||||
|
||||
<!-- ============================================================
|
||||
CORE REPORTS (one per Fusion engine method)
|
||||
============================================================ -->
|
||||
|
||||
<record id="action_fusion_report_pnl" model="ir.actions.act_window">
|
||||
<field name="name">Profit and Loss</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'pnl', 'default_report_code': 'pnl'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_balance_sheet" model="ir.actions.act_window">
|
||||
<field name="name">Balance Sheet</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'balance_sheet', 'default_report_code': 'balance_sheet'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_trial_balance" model="ir.actions.act_window">
|
||||
<field name="name">Trial Balance</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'trial_balance', 'default_report_code': 'trial_balance'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_general_ledger" model="ir.actions.act_window">
|
||||
<field name="name">General Ledger</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'general_ledger', 'default_report_code': 'general_ledger'}</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================
|
||||
SECONDARY PnL VARIANTS (engine.compute_pnl with code)
|
||||
============================================================ -->
|
||||
|
||||
<record id="action_fusion_report_cash_flow" model="ir.actions.act_window">
|
||||
<field name="name">Cash Flow Statement</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'pnl', 'default_report_code': 'cash_flow', 'default_comparison': 'previous_year'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_executive_summary" model="ir.actions.act_window">
|
||||
<field name="name">Executive Summary</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'pnl', 'default_report_code': 'executive_summary', 'default_comparison': 'previous_year'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_annual_statements" model="ir.actions.act_window">
|
||||
<field name="name">Annual Statements</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'pnl', 'default_report_code': 'annual_statements', 'default_comparison': 'previous_year'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_tax_summary" model="ir.actions.act_window">
|
||||
<field name="name">Tax Summary</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'trial_balance', 'default_report_code': 'tax_summary'}</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================
|
||||
PARTNER-GROUPED REPORTS
|
||||
============================================================ -->
|
||||
|
||||
<record id="action_fusion_report_aged_receivable" model="ir.actions.act_window">
|
||||
<field name="name">Aged Receivable</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'aged_receivable', 'default_report_code': 'aged_receivable'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_aged_payable" model="ir.actions.act_window">
|
||||
<field name="name">Aged Payable</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'aged_payable', 'default_report_code': 'aged_payable'}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_report_partner_ledger" model="ir.actions.act_window">
|
||||
<field name="name">Partner Ledger</field>
|
||||
<field name="res_model">fusion.report</field>
|
||||
<field name="view_mode">fusion_reports</field>
|
||||
<field name="context">{'default_report_type': 'partner_ledger', 'default_report_code': 'partner_ledger'}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -229,35 +229,38 @@ class SaleOrder(models.Model):
|
||||
self.env.cr.execute('RELEASE SAVEPOINT %s' % savepoint_name)
|
||||
except Exception as exc:
|
||||
self.env.cr.execute('ROLLBACK TO SAVEPOINT %s' % savepoint_name)
|
||||
self.message_post(body=_(
|
||||
self.message_post(body=(
|
||||
'Auto-MO group %s failed: %s'
|
||||
) % (tag or 'single-line', exc))
|
||||
continue
|
||||
|
||||
if created or adopted:
|
||||
# _() needs a lang in env.context; in shell/cron this may be
|
||||
# unset. Compose the message with plain format strings — this
|
||||
# text is an internal chatter log, not user-facing UI.
|
||||
msg_parts = []
|
||||
if created:
|
||||
lines_html = '<br/>'.join([
|
||||
_('MO <a href="/odoo/manufacturing/%s">%s</a> '
|
||||
'(%s, %d source line%s)') % (
|
||||
mo.id, mo.name, tag or 'untagged',
|
||||
n, 's' if n != 1 else ''
|
||||
)
|
||||
'MO <a href="/odoo/manufacturing/%s">%s</a> '
|
||||
'(%s, %d source line%s)' % (
|
||||
mo.id, mo.name, tag or 'untagged',
|
||||
n, 's' if n != 1 else ''
|
||||
)
|
||||
for mo, tag, n in created
|
||||
])
|
||||
msg_parts.append(
|
||||
_('%d draft MO(s) auto-created:<br/>%s') % (
|
||||
'%d draft MO(s) auto-created:<br/>%s' % (
|
||||
len(created), lines_html,
|
||||
)
|
||||
)
|
||||
if adopted:
|
||||
adopted_html = '<br/>'.join([
|
||||
_('MO <a href="/odoo/manufacturing/%s">%s</a> '
|
||||
'(legacy, now line-linked)') % (mo.id, mo.name)
|
||||
'MO <a href="/odoo/manufacturing/%s">%s</a> '
|
||||
'(legacy, now line-linked)' % (mo.id, mo.name)
|
||||
for mo in adopted
|
||||
])
|
||||
msg_parts.append(
|
||||
_('%d legacy MO(s) adopted:<br/>%s') % (
|
||||
'%d legacy MO(s) adopted:<br/>%s' % (
|
||||
len(adopted), adopted_html,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -113,6 +113,12 @@ class SaleOrder(models.Model):
|
||||
string='Margin %',
|
||||
compute='_compute_margin',
|
||||
)
|
||||
x_fc_margin_available = fields.Boolean(
|
||||
string='Margin Available',
|
||||
compute='_compute_margin',
|
||||
help='False when no order line has a costed coating — the '
|
||||
'margin fields should render "n/a" in the UI.',
|
||||
)
|
||||
|
||||
x_fc_workorder_count = fields.Integer(
|
||||
string='Active WOs',
|
||||
@@ -486,24 +492,34 @@ class SaleOrder(models.Model):
|
||||
|
||||
@api.depends('order_line.price_subtotal', 'amount_untaxed')
|
||||
def _compute_margin(self):
|
||||
"""Simple margin: untaxed total minus rolled-up cost from coating configs.
|
||||
"""Margin = untaxed total − rolled-up cost from coating configs.
|
||||
|
||||
x_fc_margin_percent is stored as a fraction (0.0 - 1.0) so the
|
||||
widget='percentage' formats it correctly (a 100% margin reads
|
||||
as 100%, not 10000%).
|
||||
widget='percentage' formats 100% as 100%, not 10000%.
|
||||
|
||||
x_fc_margin_available is False when NO line has a costed coating
|
||||
(i.e. fp.coating.config.unit_cost isn't populated anywhere). The
|
||||
UI should render margin fields as "n/a" in that case rather than
|
||||
showing a misleading 100%.
|
||||
"""
|
||||
for rec in self:
|
||||
has_cost_data = False
|
||||
cost = 0.0
|
||||
for line in rec.order_line:
|
||||
if line.x_fc_coating_config_id:
|
||||
cost_per_unit = getattr(
|
||||
line.x_fc_coating_config_id, 'unit_cost', 0.0,
|
||||
) or 0.0
|
||||
cost += cost_per_unit * (line.product_uom_qty or 0)
|
||||
cc = line.x_fc_coating_config_id
|
||||
if not cc:
|
||||
continue
|
||||
if 'unit_cost' not in cc._fields:
|
||||
continue
|
||||
if cc.unit_cost:
|
||||
has_cost_data = True
|
||||
cost_per_unit = cc.unit_cost or 0.0
|
||||
cost += cost_per_unit * (line.product_uom_qty or 0)
|
||||
rec.x_fc_margin_available = has_cost_data
|
||||
rec.x_fc_margin_amount = (rec.amount_untaxed or 0) - cost
|
||||
rec.x_fc_margin_percent = (
|
||||
(rec.x_fc_margin_amount / rec.amount_untaxed)
|
||||
if rec.amount_untaxed else 0.0
|
||||
if (rec.amount_untaxed and has_cost_data) else 0.0
|
||||
)
|
||||
|
||||
@api.onchange('upload_rfq_file')
|
||||
|
||||
@@ -148,11 +148,21 @@
|
||||
</group>
|
||||
<group>
|
||||
<group string="Margin">
|
||||
<div colspan="2"
|
||||
invisible="x_fc_margin_available"
|
||||
class="text-muted">
|
||||
<i class="fa fa-info-circle me-1"/>
|
||||
Margin n/a — coating cost rollup not yet
|
||||
populated on any line's treatment.
|
||||
</div>
|
||||
<field name="x_fc_margin_amount"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"/>
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
invisible="not x_fc_margin_available"/>
|
||||
<field name="x_fc_margin_percent"
|
||||
widget="percentage"/>
|
||||
widget="percentage"
|
||||
invisible="not x_fc_margin_available"/>
|
||||
<field name="x_fc_margin_available" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
@@ -188,6 +198,13 @@
|
||||
<field name="arch" type="xml">
|
||||
<list string="Sale Orders" decoration-info="state == 'draft'"
|
||||
decoration-muted="state == 'cancel'">
|
||||
<header>
|
||||
<button name="%(action_fp_direct_order_wizard)d"
|
||||
type="action"
|
||||
string="+ New Direct Order"
|
||||
class="btn-primary"
|
||||
display="always"/>
|
||||
</header>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="x_fc_po_number"/>
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
<field name="model">fp.direct.order.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Direct Order Entry">
|
||||
<div class="alert alert-info py-2 mb-0 small"
|
||||
role="alert">
|
||||
<i class="fa fa-info-circle me-1"/>
|
||||
Changes are not saved until you click
|
||||
<strong>Create & Confirm Order</strong>. Closing this
|
||||
window (Esc or X) discards your entries.
|
||||
</div>
|
||||
<div class="alert alert-warning mb-0"
|
||||
role="alert"
|
||||
invisible="not missing_info_msg">
|
||||
@@ -194,7 +201,10 @@
|
||||
type="object"
|
||||
string="Create & Confirm Order"
|
||||
class="btn-primary"/>
|
||||
<button string="Cancel" special="cancel" class="btn-secondary"/>
|
||||
<button string="Cancel"
|
||||
special="cancel"
|
||||
class="btn-secondary"
|
||||
confirm="Discard this order? All header data and line items will be lost."/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form/header" position="before">
|
||||
<div class="alert alert-danger" role="alert"
|
||||
<div class="alert alert-danger py-1 px-2 mb-0 small"
|
||||
role="alert"
|
||||
invisible="not partner_id or not partner_id.x_fc_account_hold">
|
||||
<strong>Account Hold</strong> — This customer is on account hold.
|
||||
SO confirmation, invoicing, and shipping are blocked for non-managers.
|
||||
<i class="fa fa-ban me-1"/>
|
||||
<strong>Account Hold</strong> — SO confirmation, invoicing
|
||||
and shipping are blocked for non-managers.
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user