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