diff --git a/account_reconcile_oca/__manifest__.py b/account_reconcile_oca/__manifest__.py index d25f4ca2..1bd2adc2 100644 --- a/account_reconcile_oca/__manifest__.py +++ b/account_reconcile_oca/__manifest__.py @@ -15,6 +15,7 @@ "base_sparse_field", ], "data": [ + "views/res_config_settings.xml", "security/ir.model.access.csv", "views/account_account_reconcile.xml", "views/account_bank_statement_line.xml", @@ -22,6 +23,7 @@ "views/account_journal.xml", "views/account_move.xml", "views/account_account.xml", + "views/account_bank_statement.xml", ], "demo": ["demo/demo.xml"], "post_init_hook": "post_init_hook", diff --git a/account_reconcile_oca/models/__init__.py b/account_reconcile_oca/models/__init__.py index 5be31a27..da04aa8d 100644 --- a/account_reconcile_oca/models/__init__.py +++ b/account_reconcile_oca/models/__init__.py @@ -1,5 +1,8 @@ from . import account_reconcile_abstract from . import account_journal from . import account_bank_statement_line +from . import account_bank_statement from . import account_account_reconcile from . import account_move_line +from . import res_company +from . import res_config_settings diff --git a/account_reconcile_oca/models/account_bank_statement.py b/account_reconcile_oca/models/account_bank_statement.py new file mode 100644 index 00000000..19b9bec1 --- /dev/null +++ b/account_reconcile_oca/models/account_bank_statement.py @@ -0,0 +1,15 @@ +# Copyright 2024 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models + + +class AccountBankStatement(models.Model): + _inherit = "account.bank.statement" + + def action_open_statement(self): + self.ensure_one() + action = self.env["ir.actions.act_window"]._for_xml_id( + "account_reconcile_oca.account_bank_statement_action_edit" + ) + action["res_id"] = self.id + return action diff --git a/account_reconcile_oca/models/account_bank_statement_line.py b/account_reconcile_oca/models/account_bank_statement_line.py index f3584b35..079f3930 100644 --- a/account_reconcile_oca/models/account_bank_statement_line.py +++ b/account_reconcile_oca/models/account_bank_statement_line.py @@ -3,6 +3,9 @@ from collections import defaultdict +from dateutil import rrule +from dateutil.relativedelta import relativedelta + from odoo import Command, _, api, fields, models from odoo.exceptions import UserError from odoo.tools import float_is_zero @@ -86,6 +89,55 @@ class AccountBankStatementLine(models.Model): "account.move", default=False, store=False, prefetch=False, readonly=True ) can_reconcile = fields.Boolean(sparse="reconcile_data_info") + statement_complete = fields.Boolean( + related="statement_id.is_complete", + ) + statement_valid = fields.Boolean( + related="statement_id.is_valid", + ) + statement_balance_end_real = fields.Monetary( + related="statement_id.balance_end_real", + ) + statement_name = fields.Char( + string="Statement Name", + related="statement_id.name", + ) + reconcile_aggregate = fields.Char(compute="_compute_reconcile_aggregate") + aggregate_id = fields.Integer(compute="_compute_reconcile_aggregate") + aggregate_name = fields.Char(compute="_compute_reconcile_aggregate") + + @api.model + def _reconcile_aggregate_map(self): + lang = self.env["res.lang"]._lang_get(self.env.user.lang) + week_start = rrule.weekday(int(lang.week_start) - 1) + return { + False: lambda s: (False, False), + "statement": lambda s: (s.statement_id.id, s.statement_id.name), + "day": lambda s: (s.date.toordinal(), s.date.strftime(lang.date_format)), + "week": lambda s: ( + (s.date + relativedelta(weekday=week_start(-1))).toordinal(), + (s.date + relativedelta(weekday=week_start(-1))).strftime( + lang.date_format + ), + ), + "month": lambda s: ( + s.date.replace(day=1).toordinal(), + s.date.replace(day=1).strftime(lang.date_format), + ), + } + + @api.depends("company_id", "journal_id") + def _compute_reconcile_aggregate(self): + reconcile_aggregate_map = self._reconcile_aggregate_map() + for record in self: + reconcile_aggregate = ( + record.journal_id.reconcile_aggregate + or record.company_id.reconcile_aggregate + ) + record.reconcile_aggregate = reconcile_aggregate + record.aggregate_id, record.aggregate_name = reconcile_aggregate_map[ + reconcile_aggregate + ](record) def save(self): return {"type": "ir.actions.act_window_close"} @@ -752,3 +804,25 @@ class AccountBankStatementLine(models.Model): if vals["partner_id"] is False: vals["partner_id"] = (False, self.partner_name) return vals + + def add_statement(self): + self.ensure_one() + action = self.env["ir.actions.act_window"]._for_xml_id( + "account_reconcile_oca.account_bank_statement_action_edit" + ) + previous_line_with_statement = self.env["account.bank.statement.line"].search( + [ + ("internal_index", "<", self.internal_index), + ("journal_id", "=", self.journal_id.id), + ("state", "=", "posted"), + ("statement_id", "!=", self.statement_id.id), + ("statement_id", "!=", False), + ], + limit=1, + ) + action["context"] = { + "default_journal_id": self.journal_id.id, + "default_balance_start": previous_line_with_statement.statement_id.balance_end_real, + "split_line_id": self.id, + } + return action diff --git a/account_reconcile_oca/models/account_journal.py b/account_reconcile_oca/models/account_journal.py index 613ab41a..8bdeefa9 100644 --- a/account_reconcile_oca/models/account_journal.py +++ b/account_reconcile_oca/models/account_journal.py @@ -12,6 +12,17 @@ class AccountJournal(models.Model): default="edit", required=True, ) + company_currency_id = fields.Many2one(related="company_id.currency_id") + reconcile_aggregate = fields.Selection( + [ + ("statement", "Statement"), + ("day", "Day"), + ("week", "Week"), + ("month", "Month"), + ], + string="Reconcile aggregation", + help="Aggregation to use on reconcile view", + ) def get_rainbowman_message(self): self.ensure_one() diff --git a/account_reconcile_oca/models/res_company.py b/account_reconcile_oca/models/res_company.py new file mode 100644 index 00000000..3ed88cb6 --- /dev/null +++ b/account_reconcile_oca/models/res_company.py @@ -0,0 +1,14 @@ +# Copyright 2024 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + reconcile_aggregate = fields.Selection( + selection=lambda self: self.env["account.journal"] + ._fields["reconcile_aggregate"] + .selection + ) diff --git a/account_reconcile_oca/models/res_config_settings.py b/account_reconcile_oca/models/res_config_settings.py new file mode 100644 index 00000000..8bcc2185 --- /dev/null +++ b/account_reconcile_oca/models/res_config_settings.py @@ -0,0 +1,12 @@ +# Copyright 2024 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + reconcile_aggregate = fields.Selection( + related="company_id.reconcile_aggregate", readonly=False + ) diff --git a/account_reconcile_oca/static/src/js/reconcile/reconcile_controller.esm.js b/account_reconcile_oca/static/src/js/reconcile/reconcile_controller.esm.js index 801f233f..9c83aa86 100644 --- a/account_reconcile_oca/static/src/js/reconcile/reconcile_controller.esm.js +++ b/account_reconcile_oca/static/src/js/reconcile/reconcile_controller.esm.js @@ -1,7 +1,8 @@ /** @odoo-module */ -const {useState, useSubEnv} = owl; +const {onMounted, onWillStart, useState, useSubEnv} = owl; import {KanbanController} from "@web/views/kanban/kanban_controller"; import {View} from "@web/views/view"; +import {formatMonetary} from "@web/views/fields/formatters"; import {useService} from "@web/core/utils/hooks"; export class ReconcileController extends KanbanController { @@ -9,6 +10,8 @@ export class ReconcileController extends KanbanController { super.setup(); this.state = useState({ selectedRecordId: null, + journalBalance: 0, + currency: false, }); useSubEnv({ parentController: this, @@ -20,6 +23,39 @@ export class ReconcileController extends KanbanController { this.router = useService("router"); this.activeActions = this.props.archInfo.activeActions; this.model.addEventListener("update", () => this.selectRecord(), {once: true}); + onWillStart(() => { + this.updateJournalInfo(); + }); + onMounted(() => { + this.selectRecord(); + }); + } + get journalId() { + if (this.props.resModel === "account.bank.statement.line") { + return this.props.context.active_id; + } + return false; + } + async updateJournalInfo() { + var journalId = this.journalId; + if (!journalId) { + return; + } + var result = await this.orm.call("account.journal", "read", [ + [journalId], + ["current_statement_balance", "currency_id", "company_currency_id"], + ]); + this.state.journalBalance = result[0].current_statement_balance; + this.state.currency = (result[0].currency_id || + result[0].company_currency_id)[0]; + } + get journalBalanceStr() { + if (!this.state.journalBalance) { + return ""; + } + return formatMonetary(this.state.journalBalance, { + currencyId: this.state.currency, + }); } exposeController(controller) { this.form_controller = controller; @@ -45,6 +81,7 @@ export class ReconcileController extends KanbanController { return { resId: this.state.selectedRecordId, type: "form", + noBreadcrumbs: true, context: { ...(this.props.context || {}), form_view_ref: this.props.context.view_ref, diff --git a/account_reconcile_oca/static/src/js/reconcile/reconcile_renderer.esm.js b/account_reconcile_oca/static/src/js/reconcile/reconcile_renderer.esm.js index d031e2bc..44b662c8 100644 --- a/account_reconcile_oca/static/src/js/reconcile/reconcile_renderer.esm.js +++ b/account_reconcile_oca/static/src/js/reconcile/reconcile_renderer.esm.js @@ -2,7 +2,59 @@ import {KanbanRenderer} from "@web/views/kanban/kanban_renderer"; import {ReconcileKanbanRecord} from "./reconcile_kanban_record.esm.js"; -export class ReconcileRenderer extends KanbanRenderer {} +import {formatMonetary} from "@web/views/fields/formatters"; +import {useService} from "@web/core/utils/hooks"; + +export class ReconcileRenderer extends KanbanRenderer { + setup() { + super.setup(); + this.action = useService("action"); + this.orm = useService("orm"); + } + getAggregates() { + if ( + this.env.parentController.props.resModel !== "account.bank.statement.line" + ) { + return []; + } + const {list} = this.props; + const aggregates = []; + for (const record of list.records) { + const aggregateId = record.data.aggregate_id && record.data.aggregate_id; + if ( + aggregateId && + (!aggregates.length || + aggregates[aggregates.length - 1].id !== aggregateId) + ) { + aggregates.push({ + id: aggregateId, + name: record.data.aggregate_name, + balance: record.data.statement_balance_end_real, + balanceStr: formatMonetary(record.data.statement_balance_end_real, { + currencyId: record.data.currency_id[0], + }), + }); + } + } + return aggregates; + } + async onClickStatement(statementId) { + const action = await this.orm.call( + "account.bank.statement", + "action_open_statement", + [[statementId]], + { + context: this.props.context, + } + ); + const model = this.props.list.model; + this.action.doAction(action, { + async onClose() { + model.root.load(); + }, + }); + } +} ReconcileRenderer.components = { ...KanbanRenderer.components, diff --git a/account_reconcile_oca/static/src/js/reconcile_form/reconcile_form_controller.esm.js b/account_reconcile_oca/static/src/js/reconcile_form/reconcile_form_controller.esm.js index 571b4673..d91b4bda 100644 --- a/account_reconcile_oca/static/src/js/reconcile_form/reconcile_form_controller.esm.js +++ b/account_reconcile_oca/static/src/js/reconcile_form/reconcile_form_controller.esm.js @@ -17,6 +17,9 @@ export class ReconcileFormController extends FormController { afterExecuteAction: this.afterExecuteActionButton.bind(this), }); } + displayName() { + return this.env.config.getDisplayName(); + } async reloadFormController() { var is_reconciled = this.model.root.data.is_reconciled; await this.model.root.load(); diff --git a/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js b/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js index 0f64280b..66adf0e4 100644 --- a/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js +++ b/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js @@ -45,7 +45,6 @@ export class AccountReconcileDataWidget extends Component { ); data[line].amount_currency_format = fieldUtils.format.monetary( data[line].currency_amount, - undefined, { currency: session.get_currency(data[line].line_currency_id), } @@ -53,7 +52,6 @@ export class AccountReconcileDataWidget extends Component { if (data[line].original_amount) { data[line].original_amount_format = fieldUtils.format.monetary( data[line].original_amount, - undefined, { currency: session.get_currency(data[line].currency_id), } diff --git a/account_reconcile_oca/static/src/scss/reconcile.scss b/account_reconcile_oca/static/src/scss/reconcile.scss index bd33d4c2..c7186814 100644 --- a/account_reconcile_oca/static/src/scss/reconcile.scss +++ b/account_reconcile_oca/static/src/scss/reconcile.scss @@ -6,7 +6,23 @@ flex-flow: row wrap; height: 100%; .o_kanban_renderer.o_kanban_ungrouped .o_kanban_record { + &:hover { + .o_reconcile_create_statement { + opacity: 100; + } + } margin: 0 0 0; + min-width: fit-content; + width: 100%; + .o_reconcile_create_statement { + position: absolute; + height: 4px; + margin: 0; + padding: 2px 0 0 0; + border: 0; + top: -14px; + opacity: 0; + } > div { border-right: thick solid rgba(0, 0, 0, 0); } diff --git a/account_reconcile_oca/static/src/xml/reconcile.xml b/account_reconcile_oca/static/src/xml/reconcile.xml index 574678e1..ac26c792 100644 --- a/account_reconcile_oca/static/src/xml/reconcile.xml +++ b/account_reconcile_oca/static/src/xml/reconcile.xml @@ -6,6 +6,43 @@ t-inherit-mode="primary" owl="1" > + +
+ Global Balance + +
+ +
+ + + +
+ + + +
+
+
+ + + + + Edit Bank statement + account.bank.statement + 99 + +
+ + + + + + + + + + + +
+
+
+ + + Edit Bank Statement + account.bank.statement + form + + new + +
diff --git a/account_reconcile_oca/views/account_bank_statement_line.xml b/account_reconcile_oca/views/account_bank_statement_line.xml index 6460b493..ca16bdd2 100644 --- a/account_reconcile_oca/views/account_bank_statement_line.xml +++ b/account_reconcile_oca/views/account_bank_statement_line.xml @@ -11,9 +11,28 @@ + + + + + +
- Reconcile bank statement lines + Statement lines account.bank.statement.line [('journal_id', '=', active_id)] + diff --git a/account_reconcile_oca/views/res_config_settings.xml b/account_reconcile_oca/views/res_config_settings.xml new file mode 100644 index 00000000..39358707 --- /dev/null +++ b/account_reconcile_oca/views/res_config_settings.xml @@ -0,0 +1,38 @@ + + + + + + res.config.settings + + + +
+
+
+
+
+
+
+
+
+ + + +