odoo-addon-account-dashboard-banner 18.0.1.0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of odoo-addon-account-dashboard-banner might be problematic. Click here for more details.

Files changed (28) hide show
  1. odoo/addons/account_dashboard_banner/README.rst +152 -0
  2. odoo/addons/account_dashboard_banner/__init__.py +2 -0
  3. odoo/addons/account_dashboard_banner/__manifest__.py +29 -0
  4. odoo/addons/account_dashboard_banner/i18n/account_dashboard_banner.pot +246 -0
  5. odoo/addons/account_dashboard_banner/i18n/fr.po +263 -0
  6. odoo/addons/account_dashboard_banner/models/__init__.py +1 -0
  7. odoo/addons/account_dashboard_banner/models/account_dashboard_banner_cell.py +327 -0
  8. odoo/addons/account_dashboard_banner/post_install.py +18 -0
  9. odoo/addons/account_dashboard_banner/readme/CONFIGURE.md +34 -0
  10. odoo/addons/account_dashboard_banner/readme/CONTRIBUTORS.md +1 -0
  11. odoo/addons/account_dashboard_banner/readme/DESCRIPTION.md +13 -0
  12. odoo/addons/account_dashboard_banner/readme/USAGE.md +3 -0
  13. odoo/addons/account_dashboard_banner/security/ir.model.access.csv +4 -0
  14. odoo/addons/account_dashboard_banner/static/description/account_dashboard_banner.png +0 -0
  15. odoo/addons/account_dashboard_banner/static/description/banner_cell_config.png +0 -0
  16. odoo/addons/account_dashboard_banner/static/description/cell_form_with_warning.png +0 -0
  17. odoo/addons/account_dashboard_banner/static/description/icon.png +0 -0
  18. odoo/addons/account_dashboard_banner/static/description/index.html +484 -0
  19. odoo/addons/account_dashboard_banner/static/src/views/account_dashboard_kanban_banner.esm.js +36 -0
  20. odoo/addons/account_dashboard_banner/static/src/views/account_dashboard_kanban_banner.xml +27 -0
  21. odoo/addons/account_dashboard_banner/tests/__init__.py +1 -0
  22. odoo/addons/account_dashboard_banner/tests/test_banner.py +58 -0
  23. odoo/addons/account_dashboard_banner/views/account_dashboard_banner_cell.xml +59 -0
  24. odoo/addons/account_dashboard_banner/views/account_journal_dashboard.xml +20 -0
  25. odoo_addon_account_dashboard_banner-18.0.1.0.0.2.dist-info/METADATA +169 -0
  26. odoo_addon_account_dashboard_banner-18.0.1.0.0.2.dist-info/RECORD +28 -0
  27. odoo_addon_account_dashboard_banner-18.0.1.0.0.2.dist-info/WHEEL +5 -0
  28. odoo_addon_account_dashboard_banner-18.0.1.0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,263 @@
1
+ # Translation of Odoo Server.
2
+ # This file contains the translation of the following modules:
3
+ # * account_dashboard_banner
4
+ #
5
+ msgid ""
6
+ msgstr ""
7
+ "Project-Id-Version: Odoo Server 18.0\n"
8
+ "Report-Msgid-Bugs-To: \n"
9
+ "POT-Creation-Date: 2025-07-06 14:17+0000\n"
10
+ "PO-Revision-Date: 2025-07-06 14:17+0000\n"
11
+ "Last-Translator: Alexis de Lattre <alexis.delattre@akretion.com>\n"
12
+ "Language-Team: \n"
13
+ "Language: fr\n"
14
+ "MIME-Version: 1.0\n"
15
+ "Content-Type: text/plain; charset=UTF-8\n"
16
+ "Content-Transfer-Encoding: \n"
17
+ "Plural-Forms: \n"
18
+
19
+ #. module: account_dashboard_banner
20
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__warn_type__above
21
+ msgid "Above Maximum"
22
+ msgstr "supérieur au maximum"
23
+
24
+ #. module: account_dashboard_banner
25
+ #: model:ir.model,name:account_dashboard_banner.model_account_dashboard_banner_cell
26
+ msgid "Accounting Dashboard Banner Cell"
27
+ msgstr "Cellule du bandeau du tableau de bord de la comptabilité"
28
+
29
+ #. module: account_dashboard_banner
30
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__warn_type__inside
31
+ msgid "Between Minimum and Maximum"
32
+ msgstr "entre le minimum et le maximum"
33
+
34
+ #. module: account_dashboard_banner
35
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__cell_type
36
+ msgid "Cell Type"
37
+ msgstr "Type de cellule"
38
+
39
+ #. module: account_dashboard_banner
40
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__create_uid
41
+ msgid "Created by"
42
+ msgstr "Créé par"
43
+
44
+ #. module: account_dashboard_banner
45
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__create_date
46
+ msgid "Created on"
47
+ msgstr "Créé le"
48
+
49
+ #. module: account_dashboard_banner
50
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__custom_label
51
+ msgid "Custom Label"
52
+ msgstr "Libellé personnalisé"
53
+
54
+ #. module: account_dashboard_banner
55
+ #: model_terms:ir.ui.view,arch_db:account_dashboard_banner.account_dashboard_banner_cell_form
56
+ #: model_terms:ir.ui.view,arch_db:account_dashboard_banner.account_dashboard_banner_cell_list
57
+ msgid "Custom Label (optional)"
58
+ msgstr "Libellé personnalisé (facultatif)"
59
+
60
+ #. module: account_dashboard_banner
61
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__custom_tooltip
62
+ msgid "Custom Tooltip"
63
+ msgstr "Info-bulle personnalisée"
64
+
65
+ #. module: account_dashboard_banner
66
+ #: model_terms:ir.ui.view,arch_db:account_dashboard_banner.account_dashboard_banner_cell_form
67
+ msgid "Custom Tooltip (optional)"
68
+ msgstr "Info-bulle personnalisée (facultatif)"
69
+
70
+ #. module: account_dashboard_banner
71
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__customer_debt
72
+ msgid "Customer Debt"
73
+ msgstr "Dette client"
74
+
75
+ #. module: account_dashboard_banner
76
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__customer_overdue
77
+ msgid "Customer Overdue"
78
+ msgstr "Impayés client"
79
+
80
+ #. module: account_dashboard_banner
81
+ #: model:ir.ui.menu,name:account_dashboard_banner.account_dashboard_config
82
+ msgid "Dashboard"
83
+ msgstr "Tableau de bord"
84
+
85
+ #. module: account_dashboard_banner
86
+ #: model:ir.actions.act_window,name:account_dashboard_banner.account_dashboard_banner_cell_action
87
+ #: model:ir.ui.menu,name:account_dashboard_banner.account_dashboard_banner_cell_menu
88
+ msgid "Dashboard Banner Cells"
89
+ msgstr "Cellules du bandeau du tableau de bord"
90
+
91
+ #. module: account_dashboard_banner
92
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__display_name
93
+ msgid "Display Name"
94
+ msgstr "Nom affiché"
95
+
96
+ #. module: account_dashboard_banner
97
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__income_fiscalyear
98
+ msgid "Fiscal Year-to-date Income"
99
+ msgstr "Revenus depuis le début de l'exercice comptable"
100
+
101
+ #. module: account_dashboard_banner
102
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__fiscalyear_lock_date
103
+ msgid "Global Lock Date"
104
+ msgstr "Date de verrouillage globale"
105
+
106
+ #. module: account_dashboard_banner
107
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__hard_lock_date
108
+ msgid "Hard Lock Date"
109
+ msgstr "Date de verrouillage ferme"
110
+
111
+ #. module: account_dashboard_banner
112
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__id
113
+ msgid "ID"
114
+ msgstr "ID"
115
+
116
+ #. module: account_dashboard_banner
117
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__write_uid
118
+ msgid "Last Updated by"
119
+ msgstr "Dernière mise à jour par"
120
+
121
+ #. module: account_dashboard_banner
122
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__write_date
123
+ msgid "Last Updated on"
124
+ msgstr "Dernière mise à jour le"
125
+
126
+ #. module: account_dashboard_banner
127
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__liquidity
128
+ msgid "Liquidity"
129
+ msgstr "Liquidités"
130
+
131
+ #. module: account_dashboard_banner
132
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__warn_max
133
+ msgid "Maximum"
134
+ msgstr "Maximum"
135
+
136
+ #. module: account_dashboard_banner
137
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__warn_min
138
+ msgid "Minimum"
139
+ msgstr "Minimum"
140
+
141
+ #. module: account_dashboard_banner
142
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__income_month
143
+ msgid "Month-to-date Income"
144
+ msgstr "Revenus depuis le début du mois"
145
+
146
+ #. module: account_dashboard_banner
147
+ #. odoo-python
148
+ #: code:addons/account_dashboard_banner/models/account_dashboard_banner_cell.py:0
149
+ msgid ""
150
+ "On cell '%(cell_type)s' with warning enabled, the minimum (%(warn_min)s) "
151
+ "must be under the maximum (%(warn_max)s)."
152
+ msgstr ""
153
+ "Pour la cellule '%(cell_type)s' dont l'avertissement est activé, le minimum "
154
+ "(%(warn_min)s) doit être inférieur au maximum (%(warn_max)s)."
155
+
156
+ #. module: account_dashboard_banner
157
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__purchase_lock_date
158
+ msgid "Purchase Lock Date"
159
+ msgstr "Date de verrouillage des achats"
160
+
161
+ #. module: account_dashboard_banner
162
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__income_quarter
163
+ msgid "Quarter-to-date Income"
164
+ msgstr "Revenus depuis le début du trimestre"
165
+
166
+ #. module: account_dashboard_banner
167
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__sale_lock_date
168
+ msgid "Sales Lock Date"
169
+ msgstr "Date de verrouillage des ventes"
170
+
171
+ #. module: account_dashboard_banner
172
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__sequence
173
+ msgid "Sequence"
174
+ msgstr "Séquence"
175
+
176
+ #. module: account_dashboard_banner
177
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__supplier_debt
178
+ msgid "Supplier Debt"
179
+ msgstr "Dette fournisseur"
180
+
181
+ #. module: account_dashboard_banner
182
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__tax_lock_date
183
+ msgid "Tax Return Lock Date"
184
+ msgstr "Date de verrouillage de la déclaration de TVA"
185
+
186
+ #. module: account_dashboard_banner
187
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__warn_type__under
188
+ msgid "Under Minimum"
189
+ msgstr "inférieur au minimum"
190
+
191
+ #. module: account_dashboard_banner
192
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__warn_type__outside
193
+ msgid "Under Minimum or Above Maximum"
194
+ msgstr "inférieur au minimum ou supérieur au maximum"
195
+
196
+ #. module: account_dashboard_banner
197
+ #: model_terms:ir.ui.view,arch_db:account_dashboard_banner.account_dashboard_banner_cell_form
198
+ msgid "Warn If"
199
+ msgstr "Avertissement si"
200
+
201
+ #. module: account_dashboard_banner
202
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__warn_lock_date_days
203
+ #, fuzzy
204
+ msgid "Warn Lock Date Days"
205
+ msgstr "Date de verrouillage ferme"
206
+
207
+ #. module: account_dashboard_banner
208
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__warn_type
209
+ msgid "Warn Type"
210
+ msgstr "Type d'avertissement"
211
+
212
+ #. module: account_dashboard_banner
213
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__warn_type_show
214
+ msgid "Warn Type Show"
215
+ msgstr "Afficher le type d'avertissement"
216
+
217
+ #. module: account_dashboard_banner
218
+ #: model_terms:ir.ui.view,arch_db:account_dashboard_banner.account_dashboard_banner_cell_form
219
+ msgid "Warn if lock date is older than"
220
+ msgstr "Avertissement si la date de verrouillage est antérieure à"
221
+
222
+ #. module: account_dashboard_banner
223
+ #: model:ir.model.constraint,message:account_dashboard_banner.constraint_account_dashboard_banner_cell_warn_lock_date_days_positive
224
+ msgid "Warn if lock date is older than N days must be positive or null."
225
+ msgstr ""
226
+ "Avertissement si la date de verrouillage est supérieure à N jours. Doit être "
227
+ "positif ou nul."
228
+
229
+ #. module: account_dashboard_banner
230
+ #: model:ir.model.fields,field_description:account_dashboard_banner.field_account_dashboard_banner_cell__warn
231
+ msgid "Warning"
232
+ msgstr "Avertissement"
233
+
234
+ #. module: account_dashboard_banner
235
+ #: model:ir.model.fields.selection,name:account_dashboard_banner.selection__account_dashboard_banner_cell__cell_type__income_year
236
+ msgid "Year-to-date Income"
237
+ msgstr "Revenus depuis le début de l'année"
238
+
239
+ #. module: account_dashboard_banner
240
+ #: model_terms:ir.ui.view,arch_db:account_dashboard_banner.account_dashboard_banner_cell_form
241
+ msgid "days"
242
+ msgstr "jours"
243
+
244
+ #. module: account_dashboard_banner
245
+ #. odoo-python
246
+ #: code:addons/account_dashboard_banner/models/account_dashboard_banner_cell.py:0
247
+ msgid "from %s"
248
+ msgstr "depuis le %s"
249
+
250
+ #. module: account_dashboard_banner
251
+ #. odoo-python
252
+ #: code:addons/account_dashboard_banner/models/account_dashboard_banner_cell.py:0
253
+ msgid "with due date before %s"
254
+ msgstr "avec date d'échéance antérieure au %s"
255
+
256
+ #~ msgid "Balance of account(s) %(account_codes)s%(specific)s."
257
+ #~ msgstr "Solde du/des compte(s) %(account_codes)s%(specific)s."
258
+
259
+ #~ msgid "None"
260
+ #~ msgstr "Aucun"
261
+
262
+ #~ msgid "_compute_warn_fields"
263
+ #~ msgstr "calculer les champs d'avertissement"
@@ -0,0 +1 @@
1
+ from . import account_dashboard_banner_cell
@@ -0,0 +1,327 @@
1
+ # Copyright 2025 Akretion France (https://www.akretion.com/)
2
+ # @author: Alexis de Lattre <alexis.delattre@akretion.com>
3
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
4
+
5
+ from dateutil.relativedelta import relativedelta
6
+
7
+ from odoo import _, api, fields, models
8
+ from odoo.exceptions import ValidationError
9
+ from odoo.osv import expression
10
+ from odoo.tools import date_utils
11
+ from odoo.tools.misc import format_amount, format_date
12
+
13
+
14
+ class AccountDashboardBannerCell(models.Model):
15
+ _name = "account.dashboard.banner.cell"
16
+ _description = "Accounting Dashboard Banner Cell"
17
+ _order = "sequence, id"
18
+
19
+ sequence = fields.Integer()
20
+ cell_type = fields.Selection(
21
+ [
22
+ ("income_fiscalyear", "Fiscal Year-to-date Income"),
23
+ ("income_year", "Year-to-date Income"),
24
+ ("income_quarter", "Quarter-to-date Income"),
25
+ ("income_month", "Month-to-date Income"),
26
+ ("liquidity", "Liquidity"),
27
+ ("customer_debt", "Customer Debt"),
28
+ ("customer_overdue", "Customer Overdue"),
29
+ ("supplier_debt", "Supplier Debt"),
30
+ # for lock dates, the key matches exactly the field name on res.company
31
+ ("tax_lock_date", "Tax Return Lock Date"),
32
+ ("sale_lock_date", "Sales Lock Date"),
33
+ ("purchase_lock_date", "Purchase Lock Date"),
34
+ ("fiscalyear_lock_date", "Global Lock Date"),
35
+ ("hard_lock_date", "Hard Lock Date"),
36
+ ],
37
+ required=True,
38
+ )
39
+ custom_label = fields.Char()
40
+ custom_tooltip = fields.Char()
41
+ warn = fields.Boolean(string="Warning")
42
+ warn_lock_date_days = fields.Integer(
43
+ compute="_compute_warn_fields", store=True, readonly=False, precompute=True
44
+ )
45
+ warn_min = fields.Float(string="Minimum")
46
+ warn_max = fields.Float(string="Maximum")
47
+ warn_type_show = fields.Boolean(
48
+ compute="_compute_warn_fields", store=True, precompute=True
49
+ )
50
+ warn_type = fields.Selection(
51
+ [
52
+ ("under", "Under Minimum"),
53
+ ("above", "Above Maximum"),
54
+ ("outside", "Under Minimum or Above Maximum"),
55
+ ("inside", "Between Minimum and Maximum"),
56
+ ],
57
+ default="under",
58
+ )
59
+
60
+ _sql_constraints = [
61
+ (
62
+ "warn_lock_date_days_positive",
63
+ "CHECK(warn_lock_date_days >= 0)",
64
+ "Warn if lock date is older than N days must be positive or null.",
65
+ )
66
+ ]
67
+
68
+ @api.constrains("warn_min", "warn_max", "warn_type", "warn", "cell_type")
69
+ def _check_warn_config(self):
70
+ for cell in self:
71
+ if (
72
+ cell.cell_type
73
+ and not cell.cell_type.endswith("_lock_date")
74
+ and cell.warn
75
+ and cell.warn_type in ("outside", "inside")
76
+ and cell.warn_max <= cell.warn_min
77
+ ):
78
+ cell_type2label = dict(
79
+ self.fields_get("cell_type", "selection")["cell_type"]["selection"]
80
+ )
81
+ raise ValidationError(
82
+ _(
83
+ "On cell '%(cell_type)s' with warning enabled, "
84
+ "the minimum (%(warn_min)s) must be under "
85
+ "the maximum (%(warn_max)s).",
86
+ cell_type=cell_type2label[cell.cell_type],
87
+ warn_min=cell.warn_min,
88
+ warn_max=cell.warn_max,
89
+ )
90
+ )
91
+
92
+ @api.model
93
+ def _default_warn_lock_date_days(self, cell_type):
94
+ defaultmap = {
95
+ "tax_lock_date": 61, # 2 months
96
+ "sale_lock_date": 35, # 1 month + a few days
97
+ "purchase_lock_date": 61,
98
+ "fiscalyear_lock_date": 61, # 2 months
99
+ "hard_lock_date": 520, # FY final closing, 1 year + 5 months
100
+ }
101
+ return defaultmap.get(cell_type)
102
+
103
+ @api.depends("cell_type", "warn")
104
+ def _compute_warn_fields(self):
105
+ for cell in self:
106
+ warn_type_show = False
107
+ warn_lock_date_days = 0
108
+ if cell.cell_type and cell.warn:
109
+ if cell.cell_type.endswith("_lock_date"):
110
+ warn_lock_date_days = self._default_warn_lock_date_days(
111
+ cell.cell_type
112
+ )
113
+ else:
114
+ warn_type_show = True
115
+ cell.warn_type_show = warn_type_show
116
+ cell.warn_lock_date_days = warn_lock_date_days
117
+
118
+ @api.model
119
+ def get_banner_data(self):
120
+ """This is the method called by the JS code that displays the banner"""
121
+ company = self.env.company
122
+ return self._prepare_banner_data(company)
123
+
124
+ def _prepare_speedy(self, company):
125
+ lock_date_fields = [
126
+ "tax_lock_date",
127
+ "sale_lock_date",
128
+ "purchase_lock_date",
129
+ "fiscalyear_lock_date",
130
+ "hard_lock_date",
131
+ ]
132
+ speedy = {
133
+ "cell_type2label": dict(
134
+ self.fields_get("cell_type", "selection")["cell_type"]["selection"]
135
+ ),
136
+ "lock_date2help": dict(
137
+ (key, value["help"])
138
+ for (key, value) in company.fields_get(lock_date_fields, "help").items()
139
+ ),
140
+ "today": fields.Date.context_today(self),
141
+ }
142
+ return speedy
143
+
144
+ @api.model
145
+ def _prepare_banner_data(self, company):
146
+ # The order in this list will be the display order in the banner
147
+ # In fact, it's not a list but a dict. I tried to make it work by returning
148
+ # a list but it seems OWL only accepts dicts (I always get errors on lists)
149
+ cells = self.search([])
150
+ speedy = cells._prepare_speedy(company)
151
+ res = {}
152
+ seq = 0
153
+ for cell in cells:
154
+ seq += 1
155
+ cell_data = cell._prepare_cell_data(company, speedy)
156
+ cell._update_cell_warn(cell_data)
157
+ res[seq] = cell_data
158
+ # from pprint import pprint
159
+ # pprint(res)
160
+ return res
161
+
162
+ def _prepare_cell_data_liquidity(self, company, speedy):
163
+ self.ensure_one()
164
+ journals = self.env["account.journal"].search(
165
+ [
166
+ ("company_id", "=", company.id),
167
+ ("type", "in", ("bank", "cash", "credit")),
168
+ ("default_account_id", "!=", False),
169
+ ]
170
+ )
171
+ accounts = journals.default_account_id
172
+ return (accounts, 1, False, False)
173
+
174
+ def _prepare_cell_data_supplier_debt(self, company, speedy):
175
+ accounts = (
176
+ self.env["res.partner"]
177
+ ._fields["property_account_payable_id"]
178
+ .get_company_dependent_fallback(self.env["res.partner"])
179
+ )
180
+ return (accounts, -1, False, False)
181
+
182
+ def _prepare_cell_data_income(self, company, speedy):
183
+ cell_type = self.cell_type
184
+ accounts = self.env["account.account"].search(
185
+ [
186
+ ("company_ids", "in", [company.id]),
187
+ ("account_type", "in", ("income", "income_other")),
188
+ ]
189
+ )
190
+ if cell_type == "income_fiscalyear":
191
+ start_date, end_date = date_utils.get_fiscal_year(
192
+ speedy["today"],
193
+ day=company.fiscalyear_last_day,
194
+ month=int(company.fiscalyear_last_month),
195
+ )
196
+ elif cell_type == "income_month":
197
+ start_date = speedy["today"] + relativedelta(day=1)
198
+ elif cell_type == "income_year":
199
+ start_date = speedy["today"] + relativedelta(day=1, month=1)
200
+ elif cell_type == "income_quarter":
201
+ month_start_quarter = 3 * ((speedy["today"].month - 1) // 3) + 1
202
+ start_date = speedy["today"] + relativedelta(
203
+ day=1, month=month_start_quarter
204
+ )
205
+ specific_domain = [("date", ">=", start_date)]
206
+ specific_tooltip = _("from %s") % format_date(self.env, start_date)
207
+ return (accounts, -1, specific_domain, specific_tooltip)
208
+
209
+ def _prepare_cell_data_customer_debt(self, company, speedy):
210
+ accounts = (
211
+ self.env["res.partner"]
212
+ ._fields["property_account_receivable_id"]
213
+ .get_company_dependent_fallback(self.env["res.partner"])
214
+ )
215
+ if hasattr(company, "account_default_pos_receivable_account_id"):
216
+ accounts |= company.account_default_pos_receivable_account_id
217
+ return (accounts, 1, False, False)
218
+
219
+ def _prepare_cell_data_customer_overdue(self, company, speedy):
220
+ accounts, sign, specific_domain, specific_tooltip = (
221
+ self._prepare_cell_data_customer_debt(company, speedy)
222
+ )
223
+ specific_domain = expression.OR(
224
+ [
225
+ [("date_maturity", "<", speedy["today"])],
226
+ [
227
+ ("date_maturity", "<=", speedy["today"]),
228
+ ("journal_id.type", "!=", "sale"),
229
+ ],
230
+ ]
231
+ )
232
+ specific_tooltip = _("with due date before %s") % format_date(
233
+ self.env, speedy["today"]
234
+ )
235
+ return (accounts, sign, specific_domain, specific_tooltip)
236
+
237
+ def _prepare_cell_data(self, company, speedy):
238
+ """Inherit this method to change the computation of a cell type"""
239
+ self.ensure_one()
240
+ cell_type = self.cell_type
241
+ value = raw_value = tooltip = warn = False
242
+ if cell_type.endswith("lock_date"):
243
+ raw_value = company[cell_type]
244
+ value = raw_value and format_date(self.env, raw_value)
245
+ tooltip = speedy["lock_date2help"][cell_type]
246
+ if self.warn:
247
+ if not raw_value:
248
+ warn = True
249
+ elif raw_value < speedy["today"] - relativedelta(
250
+ days=self.warn_lock_date_days
251
+ ):
252
+ warn = True
253
+ else:
254
+ accounts = False
255
+ if hasattr(self, f"_prepare_cell_data_{cell_type}"):
256
+ specific_method = getattr(self, f"_prepare_cell_data_{cell_type}")
257
+ accounts, sign, specific_domain, specific_tooltip = specific_method(
258
+ company, speedy
259
+ )
260
+ elif cell_type.startswith("income_"):
261
+ accounts, sign, specific_domain, specific_tooltip = (
262
+ self._prepare_cell_data_income(company, speedy)
263
+ )
264
+ if accounts:
265
+ domain = (specific_domain or []) + [
266
+ ("company_id", "=", company.id),
267
+ ("account_id", "in", accounts.ids),
268
+ ("date", "<=", speedy["today"]),
269
+ ("parent_state", "=", "posted"),
270
+ ]
271
+ rg_res = self.env["account.move.line"]._read_group(
272
+ domain, aggregates=["balance:sum"]
273
+ )
274
+ assert sign in (1, -1)
275
+ raw_value = rg_res and rg_res[0][0] * sign or 0
276
+ value = format_amount(self.env, raw_value, company.currency_id)
277
+ tooltip = _(
278
+ "Balance of account(s) %(account_codes)s%(specific)s.",
279
+ account_codes=", ".join(accounts.mapped("code")),
280
+ specific=specific_tooltip and f" {specific_tooltip}" or "",
281
+ )
282
+ res = {
283
+ "cell_type": cell_type,
284
+ "label": self.custom_label or speedy["cell_type2label"][cell_type],
285
+ "raw_value": raw_value,
286
+ "value": value or _("None"),
287
+ "tooltip": self.custom_tooltip or tooltip,
288
+ "warn": warn,
289
+ }
290
+ return res
291
+
292
+ def _update_cell_warn(self, cell_data):
293
+ self.ensure_one()
294
+ if (
295
+ not cell_data.get("warn")
296
+ and self.warn
297
+ and self.warn_type
298
+ and isinstance(cell_data["raw_value"], (int | float))
299
+ ):
300
+ raw_value = cell_data["raw_value"]
301
+ if (
302
+ (self.warn_type == "under" and raw_value < self.warn_min)
303
+ or (self.warn_type == "above" and raw_value > self.warn_max)
304
+ or (
305
+ self.warn_type == "outside"
306
+ and (raw_value < self.warn_min or raw_value > self.warn_max)
307
+ )
308
+ or (
309
+ self.warn_type == "inside"
310
+ and raw_value > self.warn_min
311
+ and raw_value < self.warn_max
312
+ )
313
+ ):
314
+ cell_data["warn"] = True
315
+
316
+ @api.depends("cell_type", "custom_label")
317
+ def _compute_display_name(self):
318
+ type2name = dict(
319
+ self.fields_get("cell_type", "selection")["cell_type"]["selection"]
320
+ )
321
+ for cell in self:
322
+ display_name = "-"
323
+ if cell.custom_label:
324
+ display_name = cell.custom_label
325
+ elif cell.cell_type:
326
+ display_name = type2name[cell.cell_type]
327
+ cell.display_name = display_name
@@ -0,0 +1,18 @@
1
+ # Copyright 2025 Akretion France (https://www.akretion.com/)
2
+ # @author: Alexis de Lattre <alexis.delattre@akretion.com>
3
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4
+
5
+ # I create default cells via post-install script instead of
6
+ # data/account_dashboard_banner_cell.xml
7
+ # to avoid the problem when a user deletes a cell that has an XMLID
8
+ # and Odoo would re-create the cells when the module is reloaded
9
+ def create_default_account_dashboard_cells(env):
10
+ vals_list = [
11
+ {"cell_type": "hard_lock_date", "sequence": 10, "warn": True},
12
+ {"cell_type": "income_fiscalyear", "sequence": 20},
13
+ {"cell_type": "customer_overdue", "sequence": 30},
14
+ {"cell_type": "customer_debt", "sequence": 40},
15
+ {"cell_type": "supplier_debt", "sequence": 50},
16
+ {"cell_type": "liquidity", "sequence": 60, "warn": True, "warn_type": "under"},
17
+ ]
18
+ env["account.dashboard.banner.cell"].create(vals_list)
@@ -0,0 +1,34 @@
1
+ Go to the menu **Invoicing \> Configuration \> Dashboard \> Dashboard
2
+ Banner Cells**: in this menu, you can add or remove cells from the
3
+ banner, change the cell type, modify the order via drag-and-drop,
4
+ customize the labels if necessary.
5
+
6
+ ![Cell configuration menu](../static/description/banner_cell_config.png)
7
+
8
+ Many cell types are available:
9
+
10
+ - **Income** with 4 options: *Fiscal Year-to-date Income*, *Year-to-date
11
+ Income*, *Quarter-to-date Income* and *Month-to-date Income*. It
12
+ displays the period balance of the accounts with type *Income* and
13
+ *Other Income*.
14
+ - **Liquidity**: it display the ending balance of the accounts linked to
15
+ a bank or cash or credit journal.
16
+ - **Customer Debt**: it displays the ending balance of the default
17
+ *Account Receivable* and, if the point of sale is installed, the
18
+ intermediary account used for unidentified customers.
19
+ - **Customer Overdue**: same as the *Customer Debt*, but limited to
20
+ journal items with a due date in the past.
21
+ - **Supplier Debt**: it displays the ending balance of the default
22
+ *Account Payable*.
23
+ - **Lock dates**: all the lock dates are available: *Tax Return Lock
24
+ Date*, *Sales Lock Date*, *Purchase Lock Date*, *Global Lock Date* and
25
+ *Hard Lock Date*.
26
+
27
+ The module is designed to allow the modification of the computation of
28
+ the different cell types by inheriting the method
29
+ *\_prepare_cell_data_<cell_type>()* or *\_prepare_cell_data()*.
30
+ It is also easy for a developper to add more cell types.
31
+
32
+ It is possible to display a cell as a warning cell (yellow background color instead of light grey): click on the *Warning* option and customize the conditions to trigger the warning.
33
+
34
+ ![Cell form with warning](../static/description/cell_form_with_warning.png)
@@ -0,0 +1 @@
1
+ - Alexis de Lattre \<<alexis.delattre@akretion.com>\>
@@ -0,0 +1,13 @@
1
+ The development of this module started with a simple analysis: accountants
2
+ tend to forget to update the lock dates. Part of the problem lies in the
3
+ fact that the lock dates can be seen in the wizard to update the lock
4
+ dates, but accountants have no incentive to start this wizard regularly
5
+ to check the lock dates. The idea was to display the lock dates in a
6
+ banner at the top of the accounting dashboard, so that accountants
7
+ have the value of the lock dates in front of their eyes.
8
+
9
+ With such a banner in the accounting dashboard to display the lock
10
+ dates, there was a great temptation to display other interesting
11
+ information in that banner, such as some key figures of the accounting:
12
+ liquidity, turnover, customer overdue, etc. It gave birth to the idea to
13
+ have a configurable banner in the accounting dashboard!
@@ -0,0 +1,3 @@
1
+ Enjoy the accounting dashboard with the banner at the top:
2
+
3
+ ![Accounting dashboard with banner](../static/description/account_dashboard_banner.png)
@@ -0,0 +1,4 @@
1
+ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2
+ access_account_dashboard_banner_cell_manager,Full access on account.dashboard.banner.cell,model_account_dashboard_banner_cell,account.group_account_manager,1,1,1,1
3
+ access_account_dashboard_banner_cell_user,Read access on account.dashboard.banner.cell,model_account_dashboard_banner_cell,account.group_account_user,1,0,0,0
4
+ access_account_dashboard_banner_cell_auditor,Read access on account.dashboard.banner.cell,model_account_dashboard_banner_cell,account.group_account_readonly,1,0,0,0