This commit is contained in:
gsinghpal
2026-05-16 13:18:52 -04:00
parent 191a9c82be
commit 9ebf89bde2
1080 changed files with 0 additions and 1197 deletions

Binary file not shown.

View File

@@ -0,0 +1,130 @@
# fusion_accounting_assets — Cursor / Claude Context
## Purpose
AI-augmented fixed asset management with depreciation schedules — a
Fusion-native replacement for (and coexisting with) Odoo Enterprise's
`account_asset` module. Ships in Phase 3 of the fusion_accounting roadmap.
## Architecture
Hybrid: the engine (`fusion.asset.engine`, AbstractModel) is the SINGLE
write surface for the asset lifecycle. Everything else (controllers, OWL
widget, AI tools, wizards, cron) routes through the engine's 7-method
public API:
- `compute_depreciation_schedule(asset, recompute=False)`
- `post_depreciation_entry(asset, period_date=None)`
- `dispose_asset(asset, sale_amount=0, sale_date=None, sale_partner=None, disposal_type='sale')`
- `partial_sale(asset, sold_amount, sold_qty=None, sale_date=None, sale_partner=None)`
- `pause_asset(asset, pause_date=None)`
- `resume_asset(asset, resume_date=None)`
- `reverse_disposal(asset)`
Pure-Python services live in `services/`:
- `depreciation_methods` — straight_line, declining_balance, units_of_production
- `prorate` — first/last-period prorating: full_month, days_365, days_period
- `salvage_value` — % of cost, fixed amount, zero
- `anomaly_detection` — variance vs expected schedule, low utilization
- `useful_life_predictor` + `useful_life_prompt` — LLM-suggested useful life with templated fallback
Persisted models in `models/`:
- `fusion.asset` — main model, state machine: draft → running → paused → disposed
- `fusion.asset.depreciation.line` — board lines
- `fusion.asset.category` — templates
- `fusion.asset.disposal` — disposal records
- `fusion.asset.anomaly` — flagged variances
- `fusion.asset.book.values.mv` — pre-aggregated materialized view
- `fusion.asset.engine` — AbstractModel (the API)
- `fusion.assets.cron` — cron handlers (post depreciations, MV refresh, anomaly scan)
- `account.move.line` (inherits) — adds `fusion_asset_id` linkage
- `fusion.migration.wizard` (inherits in `models/`) — adds asset backfill step
Wizards (TransientModel) in `wizards/`:
- `fusion.create.asset.wizard` — assisted creation with AI useful-life suggestion
- `fusion.disposal.wizard` — full disposal flow
- `fusion.partial.sale.wizard` — partial-quantity disposal
- `fusion.depreciation.run.wizard` — period close runner
Controller: `controllers/assets_controller.py` exposes 8 JSON-RPC
endpoints under `/fusion/assets/*` (list, get_detail, compute_schedule,
post_depreciation, dispose, get_anomalies, suggest_useful_life,
get_partner_history). All calls route through the engine.
OWL frontend: `static/src/`
- `services/assets_service.js` — central reactive state + RPC wrappers
- `views/asset_dashboard/*` — top-level dashboard view
- `components/asset_card`, `asset_detail_panel`, `depreciation_board`,
`disposal_dialog`, `ai_useful_life_panel`, `anomaly_strip` — 6 components
- `scss/_variables.scss` + `assets.scss` + `dark_mode.scss`
- `tours/assets_tours.js` — 5 OWL tour smoke tests
## Coexistence
When `account_asset` is installed the Asset Management menu hides via
`fusion_accounting_core.group_fusion_show_when_enterprise_absent` (a
computed group). The engine + AI tools remain available for the chat.
The migration wizard backfills `fusion.asset` from existing
`account.asset` records (verified live: 2 records, Task 35).
## Conventions
- **V19 deprecations to avoid:** `_sql_constraints` (use
`models.Constraint`), `@api.depends('id')` (raises
`NotImplementedError`), `@route(type='json')` (use `type='jsonrpc'`),
`numbercall` field on `ir.cron` (removed), `groups_id` on `res.users`
(use `all_group_ids` for searching), `users` field on `res.groups`
(use `user_ids`), `groups_id` on `ir.ui.menu` (use `group_ids`).
- **Materialized view refresh:** `fusion.asset.book.values.mv` is
refreshed by cron (REFRESH CONCURRENTLY in an autocommit cursor since
it can't run inside a regular Odoo transaction).
- **Provider routing:** AI features look up
`fusion_accounting.provider.asset_useful_life`, falling back to
`fusion_accounting.provider.default`. When neither is set the
templated keyword fallback in `useful_life_predictor` keeps the
feature usable offline.
## Performance baseline (Tasks 23 + 41)
| Operation | P95 | Budget | Headroom |
|------------------------------------|-------|----------|----------|
| `engine.compute_schedule` (10yr SL)| 1ms | 500ms | 500x |
| `engine.post_depreciation_entry` | <1ms | 300ms | huge |
| `engine.dispose_asset` | 5ms | 300ms | 60x |
| `controller.list` (35 assets) | 42ms | 300ms | 7x |
| `controller.get_detail` | 40ms | 500ms | 12x |
All Phase 3 perf metrics are within 1x of budget; no optimization was
needed at ship (Task 42 skipped per the conditional rule).
## Test counts (Phase 3 ship)
- 140 logical tests total in fusion_accounting_assets
- 0 failures, 0 errors
- Coverage includes: 4 engine benchmarks + 1 controller benchmark
(tagged `benchmark`), 1 local LLM smoke (tagged `local_llm`, skips
when no LLM), 5 OWL tour tests (tagged `tour`, skip without
websocket-client), Hypothesis property tests on the engine,
integration tests on the public API, controller round-trip tests, MV
shape tests.
## Known concerns / Phase 3.5 backlog
- Sub-annual depreciation frequency (currently annual only)
- Units-of-production assumes even per-period units
- Disposal journal entry not yet created — `dispose_asset` writes the
`fusion.asset.disposal` record but not the cash / gain-loss move
- Multi-currency, allocation rules, and analytic tags for depreciation
moves are out of scope for Phase 3
- Partial-sale child asset is created with no own depreciation schedule
pre-disposal
- Migration wizard inheritance lives in `models/` rather than
`wizards/` (small inconsistency with the rest of the wizard layout —
intentional to keep ORM ordering simple)
- `useful_life_predictor` always returns a usable dict (templated
fallback when LLM absent), so callers can't distinguish "AI said so"
from "fallback fired"; the `confidence` key is the only signal

View File

@@ -0,0 +1,53 @@
# fusion_accounting_assets
AI-augmented fixed asset management for Odoo 19 Community — a
Fusion-native replacement for Enterprise's `account_asset` module.
## What it does
- Three depreciation methods: straight-line, declining balance, and
units-of-production
- Asset lifecycle state machine: draft → running → paused → disposed
- Editable depreciation board with full schedule recompute
- Disposal flow (sale, scrap, donation) plus partial-sale wizard
- Daily cron for posting periodic depreciation
- AI augmentation:
- **Anomaly detection** — variance vs expected schedule, low utilization
- **Useful-life suggestion** — LLM-driven from invoice context, with a
keyword-based templated fallback so the feature still works offline
- Coexists with Enterprise `account_asset` (Enterprise wins by default;
the Fusion menu only appears when Enterprise is uninstalled)
- Migration-aware: bootstrap step backfills `fusion.asset` from existing
`account.asset` rows so the AI has memory from day 1
## Quick start
```bash
# Install
odoo --addons-path=... -i fusion_accounting_assets
# Open the dashboard (when Enterprise's account_asset is NOT installed)
# Apps -> Asset Management -> Assets
# When Enterprise IS installed: use Enterprise's UI; the engine + AI tools
# are still available via the AI chat.
```
## Configuration
- Local LLM (LM Studio, Ollama):
- `fusion_accounting.openai_base_url` =
`http://host.docker.internal:1234/v1`
- `fusion_accounting.openai_model` = your local model name
- `fusion_accounting.openai_api_key` = `lm-studio` (anything non-empty)
- `fusion_accounting.provider.asset_useful_life` = `openai`
## Public API (engine)
`fusion.asset.engine` is the single write surface. See `CLAUDE.md` for
the full 7-method signature list.
## See also
- `CLAUDE.md` — agent context
- `UPGRADE_NOTES.md` — Odoo version anchoring

View File

@@ -0,0 +1,49 @@
# fusion_accounting_assets — Upgrade Notes
## Odoo Version Anchor
This module targets **Odoo 19.0** (community-base).
Reference snapshot of Enterprise code mirrored from:
- `account_asset` (Odoo 19.0.x)
- Source: `/Users/gurpreet/Github/RePackaged-Odoo/accounting/account_asset/`
## Cross-Version Diff Strategy
When a new Odoo version ships:
1. Run `check_odoo_diff.sh` (in repo root) against the new Enterprise version
2. Note any breaking changes in `account.asset` / `account.move.line` API
3. For mirrored OWL components, diff Enterprise's new versions against ours
and port material changes (signature renames, new behaviour we want to
inherit)
4. Re-run the full test suite + tour tests against the new Odoo version
5. Update this file with the new version anchor + any deviations
## V19 Migration Notes (already applied)
- `_sql_constraints``models.Constraint` (every persisted model)
- `@api.depends('id')` → removed (none introduced)
- `@route(type='json')``type='jsonrpc'` (all 8 endpoints in
`controllers/assets_controller.py`)
- `numbercall` removed from `ir.cron` (data/cron.xml)
- `res.groups.users``user_ids` and `ir.ui.menu.groups_id`
`group_ids` (security + menu_views.xml)
## Phase 3 → Phase 3.5 Migration
If we ship Phase 3.5 (sub-annual depreciation frequency, disposal journal
entries, multi-currency, allocation rules), changes will go in
incremental commits. No DB migration needed (Phase 3 schema is
forward-compatible — new columns will be nullable / default-valued).
## Coexistence with Enterprise `account_asset`
The migration step in `fusion.migration.wizard` backfills `fusion.asset`
records from existing `account.asset` rows. It is idempotent (skips rows
already linked via the `legacy_account_asset_id` column). Verified live
on westin-v19: 2 records migrated cleanly.
When `account_asset` is installed the Asset Management menu hides via
`fusion_accounting_core.group_fusion_show_when_enterprise_absent`. The
engine and AI tools remain available for chat-driven workflows.

View File

@@ -0,0 +1,5 @@
from . import models
from . import services
from . import controllers
from . import wizards
from . import reports

View File

@@ -0,0 +1,75 @@
{
'name': 'Fusion Accounting Assets',
'version': '19.0.1.1.1',
'category': 'Accounting/Accounting',
'summary': 'AI-augmented asset management with depreciation schedules.',
'description': """
Fusion Accounting Assets
========================
A Fusion-native replacement for Odoo Enterprise's account_asset module.
CORE scope (Phase 3):
- 3 depreciation methods: straight-line, declining balance, units of production
- Asset lifecycle: draft -> running -> paused -> disposed
- Depreciation board with editable schedule
- Disposal (sale, scrap, donation) + partial sale wizards
- Daily cron for posting periodic depreciation
AI augmentation:
- Anomaly detection on utilization vs expected
- AI-suggested useful life from invoice context (LLM)
Coexists with Enterprise: when account_asset is installed, the Fusion
menu hides; the engine + AI tools remain available for the chat.
""",
'author': 'Fusion Accounting',
'license': 'LGPL-3',
'depends': [
'fusion_accounting_core',
'fusion_accounting_ai',
'fusion_accounting_migration',
'account',
'mail',
],
'data': [
'security/ir.model.access.csv',
'data/cron.xml',
'wizards/create_asset_wizard_views.xml',
'wizards/disposal_wizard_views.xml',
'wizards/partial_sale_wizard_views.xml',
'wizards/depreciation_run_wizard_views.xml',
'reports/migration_audit_report_views.xml',
'reports/migration_audit_report_action.xml',
'views/menu_views.xml',
],
'assets': {
'web.assets_backend': [
'fusion_accounting_assets/static/src/scss/_variables.scss',
'fusion_accounting_assets/static/src/scss/assets.scss',
'fusion_accounting_assets/static/src/services/assets_service.js',
'fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard.js',
'fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard.xml',
'fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard_view.js',
'fusion_accounting_assets/static/src/components/asset_card/asset_card.js',
'fusion_accounting_assets/static/src/components/asset_card/asset_card.xml',
'fusion_accounting_assets/static/src/components/asset_detail_panel/asset_detail_panel.js',
'fusion_accounting_assets/static/src/components/asset_detail_panel/asset_detail_panel.xml',
'fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.js',
'fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.xml',
'fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js',
'fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.xml',
'fusion_accounting_assets/static/src/components/ai_useful_life_panel/ai_useful_life_panel.js',
'fusion_accounting_assets/static/src/components/ai_useful_life_panel/ai_useful_life_panel.xml',
'fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js',
'fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.xml',
],
'web.assets_tests': [
'fusion_accounting_assets/static/src/tours/assets_tours.js',
],
},
'installable': True,
'auto_install': False,
'application': False,
'icon': '/fusion_accounting_assets/static/description/icon.png',
}

View File

@@ -0,0 +1 @@
from . import assets_controller

View File

@@ -0,0 +1,175 @@
"""HTTP controller: 8 JSON-RPC endpoints for the OWL asset dashboard.
All endpoints route through fusion.asset.engine. V19 type='jsonrpc'.
"""
import logging
from datetime import date, datetime
from odoo import _, http
from odoo.exceptions import ValidationError
from odoo.http import request
_logger = logging.getLogger(__name__)
def _parse_date(value):
if isinstance(value, date):
return value
if not value:
return None
return datetime.strptime(value, '%Y-%m-%d').date()
class FusionAssetsController(http.Controller):
@http.route('/fusion/assets/list', type='jsonrpc', auth='user')
def list_assets(self, state=None, category_id=None, limit=50, offset=0,
company_id=None):
company_id = int(company_id) if company_id else request.env.company.id
Asset = request.env['fusion.asset'].sudo()
domain = [('company_id', '=', company_id)]
if state:
domain.append(('state', '=', state))
if category_id:
domain.append(('category_id', '=', int(category_id)))
total = Asset.search_count(domain)
assets = Asset.search(domain, limit=int(limit), offset=int(offset),
order='acquisition_date desc')
return {
'count': len(assets),
'total': total,
'assets': [{
'id': a.id, 'name': a.name, 'code': a.code or '',
'state': a.state, 'cost': a.cost, 'salvage_value': a.salvage_value,
'book_value': a.book_value, 'total_depreciated': a.total_depreciated,
'method': a.method, 'useful_life_years': a.useful_life_years,
'acquisition_date': str(a.acquisition_date),
'in_service_date': str(a.in_service_date) if a.in_service_date else None,
'category_id': a.category_id.id if a.category_id else None,
'category_name': a.category_id.name if a.category_id else None,
'currency_code': a.currency_id.name,
} for a in assets],
}
@http.route('/fusion/assets/get_detail', type='jsonrpc', auth='user')
def get_detail(self, asset_id):
asset = request.env['fusion.asset'].browse(int(asset_id))
if not asset.exists():
raise ValidationError(_("Asset %s not found") % asset_id)
return {
'asset': {
'id': asset.id, 'name': asset.name, 'code': asset.code or '',
'state': asset.state, 'cost': asset.cost,
'salvage_value': asset.salvage_value,
'book_value': asset.book_value,
'total_depreciated': asset.total_depreciated,
'method': asset.method,
'useful_life_years': asset.useful_life_years,
'declining_rate_pct': asset.declining_rate_pct,
'total_units_expected': asset.total_units_expected,
'units_used_to_date': asset.units_used_to_date,
'prorate_convention': asset.prorate_convention,
'acquisition_date': str(asset.acquisition_date),
'in_service_date': str(asset.in_service_date) if asset.in_service_date else None,
'disposed_date': str(asset.disposed_date) if asset.disposed_date else None,
'category_id': asset.category_id.id if asset.category_id else None,
'category_name': asset.category_id.name if asset.category_id else None,
'currency_id': asset.currency_id.id,
'currency_code': asset.currency_id.name,
},
'depreciation_lines': [{
'id': l.id, 'period_index': l.period_index,
'scheduled_date': str(l.scheduled_date),
'amount': l.amount, 'accumulated': l.accumulated,
'book_value_at_end': l.book_value_at_end,
'is_posted': l.is_posted,
'posted_date': str(l.posted_date) if l.posted_date else None,
} for l in asset.depreciation_line_ids.sorted('period_index')],
'anomalies': [{
'id': a.id, 'anomaly_type': a.anomaly_type,
'severity': a.severity, 'detail': a.detail or '',
'state': a.state,
} for a in request.env['fusion.asset.anomaly'].search([
('asset_id', '=', asset.id), ('state', 'in', ('new', 'acknowledged'))
])],
}
@http.route('/fusion/assets/compute_schedule', type='jsonrpc', auth='user')
def compute_schedule(self, asset_id, recompute=False):
asset = request.env['fusion.asset'].sudo().browse(int(asset_id))
engine = request.env['fusion.asset.engine'].sudo()
return engine.compute_depreciation_schedule(asset, recompute=bool(recompute))
@http.route('/fusion/assets/post_depreciation', type='jsonrpc', auth='user')
def post_depreciation(self, asset_id, period_date=None):
asset = request.env['fusion.asset'].sudo().browse(int(asset_id))
engine = request.env['fusion.asset.engine'].sudo()
return engine.post_depreciation_entry(asset, period_date=_parse_date(period_date))
@http.route('/fusion/assets/dispose', type='jsonrpc', auth='user')
def dispose(self, asset_id, sale_amount=0, sale_date=None,
sale_partner_id=None, disposal_type='sale'):
asset = request.env['fusion.asset'].sudo().browse(int(asset_id))
engine = request.env['fusion.asset.engine'].sudo()
partner = None
if sale_partner_id:
partner = request.env['res.partner'].sudo().browse(int(sale_partner_id))
return engine.dispose_asset(
asset, sale_amount=float(sale_amount),
sale_date=_parse_date(sale_date),
sale_partner=partner, disposal_type=disposal_type,
)
@http.route('/fusion/assets/get_anomalies', type='jsonrpc', auth='user')
def get_anomalies(self, asset_id=None, severity=None, state='new', limit=50,
company_id=None):
company_id = int(company_id) if company_id else request.env.company.id
Anomaly = request.env['fusion.asset.anomaly'].sudo()
domain = [('company_id', '=', company_id)]
if asset_id:
domain.append(('asset_id', '=', int(asset_id)))
if severity:
domain.append(('severity', '=', severity))
if state:
domain.append(('state', '=', state))
anomalies = Anomaly.search(domain, limit=int(limit), order='detected_at desc')
return {
'count': len(anomalies),
'anomalies': [{
'id': a.id, 'asset_id': a.asset_id.id, 'asset_name': a.asset_id.name,
'anomaly_type': a.anomaly_type, 'severity': a.severity,
'expected': a.expected, 'actual': a.actual,
'variance_pct': a.variance_pct, 'detail': a.detail or '',
'state': a.state,
'detected_at': str(a.detected_at),
} for a in anomalies],
}
@http.route('/fusion/assets/suggest_useful_life', type='jsonrpc', auth='user')
def suggest_useful_life(self, description, amount=None, partner_name=None):
from odoo.addons.fusion_accounting_assets.services.useful_life_predictor import (
predict_useful_life,
)
return predict_useful_life(
request.env, description=description,
amount=float(amount) if amount is not None else None,
partner_name=partner_name,
)
@http.route('/fusion/assets/get_partner_history', type='jsonrpc', auth='user')
def get_partner_history(self, partner_id, limit=20):
Asset = request.env['fusion.asset'].sudo()
assets = Asset.search([
('source_invoice_line_id.partner_id', '=', int(partner_id)),
], limit=int(limit), order='acquisition_date desc')
return {
'partner_id': int(partner_id),
'count': len(assets),
'assets': [{
'id': a.id, 'name': a.name,
'cost': a.cost, 'book_value': a.book_value,
'state': a.state,
'acquisition_date': str(a.acquisition_date),
} for a in assets],
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="cron_fusion_assets_post_depreciation" model="ir.cron">
<field name="name">Fusion Assets — Post Due Depreciation</field>
<field name="model_id" ref="model_fusion_assets_cron"/>
<field name="state">code</field>
<field name="code">model._cron_post_due_depreciation()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="active" eval="True"/>
</record>
<record id="cron_fusion_assets_refresh_book_values_mv" model="ir.cron">
<field name="name">Fusion Assets — Refresh Book Values MV</field>
<field name="model_id" ref="model_fusion_assets_cron"/>
<field name="state">code</field>
<field name="code">model._cron_refresh_book_values_mv()</field>
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="active" eval="True"/>
</record>
<record id="cron_fusion_assets_anomaly_scan" model="ir.cron">
<field name="name">Fusion Assets — Monthly Anomaly Scan</field>
<field name="model_id" ref="model_fusion_assets_cron"/>
<field name="state">code</field>
<field name="code">model._cron_anomaly_scan()</field>
<field name="interval_number">30</field>
<field name="interval_type">days</field>
<field name="active" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,29 @@
-- Materialized view: per-asset book value snapshot.
-- Refreshed via cron. Used by the OWL dashboard for portfolio summaries.
CREATE MATERIALIZED VIEW IF NOT EXISTS fusion_asset_book_values_mv AS
SELECT
a.id AS id,
a.id AS asset_id,
a.company_id,
a.category_id,
a.state,
a.cost,
a.salvage_value,
COALESCE(SUM(CASE WHEN l.is_posted THEN l.amount ELSE 0 END), 0) AS total_depreciated,
a.cost - COALESCE(SUM(CASE WHEN l.is_posted THEN l.amount ELSE 0 END), 0) AS book_value,
COUNT(l.id) FILTER (WHERE l.is_posted) AS posted_periods,
COUNT(l.id) FILTER (WHERE NOT l.is_posted) AS pending_periods,
a.acquisition_date,
a.in_service_date
FROM fusion_asset a
LEFT JOIN fusion_asset_depreciation_line l ON l.asset_id = a.id
GROUP BY a.id, a.company_id, a.category_id, a.state, a.cost, a.salvage_value,
a.acquisition_date, a.in_service_date;
CREATE UNIQUE INDEX IF NOT EXISTS fusion_asset_book_values_mv_pkey
ON fusion_asset_book_values_mv (id);
CREATE INDEX IF NOT EXISTS fusion_asset_book_values_mv_company_state
ON fusion_asset_book_values_mv (company_id, state);
CREATE INDEX IF NOT EXISTS fusion_asset_book_values_mv_category
ON fusion_asset_book_values_mv (category_id) WHERE category_id IS NOT NULL;

View File

@@ -0,0 +1,332 @@
# Graph Report - /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets (2026-04-22)
## Corpus Check
- 71 files · ~16,325 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 467 nodes · 660 edges · 46 communities detected
- Extraction: 73% EXTRACTED · 27% INFERRED · 0% AMBIGUOUS · INFERRED: 179 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]]
- [[_COMMUNITY_Community 27|Community 27]]
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_COMMUNITY_Community 33|Community 33]]
- [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 37|Community 37]]
- [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 39|Community 39]]
- [[_COMMUNITY_Community 40|Community 40]]
- [[_COMMUNITY_Community 41|Community 41]]
- [[_COMMUNITY_Community 42|Community 42]]
- [[_COMMUNITY_Community 43|Community 43]]
- [[_COMMUNITY_Community 44|Community 44]]
- [[_COMMUNITY_Community 45|Community 45]]
## God Nodes (most connected - your core abstractions)
1. `compute_depreciation_schedule()` - 37 edges
2. `TestFusionAssetEngine` - 14 edges
3. `post_depreciation_entry()` - 14 edges
4. `predict_useful_life()` - 13 edges
5. `TestAssetsController` - 12 edges
6. `straight_line()` - 11 edges
7. `TestFusionAsset` - 10 edges
8. `TestAssetAnomalyDetection` - 10 edges
9. `TestAssetEngineIntegration` - 10 edges
10. `AssetsService` - 10 edges
## Surprising Connections (you probably didn't know these)
- `compute_schedule()` --calls--> `compute_depreciation_schedule()` [INFERRED]
/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/controllers/assets_controller.py → /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_engine.py
- `suggest_useful_life()` --calls--> `predict_useful_life()` [INFERRED]
/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/controllers/assets_controller.py → /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_predictor.py
- `test_straight_line_total_equals_cost_minus_salvage()` --calls--> `straight_line()` [INFERRED]
/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_engine_property.py → /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/depreciation_methods.py
- `test_straight_line_book_value_decreasing()` --calls--> `straight_line()` [INFERRED]
/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_engine_property.py → /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/depreciation_methods.py
- `test_declining_balance_never_below_salvage()` --calls--> `declining_balance()` [INFERRED]
/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_engine_property.py → /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/depreciation_methods.py
## Communities
### Community 0 - "Community 0"
Cohesion: 0.09
Nodes (15): FusionDepreciationRunWizard, Manual depreciation run wizard. Operator picks a period_date and the wizard pos, compute_depreciation_schedule(), dispose_asset(), partial_sale(), pause_asset(), post_depreciation_entry(), The asset engine — orchestrator for all asset depreciation + lifecycle. 7-metho (+7 more)
### Community 1 - "Community 1"
Cohesion: 0.07
Nodes (21): declining_balance(), DepreciationStep, Depreciation method primitives. Three methods supported: - straight_line: equal, Equal charge per period: (cost - salvage) / n_periods. Last period absorbs, Apply `rate` (e.g. 0.20 = 20%) to remaining book each period. Switches to s, Charge per period = (units_used / total_expected) * (cost - salvage)., straight_line(), units_of_production() (+13 more)
### Community 2 - "Community 2"
Cohesion: 0.07
Nodes (19): AssetAnomaly, detect_low_utilization(), detect_schedule_variance(), Asset utilization anomaly detection. Flags assets where actual usage / posted d, Compare expected accumulated depreciation vs actual posted., For units-of-production assets: flag low actual usage., FusionAssetBookValuesMV, MV of per-asset book value snapshot. Refresh via cron or model._refresh(). (+11 more)
### Community 3 - "Community 3"
Cohesion: 0.06
Nodes (10): AssetsAdapter wiring tests — fusion-mode dispatch., TestAssetsAdapter, TestAuditReport, TestFusionAssetCategory, TestFusionAssetDisposal, Integration tests verifying all 3 depreciation methods through the engine., TestDecliningBalanceIntegration, TestStraightLineIntegration (+2 more)
### Community 4 - "Community 4"
Cohesion: 0.07
Nodes (5): AiUsefulLifePanel, AssetDashboard, AssetDetailPanel, AssetsService, DisposalDialog
### Community 5 - "Community 5"
Cohesion: 0.08
Nodes (9): HttpCase, Controller tests using HttpCase., TestAssetsController, Python wrappers that run the OWL tours via HttpCase.start_tour. Tours require a, TestAssetsTours, Controller perf benchmarks tagged 'benchmark'. Engine-level benchmarks live in, TestAssetsControllerBenchmarks, Performance benchmarks tagged 'benchmark'. (+1 more)
### Community 6 - "Community 6"
Cohesion: 0.09
Nodes (16): _detect_local_llm(), Local LLM compat smoke test for the useful_life_predictor service. Auto-detects, _server_reachable(), TestLocalLLMUsefulLife, TestUsefulLifePredictor, TestUsefulLifePrompt, _get_provider(), predict_useful_life() (+8 more)
### Community 7 - "Community 7"
Cohesion: 0.12
Nodes (3): FusionAsset, Fusion Asset model. Lifecycle: draft -> running -> (paused -> running)* -> disp, TestFusionAsset
### Community 8 - "Community 8"
Cohesion: 0.14
Nodes (6): FusionCreateAssetWizard, _onchange_category_id(), Create-asset-from-invoice-line wizard. Reads an account.move.line as the source, Create the fusion.asset record + link to source invoice line., Call AI useful-life predictor., TestCreateAssetWizard
### Community 9 - "Community 9"
Cohesion: 0.23
Nodes (7): compute_salvage_value(), Salvage value (scrap value) calculation helpers. Most clients use straight % of, Compute end-of-life salvage value., Estimate remaining value if asset is sold/scrapped now., remaining_useful_life_value(), SalvageConfig, TestSalvageValue
### Community 10 - "Community 10"
Cohesion: 0.22
Nodes (4): FusionAssetDepreciationLine, Per-period depreciation board lines for an asset., Mark this line as posted (without creating the journal entry yet — engin, TestFusionAssetDepreciationLine
### Community 11 - "Community 11"
Cohesion: 0.21
Nodes (3): FusionAssetAnomaly, Persisted asset anomaly flags from the engine's variance detection., TestFusionAssetAnomaly
### Community 12 - "Community 12"
Cohesion: 0.24
Nodes (4): prorate_factor(), Prorating helpers for first-period and last-period depreciation. When an asset, Return a 0..1 factor for how much of `period`'s depreciation applies to an a, TestProrate
### Community 13 - "Community 13"
Cohesion: 0.2
Nodes (6): FusionMigrationWizard, Assets-specific migration step. Backfills fusion.asset from existing account.as, Backfill fusion.asset from account.asset (Enterprise) if it exists., Override to add assets-bootstrap step., When Enterprise account.asset is NOT installed, step is a no-op., TestAssetsMigrationRoundTrip
### Community 14 - "Community 14"
Cohesion: 0.18
Nodes (4): _compute_gain_loss(), FusionDisposalWizard, Asset disposal wizard (sale, scrap, donation, lost)., TestDisposalWizard
### Community 15 - "Community 15"
Cohesion: 0.2
Nodes (4): _compute_sold_cost(), FusionPartialSaleWizard, Partial sale wizard (sell a portion of an asset). Splits the asset into a child, TestPartialSaleWizard
### Community 16 - "Community 16"
Cohesion: 0.2
Nodes (7): compute_schedule(), dispose(), FusionAssetsController, _parse_date(), post_depreciation(), HTTP controller: 8 JSON-RPC endpoints for the OWL asset dashboard. All endpoint, suggest_useful_life()
### Community 17 - "Community 17"
Cohesion: 0.18
Nodes (3): AccountMoveLine, Inherit account.move.line to link to fusion.asset records. Lets us trace assets, TestAccountMoveLineFusionAsset
### Community 18 - "Community 18"
Cohesion: 0.25
Nodes (2): Tests for the 5 fusion-asset AI tools., TestFusionAssetTools
### Community 19 - "Community 19"
Cohesion: 0.25
Nodes (3): Coexistence tests: fusion_accounting_assets menu only visible when Enterprise ac, Engine is registered regardless of Enterprise install state., TestAssetsCoexistence
### Community 20 - "Community 20"
Cohesion: 0.5
Nodes (2): FusionAssetCategory, Asset categories with default settings (used as templates).
### Community 21 - "Community 21"
Cohesion: 0.5
Nodes (2): FusionAssetDisposal, Asset disposal records (sale, scrap, donation).
### Community 22 - "Community 22"
Cohesion: 0.5
Nodes (2): FusionAssetsMigrationAuditReport, QWeb PDF: migration audit report for fusion_accounting_assets.
### Community 23 - "Community 23"
Cohesion: 0.67
Nodes (1): AnomalyStrip
### Community 24 - "Community 24"
Cohesion: 0.67
Nodes (1): DepreciationBoard
### Community 25 - "Community 25"
Cohesion: 1.0
Nodes (1): AssetCard
### Community 26 - "Community 26"
Cohesion: 1.0
Nodes (0):
### Community 27 - "Community 27"
Cohesion: 1.0
Nodes (0):
### Community 28 - "Community 28"
Cohesion: 1.0
Nodes (0):
### Community 29 - "Community 29"
Cohesion: 1.0
Nodes (0):
### Community 30 - "Community 30"
Cohesion: 1.0
Nodes (0):
### Community 31 - "Community 31"
Cohesion: 1.0
Nodes (0):
### Community 32 - "Community 32"
Cohesion: 1.0
Nodes (0):
### Community 33 - "Community 33"
Cohesion: 1.0
Nodes (0):
### Community 34 - "Community 34"
Cohesion: 1.0
Nodes (1): For each running asset, post any due un-posted depreciation lines.
### Community 35 - "Community 35"
Cohesion: 1.0
Nodes (1): Refresh the per-asset book value MV (hourly).
### Community 36 - "Community 36"
Cohesion: 1.0
Nodes (1): For each running asset, compare expected accumulated depreciation vs pos
### Community 37 - "Community 37"
Cohesion: 1.0
Nodes (1): Compute (or re-compute) the depreciation board for an asset. If recompu
### Community 38 - "Community 38"
Cohesion: 1.0
Nodes (1): Post the next-due un-posted depreciation line. If period_date provided,
### Community 39 - "Community 39"
Cohesion: 1.0
Nodes (1): Dispose an asset (sale, scrap, donation, lost).
### Community 40 - "Community 40"
Cohesion: 1.0
Nodes (1): Partially dispose: split asset into two — sold child + remaining parent.
### Community 41 - "Community 41"
Cohesion: 1.0
Nodes (1): Pause depreciation. Wraps asset.action_pause for API symmetry and to log
### Community 42 - "Community 42"
Cohesion: 1.0
Nodes (1): Resume a paused asset.
### Community 43 - "Community 43"
Cohesion: 1.0
Nodes (1): Reverse a disposal (rare — recovery from accidental sale entry).
### Community 44 - "Community 44"
Cohesion: 1.0
Nodes (0):
### Community 45 - "Community 45"
Cohesion: 1.0
Nodes (0):
## Knowledge Gaps
- **72 isolated node(s):** `Integration tests verifying all 3 depreciation methods through the engine.`, `Property-based invariant tests for the asset engine. Hypothesis generates rando`, `Local LLM compat smoke test for the useful_life_predictor service. Auto-detects`, `Python wrappers that run the OWL tours via HttpCase.start_tour. Tours require a`, `Tests for the per-asset book value MV.` (+67 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 25`** (2 nodes): `AssetCard`, `asset_card.js`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 26`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 27`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 28`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 29`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 30`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 31`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 32`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 33`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 34`** (1 nodes): `For each running asset, post any due un-posted depreciation lines.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 35`** (1 nodes): `Refresh the per-asset book value MV (hourly).`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 36`** (1 nodes): `For each running asset, compare expected accumulated depreciation vs pos`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 37`** (1 nodes): `Compute (or re-compute) the depreciation board for an asset. If recompu`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 38`** (1 nodes): `Post the next-due un-posted depreciation line. If period_date provided,`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 39`** (1 nodes): `Dispose an asset (sale, scrap, donation, lost).`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 40`** (1 nodes): `Partially dispose: split asset into two — sold child + remaining parent.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 41`** (1 nodes): `Pause depreciation. Wraps asset.action_pause for API symmetry and to log`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 42`** (1 nodes): `Resume a paused asset.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 43`** (1 nodes): `Reverse a disposal (rare — recovery from accidental sale entry).`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 44`** (1 nodes): `assets_tours.js`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 45`** (1 nodes): `asset_dashboard_view.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 `compute_depreciation_schedule()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 14`, `Community 15`, `Community 16`?**
_High betweenness centrality (0.101) - this node is a cross-community bridge._
- **Why does `TestEngineBenchmarks` connect `Community 0` to `Community 3`, `Community 5`?**
_High betweenness centrality (0.057) - this node is a cross-community bridge._
- **Why does `TestFusionAssetEngine` connect `Community 0` to `Community 3`?**
_High betweenness centrality (0.052) - this node is a cross-community bridge._
- **Are the 32 inferred relationships involving `compute_depreciation_schedule()` (e.g. with `.test_straight_line_5yr_no_salvage()` and `.test_straight_line_10yr_with_salvage()`) actually correct?**
_`compute_depreciation_schedule()` has 32 INFERRED edges - model-reasoned connections that need verification._
- **Are the 12 inferred relationships involving `post_depreciation_entry()` (e.g. with `.test_post_depreciation_entry_marks_line_posted()` and `.test_post_depreciation_only_after_running()`) actually correct?**
_`post_depreciation_entry()` has 12 INFERRED edges - model-reasoned connections that need verification._
- **Are the 9 inferred relationships involving `predict_useful_life()` (e.g. with `.test_useful_life_with_local_llm()` and `.test_fallback_computer()`) actually correct?**
_`predict_useful_life()` has 9 INFERRED edges - model-reasoned connections that need verification._
- **What connects `Integration tests verifying all 3 depreciation methods through the engine.`, `Property-based invariant tests for the asset engine. Hypothesis generates rando`, `Local LLM compat smoke test for the useful_life_predictor service. Auto-detects` to the rest of the system?**
_72 weakly-connected nodes found - possible documentation gaps or missing edges._

View File

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

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_account_move_py", "label": "account_move.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L1"}, {"id": "account_move_accountmoveline", "label": "AccountMoveLine", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L9"}, {"id": "account_move_accountmoveline_compute_fusion_asset_count", "label": "._compute_fusion_asset_count()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L20"}, {"id": "account_move_accountmoveline_action_open_fusion_asset", "label": ".action_open_fusion_asset()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L24"}, {"id": "account_move_rationale_1", "label": "Inherit account.move.line to link to fusion.asset records. Lets us trace assets", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_account_move_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L6", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_account_move_py", "target": "account_move_accountmoveline", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L9", "weight": 1.0}, {"source": "account_move_accountmoveline", "target": "account_move_accountmoveline_compute_fusion_asset_count", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L20", "weight": 1.0}, {"source": "account_move_accountmoveline", "target": "account_move_accountmoveline_action_open_fusion_asset", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L24", "weight": 1.0}, {"source": "account_move_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_account_move_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "account_move_accountmoveline_action_open_fusion_asset", "callee": "ensure_one", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/account_move.py", "source_location": "L25"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_disposal_dialog_disposal_dialog_js", "label": "disposal_dialog.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L1"}, {"id": "disposal_dialog_disposaldialog", "label": "DisposalDialog", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L6"}, {"id": "disposal_dialog_disposaldialog_setup", "label": ".setup()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L13"}, {"id": "disposal_dialog_disposaldialog_onconfirm", "label": ".onConfirm()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L22"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_disposal_dialog_disposal_dialog_js", "target": "owl", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_disposal_dialog_disposal_dialog_js", "target": "hooks", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L4", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_disposal_dialog_disposal_dialog_js", "target": "disposal_dialog_disposaldialog", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L6", "weight": 1.0}, {"source": "disposal_dialog_disposaldialog", "target": "disposal_dialog_disposaldialog_setup", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L13", "weight": 1.0}, {"source": "disposal_dialog_disposaldialog", "target": "disposal_dialog_disposaldialog_onconfirm", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L22", "weight": 1.0}], "raw_calls": [{"caller_nid": "disposal_dialog_disposaldialog_setup", "callee": "useService", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L14"}, {"caller_nid": "disposal_dialog_disposaldialog_setup", "callee": "useState", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L15"}, {"caller_nid": "disposal_dialog_disposaldialog_setup", "callee": "slice", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L18"}, {"caller_nid": "disposal_dialog_disposaldialog_setup", "callee": "toISOString", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L18"}, {"caller_nid": "disposal_dialog_disposaldialog_onconfirm", "callee": "disposeAsset", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L24"}, {"caller_nid": "disposal_dialog_disposaldialog_onconfirm", "callee": "parseFloat", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L26"}, {"caller_nid": "disposal_dialog_disposaldialog_onconfirm", "callee": "onClose", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/disposal_dialog/disposal_dialog.js", "source_location": "L29"}]}

View File

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

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_reports_migration_audit_report_py", "label": "migration_audit_report.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L1"}, {"id": "migration_audit_report_fusionassetsmigrationauditreport", "label": "FusionAssetsMigrationAuditReport", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L6"}, {"id": "migration_audit_report_get_report_values", "label": "_get_report_values()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L11"}, {"id": "migration_audit_report_rationale_1", "label": "QWeb PDF: migration audit report for fusion_accounting_assets.", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_reports_migration_audit_report_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_reports_migration_audit_report_py", "target": "migration_audit_report_fusionassetsmigrationauditreport", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L6", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_reports_migration_audit_report_py", "target": "migration_audit_report_get_report_values", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L11", "weight": 1.0}, {"source": "migration_audit_report_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_reports_migration_audit_report_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "migration_audit_report_get_report_values", "callee": "browse", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L12"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "search", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L15"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "search", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L16"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "sum", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L19"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "sum", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L20"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "sum", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L21"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "sum", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L22"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "append", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L23"}, {"caller_nid": "migration_audit_report_get_report_values", "callee": "len", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/reports/migration_audit_report.py", "source_location": "L25"}]}

View File

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

View File

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

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_depreciation_board_depreciation_board_js", "label": "depreciation_board.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.js", "source_location": "L1"}, {"id": "depreciation_board_depreciationboard", "label": "DepreciationBoard", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.js", "source_location": "L5"}, {"id": "depreciation_board_depreciationboard_rowclass", "label": ".rowClass()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.js", "source_location": "L12"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_depreciation_board_depreciation_board_js", "target": "owl", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.js", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_depreciation_board_depreciation_board_js", "target": "depreciation_board_depreciationboard", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.js", "source_location": "L5", "weight": 1.0}, {"source": "depreciation_board_depreciationboard", "target": "depreciation_board_depreciationboard_rowclass", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/depreciation_board/depreciation_board.js", "source_location": "L12", "weight": 1.0}], "raw_calls": []}

View File

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

View File

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

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_disposal_py", "label": "fusion_asset_disposal.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L1"}, {"id": "fusion_asset_disposal_fusionassetdisposal", "label": "FusionAssetDisposal", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L14"}, {"id": "fusion_asset_disposal_compute_gain_loss", "label": "_compute_gain_loss()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L51"}, {"id": "fusion_asset_disposal_rationale_1", "label": "Asset disposal records (sale, scrap, donation).", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_disposal_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_disposal_py", "target": "fusion_asset_disposal_fusionassetdisposal", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L14", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_disposal_py", "target": "fusion_asset_disposal_compute_gain_loss", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L51", "weight": 1.0}, {"source": "fusion_asset_disposal_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_disposal_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_disposal.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}

View File

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

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_category_py", "label": "fusion_asset_category.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L1"}, {"id": "fusion_asset_category_fusionassetcategory", "label": "FusionAssetCategory", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L6"}, {"id": "fusion_asset_category_fusionassetcategory_compute_asset_count", "label": "._compute_asset_count()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L49"}, {"id": "fusion_asset_category_rationale_1", "label": "Asset categories with default settings (used as templates).", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_category_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_category_py", "target": "fusion_asset_category_fusionassetcategory", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L6", "weight": 1.0}, {"source": "fusion_asset_category_fusionassetcategory", "target": "fusion_asset_category_fusionassetcategory_compute_asset_count", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L49", "weight": 1.0}, {"source": "fusion_asset_category_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_category_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "fusion_asset_category_fusionassetcategory_compute_asset_count", "callee": "search_count", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_category.py", "source_location": "L51"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_anomaly_strip_anomaly_strip_js", "label": "anomaly_strip.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js", "source_location": "L1"}, {"id": "anomaly_strip_anomalystrip", "label": "AnomalyStrip", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js", "source_location": "L5"}, {"id": "anomaly_strip_anomalystrip_formatnumber", "label": ".formatNumber()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js", "source_location": "L11"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_anomaly_strip_anomaly_strip_js", "target": "owl", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_anomaly_strip_anomaly_strip_js", "target": "anomaly_strip_anomalystrip", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js", "source_location": "L5", "weight": 1.0}, {"source": "anomaly_strip_anomalystrip", "target": "anomaly_strip_anomalystrip_formatnumber", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js", "source_location": "L11", "weight": 1.0}], "raw_calls": [{"caller_nid": "anomaly_strip_anomalystrip_formatnumber", "callee": "format", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/anomaly_strip/anomaly_strip.js", "source_location": "L13"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_depreciation_line_py", "label": "fusion_asset_depreciation_line.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L1"}, {"id": "fusion_asset_depreciation_line_fusionassetdepreciationline", "label": "FusionAssetDepreciationLine", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L6"}, {"id": "fusion_asset_depreciation_line_fusionassetdepreciationline_action_post", "label": ".action_post()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L28"}, {"id": "fusion_asset_depreciation_line_rationale_1", "label": "Per-period depreciation board lines for an asset.", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L1"}, {"id": "fusion_asset_depreciation_line_rationale_29", "label": "Mark this line as posted (without creating the journal entry yet \u2014 engin", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L29"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_depreciation_line_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_depreciation_line_py", "target": "fusion_asset_depreciation_line_fusionassetdepreciationline", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L6", "weight": 1.0}, {"source": "fusion_asset_depreciation_line_fusionassetdepreciationline", "target": "fusion_asset_depreciation_line_fusionassetdepreciationline_action_post", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L28", "weight": 1.0}, {"source": "fusion_asset_depreciation_line_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_depreciation_line_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L1", "weight": 1.0}, {"source": "fusion_asset_depreciation_line_rationale_29", "target": "fusion_asset_depreciation_line_fusionassetdepreciationline_action_post", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L29", "weight": 1.0}], "raw_calls": [{"caller_nid": "fusion_asset_depreciation_line_fusionassetdepreciationline_action_post", "callee": "write", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L34"}, {"caller_nid": "fusion_asset_depreciation_line_fusionassetdepreciationline_action_post", "callee": "today", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_depreciation_line.py", "source_location": "L36"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_useful_life_prompt_py", "label": "useful_life_prompt.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L1"}, {"id": "useful_life_prompt_build_prompt", "label": "build_prompt()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L38"}, {"id": "useful_life_prompt_rationale_1", "label": "LLM prompt builder for AI-suggested useful life from invoice description. Outpu", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L1"}, {"id": "useful_life_prompt_rationale_40", "label": "Return (system, user) prompt tuple.", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L40"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_useful_life_prompt_py", "target": "useful_life_prompt_build_prompt", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L38", "weight": 1.0}, {"source": "useful_life_prompt_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_useful_life_prompt_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L1", "weight": 1.0}, {"source": "useful_life_prompt_rationale_40", "target": "useful_life_prompt_build_prompt", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L40", "weight": 1.0}], "raw_calls": [{"caller_nid": "useful_life_prompt_build_prompt", "callee": "append", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L43"}, {"caller_nid": "useful_life_prompt_build_prompt", "callee": "append", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L45"}, {"caller_nid": "useful_life_prompt_build_prompt", "callee": "append", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L46"}, {"caller_nid": "useful_life_prompt_build_prompt", "callee": "append", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L47"}, {"caller_nid": "useful_life_prompt_build_prompt", "callee": "join", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/useful_life_prompt.py", "source_location": "L48"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_anomaly_py", "label": "fusion_asset_anomaly.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L1"}, {"id": "fusion_asset_anomaly_fusionassetanomaly", "label": "FusionAssetAnomaly", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L14"}, {"id": "fusion_asset_anomaly_fusionassetanomaly_action_acknowledge", "label": ".action_acknowledge()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L35"}, {"id": "fusion_asset_anomaly_fusionassetanomaly_action_dismiss", "label": ".action_dismiss()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L38"}, {"id": "fusion_asset_anomaly_fusionassetanomaly_action_resolve", "label": ".action_resolve()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L41"}, {"id": "fusion_asset_anomaly_rationale_1", "label": "Persisted asset anomaly flags from the engine's variance detection.", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_anomaly_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_anomaly_py", "target": "fusion_asset_anomaly_fusionassetanomaly", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L14", "weight": 1.0}, {"source": "fusion_asset_anomaly_fusionassetanomaly", "target": "fusion_asset_anomaly_fusionassetanomaly_action_acknowledge", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L35", "weight": 1.0}, {"source": "fusion_asset_anomaly_fusionassetanomaly", "target": "fusion_asset_anomaly_fusionassetanomaly_action_dismiss", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L38", "weight": 1.0}, {"source": "fusion_asset_anomaly_fusionassetanomaly", "target": "fusion_asset_anomaly_fusionassetanomaly_action_resolve", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L41", "weight": 1.0}, {"source": "fusion_asset_anomaly_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_models_fusion_asset_anomaly_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "fusion_asset_anomaly_fusionassetanomaly_action_acknowledge", "callee": "write", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L36"}, {"caller_nid": "fusion_asset_anomaly_fusionassetanomaly_action_dismiss", "callee": "write", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L39"}, {"caller_nid": "fusion_asset_anomaly_fusionassetanomaly_action_resolve", "callee": "write", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/models/fusion_asset_anomaly.py", "source_location": "L42"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_prorate_py", "label": "prorate.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L1"}, {"id": "prorate_prorate_factor", "label": "prorate_factor()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L17"}, {"id": "prorate_rationale_1", "label": "Prorating helpers for first-period and last-period depreciation. When an asset", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L1"}, {"id": "prorate_rationale_20", "label": "Return a 0..1 factor for how much of `period`'s depreciation applies to an a", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L20"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_prorate_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L10", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_prorate_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L11", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_prorate_py", "target": "prorate_prorate_factor", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L17", "weight": 1.0}, {"source": "prorate_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_services_prorate_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L1", "weight": 1.0}, {"source": "prorate_rationale_20", "target": "prorate_prorate_factor", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L20", "weight": 1.0}], "raw_calls": [{"caller_nid": "prorate_prorate_factor", "callee": "ValueError", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/services/prorate.py", "source_location": "L34"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_views_asset_dashboard_asset_dashboard_view_js", "label": "asset_dashboard_view.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard_view.js", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_views_asset_dashboard_asset_dashboard_view_js", "target": "registry", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard_view.js", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_views_asset_dashboard_asset_dashboard_view_js", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_views_asset_dashboard_asset_dashboard", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard_view.js", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_tests_test_audit_report_py", "label": "test_audit_report.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L1"}, {"id": "test_audit_report_testauditreport", "label": "TestAuditReport", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L6"}, {"id": "transactioncase", "label": "TransactionCase", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "test_audit_report_testauditreport_test_report_renders", "label": ".test_report_renders()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L8"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_tests_test_audit_report_py", "target": "odoo_tests_common", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_tests_test_audit_report_py", "target": "odoo_tests", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_tests_test_audit_report_py", "target": "test_audit_report_testauditreport", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L6", "weight": 1.0}, {"source": "test_audit_report_testauditreport", "target": "transactioncase", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L6", "weight": 1.0}, {"source": "test_audit_report_testauditreport", "target": "test_audit_report_testauditreport_test_report_renders", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L8", "weight": 1.0}], "raw_calls": [{"caller_nid": "test_audit_report_testauditreport_test_report_renders", "callee": "create", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L9"}, {"caller_nid": "test_audit_report_testauditreport_test_report_renders", "callee": "_render_qweb_pdf", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L11"}, {"caller_nid": "test_audit_report_testauditreport_test_report_renders", "callee": "sudo", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L11"}, {"caller_nid": "test_audit_report_testauditreport_test_report_renders", "callee": "assertGreater", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L16"}, {"caller_nid": "test_audit_report_testauditreport_test_report_renders", "callee": "len", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L16"}, {"caller_nid": "test_audit_report_testauditreport_test_report_renders", "callee": "skipTest", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/tests/test_audit_report.py", "source_location": "L18"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_tours_assets_tours_js", "label": "assets_tours.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/tours/assets_tours.js", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_tours_assets_tours_js", "target": "registry", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/tours/assets_tours.js", "source_location": "L3", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_asset_card_asset_card_js", "label": "asset_card.js", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/asset_card/asset_card.js", "source_location": "L1"}, {"id": "asset_card_assetcard", "label": "AssetCard", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/asset_card/asset_card.js", "source_location": "L5"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_asset_card_asset_card_js", "target": "owl", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/asset_card/asset_card.js", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_assets_static_src_components_asset_card_asset_card_js", "target": "asset_card_assetcard", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_assets/static/src/components/asset_card/asset_card.js", "source_location": "L5", "weight": 1.0}], "raw_calls": []}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
from . import fusion_asset_category
from . import fusion_asset
from . import fusion_asset_depreciation_line
from . import fusion_asset_disposal
from . import fusion_asset_anomaly
from . import account_move
from . import fusion_asset_engine
from . import fusion_assets_cron
from . import fusion_asset_book_values_mv
from . import fusion_migration_wizard

View File

@@ -0,0 +1,34 @@
"""Inherit account.move.line to link to fusion.asset records.
Lets us trace assets back to their source invoice line.
"""
from odoo import fields, models
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
fusion_asset_id = fields.Many2one(
'fusion.asset', string='Created Asset',
copy=False, ondelete='set null',
help="Fusion asset record created from this invoice line.",
)
fusion_asset_count = fields.Integer(compute='_compute_fusion_asset_count')
def _compute_fusion_asset_count(self):
for line in self:
line.fusion_asset_count = 1 if line.fusion_asset_id else 0
def action_open_fusion_asset(self):
self.ensure_one()
if not self.fusion_asset_id:
return
return {
'type': 'ir.actions.act_window',
'res_model': 'fusion.asset',
'res_id': self.fusion_asset_id.id,
'view_mode': 'form',
'target': 'current',
}

View File

@@ -0,0 +1,164 @@
"""Fusion Asset model.
Lifecycle: draft -> running -> (paused -> running)* -> disposed.
- draft: created, not yet running depreciation
- running: depreciation board active, periodic posts happen
- paused: depreciation suspended (e.g. asset out for repair)
- disposed: sold/scrapped/donated; no further depreciation
"""
import logging
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
METHOD_SELECTION = [
('straight_line', 'Straight Line'),
('declining_balance', 'Declining Balance'),
('units_of_production', 'Units of Production'),
]
PRORATE_SELECTION = [
('full_month', 'Full Month'),
('days_365', 'Days / 365'),
('days_period', 'Days in Period'),
]
STATE_SELECTION = [
('draft', 'Draft'),
('running', 'Running'),
('paused', 'Paused'),
('disposed', 'Disposed'),
]
class FusionAsset(models.Model):
_name = "fusion.asset"
_description = "Fusion Fixed Asset"
_order = "acquisition_date desc, id desc"
_inherit = ['mail.thread', 'mail.activity.mixin']
name = fields.Char(required=True, tracking=True)
code = fields.Char(help="Internal asset code (e.g. tag number).")
company_id = fields.Many2one(
'res.company', required=True,
default=lambda self: self.env.company,
)
category_id = fields.Many2one('fusion.asset.category', tracking=True)
state = fields.Selection(
STATE_SELECTION, default='draft', required=True, tracking=True,
)
cost = fields.Monetary(
required=True, tracking=True,
help="Original acquisition cost.",
)
salvage_value = fields.Monetary(
default=0.0, tracking=True,
help="Estimated end-of-life value.",
)
acquisition_date = fields.Date(
required=True, default=fields.Date.today, tracking=True,
)
in_service_date = fields.Date(
tracking=True,
help="Date depreciation actually begins.",
)
disposed_date = fields.Date(readonly=True, tracking=True)
currency_id = fields.Many2one(
'res.currency', required=True,
default=lambda self: self.env.company.currency_id,
)
method = fields.Selection(
METHOD_SELECTION, required=True, default='straight_line', tracking=True,
)
useful_life_years = fields.Integer(
default=5, tracking=True,
help="For straight_line / declining_balance.",
)
declining_rate_pct = fields.Float(
default=20.0,
help="For declining_balance method, e.g. 20.0 = 20%/year.",
)
total_units_expected = fields.Float(
help="For units_of_production method.",
)
units_used_to_date = fields.Float(
default=0.0,
help="For units_of_production: track usage.",
)
prorate_convention = fields.Selection(
PRORATE_SELECTION, default='days_period', required=True,
)
source_invoice_line_id = fields.Many2one(
'account.move.line', string='Source Invoice Line',
help="The invoice line that originated this asset.",
)
parent_id = fields.Many2one(
'fusion.asset', help='For partial-sale child assets.',
)
depreciation_line_ids = fields.One2many(
'fusion.asset.depreciation.line', 'asset_id',
string='Depreciation Lines',
)
book_value = fields.Monetary(compute='_compute_book_value', store=True)
total_depreciated = fields.Monetary(compute='_compute_book_value', store=True)
last_posted_date = fields.Date(compute='_compute_last_posted_date', store=True)
@api.depends('cost', 'depreciation_line_ids.amount', 'depreciation_line_ids.is_posted')
def _compute_book_value(self):
for asset in self:
posted = sum(l.amount for l in asset.depreciation_line_ids if l.is_posted)
asset.total_depreciated = posted
asset.book_value = asset.cost - posted
@api.depends('depreciation_line_ids.is_posted', 'depreciation_line_ids.scheduled_date')
def _compute_last_posted_date(self):
for asset in self:
posted_dates = [
l.scheduled_date for l in asset.depreciation_line_ids if l.is_posted
]
asset.last_posted_date = max(posted_dates) if posted_dates else False
def action_set_running(self):
for asset in self:
if asset.state != 'draft':
raise ValidationError(_("Only draft assets can be set running."))
if not asset.in_service_date:
asset.in_service_date = fields.Date.today()
asset.state = 'running'
def action_pause(self):
for asset in self:
if asset.state != 'running':
raise ValidationError(_("Only running assets can be paused."))
asset.state = 'paused'
def action_resume(self):
for asset in self:
if asset.state != 'paused':
raise ValidationError(_("Only paused assets can be resumed."))
asset.state = 'running'
def action_set_draft(self):
for asset in self:
if asset.state not in ('draft', 'paused'):
raise ValidationError(
_("Cannot reset to draft from %s.") % asset.state,
)
asset.state = 'draft'
_check_cost_positive = models.Constraint(
'CHECK(cost >= 0)',
'Asset cost must be non-negative.',
)
_check_salvage_lte_cost = models.Constraint(
'CHECK(salvage_value >= 0 AND salvage_value <= cost)',
'Salvage value must be between 0 and cost.',
)

View File

@@ -0,0 +1,42 @@
"""Persisted asset anomaly flags from the engine's variance detection."""
from odoo import fields, models
SEVERITY = [('low', 'Low'), ('medium', 'Medium'), ('high', 'High')]
ANOMALY_TYPES = [
('behind_schedule', 'Behind Schedule'),
('ahead_of_schedule', 'Ahead of Schedule'),
('low_utilization', 'Low Utilization'),
]
class FusionAssetAnomaly(models.Model):
_name = "fusion.asset.anomaly"
_description = "Flagged Asset Anomaly"
_order = "detected_at desc, severity desc"
asset_id = fields.Many2one('fusion.asset', required=True, ondelete='cascade')
company_id = fields.Many2one(related='asset_id.company_id', store=True)
anomaly_type = fields.Selection(ANOMALY_TYPES, required=True)
severity = fields.Selection(SEVERITY, required=True)
expected = fields.Float()
actual = fields.Float()
variance_pct = fields.Float()
detail = fields.Text()
detected_at = fields.Datetime(default=fields.Datetime.now, required=True)
state = fields.Selection([
('new', 'New'),
('acknowledged', 'Acknowledged'),
('resolved', 'Resolved'),
('dismissed', 'Dismissed'),
], default='new', required=True)
def action_acknowledge(self):
self.write({'state': 'acknowledged'})
def action_dismiss(self):
self.write({'state': 'dismissed'})
def action_resolve(self):
self.write({'state': 'resolved'})

View File

@@ -0,0 +1,59 @@
"""MV of per-asset book value snapshot. Refresh via cron or model._refresh()."""
import logging
import os
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class FusionAssetBookValuesMV(models.Model):
_name = "fusion.asset.book.values.mv"
_description = "MV of asset book value snapshot"
_auto = False
_table = "fusion_asset_book_values_mv"
_order = "book_value desc"
asset_id = fields.Many2one('fusion.asset', readonly=True)
company_id = fields.Many2one('res.company', readonly=True)
category_id = fields.Many2one('fusion.asset.category', readonly=True)
state = fields.Char(readonly=True)
cost = fields.Float(readonly=True)
salvage_value = fields.Float(readonly=True)
total_depreciated = fields.Float(readonly=True)
book_value = fields.Float(readonly=True)
posted_periods = fields.Integer(readonly=True)
pending_periods = fields.Integer(readonly=True)
acquisition_date = fields.Date(readonly=True)
in_service_date = fields.Date(readonly=True)
def init(self):
sql_path = os.path.join(
os.path.dirname(__file__), '..', 'data', 'sql',
'create_mv_asset_book_values.sql',
)
with open(sql_path, 'r') as f:
self.env.cr.execute(f.read())
_logger.info("fusion_asset_book_values_mv: created/verified MV")
@api.model
def _refresh(self, *, concurrently=True):
# CONCURRENTLY requires a unique index (we have one) and that the MV
# has been populated at least once. Wrap the concurrent attempt in a
# savepoint so a failure (e.g. first-ever refresh before the MV is
# populated) does NOT poison the surrounding transaction; we then
# fall back to a plain REFRESH.
if concurrently:
try:
with self.env.cr.savepoint():
self.env.cr.execute(
"REFRESH MATERIALIZED VIEW CONCURRENTLY "
"fusion_asset_book_values_mv"
)
return
except Exception as e: # noqa: BLE001
_logger.warning("Concurrent MV refresh failed (%s); fallback", e)
self.env.cr.execute(
"REFRESH MATERIALIZED VIEW fusion_asset_book_values_mv"
)

View File

@@ -0,0 +1,53 @@
"""Asset categories with default settings (used as templates)."""
from odoo import api, fields, models
class FusionAssetCategory(models.Model):
_name = "fusion.asset.category"
_description = "Fusion Asset Category"
_order = "sequence, name"
name = fields.Char(required=True, translate=True)
sequence = fields.Integer(default=10)
company_id = fields.Many2one(
'res.company', default=lambda self: self.env.company,
)
method = fields.Selection([
('straight_line', 'Straight Line'),
('declining_balance', 'Declining Balance'),
('units_of_production', 'Units of Production'),
], default='straight_line', required=True)
useful_life_years = fields.Integer(default=5)
declining_rate_pct = fields.Float(default=20.0)
salvage_value_pct = fields.Float(
default=0.0,
help="% of cost (used for new assets in this category).",
)
prorate_convention = fields.Selection([
('full_month', 'Full Month'),
('days_365', 'Days / 365'),
('days_period', 'Days in Period'),
], default='days_period', required=True)
asset_account_id = fields.Many2one(
'account.account', string='Asset Account',
domain="[('account_type', 'in', ('asset_fixed', 'asset_non_current'))]",
)
depreciation_account_id = fields.Many2one(
'account.account', string='Depreciation Account',
domain="[('account_type', '=', 'asset_fixed')]",
)
expense_account_id = fields.Many2one(
'account.account', string='Expense Account',
domain="[('account_type', '=', 'expense_depreciation')]",
)
asset_count = fields.Integer(compute='_compute_asset_count')
def _compute_asset_count(self):
for cat in self:
cat.asset_count = self.env['fusion.asset'].search_count([
('category_id', '=', cat.id),
])

View File

@@ -0,0 +1,42 @@
"""Per-period depreciation board lines for an asset."""
from odoo import fields, models
class FusionAssetDepreciationLine(models.Model):
_name = "fusion.asset.depreciation.line"
_description = "Asset Depreciation Board Line"
_order = "asset_id, scheduled_date"
asset_id = fields.Many2one('fusion.asset', required=True, ondelete='cascade')
company_id = fields.Many2one(related='asset_id.company_id', store=True)
currency_id = fields.Many2one(related='asset_id.currency_id', store=True)
period_index = fields.Integer(required=True)
scheduled_date = fields.Date(required=True)
amount = fields.Monetary(required=True)
accumulated = fields.Monetary()
book_value_at_end = fields.Monetary()
is_posted = fields.Boolean(default=False, copy=False)
posted_date = fields.Date(copy=False)
move_id = fields.Many2one(
'account.move', copy=False,
help="Journal entry created when this line was posted.",
)
def action_post(self):
"""Mark this line as posted (without creating the journal entry yet —
engine method post_depreciation_entry handles the actual entry creation)."""
for line in self:
if line.is_posted:
continue
line.write({
'is_posted': True,
'posted_date': fields.Date.today(),
})
_unique_period_per_asset = models.Constraint(
'UNIQUE(asset_id, period_index)',
'A depreciation line for that period already exists.',
)

View File

@@ -0,0 +1,56 @@
"""Asset disposal records (sale, scrap, donation)."""
from odoo import api, fields, models
DISPOSAL_TYPES = [
('sale', 'Sale'),
('scrap', 'Scrap'),
('donation', 'Donation'),
('lost', 'Lost / Stolen'),
]
class FusionAssetDisposal(models.Model):
_name = "fusion.asset.disposal"
_description = "Asset Disposal Record"
_order = "disposal_date desc, id desc"
_inherit = ['mail.thread']
asset_id = fields.Many2one(
'fusion.asset', required=True, ondelete='restrict', tracking=True,
)
company_id = fields.Many2one(related='asset_id.company_id', store=True)
currency_id = fields.Many2one(related='asset_id.currency_id', store=True)
disposal_type = fields.Selection(
DISPOSAL_TYPES, required=True, default='sale', tracking=True,
)
disposal_date = fields.Date(
required=True, default=fields.Date.today, tracking=True,
)
sale_amount = fields.Monetary(
default=0.0, tracking=True,
help="Cash received (for sale disposal type).",
)
sale_partner_id = fields.Many2one('res.partner', tracking=True)
book_value_at_disposal = fields.Monetary(
readonly=True,
help="Asset book value at disposal date.",
)
gain_loss_amount = fields.Monetary(compute='_compute_gain_loss', store=True)
notes = fields.Text()
move_id = fields.Many2one(
'account.move', readonly=True, copy=False,
help="Journal entry created for this disposal.",
)
@api.depends('sale_amount', 'book_value_at_disposal', 'disposal_type')
def _compute_gain_loss(self):
for d in self:
if d.disposal_type == 'sale':
d.gain_loss_amount = d.sale_amount - d.book_value_at_disposal
else:
d.gain_loss_amount = -d.book_value_at_disposal

View File

@@ -0,0 +1,398 @@
"""The asset engine — orchestrator for all asset depreciation + lifecycle.
7-method public API. No direct ORM writes to fusion.asset.depreciation.line
or account.move from anywhere else; everything routes through here for
consistent validation, audit, and side-effect handling.
"""
import logging
from datetime import date, timedelta
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from ..services.depreciation_methods import (
straight_line,
declining_balance,
units_of_production,
)
_logger = logging.getLogger(__name__)
class FusionAssetEngine(models.AbstractModel):
_name = "fusion.asset.engine"
_description = "Fusion Asset Engine"
# ============================================================
# PUBLIC API (7 methods)
# ============================================================
@api.model
def compute_depreciation_schedule(self, asset, *, recompute: bool = False) -> dict:
"""Compute (or re-compute) the depreciation board for an asset.
If recompute=False and posted lines exist, ONLY un-posted future lines
are regenerated. If recompute=True, all unposted lines are wiped and
regenerated from scratch using current asset config.
"""
if not asset:
raise ValidationError(_("asset is required"))
asset.ensure_one()
self._validate_asset_for_schedule(asset)
Line = self.env['fusion.asset.depreciation.line'].sudo()
if recompute:
Line.search([
('asset_id', '=', asset.id),
('is_posted', '=', False),
]).unlink()
existing_posted = Line.search([
('asset_id', '=', asset.id),
('is_posted', '=', True),
], order='period_index')
start_period = max([l.period_index for l in existing_posted], default=-1) + 1
accumulated_so_far = sum(l.amount for l in existing_posted)
steps = self._compute_steps(asset)
new_steps = steps[start_period:]
base_date = asset.in_service_date or asset.acquisition_date
# Accumulated baseline at the boundary between posted and to-be-created
# lines: subtract the accumulated value the algorithm itself reports at
# that boundary, then re-add the actually-posted total. This keeps the
# board's accumulated column monotonic when picking up mid-life.
baseline_offset = 0.0
if start_period > 0 and start_period <= len(steps):
baseline_offset = steps[start_period - 1].accumulated_depreciation
line_vals = []
for s in new_steps:
scheduled_date = self._add_periods(base_date, s.period_index)
running_accumulated = round(
accumulated_so_far + s.accumulated_depreciation - baseline_offset, 2
)
line_vals.append({
'asset_id': asset.id,
'period_index': s.period_index,
'scheduled_date': scheduled_date,
'amount': s.period_amount,
'accumulated': running_accumulated,
'book_value_at_end': s.book_value_at_end,
'is_posted': False,
})
if line_vals:
Line.create(line_vals)
return {
'asset_id': asset.id,
'lines_created': len(line_vals),
'total_lines': len(asset.depreciation_line_ids),
'method': asset.method,
}
@api.model
def post_depreciation_entry(self, asset, *, period_date: date = None) -> dict:
"""Post the next-due un-posted depreciation line.
If period_date provided, post all lines whose scheduled_date <= period_date.
Otherwise, post the single next un-posted line (the earliest one).
"""
asset.ensure_one()
if asset.state != 'running':
raise ValidationError(
_("Cannot post depreciation for asset in state %s") % asset.state
)
Line = self.env['fusion.asset.depreciation.line'].sudo()
domain = [('asset_id', '=', asset.id), ('is_posted', '=', False)]
if period_date:
domain.append(('scheduled_date', '<=', period_date))
unposted = Line.search(domain, order='scheduled_date, period_index')
if not unposted:
return {'posted_count': 0, 'reason': 'no unposted lines due'}
if not period_date:
unposted = unposted[:1]
posted_ids = []
for line in unposted:
self._create_journal_entry(asset, line)
line.action_post()
posted_ids.append(line.id)
return {'posted_count': len(posted_ids), 'posted_line_ids': posted_ids}
@api.model
def dispose_asset(self, asset, *, sale_amount: float = 0.0,
sale_date: date = None, sale_partner=None,
disposal_type: str = 'sale') -> dict:
"""Dispose an asset (sale, scrap, donation, lost)."""
asset.ensure_one()
if asset.state == 'disposed':
raise ValidationError(_("Asset already disposed."))
sale_date = sale_date or fields.Date.today()
Line = self.env['fusion.asset.depreciation.line'].sudo()
future_unposted = Line.search([
('asset_id', '=', asset.id),
('is_posted', '=', False),
('scheduled_date', '>', sale_date),
])
future_unposted.unlink()
asset.invalidate_recordset(['book_value', 'total_depreciated'])
book_value = asset.book_value
Disposal = self.env['fusion.asset.disposal'].sudo()
partner_id = False
if sale_partner:
partner_id = sale_partner.id if hasattr(sale_partner, 'id') else sale_partner
disposal = Disposal.create({
'asset_id': asset.id,
'disposal_type': disposal_type,
'disposal_date': sale_date,
'sale_amount': sale_amount,
'sale_partner_id': partner_id,
'book_value_at_disposal': book_value,
})
asset.write({
'state': 'disposed',
'disposed_date': sale_date,
})
return {
'asset_id': asset.id,
'disposal_id': disposal.id,
'gain_loss_amount': disposal.gain_loss_amount,
'book_value_at_disposal': book_value,
}
@api.model
def partial_sale(self, asset, *, sold_amount: float, sold_qty: float = None,
sale_date: date = None, sale_partner=None) -> dict:
"""Partially dispose: split asset into two — sold child + remaining parent.
sold_amount is cash received for the sold portion.
sold_qty is the ratio of original cost to attribute to the sold portion (0..1).
If sold_qty is None, defaults to sold_amount / cost.
"""
asset.ensure_one()
if asset.state == 'disposed':
raise ValidationError(_("Cannot partially sell a disposed asset."))
if sold_qty is None:
sold_qty = sold_amount / asset.cost if asset.cost else 0
if not (0 < sold_qty < 1):
raise ValidationError(
_("sold_qty must be strictly between 0 and 1; got %s") % sold_qty
)
sale_date = sale_date or fields.Date.today()
Asset = self.env['fusion.asset'].sudo()
sold_cost = round(asset.cost * sold_qty, 2)
sold_salvage = round(asset.salvage_value * sold_qty, 2)
child_vals = {
'name': f"{asset.name} (sold portion)",
'parent_id': asset.id,
'cost': sold_cost,
'salvage_value': sold_salvage,
'acquisition_date': asset.acquisition_date,
'in_service_date': asset.in_service_date,
'method': asset.method,
'useful_life_years': asset.useful_life_years,
'declining_rate_pct': asset.declining_rate_pct,
'prorate_convention': asset.prorate_convention,
'company_id': asset.company_id.id,
'state': 'running',
}
if asset.category_id:
child_vals['category_id'] = asset.category_id.id
child = Asset.create(child_vals)
new_cost = round(asset.cost - sold_cost, 2)
new_salvage = round(asset.salvage_value - sold_salvage, 2)
asset.write({
'cost': new_cost,
'salvage_value': new_salvage,
})
self.compute_depreciation_schedule(asset, recompute=True)
result = self.dispose_asset(
child, sale_amount=sold_amount, sale_date=sale_date,
sale_partner=sale_partner, disposal_type='sale',
)
return {
'parent_asset_id': asset.id,
'child_asset_id': child.id,
'disposal_id': result['disposal_id'],
'gain_loss_amount': result['gain_loss_amount'],
}
@api.model
def pause_asset(self, asset, pause_date: date = None) -> dict:
"""Pause depreciation. Wraps asset.action_pause for API symmetry and
to log the pause date for downstream auditing."""
asset.ensure_one()
asset.action_pause()
return {
'asset_id': asset.id,
'pause_date': pause_date or fields.Date.today(),
'state': 'paused',
}
@api.model
def resume_asset(self, asset, resume_date: date = None) -> dict:
"""Resume a paused asset."""
asset.ensure_one()
asset.action_resume()
return {
'asset_id': asset.id,
'resume_date': resume_date or fields.Date.today(),
'state': 'running',
}
@api.model
def reverse_disposal(self, asset) -> dict:
"""Reverse a disposal (rare — recovery from accidental sale entry)."""
asset.ensure_one()
if asset.state != 'disposed':
raise ValidationError(_("Asset is not disposed."))
Disposal = self.env['fusion.asset.disposal'].sudo()
last_disposal = Disposal.search(
[('asset_id', '=', asset.id)],
order='disposal_date desc, id desc', limit=1,
)
if last_disposal and last_disposal.move_id:
try:
last_disposal.move_id.button_cancel()
except Exception as e: # noqa: BLE001
_logger.warning("Could not cancel disposal move: %s", e)
if last_disposal:
last_disposal.unlink()
asset.write({'state': 'running', 'disposed_date': False})
return {'asset_id': asset.id, 'state': 'running'}
# ============================================================
# PRIVATE HELPERS
# ============================================================
def _validate_asset_for_schedule(self, asset):
if asset.cost <= 0:
raise ValidationError(_("Asset cost must be > 0 to compute schedule."))
if asset.method == 'units_of_production' and not asset.total_units_expected:
raise ValidationError(_(
"Units of Production assets need total_units_expected set."
))
if asset.method in ('straight_line', 'declining_balance'):
if asset.useful_life_years < 1:
raise ValidationError(_("useful_life_years must be >= 1."))
if asset.salvage_value > asset.cost:
raise ValidationError(_("Salvage value cannot exceed cost."))
def _compute_steps(self, asset) -> list:
"""Dispatch to the appropriate depreciation method service."""
if asset.method == 'straight_line':
return straight_line(
cost=asset.cost,
salvage_value=asset.salvage_value,
n_periods=asset.useful_life_years,
)
if asset.method == 'declining_balance':
return declining_balance(
cost=asset.cost,
salvage_value=asset.salvage_value,
n_periods=asset.useful_life_years,
rate=asset.declining_rate_pct / 100.0,
)
if asset.method == 'units_of_production':
# Phase 3 simple: assume even per-period units. Phase 3.5 can read
# from a per-period usage table populated by maintenance/IoT data.
if asset.useful_life_years:
per_period = asset.total_units_expected / asset.useful_life_years
periods = asset.useful_life_years
else:
per_period = asset.total_units_expected
periods = 1
return units_of_production(
cost=asset.cost,
salvage_value=asset.salvage_value,
total_units_expected=asset.total_units_expected,
units_per_period=[per_period] * periods,
)
return []
def _add_periods(self, base_date: date, n_periods: int) -> date:
"""Add (n_periods + 1) yearly increments to base_date and step back one
day, giving the period-end date.
Phase 3.5 can split this into monthly/quarterly variants when the asset
carries a sub-annual frequency.
"""
try:
return base_date.replace(year=base_date.year + n_periods + 1) - timedelta(days=1)
except ValueError:
return base_date.replace(
year=base_date.year + n_periods + 1, day=28,
) - timedelta(days=1)
def _create_journal_entry(self, asset, line):
"""Create the journal entry for a depreciation line.
Phase 3 keeps this minimal: requires the category to have both
depreciation_account_id and expense_account_id wired up. Without that,
the line is still posted (is_posted flag) but no move is created.
Phase 3.5 will add multi-currency, allocation rules, and analytic tags.
"""
category = asset.category_id
if not category or not (category.depreciation_account_id and category.expense_account_id):
_logger.debug(
"No accounts on category for asset %s; skipping journal entry",
asset.id,
)
return None
Move = self.env['account.move'].sudo()
journal = self.env['account.journal'].search([
('type', '=', 'general'),
('company_id', '=', asset.company_id.id),
], limit=1)
if not journal:
_logger.warning(
"No general journal for company %s; skipping move creation",
asset.company_id.name,
)
return None
try:
move = Move.create({
'date': line.scheduled_date,
'journal_id': journal.id,
'ref': f"Depreciation: {asset.name} (P{line.period_index + 1})",
'line_ids': [
(0, 0, {
'name': f"Depreciation expense - {asset.name}",
'account_id': category.expense_account_id.id,
'debit': line.amount,
'credit': 0,
}),
(0, 0, {
'name': f"Accumulated depreciation - {asset.name}",
'account_id': category.depreciation_account_id.id,
'debit': 0,
'credit': line.amount,
}),
],
})
move.action_post()
line.write({'move_id': move.id})
return move
except Exception as e: # noqa: BLE001
_logger.warning(
"Failed to create depreciation move for asset %s line %s: %s",
asset.id, line.id, e,
)
return None

View File

@@ -0,0 +1,96 @@
"""Cron handlers for fusion_accounting_assets.
- _cron_post_due_depreciation: daily, post due depreciation lines for running assets
- _cron_anomaly_scan: monthly, scan for schedule variance and create anomaly records
"""
import logging
from odoo import api, fields, models
from ..services.anomaly_detection import detect_schedule_variance
_logger = logging.getLogger(__name__)
class FusionAssetsCron(models.AbstractModel):
_name = "fusion.assets.cron"
_description = "Fusion Assets Cron Handlers"
@api.model
def _cron_post_due_depreciation(self):
"""For each running asset, post any due un-posted depreciation lines."""
today = fields.Date.today()
engine = self.env['fusion.asset.engine']
Asset = self.env['fusion.asset']
running_assets = Asset.search([('state', '=', 'running')])
posted_total = 0
for asset in running_assets:
try:
with self.env.cr.savepoint():
result = engine.post_depreciation_entry(asset, period_date=today)
posted_total += result.get('posted_count', 0)
except Exception as e: # noqa: BLE001
_logger.warning("Cron post failed for asset %s: %s", asset.id, e)
_logger.info(
"Cron: posted depreciation on %d lines across %d running assets",
posted_total, len(running_assets),
)
# Keep the book-value MV in sync after posting so the dashboard
# reflects today's numbers without waiting for the dedicated MV cron.
try:
self.env['fusion.asset.book.values.mv']._refresh()
except Exception as e: # noqa: BLE001
_logger.warning("Post-cron MV refresh failed: %s", e)
@api.model
def _cron_refresh_book_values_mv(self):
"""Refresh the per-asset book value MV (hourly)."""
self.env['fusion.asset.book.values.mv']._refresh()
@api.model
def _cron_anomaly_scan(self):
"""For each running asset, compare expected accumulated depreciation
vs posted, and persist any variance flags."""
Asset = self.env['fusion.asset']
Anomaly = self.env['fusion.asset.anomaly']
running_assets = Asset.search([('state', '=', 'running')])
flagged = 0
today = fields.Date.today()
for asset in running_assets:
try:
expected = sum(
l.amount for l in asset.depreciation_line_ids
if l.scheduled_date and l.scheduled_date <= today
)
actual = asset.total_depreciated
anomaly = detect_schedule_variance(
asset_id=asset.id, asset_name=asset.name,
expected_accumulated=expected, actual_accumulated=actual,
)
if anomaly is None:
continue
anomaly_dict = anomaly.to_dict()
existing = Anomaly.search([
('asset_id', '=', asset.id),
('anomaly_type', '=', anomaly_dict['anomaly_type']),
('state', 'in', ('new', 'acknowledged')),
], limit=1)
if existing:
continue
Anomaly.create({
'asset_id': asset.id,
'anomaly_type': anomaly_dict['anomaly_type'],
'severity': anomaly_dict['severity'],
'expected': anomaly_dict['expected'],
'actual': anomaly_dict['actual'],
'variance_pct': anomaly_dict['variance_pct'],
'detail': anomaly_dict['detail'],
})
flagged += 1
except Exception as e: # noqa: BLE001
_logger.warning("Cron anomaly scan failed for asset %s: %s", asset.id, e)
_logger.info(
"Cron: scanned %d assets, flagged %d anomalies",
len(running_assets), flagged,
)

View File

@@ -0,0 +1,105 @@
"""Assets-specific migration step.
Backfills fusion.asset from existing account.asset rows (Enterprise) so users
get all their existing assets in the Fusion namespace after switchover."""
import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
# Map Enterprise method names to Fusion method names
ENTERPRISE_METHOD_MAP = {
'linear': 'straight_line',
'degressive': 'declining_balance',
'degressive_then_linear': 'declining_balance', # simplified
'manual': 'straight_line',
'unit_of_production': 'units_of_production',
'units_of_production': 'units_of_production',
}
class FusionMigrationWizard(models.TransientModel):
_inherit = "fusion.migration.wizard"
def _assets_bootstrap_step(self):
"""Backfill fusion.asset from account.asset (Enterprise) if it exists."""
result = {
'step': 'assets_bootstrap',
'enterprise_module_present': False,
'created': 0, 'skipped': 0, 'errors': [],
}
# Check if Enterprise account.asset exists
AccountAsset = self.env.get('account.asset')
if AccountAsset is None:
result['enterprise_module_present'] = False
return result
result['enterprise_module_present'] = True
FusionAsset = self.env['fusion.asset'].sudo()
# Iterate Enterprise records
company_id = self.company_id.id if 'company_id' in self._fields and self.company_id else None
domain = []
if company_id:
domain.append(('company_id', '=', company_id))
try:
ea_records = AccountAsset.sudo().search(domain, limit=10000)
except Exception as e:
result['errors'].append(f"Enterprise search failed: {e}")
return result
for ea in ea_records:
try:
# Idempotent: skip if a fusion asset with same source name exists
existing = FusionAsset.search([
('name', '=', ea.name),
('cost', '=', getattr(ea, 'original_value', 0) or 0),
('company_id', '=', ea.company_id.id),
], limit=1)
if existing:
result['skipped'] += 1
continue
# Map state — Enterprise has 'draft', 'open' (running), 'paused', 'close' (disposed)
ea_state = getattr(ea, 'state', 'draft')
state_map = {'draft': 'draft', 'open': 'running',
'paused': 'paused', 'close': 'disposed',
'model': 'draft'}
state = state_map.get(ea_state, 'draft')
method = ENTERPRISE_METHOD_MAP.get(
getattr(ea, 'method', 'linear'), 'straight_line')
FusionAsset.create({
'name': ea.name,
'cost': getattr(ea, 'original_value', 0) or 0,
'salvage_value': getattr(ea, 'salvage_value', 0) or 0,
'acquisition_date': getattr(ea, 'acquisition_date', False) or fields.Date.today(),
'in_service_date': getattr(ea, 'prorata_date', False) or False,
'method': method,
'useful_life_years': getattr(ea, 'method_number', 5) or 5,
'declining_rate_pct': getattr(ea, 'method_progress_factor', 0.2) * 100 if hasattr(ea, 'method_progress_factor') else 20.0,
'company_id': ea.company_id.id,
'state': state,
})
result['created'] += 1
except Exception as e:
result['errors'].append(f"Asset {ea.id}: {e}")
_logger.info(
"fusion_accounting_assets migration: %d created, %d skipped, %d errors",
result['created'], result['skipped'], len(result['errors']))
return result
def action_run_migration(self):
"""Override to add assets-bootstrap step."""
result = super().action_run_migration() if hasattr(super(), 'action_run_migration') else None
try:
self._assets_bootstrap_step()
except Exception as e:
_logger.warning("assets_bootstrap_step failed: %s", e)
return result

View File

@@ -0,0 +1 @@
from . import migration_audit_report

View File

@@ -0,0 +1,36 @@
"""QWeb PDF: migration audit report for fusion_accounting_assets."""
from odoo import api, models
class FusionAssetsMigrationAuditReport(models.AbstractModel):
_name = "report.fusion_accounting_assets.migration_audit_template"
_description = "Fusion Assets Migration Audit"
@api.model
def _get_report_values(self, docids, data=None):
wizards = self.env['fusion.migration.wizard'].browse(docids) if docids else self.env['fusion.migration.wizard']
Asset = self.env['fusion.asset']
company_stats = []
for company in self.env['res.company'].search([]):
assets = Asset.search([('company_id', '=', company.id)])
by_state = {}
for state in ('draft', 'running', 'paused', 'disposed'):
by_state[state] = sum(1 for a in assets if a.state == state)
total_cost = sum(a.cost for a in assets)
total_book = sum(a.book_value for a in assets)
total_dep = sum(a.total_depreciated for a in assets)
company_stats.append({
'company': company,
'count': len(assets),
'by_state': by_state,
'total_cost': total_cost,
'total_book_value': total_book,
'total_depreciated': total_dep,
})
return {
'doc_ids': docids,
'doc_model': 'fusion.migration.wizard',
'docs': wizards,
'company_stats': company_stats,
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_report_assets_migration_audit" model="ir.actions.report">
<field name="name">Assets Migration Audit</field>
<field name="model">fusion.migration.wizard</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_accounting_assets.migration_audit_template</field>
<field name="report_file">fusion_accounting_assets.migration_audit_template</field>
<field name="binding_model_id" ref="fusion_accounting_migration.model_fusion_migration_wizard"/>
</record>
</odoo>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="migration_audit_template">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div class="page">
<h2>Fusion Assets Migration Audit</h2>
<p>
<span t-esc="context_timestamp(datetime.datetime.now()).strftime('%Y-%m-%d %H:%M')"/>
</p>
<h3>Per-Company Summary</h3>
<table class="table table-sm">
<thead>
<tr>
<th>Company</th>
<th class="text-end">Total Assets</th>
<th class="text-end">Draft</th>
<th class="text-end">Running</th>
<th class="text-end">Paused</th>
<th class="text-end">Disposed</th>
<th class="text-end">Total Cost</th>
<th class="text-end">Total NBV</th>
<th class="text-end">Total Depreciated</th>
</tr>
</thead>
<tbody>
<tr t-foreach="company_stats" t-as="cs">
<td><span t-field="cs['company'].name"/></td>
<td class="text-end"><span t-esc="cs['count']"/></td>
<td class="text-end"><span t-esc="cs['by_state']['draft']"/></td>
<td class="text-end"><span t-esc="cs['by_state']['running']"/></td>
<td class="text-end"><span t-esc="cs['by_state']['paused']"/></td>
<td class="text-end"><span t-esc="cs['by_state']['disposed']"/></td>
<td class="text-end"><span t-esc="'{:,.2f}'.format(cs['total_cost'])"/></td>
<td class="text-end"><span t-esc="'{:,.2f}'.format(cs['total_book_value'])"/></td>
<td class="text-end"><span t-esc="'{:,.2f}'.format(cs['total_depreciated'])"/></td>
</tr>
</tbody>
</table>
<p class="text-muted small">
Generated by Fusion Accounting Assets
</p>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,15 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fusion_asset_user,fusion.asset.user,model_fusion_asset,base.group_user,1,0,0,0
access_fusion_asset_admin,fusion.asset.admin,model_fusion_asset,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
access_fusion_asset_depreciation_line_user,fusion.asset.depreciation.line.user,model_fusion_asset_depreciation_line,base.group_user,1,0,0,0
access_fusion_asset_depreciation_line_admin,fusion.asset.depreciation.line.admin,model_fusion_asset_depreciation_line,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
access_fusion_asset_category_user,fusion.asset.category.user,model_fusion_asset_category,base.group_user,1,0,0,0
access_fusion_asset_category_admin,fusion.asset.category.admin,model_fusion_asset_category,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
access_fusion_asset_disposal_user,fusion.asset.disposal.user,model_fusion_asset_disposal,base.group_user,1,0,0,0
access_fusion_asset_disposal_admin,fusion.asset.disposal.admin,model_fusion_asset_disposal,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
access_fusion_asset_anomaly_user,fusion.asset.anomaly.user,model_fusion_asset_anomaly,base.group_user,1,0,0,0
access_fusion_asset_anomaly_admin,fusion.asset.anomaly.admin,model_fusion_asset_anomaly,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
access_fusion_create_asset_wizard_user,fusion.create.asset.wizard.user,model_fusion_create_asset_wizard,base.group_user,1,1,1,0
access_fusion_disposal_wizard_user,fusion.disposal.wizard.user,model_fusion_disposal_wizard,base.group_user,1,1,1,0
access_fusion_partial_sale_wizard_user,fusion.partial.sale.wizard.user,model_fusion_partial_sale_wizard,base.group_user,1,1,1,0
access_fusion_depreciation_run_wizard_user,fusion.depreciation.run.wizard.user,model_fusion_depreciation_run_wizard,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fusion_asset_user fusion.asset.user model_fusion_asset base.group_user 1 0 0 0
3 access_fusion_asset_admin fusion.asset.admin model_fusion_asset fusion_accounting_core.group_fusion_accounting_admin 1 1 1 1
4 access_fusion_asset_depreciation_line_user fusion.asset.depreciation.line.user model_fusion_asset_depreciation_line base.group_user 1 0 0 0
5 access_fusion_asset_depreciation_line_admin fusion.asset.depreciation.line.admin model_fusion_asset_depreciation_line fusion_accounting_core.group_fusion_accounting_admin 1 1 1 1
6 access_fusion_asset_category_user fusion.asset.category.user model_fusion_asset_category base.group_user 1 0 0 0
7 access_fusion_asset_category_admin fusion.asset.category.admin model_fusion_asset_category fusion_accounting_core.group_fusion_accounting_admin 1 1 1 1
8 access_fusion_asset_disposal_user fusion.asset.disposal.user model_fusion_asset_disposal base.group_user 1 0 0 0
9 access_fusion_asset_disposal_admin fusion.asset.disposal.admin model_fusion_asset_disposal fusion_accounting_core.group_fusion_accounting_admin 1 1 1 1
10 access_fusion_asset_anomaly_user fusion.asset.anomaly.user model_fusion_asset_anomaly base.group_user 1 0 0 0
11 access_fusion_asset_anomaly_admin fusion.asset.anomaly.admin model_fusion_asset_anomaly fusion_accounting_core.group_fusion_accounting_admin 1 1 1 1
12 access_fusion_create_asset_wizard_user fusion.create.asset.wizard.user model_fusion_create_asset_wizard base.group_user 1 1 1 0
13 access_fusion_disposal_wizard_user fusion.disposal.wizard.user model_fusion_disposal_wizard base.group_user 1 1 1 0
14 access_fusion_partial_sale_wizard_user fusion.partial.sale.wizard.user model_fusion_partial_sale_wizard base.group_user 1 1 1 0
15 access_fusion_depreciation_run_wizard_user fusion.depreciation.run.wizard.user model_fusion_depreciation_run_wizard base.group_user 1 1 1 0

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