mirror of
https://github.com/bringout/oca-ocb-report.git
synced 2026-04-18 01:42:02 +02:00
Initial commit: Report packages
This commit is contained in:
commit
bc5e1e9efa
604 changed files with 474102 additions and 0 deletions
12
README.md
Normal file
12
README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Report
|
||||||
|
|
||||||
|
This repository contains OCA OCB packages for report.
|
||||||
|
|
||||||
|
## Packages Included
|
||||||
|
|
||||||
|
- odoo-bringout-oca-ocb-spreadsheet
|
||||||
|
- odoo-bringout-oca-ocb-spreadsheet_dashboard
|
||||||
|
- odoo-bringout-oca-ocb-spreadsheet_dashboard_hr_expense
|
||||||
|
- odoo-bringout-oca-ocb-spreadsheet_dashboard_hr_timesheet
|
||||||
|
- odoo-bringout-oca-ocb-spreadsheet_dashboard_pos_hr
|
||||||
|
- odoo-bringout-oca-ocb-spreadsheet_dashboard_purchase
|
||||||
47
odoo-bringout-oca-ocb-spreadsheet/README.md
Normal file
47
odoo-bringout-oca-ocb-spreadsheet/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Spreadsheet
|
||||||
|
|
||||||
|
Spreadsheet
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install odoo-bringout-oca-ocb-spreadsheet
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This addon depends on:
|
||||||
|
- bus
|
||||||
|
- web
|
||||||
|
|
||||||
|
## Manifest Information
|
||||||
|
|
||||||
|
- **Name**: Spreadsheet
|
||||||
|
- **Version**: 1.0
|
||||||
|
- **Category**: Hidden
|
||||||
|
- **License**: LGPL-3
|
||||||
|
- **Installable**: True
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `spreadsheet`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- Overview: doc/OVERVIEW.md
|
||||||
|
- Architecture: doc/ARCHITECTURE.md
|
||||||
|
- Models: doc/MODELS.md
|
||||||
|
- Controllers: doc/CONTROLLERS.md
|
||||||
|
- Wizards: doc/WIZARDS.md
|
||||||
|
- Reports: doc/REPORTS.md
|
||||||
|
- Security: doc/SECURITY.md
|
||||||
|
- Install: doc/INSTALL.md
|
||||||
|
- Usage: doc/USAGE.md
|
||||||
|
- Configuration: doc/CONFIGURATION.md
|
||||||
|
- Dependencies: doc/DEPENDENCIES.md
|
||||||
|
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||||
|
- FAQ: doc/FAQ.md
|
||||||
32
odoo-bringout-oca-ocb-spreadsheet/doc/ARCHITECTURE.md
Normal file
32
odoo-bringout-oca-ocb-spreadsheet/doc/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||||
|
V --> C[Controllers]
|
||||||
|
V --> W[Wizards – Transient Models]
|
||||||
|
C --> M[Models and ORM]
|
||||||
|
W --> M
|
||||||
|
M --> R[Reports]
|
||||||
|
DX[Data XML] --> M
|
||||||
|
S[Security – ACLs and Groups] -. enforces .-> M
|
||||||
|
|
||||||
|
subgraph Spreadsheet Module - spreadsheet
|
||||||
|
direction LR
|
||||||
|
M:::layer
|
||||||
|
W:::layer
|
||||||
|
C:::layer
|
||||||
|
V:::layer
|
||||||
|
R:::layer
|
||||||
|
S:::layer
|
||||||
|
DX:::layer
|
||||||
|
end
|
||||||
|
|
||||||
|
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Views include tree/form/kanban templates and report templates.
|
||||||
|
- Controllers provide website/portal routes when present.
|
||||||
|
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||||
|
- Data XML loads data/demo records; Security defines groups and access.
|
||||||
3
odoo-bringout-oca-ocb-spreadsheet/doc/CONFIGURATION.md
Normal file
3
odoo-bringout-oca-ocb-spreadsheet/doc/CONFIGURATION.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
Refer to Odoo settings for spreadsheet. Configure related models, access rights, and options as needed.
|
||||||
3
odoo-bringout-oca-ocb-spreadsheet/doc/CONTROLLERS.md
Normal file
3
odoo-bringout-oca-ocb-spreadsheet/doc/CONTROLLERS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Controllers
|
||||||
|
|
||||||
|
This module does not define custom HTTP controllers.
|
||||||
6
odoo-bringout-oca-ocb-spreadsheet/doc/DEPENDENCIES.md
Normal file
6
odoo-bringout-oca-ocb-spreadsheet/doc/DEPENDENCIES.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
This addon depends on:
|
||||||
|
|
||||||
|
- [bus](../../odoo-bringout-oca-ocb-bus)
|
||||||
|
- [web](../../odoo-bringout-oca-ocb-web)
|
||||||
4
odoo-bringout-oca-ocb-spreadsheet/doc/FAQ.md
Normal file
4
odoo-bringout-oca-ocb-spreadsheet/doc/FAQ.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||||
|
- Q: How to enable? A: Start server with --addon spreadsheet or install in UI.
|
||||||
7
odoo-bringout-oca-ocb-spreadsheet/doc/INSTALL.md
Normal file
7
odoo-bringout-oca-ocb-spreadsheet/doc/INSTALL.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install odoo-bringout-oca-ocb-spreadsheet"
|
||||||
|
# or
|
||||||
|
uv pip install odoo-bringout-oca-ocb-spreadsheet"
|
||||||
|
```
|
||||||
13
odoo-bringout-oca-ocb-spreadsheet/doc/MODELS.md
Normal file
13
odoo-bringout-oca-ocb-spreadsheet/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Models
|
||||||
|
|
||||||
|
Detected core models and extensions in spreadsheet.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class res_currency
|
||||||
|
class res_currency_rate
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Classes show model technical names; fields omitted for brevity.
|
||||||
|
- Items listed under _inherit are extensions of existing models.
|
||||||
6
odoo-bringout-oca-ocb-spreadsheet/doc/OVERVIEW.md
Normal file
6
odoo-bringout-oca-ocb-spreadsheet/doc/OVERVIEW.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
Packaged Odoo addon: spreadsheet. Provides features documented in upstream Odoo 16 under this addon.
|
||||||
|
|
||||||
|
- Source: OCA/OCB 16.0, addon spreadsheet
|
||||||
|
- License: LGPL-3
|
||||||
3
odoo-bringout-oca-ocb-spreadsheet/doc/REPORTS.md
Normal file
3
odoo-bringout-oca-ocb-spreadsheet/doc/REPORTS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Reports
|
||||||
|
|
||||||
|
This module does not define custom reports.
|
||||||
8
odoo-bringout-oca-ocb-spreadsheet/doc/SECURITY.md
Normal file
8
odoo-bringout-oca-ocb-spreadsheet/doc/SECURITY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Security
|
||||||
|
|
||||||
|
This module does not define custom security rules or access controls beyond Odoo defaults.
|
||||||
|
|
||||||
|
Default Odoo security applies:
|
||||||
|
- Base user access through standard groups
|
||||||
|
- Model access inherited from dependencies
|
||||||
|
- No custom row-level security rules
|
||||||
5
odoo-bringout-oca-ocb-spreadsheet/doc/TROUBLESHOOTING.md
Normal file
5
odoo-bringout-oca-ocb-spreadsheet/doc/TROUBLESHOOTING.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
- Ensure Python and Odoo environment matches repo guidance.
|
||||||
|
- Check database connectivity and logs if startup fails.
|
||||||
|
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||||
7
odoo-bringout-oca-ocb-spreadsheet/doc/USAGE.md
Normal file
7
odoo-bringout-oca-ocb-spreadsheet/doc/USAGE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Start Odoo including this addon (from repo root):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon spreadsheet
|
||||||
|
```
|
||||||
3
odoo-bringout-oca-ocb-spreadsheet/doc/WIZARDS.md
Normal file
3
odoo-bringout-oca-ocb-spreadsheet/doc/WIZARDS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Wizards
|
||||||
|
|
||||||
|
This module does not include UI wizards.
|
||||||
43
odoo-bringout-oca-ocb-spreadsheet/pyproject.toml
Normal file
43
odoo-bringout-oca-ocb-spreadsheet/pyproject.toml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[project]
|
||||||
|
name = "odoo-bringout-oca-ocb-spreadsheet"
|
||||||
|
version = "16.0.0"
|
||||||
|
description = "Spreadsheet - Spreadsheet"
|
||||||
|
authors = [
|
||||||
|
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"odoo-bringout-oca-ocb-bus>=16.0.0",
|
||||||
|
"odoo-bringout-oca-ocb-web>=16.0.0",
|
||||||
|
"requests>=2.25.1"
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">= 3.11"
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Topic :: Office/Business",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
homepage = "https://github.com/bringout/0"
|
||||||
|
repository = "https://github.com/bringout/0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.metadata]
|
||||||
|
allow-direct-references = true
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["spreadsheet"]
|
||||||
|
|
||||||
|
[tool.rye]
|
||||||
|
managed = true
|
||||||
|
dev-dependencies = [
|
||||||
|
"pytest>=8.4.1",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
{
|
||||||
|
'name': "Spreadsheet",
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Hidden',
|
||||||
|
'summary': 'Spreadsheet',
|
||||||
|
'description': 'Spreadsheet',
|
||||||
|
'depends': ['bus', 'web'],
|
||||||
|
'data': [],
|
||||||
|
'demo': [],
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': False,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
'assets': {
|
||||||
|
'spreadsheet.o_spreadsheet': [
|
||||||
|
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js',
|
||||||
|
'spreadsheet/static/src/**/*.js',
|
||||||
|
# Load all o_spreadsheet templates first to allow to inherit them
|
||||||
|
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml',
|
||||||
|
'spreadsheet/static/src/**/*.xml',
|
||||||
|
('remove', 'spreadsheet/static/src/assets_backend/**/*')
|
||||||
|
],
|
||||||
|
'web.assets_backend': [
|
||||||
|
'spreadsheet/static/src/**/*.scss',
|
||||||
|
'spreadsheet/static/src/assets_backend/**/*',
|
||||||
|
('remove', 'spreadsheet/static/src/**/*.dark.scss'),
|
||||||
|
],
|
||||||
|
"web.dark_mode_assets_backend": [
|
||||||
|
'spreadsheet/static/src/**/*.dark.scss',
|
||||||
|
],
|
||||||
|
'web.qunit_suite_tests': [
|
||||||
|
'spreadsheet/static/tests/**/*',
|
||||||
|
('include', 'spreadsheet.o_spreadsheet')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
5785
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/af.po
Normal file
5785
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/af.po
Normal file
File diff suppressed because it is too large
Load diff
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/am.po
Normal file
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/am.po
Normal file
File diff suppressed because it is too large
Load diff
5895
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ar.po
Normal file
5895
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ar.po
Normal file
File diff suppressed because it is too large
Load diff
5810
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/az.po
Normal file
5810
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/az.po
Normal file
File diff suppressed because it is too large
Load diff
5807
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/be.po
Normal file
5807
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/be.po
Normal file
File diff suppressed because it is too large
Load diff
5821
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/bg.po
Normal file
5821
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/bg.po
Normal file
File diff suppressed because it is too large
Load diff
5803
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/bs.po
Normal file
5803
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/bs.po
Normal file
File diff suppressed because it is too large
Load diff
5968
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ca.po
Normal file
5968
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ca.po
Normal file
File diff suppressed because it is too large
Load diff
5870
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/cs.po
Normal file
5870
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/cs.po
Normal file
File diff suppressed because it is too large
Load diff
5838
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/da.po
Normal file
5838
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/da.po
Normal file
File diff suppressed because it is too large
Load diff
6005
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/de.po
Normal file
6005
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/de.po
Normal file
File diff suppressed because it is too large
Load diff
5974
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/es.po
Normal file
5974
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/es.po
Normal file
File diff suppressed because it is too large
Load diff
5963
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/es_MX.po
Normal file
5963
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/es_MX.po
Normal file
File diff suppressed because it is too large
Load diff
5825
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/et.po
Normal file
5825
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/et.po
Normal file
File diff suppressed because it is too large
Load diff
5910
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/fa.po
Normal file
5910
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/fa.po
Normal file
File diff suppressed because it is too large
Load diff
5959
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/fi.po
Normal file
5959
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/fi.po
Normal file
File diff suppressed because it is too large
Load diff
5984
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/fr.po
Normal file
5984
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/fr.po
Normal file
File diff suppressed because it is too large
Load diff
5785
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/gu.po
Normal file
5785
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/gu.po
Normal file
File diff suppressed because it is too large
Load diff
5822
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/he.po
Normal file
5822
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/he.po
Normal file
File diff suppressed because it is too large
Load diff
5810
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hi.po
Normal file
5810
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hi.po
Normal file
File diff suppressed because it is too large
Load diff
5821
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hr.po
Normal file
5821
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hr.po
Normal file
File diff suppressed because it is too large
Load diff
5815
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hu.po
Normal file
5815
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hu.po
Normal file
File diff suppressed because it is too large
Load diff
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hy.po
Normal file
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/hy.po
Normal file
File diff suppressed because it is too large
Load diff
5937
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/id.po
Normal file
5937
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/id.po
Normal file
File diff suppressed because it is too large
Load diff
5790
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/is.po
Normal file
5790
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/is.po
Normal file
File diff suppressed because it is too large
Load diff
5994
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/it.po
Normal file
5994
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/it.po
Normal file
File diff suppressed because it is too large
Load diff
5820
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ja.po
Normal file
5820
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ja.po
Normal file
File diff suppressed because it is too large
Load diff
5809
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/km.po
Normal file
5809
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/km.po
Normal file
File diff suppressed because it is too large
Load diff
5821
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ko.po
Normal file
5821
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ko.po
Normal file
File diff suppressed because it is too large
Load diff
5787
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/lo.po
Normal file
5787
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/lo.po
Normal file
File diff suppressed because it is too large
Load diff
5816
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/lt.po
Normal file
5816
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/lt.po
Normal file
File diff suppressed because it is too large
Load diff
5813
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/lv.po
Normal file
5813
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/lv.po
Normal file
File diff suppressed because it is too large
Load diff
5786
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ml.po
Normal file
5786
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ml.po
Normal file
File diff suppressed because it is too large
Load diff
5817
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/mn.po
Normal file
5817
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/mn.po
Normal file
File diff suppressed because it is too large
Load diff
5791
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ms.po
Normal file
5791
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ms.po
Normal file
File diff suppressed because it is too large
Load diff
5812
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/nb.po
Normal file
5812
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/nb.po
Normal file
File diff suppressed because it is too large
Load diff
5972
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/nl.po
Normal file
5972
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/nl.po
Normal file
File diff suppressed because it is too large
Load diff
5785
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/no.po
Normal file
5785
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/no.po
Normal file
File diff suppressed because it is too large
Load diff
5959
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/pl.po
Normal file
5959
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/pl.po
Normal file
File diff suppressed because it is too large
Load diff
5823
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/pt.po
Normal file
5823
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/pt.po
Normal file
File diff suppressed because it is too large
Load diff
5956
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/pt_BR.po
Normal file
5956
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/pt_BR.po
Normal file
File diff suppressed because it is too large
Load diff
5969
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ro.po
Normal file
5969
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ro.po
Normal file
File diff suppressed because it is too large
Load diff
5970
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ru.po
Normal file
5970
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ru.po
Normal file
File diff suppressed because it is too large
Load diff
5816
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sk.po
Normal file
5816
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sk.po
Normal file
File diff suppressed because it is too large
Load diff
5820
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sl.po
Normal file
5820
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sl.po
Normal file
File diff suppressed because it is too large
Load diff
5803
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/spreadsheet.pot
Normal file
5803
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/spreadsheet.pot
Normal file
File diff suppressed because it is too large
Load diff
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sq.po
Normal file
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sq.po
Normal file
File diff suppressed because it is too large
Load diff
5913
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sr.po
Normal file
5913
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sr.po
Normal file
File diff suppressed because it is too large
Load diff
5952
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sv.po
Normal file
5952
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sv.po
Normal file
File diff suppressed because it is too large
Load diff
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sw.po
Normal file
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sw.po
Normal file
File diff suppressed because it is too large
Load diff
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ta.po
Normal file
5781
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ta.po
Normal file
File diff suppressed because it is too large
Load diff
5872
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/th.po
Normal file
5872
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/th.po
Normal file
File diff suppressed because it is too large
Load diff
5892
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/tr.po
Normal file
5892
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/tr.po
Normal file
File diff suppressed because it is too large
Load diff
5921
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/uk.po
Normal file
5921
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/uk.po
Normal file
File diff suppressed because it is too large
Load diff
5891
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/vi.po
Normal file
5891
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/vi.po
Normal file
File diff suppressed because it is too large
Load diff
5824
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/zh_CN.po
Normal file
5824
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/zh_CN.po
Normal file
File diff suppressed because it is too large
Load diff
5812
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/zh_TW.po
Normal file
5812
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/zh_TW.po
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import res_currency
|
||||||
|
from . import res_currency_rate
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,54 @@
|
||||||
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResCurrency(models.Model):
|
||||||
|
_inherit = "res.currency"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_currencies_for_spreadsheet(self, currency_names):
|
||||||
|
"""
|
||||||
|
Returns the currency structure of provided currency names.
|
||||||
|
This function is meant to be called by the spreadsheet js lib,
|
||||||
|
hence the formatting of the result.
|
||||||
|
|
||||||
|
:currency_names list(str): list of currency names (e.g. ["EUR", "USD", "CAD"])
|
||||||
|
:return: list of dicts of the form `{ "code": str, "symbol": str, "decimalPlaces": int, "position":str }`
|
||||||
|
"""
|
||||||
|
currencies = self.with_context(active_test=False).search(
|
||||||
|
[("name", "in", currency_names)],
|
||||||
|
)
|
||||||
|
result = []
|
||||||
|
for currency_name in currency_names:
|
||||||
|
currency = next(filter(lambda curr: curr.name == currency_name, currencies), None)
|
||||||
|
if currency:
|
||||||
|
currency_data = {
|
||||||
|
"code": currency.name,
|
||||||
|
"symbol": currency.symbol,
|
||||||
|
"decimalPlaces": currency.decimal_places,
|
||||||
|
"position": currency.position,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
currency_data = None
|
||||||
|
result.append(currency_data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_company_currency_for_spreadsheet(self, company_id=None):
|
||||||
|
"""
|
||||||
|
Returns the currency structure for the currency of the company.
|
||||||
|
This function is meant to be called by the spreadsheet js lib,
|
||||||
|
hence the formatting of the result.
|
||||||
|
|
||||||
|
:company_id int: Id of the company
|
||||||
|
:return: dict of the form `{ "code": str, "symbol": str, "decimalPlaces": int, "position":str }`
|
||||||
|
"""
|
||||||
|
company = self.env["res.company"].browse(company_id) if company_id else self.env.company
|
||||||
|
if not company.exists():
|
||||||
|
return False
|
||||||
|
currency = company.currency_id
|
||||||
|
return {
|
||||||
|
"code": currency.name,
|
||||||
|
"symbol": currency.symbol,
|
||||||
|
"decimalPlaces": currency.decimal_places,
|
||||||
|
"position": currency.position,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResCurrencyRate(models.Model):
|
||||||
|
_inherit = "res.currency.rate"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_rate_for_spreadsheet(self, currency_from_code, currency_to_code, date=None):
|
||||||
|
if not currency_from_code or not currency_to_code:
|
||||||
|
return False
|
||||||
|
Currency = self.env["res.currency"].with_context({"active_test": False})
|
||||||
|
currency_from = Currency.search([("name", "=", currency_from_code)])
|
||||||
|
currency_to = Currency.search([("name", "=", currency_to_code)])
|
||||||
|
if not currency_from or not currency_to:
|
||||||
|
return False
|
||||||
|
company = self.env.company
|
||||||
|
date = fields.Date.from_string(date) if date else fields.Date.context_today(self)
|
||||||
|
return Currency._get_conversion_rate(currency_from, currency_to, company, date)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_rates_for_spreadsheet(self, requests):
|
||||||
|
result = []
|
||||||
|
for request in requests:
|
||||||
|
record = request.copy()
|
||||||
|
record.update({
|
||||||
|
"rate": self._get_rate_for_spreadsheet(request["from"], request["to"], request.get("date")),
|
||||||
|
})
|
||||||
|
result.append(record)
|
||||||
|
return result
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 38 38"><defs><style>.cls-1{fill:#429646;}.cls-1,.cls-2{fill-rule:evenodd;}.cls-2{fill:#fff;fill-opacity:0.93;}.cls-3,.cls-4{fill:none;stroke:#429646;}.cls-3{stroke-width:2px;}.cls-4{stroke-miterlimit:10;stroke-width:3px;}</style></defs><title>E1</title><polygon class="cls-1" points="34 2 34 34 4 34 34 2"/><path id="pdf-a" class="cls-2" d="M6,1H32a3,3,0,0,1,3,3V33a3,3,0,0,1-3,3H6a3,3,0,0,1-3-3V4A3,3,0,0,1,6,1Z"/><path class="cls-3" d="M7,2H31a3,3,0,0,1,3,3V32a3,3,0,0,1-3,3H7a3,3,0,0,1-3-3V5A3,3,0,0,1,7,2Z"/><line class="cls-4" x1="9.26" y1="26.6" x2="14.22" y2="26.6"/><line class="cls-4" x1="9.26" y1="21.28" x2="14.22" y2="21.28"/><line class="cls-4" x1="9.26" y1="15.95" x2="14.22" y2="15.95"/><line class="cls-4" x1="9.26" y1="10.62" x2="14.22" y2="10.62"/><line class="cls-4" x1="16.52" y1="26.6" x2="21.48" y2="26.6"/><line class="cls-4" x1="16.52" y1="21.28" x2="21.48" y2="21.28"/><line class="cls-4" x1="16.52" y1="15.95" x2="21.48" y2="15.95"/><line class="cls-4" x1="16.52" y1="10.62" x2="21.48" y2="10.62"/><line class="cls-4" x1="23.77" y1="26.6" x2="28.73" y2="26.6"/><line class="cls-4" x1="23.77" y1="21.28" x2="28.73" y2="21.28"/><line class="cls-4" x1="23.77" y1="15.95" x2="28.73" y2="15.95"/><line class="cls-4" x1="23.77" y1="10.62" x2="28.73" y2="10.62"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,363 @@
|
||||||
|
/*!
|
||||||
|
* chartjs-gauge.js v0.3.0
|
||||||
|
* https://github.com/haiiaaa/chartjs-gauge/
|
||||||
|
* (c) 2021 chartjs-gauge.js Contributors
|
||||||
|
* Released under the MIT License
|
||||||
|
*/
|
||||||
|
(function (global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js')) :
|
||||||
|
typeof define === 'function' && define.amd ? define(['chart.js'], factory) :
|
||||||
|
(global = global || self, global.Gauge = factory(global.Chart));
|
||||||
|
}(this, (function (Chart) { 'use strict';
|
||||||
|
|
||||||
|
Chart = Chart && Object.prototype.hasOwnProperty.call(Chart, 'default') ? Chart['default'] : Chart;
|
||||||
|
|
||||||
|
function _defineProperty(obj, key, value) {
|
||||||
|
if (key in obj) {
|
||||||
|
Object.defineProperty(obj, key, {
|
||||||
|
value: value,
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
obj[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ownKeys(object, enumerableOnly) {
|
||||||
|
var keys = Object.keys(object);
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
var symbols = Object.getOwnPropertySymbols(object);
|
||||||
|
if (enumerableOnly) symbols = symbols.filter(function (sym) {
|
||||||
|
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
||||||
|
});
|
||||||
|
keys.push.apply(keys, symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _objectSpread2(target) {
|
||||||
|
for (var i = 1; i < arguments.length; i++) {
|
||||||
|
var source = arguments[i] != null ? arguments[i] : {};
|
||||||
|
|
||||||
|
if (i % 2) {
|
||||||
|
ownKeys(Object(source), true).forEach(function (key) {
|
||||||
|
_defineProperty(target, key, source[key]);
|
||||||
|
});
|
||||||
|
} else if (Object.getOwnPropertyDescriptors) {
|
||||||
|
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
||||||
|
} else {
|
||||||
|
ownKeys(Object(source)).forEach(function (key) {
|
||||||
|
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chart.defaults._set('gauge', {
|
||||||
|
needle: {
|
||||||
|
// Needle circle radius as the percentage of the chart area width
|
||||||
|
radiusPercentage: 2,
|
||||||
|
// Needle width as the percentage of the chart area width
|
||||||
|
widthPercentage: 3.2,
|
||||||
|
// Needle length as the percentage of the interval between inner radius (0%) and outer radius (100%) of the arc
|
||||||
|
lengthPercentage: 80,
|
||||||
|
// The color of the needle
|
||||||
|
color: 'rgba(0, 0, 0, 1)'
|
||||||
|
},
|
||||||
|
valueLabel: {
|
||||||
|
// fontSize: undefined
|
||||||
|
display: true,
|
||||||
|
formatter: null,
|
||||||
|
color: 'rgba(255, 255, 255, 1)',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 1)',
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: {
|
||||||
|
top: 5,
|
||||||
|
right: 5,
|
||||||
|
bottom: 5,
|
||||||
|
left: 5
|
||||||
|
},
|
||||||
|
bottomMarginPercentage: 5
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
duration: 1000,
|
||||||
|
animateRotate: true,
|
||||||
|
animateScale: false
|
||||||
|
},
|
||||||
|
// The percentage of the chart that we cut out of the middle.
|
||||||
|
cutoutPercentage: 50,
|
||||||
|
// The rotation of the chart, where the first data arc begins.
|
||||||
|
rotation: -Math.PI,
|
||||||
|
// The total circumference of the chart.
|
||||||
|
circumference: Math.PI,
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var GaugeController = Chart.controllers.doughnut.extend({
|
||||||
|
getValuePercent: function getValuePercent(_ref, value) {
|
||||||
|
var minValue = _ref.minValue,
|
||||||
|
data = _ref.data;
|
||||||
|
var min = minValue || 0;
|
||||||
|
var max = [undefined, null].includes(data[data.length - 1]) ? 1 : data[data.length - 1];
|
||||||
|
var length = max - min;
|
||||||
|
var percent = (value - min) / length;
|
||||||
|
return percent;
|
||||||
|
},
|
||||||
|
getWidth: function getWidth(chart) {
|
||||||
|
return chart.chartArea.right - chart.chartArea.left;
|
||||||
|
},
|
||||||
|
getTranslation: function getTranslation(chart) {
|
||||||
|
var chartArea = chart.chartArea,
|
||||||
|
offsetX = chart.offsetX,
|
||||||
|
offsetY = chart.offsetY;
|
||||||
|
var centerX = (chartArea.left + chartArea.right) / 2;
|
||||||
|
var centerY = (chartArea.top + chartArea.bottom) / 2;
|
||||||
|
var dx = centerX + offsetX;
|
||||||
|
var dy = centerY + offsetY;
|
||||||
|
return {
|
||||||
|
dx: dx,
|
||||||
|
dy: dy
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getAngle: function getAngle(_ref2) {
|
||||||
|
var chart = _ref2.chart,
|
||||||
|
valuePercent = _ref2.valuePercent;
|
||||||
|
var _chart$options = chart.options,
|
||||||
|
rotation = _chart$options.rotation,
|
||||||
|
circumference = _chart$options.circumference;
|
||||||
|
return rotation + circumference * valuePercent;
|
||||||
|
},
|
||||||
|
|
||||||
|
/* TODO set min padding, not applied until chart.update() (also chartArea must have been set)
|
||||||
|
setBottomPadding(chart) {
|
||||||
|
const needleRadius = this.getNeedleRadius(chart);
|
||||||
|
const padding = this.chart.config.options.layout.padding;
|
||||||
|
if (needleRadius > padding.bottom) {
|
||||||
|
padding.bottom = needleRadius;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
drawNeedle: function drawNeedle(ease) {
|
||||||
|
if (!this.chart.animating) {
|
||||||
|
// triggered when hovering
|
||||||
|
ease = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _this$chart = this.chart,
|
||||||
|
ctx = _this$chart.ctx,
|
||||||
|
config = _this$chart.config,
|
||||||
|
innerRadius = _this$chart.innerRadius,
|
||||||
|
outerRadius = _this$chart.outerRadius;
|
||||||
|
var dataset = config.data.datasets[this.index];
|
||||||
|
|
||||||
|
var _this$getMeta = this.getMeta(),
|
||||||
|
previous = _this$getMeta.previous;
|
||||||
|
|
||||||
|
var _config$options$needl = config.options.needle,
|
||||||
|
radiusPercentage = _config$options$needl.radiusPercentage,
|
||||||
|
widthPercentage = _config$options$needl.widthPercentage,
|
||||||
|
lengthPercentage = _config$options$needl.lengthPercentage,
|
||||||
|
color = _config$options$needl.color;
|
||||||
|
var width = this.getWidth(this.chart);
|
||||||
|
var needleRadius = radiusPercentage / 100 * width;
|
||||||
|
var needleWidth = widthPercentage / 100 * width;
|
||||||
|
var needleLength = lengthPercentage / 100 * (outerRadius - innerRadius) + innerRadius; // center
|
||||||
|
|
||||||
|
var _this$getTranslation = this.getTranslation(this.chart),
|
||||||
|
dx = _this$getTranslation.dx,
|
||||||
|
dy = _this$getTranslation.dy; // interpolate
|
||||||
|
|
||||||
|
|
||||||
|
var origin = this.getAngle({
|
||||||
|
chart: this.chart,
|
||||||
|
valuePercent: previous.valuePercent
|
||||||
|
}); // TODO valuePercent is in current.valuePercent also
|
||||||
|
|
||||||
|
var target = this.getAngle({
|
||||||
|
chart: this.chart,
|
||||||
|
valuePercent: this.getValuePercent(dataset, dataset.value)
|
||||||
|
});
|
||||||
|
var angle = origin + (target - origin) * ease; // draw
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(dx, dy);
|
||||||
|
ctx.rotate(angle);
|
||||||
|
ctx.fillStyle = color; // draw circle
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(0, 0, needleRadius, needleRadius, 0, 0, 2 * Math.PI);
|
||||||
|
ctx.fill(); // draw needle
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, needleWidth / 2);
|
||||||
|
ctx.lineTo(needleLength, 0);
|
||||||
|
ctx.lineTo(0, -needleWidth / 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
},
|
||||||
|
drawValueLabel: function drawValueLabel(ease) {
|
||||||
|
// eslint-disable-line no-unused-vars
|
||||||
|
if (!this.chart.config.options.valueLabel.display) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _this$chart2 = this.chart,
|
||||||
|
ctx = _this$chart2.ctx,
|
||||||
|
config = _this$chart2.config;
|
||||||
|
var defaultFontFamily = config.options.defaultFontFamily;
|
||||||
|
var dataset = config.data.datasets[this.index];
|
||||||
|
var _config$options$value = config.options.valueLabel,
|
||||||
|
formatter = _config$options$value.formatter,
|
||||||
|
fontSize = _config$options$value.fontSize,
|
||||||
|
color = _config$options$value.color,
|
||||||
|
backgroundColor = _config$options$value.backgroundColor,
|
||||||
|
borderRadius = _config$options$value.borderRadius,
|
||||||
|
padding = _config$options$value.padding,
|
||||||
|
bottomMarginPercentage = _config$options$value.bottomMarginPercentage;
|
||||||
|
var width = this.getWidth(this.chart);
|
||||||
|
var bottomMargin = bottomMarginPercentage / 100 * width;
|
||||||
|
|
||||||
|
var fmt = formatter || function (value) {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
var valueText = fmt(dataset.value).toString();
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
|
||||||
|
if (fontSize) {
|
||||||
|
ctx.font = "".concat(fontSize, "px ").concat(defaultFontFamily);
|
||||||
|
} // const { width: textWidth, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(valueText);
|
||||||
|
// const textHeight = actualBoundingBoxAscent + actualBoundingBoxDescent;
|
||||||
|
|
||||||
|
|
||||||
|
var _ctx$measureText = ctx.measureText(valueText),
|
||||||
|
textWidth = _ctx$measureText.width; // approximate height until browsers support advanced TextMetrics
|
||||||
|
|
||||||
|
|
||||||
|
var textHeight = Math.max(ctx.measureText('m').width, ctx.measureText("\uFF37").width);
|
||||||
|
var x = -(padding.left + textWidth / 2);
|
||||||
|
var y = -(padding.top + textHeight / 2);
|
||||||
|
var w = padding.left + textWidth + padding.right;
|
||||||
|
var h = padding.top + textHeight + padding.bottom; // center
|
||||||
|
|
||||||
|
var _this$getTranslation2 = this.getTranslation(this.chart),
|
||||||
|
dx = _this$getTranslation2.dx,
|
||||||
|
dy = _this$getTranslation2.dy; // add rotation
|
||||||
|
|
||||||
|
|
||||||
|
var rotation = this.chart.options.rotation % (Math.PI * 2.0);
|
||||||
|
dx += bottomMargin * Math.cos(rotation + Math.PI / 2);
|
||||||
|
dy += bottomMargin * Math.sin(rotation + Math.PI / 2); // draw
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(dx, dy); // draw background
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
Chart.helpers.canvas.roundedRect(ctx, x, y, w, h, borderRadius);
|
||||||
|
ctx.fillStyle = backgroundColor;
|
||||||
|
ctx.fill(); // draw value text
|
||||||
|
|
||||||
|
ctx.fillStyle = color || config.options.defaultFontColor;
|
||||||
|
var magicNumber = 0.075; // manual testing
|
||||||
|
|
||||||
|
ctx.fillText(valueText, 0, textHeight * magicNumber);
|
||||||
|
ctx.restore();
|
||||||
|
},
|
||||||
|
// overrides
|
||||||
|
update: function update(reset) {
|
||||||
|
var dataset = this.chart.config.data.datasets[this.index];
|
||||||
|
dataset.minValue = dataset.minValue || 0;
|
||||||
|
var meta = this.getMeta();
|
||||||
|
var initialValue = {
|
||||||
|
valuePercent: 0
|
||||||
|
}; // animations on will call update(reset) before update()
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
meta.previous = null;
|
||||||
|
meta.current = initialValue;
|
||||||
|
} else {
|
||||||
|
dataset.data.sort(function (a, b) {
|
||||||
|
return a - b;
|
||||||
|
});
|
||||||
|
meta.previous = meta.current || initialValue;
|
||||||
|
meta.current = {
|
||||||
|
valuePercent: this.getValuePercent(dataset, dataset.value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Chart.controllers.doughnut.prototype.update.call(this, reset);
|
||||||
|
},
|
||||||
|
updateElement: function updateElement(arc, index, reset) {
|
||||||
|
// TODO handle reset and options.animation
|
||||||
|
Chart.controllers.doughnut.prototype.updateElement.call(this, arc, index, reset);
|
||||||
|
var dataset = this.getDataset();
|
||||||
|
var data = dataset.data; // const { options } = this.chart.config;
|
||||||
|
// scale data
|
||||||
|
|
||||||
|
var previousValue = index === 0 ? dataset.minValue : data[index - 1];
|
||||||
|
var value = data[index];
|
||||||
|
var startAngle = this.getAngle({
|
||||||
|
chart: this.chart,
|
||||||
|
valuePercent: this.getValuePercent(dataset, previousValue)
|
||||||
|
});
|
||||||
|
var endAngle = this.getAngle({
|
||||||
|
chart: this.chart,
|
||||||
|
valuePercent: this.getValuePercent(dataset, value)
|
||||||
|
});
|
||||||
|
var circumference = endAngle - startAngle;
|
||||||
|
arc._model = _objectSpread2({}, arc._model, {
|
||||||
|
startAngle: startAngle,
|
||||||
|
endAngle: endAngle,
|
||||||
|
circumference: circumference
|
||||||
|
});
|
||||||
|
},
|
||||||
|
draw: function draw(ease) {
|
||||||
|
Chart.controllers.doughnut.prototype.draw.call(this, ease);
|
||||||
|
this.drawNeedle(ease);
|
||||||
|
this.drawValueLabel(ease);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* eslint-disable max-len, func-names */
|
||||||
|
var polyfill = function polyfill() {
|
||||||
|
if (CanvasRenderingContext2D.prototype.ellipse === undefined) {
|
||||||
|
CanvasRenderingContext2D.prototype.ellipse = function (x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {
|
||||||
|
this.save();
|
||||||
|
this.translate(x, y);
|
||||||
|
this.rotate(rotation);
|
||||||
|
this.scale(radiusX, radiusY);
|
||||||
|
this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
|
||||||
|
this.restore();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
polyfill();
|
||||||
|
Chart.controllers.gauge = GaugeController;
|
||||||
|
|
||||||
|
Chart.Gauge = function (context, config) {
|
||||||
|
config.type = 'gauge';
|
||||||
|
return new Chart(context, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
var index = Chart.Gauge;
|
||||||
|
|
||||||
|
return index;
|
||||||
|
|
||||||
|
})));
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import { DataSources } from "@spreadsheet/data_sources/data_sources";
|
||||||
|
import { migrate } from "@spreadsheet/o_spreadsheet/migration";
|
||||||
|
import { download } from "@web/core/network/download";
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
|
import spreadsheet from "../o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
|
||||||
|
const { Model } = spreadsheet;
|
||||||
|
|
||||||
|
async function downloadSpreadsheet(env, action) {
|
||||||
|
let { orm, name, data, stateUpdateMessages, xlsxData } = action.params;
|
||||||
|
if (!xlsxData) {
|
||||||
|
const dataSources = new DataSources(orm);
|
||||||
|
const model = new Model(migrate(data), { dataSources }, stateUpdateMessages);
|
||||||
|
await waitForDataLoaded(model);
|
||||||
|
xlsxData = model.exportXLSX();
|
||||||
|
}
|
||||||
|
await download({
|
||||||
|
url: "/spreadsheet/xlsx",
|
||||||
|
data: {
|
||||||
|
zip_name: `${name}.xlsx`,
|
||||||
|
files: new Blob([JSON.stringify(xlsxData.files)], { type: "application/json" }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the spreadsheet does not contains cells that are in loading state
|
||||||
|
* @param {Model} model
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function waitForDataLoaded(model) {
|
||||||
|
const dataSources = model.config.dataSources;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function check() {
|
||||||
|
model.dispatch("EVALUATE_CELLS");
|
||||||
|
if (isLoaded(model)) {
|
||||||
|
dataSources.removeEventListener("data-source-updated", check);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSources.addEventListener("data-source-updated", check);
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLoaded(model) {
|
||||||
|
for (const sheetId of model.getters.getSheetIds()) {
|
||||||
|
for (const cell of Object.values(model.getters.getCells(sheetId))) {
|
||||||
|
if (
|
||||||
|
cell.evaluated &&
|
||||||
|
cell.evaluated.type === "error" &&
|
||||||
|
cell.evaluated.error.message === _t("Data is loading")
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
registry
|
||||||
|
.category("actions")
|
||||||
|
.add("action_download_spreadsheet", downloadSpreadsheet, { force: true });
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import { _lt } from "@web/core/l10n/translation";
|
||||||
|
|
||||||
|
export const FILTER_DATE_OPTION = {
|
||||||
|
quarter: ["first_quarter", "second_quarter", "third_quarter", "fourth_quarter"],
|
||||||
|
year: ["this_year", "last_year", "antepenultimate_year"],
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO Remove this mapping, We should only need number > description to avoid multiple conversions
|
||||||
|
// This would require a migration though
|
||||||
|
export const monthsOptions = [
|
||||||
|
{ id: "january", description: _lt("January") },
|
||||||
|
{ id: "february", description: _lt("February") },
|
||||||
|
{ id: "march", description: _lt("March") },
|
||||||
|
{ id: "april", description: _lt("April") },
|
||||||
|
{ id: "may", description: _lt("May") },
|
||||||
|
{ id: "june", description: _lt("June") },
|
||||||
|
{ id: "july", description: _lt("July") },
|
||||||
|
{ id: "august", description: _lt("August") },
|
||||||
|
{ id: "september", description: _lt("September") },
|
||||||
|
{ id: "october", description: _lt("October") },
|
||||||
|
{ id: "november", description: _lt("November") },
|
||||||
|
{ id: "december", description: _lt("December") },
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
|
import { getBundle, loadBundle } from "@web/core/assets";
|
||||||
|
import { sprintf } from "@web/core/utils/strings";
|
||||||
|
|
||||||
|
const actionRegistry = registry.category("actions");
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {object} env
|
||||||
|
* @param {string} actionName
|
||||||
|
* @param {function} actionLazyLoader
|
||||||
|
*/
|
||||||
|
export async function loadSpreadsheetAction(env, actionName, actionLazyLoader) {
|
||||||
|
const desc = await getBundle("spreadsheet.o_spreadsheet");
|
||||||
|
await loadBundle(desc);
|
||||||
|
|
||||||
|
if (actionRegistry.get(actionName) === actionLazyLoader) {
|
||||||
|
// At this point, the real spreadsheet client action should be loaded and have
|
||||||
|
// replaced this function in the action registry. If it's not the case,
|
||||||
|
// it probably means that there was a crash in the bundle (e.g. syntax
|
||||||
|
// error). In this case, this action will remain in the registry, which
|
||||||
|
// will lead to an infinite loop. To prevent that, we push another action
|
||||||
|
// in the registry.
|
||||||
|
actionRegistry.add(
|
||||||
|
actionName,
|
||||||
|
() => {
|
||||||
|
const msg = sprintf(env._t("%s couldn't be loaded"), actionName);
|
||||||
|
env.services.notification.add(msg, { type: "danger" });
|
||||||
|
},
|
||||||
|
{ force: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSpreadsheetDownloadAction = async (env, context) => {
|
||||||
|
await loadSpreadsheetAction(env, "action_download_spreadsheet", loadSpreadsheetDownloadAction);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
target: "current",
|
||||||
|
tag: "action_download_spreadsheet",
|
||||||
|
type: "ir.actions.client",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
actionRegistry.add("action_download_spreadsheet", loadSpreadsheetDownloadAction);
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import { OdooViewsDataSource } from "@spreadsheet/data_sources/odoo_views_data_source";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { GraphModel as ChartModel} from "@web/views/graph/graph_model";
|
||||||
|
|
||||||
|
export default class ChartDataSource extends OdooViewsDataSource {
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @param {Object} services Services (see DataSource)
|
||||||
|
*/
|
||||||
|
constructor(services, params) {
|
||||||
|
super(services, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _load() {
|
||||||
|
await super._load();
|
||||||
|
const metaData = {
|
||||||
|
fieldAttrs: {},
|
||||||
|
...this._metaData,
|
||||||
|
};
|
||||||
|
this._model = new ChartModel(
|
||||||
|
{
|
||||||
|
_t,
|
||||||
|
},
|
||||||
|
metaData,
|
||||||
|
{
|
||||||
|
orm: this._orm,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await this._model.load(this._searchParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
if (!this.isReady()) {
|
||||||
|
this.load();
|
||||||
|
return { datasets: [], labels: [] };
|
||||||
|
}
|
||||||
|
if (!this._isValid) {
|
||||||
|
return { datasets: [], labels: [] };
|
||||||
|
}
|
||||||
|
return this._model.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
|
||||||
|
const { chartComponentRegistry } = spreadsheet.registries;
|
||||||
|
const { ChartJsComponent } = spreadsheet.components;
|
||||||
|
|
||||||
|
chartComponentRegistry.add("odoo_bar", ChartJsComponent);
|
||||||
|
chartComponentRegistry.add("odoo_line", ChartJsComponent);
|
||||||
|
chartComponentRegistry.add("odoo_pie", ChartJsComponent);
|
||||||
|
|
||||||
|
import OdooChartCorePlugin from "./plugins/odoo_chart_core_plugin";
|
||||||
|
import ChartOdooMenuPlugin from "./plugins/chart_odoo_menu_plugin";
|
||||||
|
import OdooChartUIPlugin from "./plugins/odoo_chart_ui_plugin";
|
||||||
|
|
||||||
|
export { OdooChartCorePlugin, ChartOdooMenuPlugin, OdooChartUIPlugin };
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { OdooChart } from "./odoo_chart";
|
||||||
|
|
||||||
|
const { chartRegistry } = spreadsheet.registries;
|
||||||
|
|
||||||
|
const { getDefaultChartJsRuntime, chartFontColor, ChartColors } = spreadsheet.helpers;
|
||||||
|
|
||||||
|
export class OdooBarChart extends OdooChart {
|
||||||
|
constructor(definition, sheetId, getters) {
|
||||||
|
super(definition, sheetId, getters);
|
||||||
|
this.verticalAxisPosition = definition.verticalAxisPosition;
|
||||||
|
this.stacked = definition.stacked;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefinition() {
|
||||||
|
return {
|
||||||
|
...super.getDefinition(),
|
||||||
|
verticalAxisPosition: this.verticalAxisPosition,
|
||||||
|
stacked: this.stacked,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chartRegistry.add("odoo_bar", {
|
||||||
|
match: (type) => type === "odoo_bar",
|
||||||
|
createChart: (definition, sheetId, getters) => new OdooBarChart(definition, sheetId, getters),
|
||||||
|
getChartRuntime: createOdooChartRuntime,
|
||||||
|
validateChartDefinition: (validator, definition) =>
|
||||||
|
OdooBarChart.validateChartDefinition(validator, definition),
|
||||||
|
transformDefinition: (definition) => OdooBarChart.transformDefinition(definition),
|
||||||
|
getChartDefinitionFromContextCreation: () => OdooBarChart.getDefinitionFromContextCreation(),
|
||||||
|
name: _t("Bar"),
|
||||||
|
});
|
||||||
|
|
||||||
|
function createOdooChartRuntime(chart, getters) {
|
||||||
|
const background = chart.background || "#FFFFFF";
|
||||||
|
const { datasets, labels } = chart.dataSource.getData();
|
||||||
|
const chartJsConfig = getBarConfiguration(chart, labels);
|
||||||
|
const colors = new ChartColors();
|
||||||
|
for (const { label, data } of datasets) {
|
||||||
|
const color = colors.next();
|
||||||
|
const dataset = {
|
||||||
|
label,
|
||||||
|
data,
|
||||||
|
borderColor: color,
|
||||||
|
backgroundColor: color,
|
||||||
|
};
|
||||||
|
chartJsConfig.data.datasets.push(dataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { background, chartJsConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBarConfiguration(chart, labels) {
|
||||||
|
const fontColor = chartFontColor(chart.background);
|
||||||
|
const config = getDefaultChartJsRuntime(chart, labels, fontColor);
|
||||||
|
config.type = chart.type.replace("odoo_", "");
|
||||||
|
const legend = {
|
||||||
|
...config.options.legend,
|
||||||
|
display: chart.legendPosition !== "none",
|
||||||
|
labels: { fontColor },
|
||||||
|
};
|
||||||
|
legend.position = chart.legendPosition;
|
||||||
|
config.options.legend = legend;
|
||||||
|
config.options.layout = {
|
||||||
|
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||||
|
};
|
||||||
|
config.options.scales = {
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
ticks: {
|
||||||
|
// x axis configuration
|
||||||
|
maxRotation: 60,
|
||||||
|
minRotation: 15,
|
||||||
|
padding: 5,
|
||||||
|
labelOffset: 2,
|
||||||
|
fontColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
position: chart.verticalAxisPosition,
|
||||||
|
ticks: {
|
||||||
|
fontColor,
|
||||||
|
// y axis configuration
|
||||||
|
beginAtZero: true, // the origin of the y axis is always zero
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
if (chart.stacked) {
|
||||||
|
config.options.scales.xAxes[0].stacked = true;
|
||||||
|
config.options.scales.yAxes[0].stacked = true;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import ChartDataSource from "../data_source/chart_data_source";
|
||||||
|
|
||||||
|
const { AbstractChart, CommandResult } = spreadsheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import("@web/search/search_model").SearchParams} SearchParams
|
||||||
|
*
|
||||||
|
* @typedef MetaData
|
||||||
|
* @property {Array<Object>} domains
|
||||||
|
* @property {Array<string>} groupBy
|
||||||
|
* @property {string} measure
|
||||||
|
* @property {string} mode
|
||||||
|
* @property {string} [order]
|
||||||
|
* @property {string} resModel
|
||||||
|
* @property {boolean} stacked
|
||||||
|
*
|
||||||
|
* @typedef OdooChartDefinition
|
||||||
|
* @property {string} type
|
||||||
|
* @property {MetaData} metaData
|
||||||
|
* @property {SearchParams} searchParams
|
||||||
|
* @property {string} title
|
||||||
|
* @property {string} background
|
||||||
|
* @property {string} legendPosition
|
||||||
|
*
|
||||||
|
* @typedef OdooChartDefinitionDataSource
|
||||||
|
* @property {MetaData} metaData
|
||||||
|
* @property {SearchParams} searchParams
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class OdooChart extends AbstractChart {
|
||||||
|
/**
|
||||||
|
* @param {OdooChartDefinition} definition
|
||||||
|
* @param {string} sheetId
|
||||||
|
* @param {Object} getters
|
||||||
|
*/
|
||||||
|
constructor(definition, sheetId, getters) {
|
||||||
|
super(definition, sheetId, getters);
|
||||||
|
this.type = definition.type;
|
||||||
|
this.metaData = definition.metaData;
|
||||||
|
this.searchParams = definition.searchParams;
|
||||||
|
this.legendPosition = definition.legendPosition;
|
||||||
|
this.background = definition.background;
|
||||||
|
this.dataSource = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static transformDefinition(definition) {
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateChartDefinition(validator, definition) {
|
||||||
|
return CommandResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefinitionFromContextCreation() {
|
||||||
|
throw new Error("It's not possible to convert an Odoo chart to a native chart");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {OdooChartDefinitionDataSource}
|
||||||
|
*/
|
||||||
|
getDefinitionForDataSource() {
|
||||||
|
return {
|
||||||
|
metaData: {
|
||||||
|
...this.metaData,
|
||||||
|
mode: this.type.replace("odoo_", ""),
|
||||||
|
},
|
||||||
|
searchParams: this.searchParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {OdooChartDefinition}
|
||||||
|
*/
|
||||||
|
getDefinition() {
|
||||||
|
return {
|
||||||
|
//@ts-ignore Defined in the parent class
|
||||||
|
title: this.title,
|
||||||
|
background: this.background,
|
||||||
|
legendPosition: this.legendPosition,
|
||||||
|
metaData: this.metaData,
|
||||||
|
searchParams: this.searchParams,
|
||||||
|
type: this.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefinitionForExcel() {
|
||||||
|
// Export not supported
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {OdooChart}
|
||||||
|
*/
|
||||||
|
updateRanges() {
|
||||||
|
// No range on this graph
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {OdooChart}
|
||||||
|
*/
|
||||||
|
copyForSheetId() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {OdooChart}
|
||||||
|
*/
|
||||||
|
copyInSheetId() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getContextCreation() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
getSheetIdsUsedInChartRanges() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
setDataSource(dataSource) {
|
||||||
|
if (dataSource instanceof ChartDataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error("Only ChartDataSources can be added.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { OdooChart } from "./odoo_chart";
|
||||||
|
import { LINE_FILL_TRANSPARENCY } from "@web/views/graph/graph_renderer";
|
||||||
|
|
||||||
|
const { chartRegistry } = spreadsheet.registries;
|
||||||
|
|
||||||
|
const {
|
||||||
|
getDefaultChartJsRuntime,
|
||||||
|
chartFontColor,
|
||||||
|
ChartColors,
|
||||||
|
getFillingMode,
|
||||||
|
colorToRGBA,
|
||||||
|
rgbaToHex,
|
||||||
|
} = spreadsheet.helpers;
|
||||||
|
|
||||||
|
export class OdooLineChart extends OdooChart {
|
||||||
|
constructor(definition, sheetId, getters) {
|
||||||
|
super(definition, sheetId, getters);
|
||||||
|
this.verticalAxisPosition = definition.verticalAxisPosition;
|
||||||
|
this.stacked = definition.stacked;
|
||||||
|
this.cumulative = definition.cumulative;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefinition() {
|
||||||
|
return {
|
||||||
|
...super.getDefinition(),
|
||||||
|
verticalAxisPosition: this.verticalAxisPosition,
|
||||||
|
stacked: this.stacked,
|
||||||
|
cumulative: this.cumulative,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chartRegistry.add("odoo_line", {
|
||||||
|
match: (type) => type === "odoo_line",
|
||||||
|
createChart: (definition, sheetId, getters) => new OdooLineChart(definition, sheetId, getters),
|
||||||
|
getChartRuntime: createOdooChartRuntime,
|
||||||
|
validateChartDefinition: (validator, definition) =>
|
||||||
|
OdooLineChart.validateChartDefinition(validator, definition),
|
||||||
|
transformDefinition: (definition) => OdooLineChart.transformDefinition(definition),
|
||||||
|
getChartDefinitionFromContextCreation: () => OdooLineChart.getDefinitionFromContextCreation(),
|
||||||
|
name: _t("Line"),
|
||||||
|
});
|
||||||
|
|
||||||
|
function createOdooChartRuntime(chart, getters) {
|
||||||
|
const background = chart.background || "#FFFFFF";
|
||||||
|
const { datasets, labels } = chart.dataSource.getData();
|
||||||
|
const chartJsConfig = getLineConfiguration(chart, labels);
|
||||||
|
const colors = new ChartColors();
|
||||||
|
for (let [index, { label, data }] of datasets.entries()) {
|
||||||
|
const color = colors.next();
|
||||||
|
const backgroundRGBA = colorToRGBA(color);
|
||||||
|
if (chart.stacked) {
|
||||||
|
// use the transparency of Odoo to keep consistency
|
||||||
|
backgroundRGBA.a = LINE_FILL_TRANSPARENCY;
|
||||||
|
}
|
||||||
|
if (chart.cumulative) {
|
||||||
|
let accumulator = 0;
|
||||||
|
data = data.map((value) => {
|
||||||
|
accumulator += value;
|
||||||
|
return accumulator;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundColor = rgbaToHex(backgroundRGBA);
|
||||||
|
const dataset = {
|
||||||
|
label,
|
||||||
|
data,
|
||||||
|
lineTension: 0,
|
||||||
|
borderColor: color,
|
||||||
|
backgroundColor,
|
||||||
|
pointBackgroundColor: color,
|
||||||
|
fill: chart.stacked ? getFillingMode(index) : false,
|
||||||
|
};
|
||||||
|
chartJsConfig.data.datasets.push(dataset);
|
||||||
|
}
|
||||||
|
return { background, chartJsConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLineConfiguration(chart, labels) {
|
||||||
|
const fontColor = chartFontColor(chart.background);
|
||||||
|
const config = getDefaultChartJsRuntime(chart, labels, fontColor);
|
||||||
|
config.type = chart.type.replace("odoo_", "");
|
||||||
|
const legend = {
|
||||||
|
...config.options.legend,
|
||||||
|
display: chart.legendPosition !== "none",
|
||||||
|
labels: {
|
||||||
|
fontColor,
|
||||||
|
generateLabels(chart) {
|
||||||
|
const { data } = chart;
|
||||||
|
const labels = window.Chart.defaults.global.legend.labels.generateLabels(chart);
|
||||||
|
for (const [index, label] of labels.entries()) {
|
||||||
|
label.fillStyle = data.datasets[index].borderColor;
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
legend.position = chart.legendPosition;
|
||||||
|
config.options.legend = legend;
|
||||||
|
config.options.layout = {
|
||||||
|
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||||
|
};
|
||||||
|
config.options.scales = {
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
ticks: {
|
||||||
|
// x axis configuration
|
||||||
|
maxRotation: 60,
|
||||||
|
minRotation: 15,
|
||||||
|
padding: 5,
|
||||||
|
labelOffset: 2,
|
||||||
|
fontColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
position: chart.verticalAxisPosition,
|
||||||
|
ticks: {
|
||||||
|
fontColor,
|
||||||
|
// y axis configuration
|
||||||
|
beginAtZero: true, // the origin of the y axis is always zero
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
if (chart.stacked) {
|
||||||
|
config.options.scales.yAxes[0].stacked = true;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { OdooChart } from "./odoo_chart";
|
||||||
|
|
||||||
|
const { chartRegistry } = spreadsheet.registries;
|
||||||
|
|
||||||
|
const { getDefaultChartJsRuntime, chartFontColor, ChartColors } = spreadsheet.helpers;
|
||||||
|
|
||||||
|
chartRegistry.add("odoo_pie", {
|
||||||
|
match: (type) => type === "odoo_pie",
|
||||||
|
createChart: (definition, sheetId, getters) => new OdooChart(definition, sheetId, getters),
|
||||||
|
getChartRuntime: createOdooChartRuntime,
|
||||||
|
validateChartDefinition: (validator, definition) =>
|
||||||
|
OdooChart.validateChartDefinition(validator, definition),
|
||||||
|
transformDefinition: (definition) => OdooChart.transformDefinition(definition),
|
||||||
|
getChartDefinitionFromContextCreation: () => OdooChart.getDefinitionFromContextCreation(),
|
||||||
|
name: _t("Pie"),
|
||||||
|
});
|
||||||
|
|
||||||
|
function createOdooChartRuntime(chart, getters) {
|
||||||
|
const background = chart.background || "#FFFFFF";
|
||||||
|
const { datasets, labels } = chart.dataSource.getData();
|
||||||
|
const chartJsConfig = getPieConfiguration(chart, labels);
|
||||||
|
const colors = new ChartColors();
|
||||||
|
for (const { label, data } of datasets) {
|
||||||
|
const backgroundColor = getPieColors(colors, datasets);
|
||||||
|
const dataset = {
|
||||||
|
label,
|
||||||
|
data,
|
||||||
|
borderColor: "#FFFFFF",
|
||||||
|
backgroundColor,
|
||||||
|
};
|
||||||
|
chartJsConfig.data.datasets.push(dataset);
|
||||||
|
}
|
||||||
|
return { background, chartJsConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPieConfiguration(chart, labels) {
|
||||||
|
const fontColor = chartFontColor(chart.background);
|
||||||
|
const config = getDefaultChartJsRuntime(chart, labels, fontColor);
|
||||||
|
config.type = chart.type.replace("odoo_", "");
|
||||||
|
const legend = {
|
||||||
|
...config.options.legend,
|
||||||
|
display: chart.legendPosition !== "none",
|
||||||
|
labels: { fontColor },
|
||||||
|
};
|
||||||
|
legend.position = chart.legendPosition;
|
||||||
|
config.options.legend = legend;
|
||||||
|
config.options.layout = {
|
||||||
|
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||||
|
};
|
||||||
|
config.options.tooltips = {
|
||||||
|
callbacks: {
|
||||||
|
title: function (tooltipItems, data) {
|
||||||
|
return data.datasets[tooltipItems[0].datasetIndex].label;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPieColors(colors, dataSetsValues) {
|
||||||
|
const pieColors = [];
|
||||||
|
const maxLength = Math.max(...dataSetsValues.map((ds) => ds.data.length));
|
||||||
|
for (let i = 0; i <= maxLength; i++) {
|
||||||
|
pieColors.push(colors.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
return pieColors;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import { useService } from "@web/core/utils/hooks";
|
||||||
|
|
||||||
|
patch(spreadsheet.components.ChartFigure.prototype, "spreadsheet.ChartFigure", {
|
||||||
|
setup() {
|
||||||
|
this._super();
|
||||||
|
this.menuService = useService("menu");
|
||||||
|
this.actionService = useService("action");
|
||||||
|
},
|
||||||
|
async navigateToOdooMenu() {
|
||||||
|
const menu = this.env.model.getters.getChartOdooMenu(this.props.figure.id);
|
||||||
|
if (!menu) {
|
||||||
|
throw new Error(`Cannot find any menu associated with the chart`);
|
||||||
|
}
|
||||||
|
await this.actionService.doAction(menu.actionID);
|
||||||
|
},
|
||||||
|
get hasOdooMenu() {
|
||||||
|
return this.env.model.getters.getChartOdooMenu(this.props.figure.id) !== undefined;
|
||||||
|
},
|
||||||
|
async onClick() {
|
||||||
|
if (this.env.isDashboard() && this.hasOdooMenu) {
|
||||||
|
this.navigateToOdooMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
.o-chart-menu {
|
||||||
|
.o-chart-menu-item {
|
||||||
|
padding-left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o-chart-external-link {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<odoo>
|
||||||
|
<div t-name="spreadsheet.ChartFigure" t-inherit="o-spreadsheet-ChartFigure" t-inherit-mode="extension" owl="1">
|
||||||
|
<xpath expr="//div[hasclass('o-chart-menu-item')]" position="before">
|
||||||
|
<div
|
||||||
|
t-if="hasOdooMenu and !env.isDashboard()"
|
||||||
|
class="o-chart-menu-item o-chart-external-link"
|
||||||
|
t-on-click="navigateToOdooMenu">
|
||||||
|
<span class="fa fa-external-link" />
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//div[hasclass('o-chart-container')]" position="attributes">
|
||||||
|
<attribute name="t-on-click">() => this.onClick()</attribute>
|
||||||
|
<attribute name="t-att-role">env.isDashboard() and hasOdooMenu ? "button" : ""</attribute>
|
||||||
|
</xpath>
|
||||||
|
</div>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import { omit } from "@web/core/utils/objects";
|
||||||
|
|
||||||
|
const { coreTypes, helpers } = spreadsheet;
|
||||||
|
const { deepEquals } = helpers;
|
||||||
|
|
||||||
|
/** Plugin that link charts with Odoo menus. It can contain either the Id of the odoo menu, or its xml id. */
|
||||||
|
export default class ChartOdooMenuPlugin extends spreadsheet.CorePlugin {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.odooMenuReference = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a spreadsheet command
|
||||||
|
* @param {Object} cmd Command
|
||||||
|
*/
|
||||||
|
handle(cmd) {
|
||||||
|
switch (cmd.type) {
|
||||||
|
case "LINK_ODOO_MENU_TO_CHART":
|
||||||
|
this.history.update("odooMenuReference", cmd.chartId, cmd.odooMenuId);
|
||||||
|
break;
|
||||||
|
case "DELETE_FIGURE":
|
||||||
|
this.history.update("odooMenuReference", cmd.id, undefined);
|
||||||
|
break;
|
||||||
|
case "DUPLICATE_SHEET":
|
||||||
|
this.updateOnDuplicateSheet(cmd.sheetId, cmd.sheetIdTo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOnDuplicateSheet(sheetIdFrom, sheetIdTo) {
|
||||||
|
for (const oldChartId of this.getters.getChartIds(sheetIdFrom)) {
|
||||||
|
if (!this.odooMenuReference[oldChartId]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const oldChartDefinition = this.getters.getChartDefinition(oldChartId);
|
||||||
|
const oldFigure = this.getters.getFigure(sheetIdFrom, oldChartId);
|
||||||
|
const newChartId = this.getters.getChartIds(sheetIdTo).find((newChartId) => {
|
||||||
|
const newChartDefinition = this.getters.getChartDefinition(newChartId);
|
||||||
|
const newFigure = this.getters.getFigure(sheetIdTo, newChartId);
|
||||||
|
return (
|
||||||
|
deepEquals(oldChartDefinition, newChartDefinition) &&
|
||||||
|
deepEquals(omit(newFigure, "id"), omit(oldFigure, "id")) // compare size and position
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newChartId) {
|
||||||
|
this.history.update(
|
||||||
|
"odooMenuReference",
|
||||||
|
newChartId,
|
||||||
|
this.odooMenuReference[oldChartId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get odoo menu linked to the chart
|
||||||
|
*
|
||||||
|
* @param {string} chartId
|
||||||
|
* @returns {object | undefined}
|
||||||
|
*/
|
||||||
|
getChartOdooMenu(chartId) {
|
||||||
|
const menuId = this.odooMenuReference[chartId];
|
||||||
|
return menuId ? this.getters.getIrMenu(menuId) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
import(data) {
|
||||||
|
if (data.chartOdooMenusReferences) {
|
||||||
|
this.odooMenuReference = data.chartOdooMenusReferences;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export(data) {
|
||||||
|
data.chartOdooMenusReferences = this.odooMenuReference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChartOdooMenuPlugin.modes = ["normal", "headless"];
|
||||||
|
ChartOdooMenuPlugin.getters = ["getChartOdooMenu"];
|
||||||
|
|
||||||
|
coreTypes.add("LINK_ODOO_MENU_TO_CHART");
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
import spreadsheet from "../../o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import ChartDataSource from "../data_source/chart_data_source";
|
||||||
|
import { globalFiltersFieldMatchers } from "@spreadsheet/global_filters/plugins/global_filters_core_plugin";
|
||||||
|
import { sprintf } from "@web/core/utils/strings";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { checkFilterFieldMatching } from "@spreadsheet/global_filters/helpers";
|
||||||
|
import CommandResult from "../../o_spreadsheet/cancelled_reason";
|
||||||
|
|
||||||
|
const { CorePlugin } = spreadsheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Chart
|
||||||
|
* @property {string} dataSourceId
|
||||||
|
* @property {Object} fieldMatching
|
||||||
|
*
|
||||||
|
* @typedef {import("@spreadsheet/global_filters/plugins/global_filters_core_plugin").FieldMatching} FieldMatching
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class OdooChartCorePlugin extends CorePlugin {
|
||||||
|
constructor(getters, history, range, dispatch, config, uuidGenerator) {
|
||||||
|
super(getters, history, range, dispatch, config, uuidGenerator);
|
||||||
|
this.dataSources = config.dataSources;
|
||||||
|
|
||||||
|
/** @type {Object.<string, Chart>} */
|
||||||
|
this.charts = {};
|
||||||
|
|
||||||
|
globalFiltersFieldMatchers["chart"] = {
|
||||||
|
geIds: () => this.getters.getOdooChartIds(),
|
||||||
|
getDisplayName: (chartId) => this.getters.getOdooChartDisplayName(chartId),
|
||||||
|
getTag: async (chartId) => {
|
||||||
|
const model = await this.getChartDataSource(chartId).getModelLabel();
|
||||||
|
return sprintf(_t("Chart - %s"), model);
|
||||||
|
},
|
||||||
|
getFieldMatching: (chartId, filterId) =>
|
||||||
|
this.getOdooChartFieldMatching(chartId, filterId),
|
||||||
|
waitForReady: () => this.getOdooChartsWaitForReady(),
|
||||||
|
getModel: (chartId) =>
|
||||||
|
this.getters.getChart(chartId).getDefinitionForDataSource().metaData.resModel,
|
||||||
|
getFields: (chartId) => this.getChartDataSource(chartId).getFields(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
allowDispatch(cmd) {
|
||||||
|
switch (cmd.type) {
|
||||||
|
case "ADD_GLOBAL_FILTER":
|
||||||
|
case "EDIT_GLOBAL_FILTER":
|
||||||
|
if (cmd.chart) {
|
||||||
|
return checkFilterFieldMatching(cmd.chart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CommandResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a spreadsheet command
|
||||||
|
*
|
||||||
|
* @param {Object} cmd Command
|
||||||
|
*/
|
||||||
|
handle(cmd) {
|
||||||
|
switch (cmd.type) {
|
||||||
|
case "CREATE_CHART": {
|
||||||
|
switch (cmd.definition.type) {
|
||||||
|
case "odoo_pie":
|
||||||
|
case "odoo_bar":
|
||||||
|
case "odoo_line":
|
||||||
|
this._addOdooChart(cmd.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "UPDATE_CHART": {
|
||||||
|
switch (cmd.definition.type) {
|
||||||
|
case "odoo_pie":
|
||||||
|
case "odoo_bar":
|
||||||
|
case "odoo_line":
|
||||||
|
this._setChartDataSource(cmd.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "DELETE_FIGURE": {
|
||||||
|
const charts = { ...this.charts };
|
||||||
|
delete charts[cmd.id];
|
||||||
|
this.history.update("charts", charts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "REMOVE_GLOBAL_FILTER":
|
||||||
|
this._onFilterDeletion(cmd.id);
|
||||||
|
break;
|
||||||
|
case "ADD_GLOBAL_FILTER":
|
||||||
|
case "EDIT_GLOBAL_FILTER":
|
||||||
|
if (cmd.chart) {
|
||||||
|
this._setOdooChartFieldMatching(cmd.filter.id, cmd.chart);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Getters
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the odoo chart ids
|
||||||
|
* @returns {Array<string>}
|
||||||
|
*/
|
||||||
|
getOdooChartIds() {
|
||||||
|
const ids = [];
|
||||||
|
for (const sheetId of this.getters.getSheetIds()) {
|
||||||
|
ids.push(
|
||||||
|
...this.getters
|
||||||
|
.getChartIds(sheetId)
|
||||||
|
.filter((id) => this.getters.getChartType(id).startsWith("odoo_"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} chartId
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getChartFieldMatch(chartId) {
|
||||||
|
return this.charts[chartId].fieldMatching;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {ChartDataSource|undefined}
|
||||||
|
*/
|
||||||
|
getChartDataSource(id) {
|
||||||
|
const dataSourceId = this.charts[id].dataSourceId;
|
||||||
|
return this.dataSources.get(dataSourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} chartId
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getOdooChartDisplayName(chartId) {
|
||||||
|
return this.getters.getChart(chartId).title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import the pivots
|
||||||
|
*
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
|
import(data) {
|
||||||
|
for (const sheet of data.sheets) {
|
||||||
|
if (sheet.figures) {
|
||||||
|
for (const figure of sheet.figures) {
|
||||||
|
if (figure.tag === "chart" && figure.data.type.startsWith("odoo_")) {
|
||||||
|
this._addOdooChart(figure.id, figure.data.fieldMatching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Export the pivots
|
||||||
|
*
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
|
export(data) {
|
||||||
|
for (const sheet of data.sheets) {
|
||||||
|
if (sheet.figures) {
|
||||||
|
for (const figure of sheet.figures) {
|
||||||
|
if (figure.tag === "chart" && figure.data.type.startsWith("odoo_")) {
|
||||||
|
figure.data.fieldMatching = this.getChartFieldMatch(figure.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return {Promise[]}
|
||||||
|
*/
|
||||||
|
getOdooChartsWaitForReady() {
|
||||||
|
return this.getOdooChartIds().map((chartId) =>
|
||||||
|
this.getChartDataSource(chartId).loadMetadata()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current pivotFieldMatching of a chart
|
||||||
|
*
|
||||||
|
* @param {string} chartId
|
||||||
|
* @param {string} filterId
|
||||||
|
*/
|
||||||
|
getOdooChartFieldMatching(chartId, filterId) {
|
||||||
|
return this.charts[chartId].fieldMatching[filterId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current pivotFieldMatching of a chart
|
||||||
|
*
|
||||||
|
* @param {string} filterId
|
||||||
|
* @param {Record<string,FieldMatching>} chartFieldMatches
|
||||||
|
*/
|
||||||
|
_setOdooChartFieldMatching(filterId, chartFieldMatches) {
|
||||||
|
const charts = { ...this.charts };
|
||||||
|
for (const [chartId, fieldMatch] of Object.entries(chartFieldMatches)) {
|
||||||
|
charts[chartId].fieldMatching[filterId] = fieldMatch;
|
||||||
|
}
|
||||||
|
this.history.update("charts", charts);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onFilterDeletion(filterId) {
|
||||||
|
const charts = { ...this.charts };
|
||||||
|
for (const chartId in charts) {
|
||||||
|
this.history.update("charts", chartId, "fieldMatching", filterId, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} chartId
|
||||||
|
* @param {string} dataSourceId
|
||||||
|
*/
|
||||||
|
_addOdooChart(chartId, fieldMatching = {}) {
|
||||||
|
const dataSourceId = this.uuidGenerator.uuidv4();
|
||||||
|
const charts = { ...this.charts };
|
||||||
|
charts[chartId] = {
|
||||||
|
dataSourceId,
|
||||||
|
fieldMatching,
|
||||||
|
};
|
||||||
|
const definition = this.getters.getChart(chartId).getDefinitionForDataSource();
|
||||||
|
if (!this.dataSources.contains(dataSourceId)) {
|
||||||
|
this.dataSources.add(dataSourceId, ChartDataSource, definition);
|
||||||
|
}
|
||||||
|
this.history.update("charts", charts);
|
||||||
|
this._setChartDataSource(chartId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the catasource on the corresponding chart
|
||||||
|
* @param {string} chartId
|
||||||
|
*/
|
||||||
|
_setChartDataSource(chartId) {
|
||||||
|
const chart = this.getters.getChart(chartId);
|
||||||
|
chart.setDataSource(this.getters.getChartDataSource(chartId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OdooChartCorePlugin.getters = [
|
||||||
|
"getChartDataSource",
|
||||||
|
"getOdooChartIds",
|
||||||
|
"getChartFieldMatch",
|
||||||
|
"getOdooChartDisplayName",
|
||||||
|
"getOdooChartFieldMatching",
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import spreadsheet from "../../o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
import { Domain } from "@web/core/domain";
|
||||||
|
|
||||||
|
const { UIPlugin } = spreadsheet;
|
||||||
|
|
||||||
|
export default class OdooChartUIPlugin extends UIPlugin {
|
||||||
|
beforeHandle(cmd) {
|
||||||
|
switch (cmd.type) {
|
||||||
|
case "START":
|
||||||
|
// make sure the domains are correctly set before
|
||||||
|
// any evaluation
|
||||||
|
this._addDomains();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a spreadsheet command
|
||||||
|
*
|
||||||
|
* @param {Object} cmd Command
|
||||||
|
*/
|
||||||
|
handle(cmd) {
|
||||||
|
switch (cmd.type) {
|
||||||
|
case "ADD_GLOBAL_FILTER":
|
||||||
|
case "EDIT_GLOBAL_FILTER":
|
||||||
|
case "REMOVE_GLOBAL_FILTER":
|
||||||
|
case "SET_GLOBAL_FILTER_VALUE":
|
||||||
|
case "CLEAR_GLOBAL_FILTER_VALUE":
|
||||||
|
this._addDomains();
|
||||||
|
break;
|
||||||
|
case "UNDO":
|
||||||
|
case "REDO":
|
||||||
|
if (
|
||||||
|
cmd.commands.find((command) =>
|
||||||
|
[
|
||||||
|
"ADD_GLOBAL_FILTER",
|
||||||
|
"EDIT_GLOBAL_FILTER",
|
||||||
|
"REMOVE_GLOBAL_FILTER",
|
||||||
|
].includes(command.type)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this._addDomains();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an additional domain to a chart
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {string} chartId chart id
|
||||||
|
*/
|
||||||
|
_addDomain(chartId) {
|
||||||
|
const domainList = [];
|
||||||
|
for (const [filterId, fieldMatch] of Object.entries(
|
||||||
|
this.getters.getChartFieldMatch(chartId)
|
||||||
|
)) {
|
||||||
|
domainList.push(this.getters.getGlobalFilterDomain(filterId, fieldMatch));
|
||||||
|
}
|
||||||
|
const domain = Domain.combine(domainList, "AND").toString();
|
||||||
|
this.getters.getChartDataSource(chartId).addDomain(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an additional domain to all chart
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_addDomains() {
|
||||||
|
for (const chartId of this.getters.getOdooChartIds()) {
|
||||||
|
this._addDomain(chartId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OdooChartUIPlugin.getters = [];
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||||
|
const { inverseCommandRegistry, otRegistry } = spreadsheet.registries;
|
||||||
|
|
||||||
|
function identity(cmd) {
|
||||||
|
return [cmd];
|
||||||
|
}
|
||||||
|
|
||||||
|
otRegistry.addTransformation(
|
||||||
|
"DELETE_FIGURE",
|
||||||
|
["LINK_ODOO_MENU_TO_CHART"],
|
||||||
|
(toTransform, executed) => {
|
||||||
|
if (executed.id === toTransform.chartId) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return toTransform;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
inverseCommandRegistry.add("LINK_ODOO_MENU_TO_CHART", identity);
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue