[IMP] account_reconcile_oca: Finish creation of module

* refactoring JS in order to add logic
* Improve views
* Minor fixes in Odoo code in order to fix all possible options
pull/500/head
Enric Tobella 2023-03-03 23:07:35 +01:00
parent 0ea9421d14
commit 8ad989920e
30 changed files with 573 additions and 145 deletions

View File

@ -27,17 +27,14 @@
"post_init_hook": "post_init_hook",
"assets": {
"web.assets_backend": [
"account_reconcile_oca/static/src/js/reconcile_manual_view.esm.js",
"account_reconcile_oca/static/src/js/reconcile_data_widget.esm.js",
"account_reconcile_oca/static/src/js/reconcile_chatter_field.esm.js",
"account_reconcile_oca/static/src/js/selection_badge_uncheck.esm.js",
"account_reconcile_oca/static/src/js/reconcile_move_line_view.esm.js",
"account_reconcile_oca/static/src/js/reconcile_move_line_widget.esm.js",
"account_reconcile_oca/static/src/js/reconcile_kanban_record.esm.js",
"account_reconcile_oca/static/src/js/reconcile_renderer.esm.js",
"account_reconcile_oca/static/src/js/reconcile_controller.esm.js",
"account_reconcile_oca/static/src/js/reconcile_view.esm.js",
"account_reconcile_oca/static/src/js/reconcile_form_view.esm.js",
"account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js",
"account_reconcile_oca/static/src/js/widgets/reconcile_chatter_field.esm.js",
"account_reconcile_oca/static/src/js/widgets/selection_badge_uncheck.esm.js",
"account_reconcile_oca/static/src/js/widgets/reconcile_move_line_widget.esm.js",
"account_reconcile_oca/static/src/js/reconcile_move_line/*.esm.js",
"account_reconcile_oca/static/src/js/reconcile_form/*.esm.js",
"account_reconcile_oca/static/src/js/reconcile_manual/*.esm.js",
"account_reconcile_oca/static/src/js/reconcile/*.esm.js",
"account_reconcile_oca/static/src/xml/reconcile.xml",
"account_reconcile_oca/static/src/scss/reconcile.scss",
],

View File

@ -17,11 +17,11 @@ class AccountAccountReconcile(models.Model):
reconcile_data_info = fields.Serialized(inverse="_inverse_reconcile_data_info")
partner_id = fields.Many2one("res.partner")
account_id = fields.Many2one("account.account")
name = fields.Char()
is_reconciled = fields.Boolean()
currency_id = fields.Many2one("res.currency")
partner_id = fields.Many2one("res.partner", readonly=True)
account_id = fields.Many2one("account.account", readonly=True)
name = fields.Char(readonly=True)
is_reconciled = fields.Boolean(readonly=True)
currency_id = fields.Many2one("res.currency", readonly=True)
@property
def _table_query(self):

View File

@ -40,12 +40,23 @@ class AccountBankStatementLine(models.Model):
)
manual_partner_id = fields.Many2one(
"res.partner",
domain=[('parent_id', '=', False)],
domain=[("parent_id", "=", False)],
check_company=True,
store=False,
default=False,
prefetch=False,
)
analytic_distribution = fields.Json(
store=False,
default=False,
prefetch=False,
)
analytic_precision = fields.Integer(
store=False,
default=lambda self: self.env["decimal.precision"].precision_get(
"Percentage Analytic"
),
)
manual_model_id = fields.Many2one(
"account.reconcile.model",
check_company=True,
@ -54,13 +65,21 @@ class AccountBankStatementLine(models.Model):
prefetch=False,
domain=[("rule_type", "=", "writeoff_button")],
)
manual_delete = fields.Boolean(
store=False,
default=False,
prefetch=False,
)
manual_name = fields.Char(store=False, default=False, prefetch=False)
manual_amount = fields.Monetary(store=False, default=False, prefetch=False)
manual_original_amount = fields.Monetary(
default=False, store=False, prefetch=False, readonly=True
)
manual_move_type = fields.Selection(
lambda r: r.env["account.move"]._fields["move_type"].selection,
default=False,
store=False,
prefetch=False,
readonly=True,
)
manual_move_id = fields.Many2one(
"account.move", default=False, store=False, prefetch=False, readonly=True
)
can_reconcile = fields.Boolean(sparse="reconcile_data_info")
def save(self):
@ -86,7 +105,8 @@ class AccountBankStatementLine(models.Model):
data,
self.manual_model_id,
self.reconcile_data_info["reconcile_auxiliary_id"],
)
),
self.manual_reference
)
else:
# Refreshing data
@ -116,12 +136,14 @@ class AccountBankStatementLine(models.Model):
)
)
self.reconcile_data_info = self._recompute_suspense_line(
new_data, self.reconcile_data_info["reconcile_auxiliary_id"]
new_data,
self.reconcile_data_info["reconcile_auxiliary_id"],
self.manual_reference,
)
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
self.add_account_move_line_id = False
def _recompute_suspense_line(self, data, reconcile_auxiliary_id):
def _recompute_suspense_line(self, data, reconcile_auxiliary_id, manual_reference):
can_reconcile = True
total_amount = 0
new_data = []
@ -175,6 +197,7 @@ class AccountBankStatementLine(models.Model):
"counterparts": counterparts,
"reconcile_auxiliary_id": reconcile_auxiliary_id,
"can_reconcile": can_reconcile,
"manual_reference": manual_reference,
}
def _check_line_changed(self, line):
@ -208,7 +231,11 @@ class AccountBankStatementLine(models.Model):
"manual_name": False,
"manual_partner_id": False,
"manual_line_id": False,
"manual_move_id": False,
"manual_move_type": False,
"manual_kind": False,
"manual_original_amount": False,
"analytic_distribution": False,
}
)
continue
@ -220,10 +247,17 @@ class AccountBankStatementLine(models.Model):
line.get("partner_id") and line["partner_id"][0]
)
self.manual_line_id = line["id"]
self.analytic_distribution = line.get("analytic_distribution", {})
if self.manual_line_id:
self.manual_move_id = self.manual_line_id.move_id
self.manual_move_type = self.manual_line_id.move_id.move_type
self.manual_kind = line["kind"]
self.manual_original_amount = line.get("original_amount", 0.0)
new_data.append(line)
self.reconcile_data_info = self._recompute_suspense_line(
new_data, self.reconcile_data_info["reconcile_auxiliary_id"]
new_data,
self.reconcile_data_info["reconcile_auxiliary_id"],
self.manual_reference,
)
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
@ -232,6 +266,7 @@ class AccountBankStatementLine(models.Model):
"manual_partner_id",
"manual_name",
"manual_amount",
"analytic_distribution",
)
def _onchange_manual_reconcile_vals(self):
self.ensure_one()
@ -256,6 +291,7 @@ class AccountBankStatementLine(models.Model):
"debit": self.manual_amount
if self.manual_amount > 0
else 0.0,
"analytic_distribution": self.analytic_distribution,
"kind": line["kind"]
if line["kind"] != "suspense"
else "other",
@ -265,7 +301,9 @@ class AccountBankStatementLine(models.Model):
self._update_move_partner()
new_data.append(line)
self.reconcile_data_info = self._recompute_suspense_line(
new_data, self.reconcile_data_info["reconcile_auxiliary_id"]
new_data,
self.reconcile_data_info["reconcile_auxiliary_id"],
self.manual_reference,
)
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
@ -310,21 +348,26 @@ class AccountBankStatementLine(models.Model):
for line in reconcile_model._apply_lines_for_bank_widget(
-liquidity_amount, self._retrieve_partner(), self
):
amount = line["amount_currency"]
new_line = {
"reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id,
"id": False,
"amount": amount,
"debit": amount if amount > 0 else 0.0,
"credit": -amount if amount < 0 else 0.0,
"kind": "other",
"account_id": self.env["account.account"]
.browse(line["account_id"])
.name_get()[0],
"date": fields.Date.to_string(self.date),
"name": line.get("name"),
"currency_id": line.get("currency_id"),
}
new_line = line.copy()
amount = line.get("amount_currency")
if self.foreign_currency_id:
amount = self.foreign_currency_id.compute(
amount, self.journal_id.currency_id or self.company_currency_id
)
new_line.update(
{
"reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id,
"id": False,
"amount": amount,
"debit": amount if amount > 0 else 0,
"credit": -amount if amount < 0 else 0,
"kind": "other",
"account_id": self.env["account.account"]
.browse(line["account_id"])
.name_get()[0],
"date": fields.Date.to_string(self.date),
}
)
reconcile_auxiliary_id += 1
if line.get("partner_id"):
new_line["partner_id"] = (
@ -377,7 +420,8 @@ class AccountBankStatementLine(models.Model):
return self._recompute_suspense_line(
*self._reconcile_data_by_model(
data, res["model"], reconcile_auxiliary_id
)
),
self.manual_reference
)
elif res and res.get("amls"):
amount = self.amount
@ -387,10 +431,13 @@ class AccountBankStatementLine(models.Model):
)
amount -= line_data.get("amount")
data.append(line_data)
return self._recompute_suspense_line(data, reconcile_auxiliary_id)
return self._recompute_suspense_line(
data, reconcile_auxiliary_id, self.manual_reference
)
return self._recompute_suspense_line(
data + [self._get_reconcile_line(line, "other") for line in other_lines],
reconcile_auxiliary_id,
self.manual_reference,
)
def clean_reconcile(self):
@ -414,7 +461,9 @@ class AccountBankStatementLine(models.Model):
to_reconcile = []
with move._check_balanced(container):
move.with_context(
skip_account_move_synchronization=True, force_delete=True
skip_account_move_synchronization=True,
force_delete=True,
skip_invoice_sync=True,
).write(
{
"line_ids": lines_to_remove,
@ -425,7 +474,11 @@ class AccountBankStatementLine(models.Model):
continue
line = (
self.env["account.move.line"]
.with_context(check_move_validity=False)
.with_context(
check_move_validity=False,
skip_sync_invoice=True,
skip_invoice_sync=True,
)
.create(self._reconcile_move_line_vals(line_vals))
)
if line_vals.get("counterpart_line_id"):
@ -461,7 +514,11 @@ class AccountBankStatementLine(models.Model):
).copy_data({"move_id": move.id})[0]
to_reconcile[line.account_id.id] |= (
self.env["account.move.line"]
.with_context(check_move_validity=False, skip_invoice_sync=True)
.with_context(
check_move_validity=False,
skip_sync_invoice=True,
skip_invoice_sync=True,
)
.create(line_data)
)
move.write(
@ -509,17 +566,7 @@ class AccountBankStatementLine(models.Model):
)
def _unreconcile_bank_line_edit(self, data):
self.move_id.button_draft()
self.move_id.line_ids.unlink()
self.move_id.write(
{
"line_ids": [
(0, 0, line_vals)
for line_vals in self._prepare_move_line_default_vals()
]
}
)
self.move_id.action_post()
self.action_undo_reconciliation()
def _unreconcile_bank_line_keep(self, data):
raise UserError(_("Keep suspense move lines mode cannot be unreconciled"))
@ -531,6 +578,13 @@ class AccountBankStatementLine(models.Model):
"partner_id": line.get("partner_id") and line["partner_id"][0],
"credit": line["credit"],
"debit": line["debit"],
"tax_ids": line.get("tax_ids", []),
"tax_tag_ids": line.get("tax_tag_ids", []),
"group_tax_id": line.get("group_tax_id"),
"tax_repartition_line_id": line.get("tax_repartition_line_id"),
"analytic_distribution": line.get("analytic_distribution"),
"name": line.get("name"),
"reconcile_model_id": line.get("reconcile_model_id"),
}
@api.model_create_multi
@ -556,7 +610,8 @@ class AccountBankStatementLine(models.Model):
data = record._recompute_suspense_line(
*record._reconcile_data_by_model(
data, res["model"], reconcile_auxiliary_id
)
),
self.manual_reference
)
elif res.get("amls"):
amount = self.amount
@ -566,10 +621,65 @@ class AccountBankStatementLine(models.Model):
)
amount -= line_data.get("amount")
data.append(line_data)
data = record._recompute_suspense_line(data, reconcile_auxiliary_id)
data = record._recompute_suspense_line(
data, reconcile_auxiliary_id, self.manual_reference
)
if not data.get("can_reconcile"):
continue
getattr(
record, "_reconcile_bank_line_%s" % record.journal_id.reconcile_mode
)(data["data"])
return result
def button_manual_reference_full_paid(self):
self.ensure_one()
if not self.reconcile_data_info["manual_reference"]:
return
manual_reference = self.reconcile_data_info["manual_reference"]
data = self.reconcile_data_info.get("data", [])
new_data = []
reconcile_auxiliary_id = self.reconcile_data_info["reconcile_auxiliary_id"]
for line in data:
if line["reference"] == manual_reference and line.get("id"):
total_amount = -line["amount"] + line["original_amount_unsigned"]
original_amount = line["original_amount_unsigned"]
new_data.append(
self._get_reconcile_line(
self.env["account.move.line"].browse(line["id"]),
"other",
is_counterpart=True,
max_amount=original_amount,
)
)
new_data.append(
{
"reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id,
"id": False,
"account_id": line["account_id"],
"partner_id": line.get("partner_id"),
"date": line["date"],
"name": line["name"],
"amount": -total_amount,
"credit": total_amount if total_amount > 0 else 0.0,
"debit": -total_amount if total_amount < 0 else 0.0,
"kind": "other",
"currency_id": line["currency_id"],
}
)
reconcile_auxiliary_id += 1
else:
new_data.append(line)
self.reconcile_data_info = self._recompute_suspense_line(
new_data, reconcile_auxiliary_id, self.manual_reference
)
self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
def action_to_check(self):
self.ensure_one()
self.move_id.to_check = True
if self.can_reconcile and self.journal_id.reconcile_mode == "edit":
self.reconcile_bank_line()
def action_checked(self):
self.ensure_one()
self.move_id.to_check = False

View File

@ -13,14 +13,6 @@ class AccountJournal(models.Model):
required=True,
)
def action_open_reconcile_to_check(self):
self.ensure_one()
action = self.env["ir.actions.act_window"]._for_xml_id(
"account_reconcile_oca.action_bank_statement_line_reconcile"
)
action["domain"] = [("id", "=", self.to_check_ids().ids)]
return action
def get_rainbowman_message(self):
self.ensure_one()
if self.get_journal_dashboard_datas()["number_to_reconcile"] > 0:

View File

@ -53,12 +53,14 @@ class AccountReconcileAbstract(models.AbstractModel):
"credit": -amount if amount < 0 else 0.0,
"amount": amount,
"currency_id": line.currency_id.id,
"analytic_distribution": line.analytic_distribution,
"kind": kind,
}
if not float_is_zero(
amount - original_amount, precision_digits=line.currency_id.decimal_places
):
vals["original_amount"] = abs(original_amount)
vals["original_amount_unsigned"] = original_amount
if is_counterpart:
vals["counterpart_line_id"] = line.id
return vals

View File

@ -17,6 +17,7 @@ export class ReconcileController extends KanbanController {
this.effect = useService("effect");
this.orm = useService("orm");
this.action = useService("action");
this.router = useService("router");
this.activeActions = this.props.archInfo.activeActions;
this.model.addEventListener("update", () => this.selectRecord(), {once: true});
}
@ -55,7 +56,9 @@ export class ReconcileController extends KanbanController {
}
async selectRecord(record) {
var resId = undefined;
if (record === undefined) {
if (record === undefined && this.props.resId) {
resId = this.props.resId;
} else if (record === undefined) {
var records = this.model.root.records.filter(
(modelRecord) =>
!modelRecord.data.is_reconciled || modelRecord.data.to_check
@ -85,10 +88,14 @@ export class ReconcileController extends KanbanController {
if (!this.state.selectedRecordId || this.state.selectedRecordId !== resId) {
this.state.selectedRecordId = resId;
}
this.updateURL(resId);
}
async openRecord(record) {
this.selectRecord(record);
}
updateURL(resId) {
this.router.pushState({id: resId});
}
}
ReconcileController.components = {
...ReconcileController.components,

View File

@ -1,8 +1,6 @@
/** @odoo-module */
import {FormController} from "@web/views/form/form_controller";
import {formView} from "@web/views/form/form_view";
import {registry} from "@web/core/registry";
import {useService} from "@web/core/utils/hooks";
import {useViewButtons} from "@web/views/view_button/view_button_hook";
const {useRef} = owl;
@ -42,10 +40,3 @@ export class ReconcileFormController extends FormController {
}
}
}
export const ReconcileFormView = {
...formView,
Controller: ReconcileFormController,
};
registry.category("views").add("reconcile_form", ReconcileFormView);

View File

@ -0,0 +1,32 @@
/** @odoo-module */
import {Notebook} from "@web/core/notebook/notebook";
import {onWillDestroy} from "@odoo/owl";
export class ReconcileFormNotebook extends Notebook {
setup() {
super.setup(...arguments);
const onPageNavigate = this.onPageNavigate.bind(this);
this.env.bus.addEventListener("RECONCILE_PAGE_NAVIGATE", onPageNavigate);
onWillDestroy(() => {
this.env.bus.removeEventListener("RECONCILE_PAGE_NAVIGATE", onPageNavigate);
});
}
onPageNavigate(ev) {
for (const page of this.pages) {
if (
ev.detail.detail.name === page[1].name &&
this.state.currentPage !== page[0]
) {
ev.preventDefault();
ev.detail.detail.originalEv.preventDefault();
this.state.currentPage = page[0];
return;
}
}
}
}
ReconcileFormNotebook.props = {
...Notebook.props,
};

View File

@ -0,0 +1,11 @@
/** @odoo-module */
import {FormRenderer} from "@web/views/form/form_renderer";
import {ReconcileFormNotebook} from "./reconcile_form_notebook.esm.js";
export class ReconcileFormRenderer extends FormRenderer {}
ReconcileFormRenderer.components = {
...ReconcileFormRenderer.components,
Notebook: ReconcileFormNotebook,
};

View File

@ -0,0 +1,14 @@
/** @odoo-module */
import {ReconcileFormController} from "./reconcile_form_controller.esm.js";
import {ReconcileFormRenderer} from "./reconcile_form_renderer.esm.js";
import {formView} from "@web/views/form/form_view";
import {registry} from "@web/core/registry";
export const ReconcileFormView = {
...formView,
Controller: ReconcileFormController,
Renderer: ReconcileFormRenderer,
};
registry.category("views").add("reconcile_form", ReconcileFormView);

View File

@ -1,14 +1,13 @@
/** @odoo-module */
import {FormController} from "@web/views/form/form_controller";
import {formView} from "@web/views/form/form_view";
import {registry} from "@web/core/registry";
import {useViewButtons} from "@web/views/view_button/view_button_hook";
const {useRef} = owl;
export class FormManualReconcileController extends FormController {
export class ReconcileManualController extends FormController {
setup() {
super.setup(...arguments);
this.env.exposeController(this);
const rootRef = useRef("root");
useViewButtons(this.model, rootRef, {
reload: this.reloadFormController.bind(this),
@ -29,10 +28,3 @@ export class FormManualReconcileController extends FormController {
}
}
}
export const FormManualReconcileView = {
...formView,
Controller: FormManualReconcileController,
};
registry.category("views").add("reconcile_manual", FormManualReconcileView);

View File

@ -0,0 +1,12 @@
/** @odoo-module */
import {ReconcileManualController} from "./reconcile_manual_controller.esm.js";
import {formView} from "@web/views/form/form_view";
import {registry} from "@web/core/registry";
export const FormManualReconcileView = {
...formView,
Controller: ReconcileManualController,
};
registry.category("views").add("reconcile_manual", FormManualReconcileView);

View File

@ -0,0 +1,18 @@
/** @odoo-module */
import {ListController} from "@web/views/list/list_controller";
export class ReconcileMoveLineController extends ListController {
async openRecord(record) {
var data = {};
data[this.props.parentField] = [record.resId, record.display_name];
this.props.parentRecord.update(data);
}
}
ReconcileMoveLineController.template = `account_reconcile_oca.ReconcileMoveLineController`;
ReconcileMoveLineController.props = {
...ListController.props,
parentRecord: {type: Object, optional: true},
parentField: {type: String, optional: true},
};

View File

@ -0,0 +1,22 @@
/** @odoo-module */
import {ListRenderer} from "@web/views/list/list_renderer";
export class ReconcileMoveLineRenderer extends ListRenderer {
getRowClass(record) {
var classes = super.getRowClass(record);
if (
this.props.parentRecord.data.reconcile_data_info.counterparts.includes(
record.resId
)
) {
classes += " o_field_account_reconcile_oca_move_line_selected";
}
return classes;
}
}
ReconcileMoveLineRenderer.props = [
...ListRenderer.props,
"parentRecord",
"parentField",
];

View File

@ -0,0 +1,15 @@
/** @odoo-module */
import {ReconcileMoveLineController} from "./reconcile_move_line_controller.esm.js";
import {ReconcileMoveLineRenderer} from "./reconcile_move_line_renderer.esm.js";
import {listView} from "@web/views/list/list_view";
import {registry} from "@web/core/registry";
export const ReconcileMoveLineView = {
...listView,
Controller: ReconcileMoveLineController,
Renderer: ReconcileMoveLineRenderer,
};
registry.category("views").add("reconcile_move_line", ReconcileMoveLineView);

View File

@ -1,46 +0,0 @@
/** @odoo-module */
import {ListController} from "@web/views/list/list_controller";
import {ListRenderer} from "@web/views/list/list_renderer";
import {listView} from "@web/views/list/list_view";
import {registry} from "@web/core/registry";
export class ReconcileMoveLineRenderer extends ListRenderer {
getRowClass(record) {
var classes = super.getRowClass(record);
if (
this.props.parentRecord.data.reconcile_data_info.counterparts.includes(
record.resId
)
) {
classes += " o_field_account_reconcile_oca_move_line_selected";
}
return classes;
}
}
ReconcileMoveLineRenderer.props = [
...ListRenderer.props,
"parentRecord",
"parentField",
];
export class ReconcileMoveLineController extends ListController {
async openRecord(record) {
var data = {};
data[this.props.parentField] = [record.resId, record.display_name];
this.props.parentRecord.update(data);
}
}
ReconcileMoveLineController.template = `account_reconcile_oca.ReconcileMoveLineController`;
ReconcileMoveLineController.props = {
...ListController.props,
parentRecord: {type: Object, optional: true},
parentField: {type: String, optional: true},
};
export const ReconcileMoveLineView = {
...listView,
Controller: ReconcileMoveLineController,
Renderer: ReconcileMoveLineRenderer,
};
registry.category("views").add("reconcile_move_line", ReconcileMoveLineView);

View File

@ -1,7 +1,7 @@
/** @odoo-module **/
import {registry} from "@web/core/registry";
import {ChatterContainer} from "@mail/components/chatter_container/chatter_container";
import {registry} from "@web/core/registry";
const {Component} = owl;

View File

@ -1,8 +1,8 @@
/** @odoo-module **/
import fieldUtils from "web.field_utils";
import session from "web.session";
import {registry} from "@web/core/registry";
import session from "web.session";
const {Component} = owl;
@ -56,6 +56,13 @@ export class AccountReconcileDataWidget extends Component {
this.props.record.update({
manual_reference: line.reference,
});
const triggerEv = new CustomEvent("reconcile-page-navigate", {
detail: {
name: "manual",
originalEv: ev,
},
});
this.env.bus.trigger("RECONCILE_PAGE_NAVIGATE", triggerEv);
}
}
AccountReconcileDataWidget.template = "account_reconcile_oca.ReconcileDataWidget";

View File

@ -63,6 +63,9 @@
&.liquidity {
font-weight: bold;
}
&.selected {
background-color: rgba($o-brand-primary, 0.2);
}
}
}
}

View File

@ -74,7 +74,7 @@
>
<tr
t-on-click="(ev) => this.selectReconcileLine(ev, reconcile_line)"
t-att-class="'o_reconcile_widget_line ' + reconcile_line.kind"
t-att-class="'o_reconcile_widget_line ' + reconcile_line.kind + (props.record.data.manual_reference == reconcile_line.reference ? ' selected ' : ' ')"
>
<td t-esc="reconcile_line.account_id[1]" />
<td>

View File

@ -64,6 +64,87 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
# Testing reconcile action
def test_reconcile_invoice_currency(self):
inv1 = self.create_invoice(currency_id=self.currency_usd_id, invoice_amount=100)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
}
)
bank_stmt_line = self.acc_bank_stmt_line_model.create(
{
"name": "testLine",
"journal_id": self.bank_journal_euro.id,
"statement_id": bank_stmt.id,
"amount": 50,
"amount_currency": 100,
"foreign_currency_id": self.currency_usd_id,
"date": time.strftime("%Y-07-15"),
}
)
with Form(
bank_stmt_line,
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
) as f:
self.assertFalse(f.can_reconcile)
f.add_account_move_line_id = inv1.line_ids.filtered(
lambda l: l.account_id.account_type == "asset_receivable"
)
self.assertFalse(f.add_account_move_line_id)
self.assertTrue(f.can_reconcile)
def test_reconcile_invoice_reconcile_full(self):
"""
We want to test the reconcile widget for bank statements on invoices.
As we use edit mode by default, we will also check what happens when
we press unreconcile
"""
inv1 = self.create_invoice(
currency_id=self.currency_euro_id, invoice_amount=100
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
}
)
bank_stmt_line = self.acc_bank_stmt_line_model.create(
{
"name": "testLine",
"journal_id": self.bank_journal_euro.id,
"statement_id": bank_stmt.id,
"amount": 50,
"date": time.strftime("%Y-07-15"),
}
)
receivable1 = inv1.line_ids.filtered(
lambda l: l.account_id.account_type == "asset_receivable"
)
with Form(
bank_stmt_line,
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
) as f:
self.assertFalse(f.can_reconcile)
f.add_account_move_line_id = receivable1
self.assertFalse(f.add_account_move_line_id)
self.assertTrue(f.can_reconcile)
f.manual_reference = "account.move.line;%s" % receivable1.id
self.assertEqual(-50, f.manual_amount)
self.assertEqual(2, len(bank_stmt_line.reconcile_data_info["data"]))
bank_stmt_line.button_manual_reference_full_paid()
self.assertEqual(3, len(bank_stmt_line.reconcile_data_info["data"]))
with Form(
bank_stmt_line,
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
) as f:
f.manual_reference = "account.move.line;%s" % receivable1.id
self.assertEqual(-100, f.manual_amount)
def test_reconcile_invoice_unreconcile(self):
"""
We want to test the reconcile widget for bank statements on invoices.
@ -348,6 +429,85 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
with self.assertRaises(UserError):
bank_stmt_line.unreconcile_bank_line()
# Testing to check functionality
def test_reconcile_invoice_to_check_reconciled(self):
"""
We want to test the reconcile widget for bank statements on invoices.
As we use edit mode by default, we will also check what happens when
we press unreconcile
"""
inv1 = self.create_invoice(
currency_id=self.currency_euro_id, invoice_amount=100
)
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
}
)
bank_stmt_line = self.acc_bank_stmt_line_model.create(
{
"name": "testLine",
"journal_id": self.bank_journal_euro.id,
"statement_id": bank_stmt.id,
"amount": 100,
"date": time.strftime("%Y-07-15"),
}
)
receivable1 = inv1.line_ids.filtered(
lambda l: l.account_id.account_type == "asset_receivable"
)
with Form(
bank_stmt_line,
view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
) as f:
self.assertFalse(f.can_reconcile)
f.add_account_move_line_id = receivable1
self.assertTrue(f.can_reconcile)
self.assertFalse(bank_stmt_line.is_reconciled)
self.assertFalse(bank_stmt_line.to_check)
bank_stmt_line.action_to_check()
self.assertTrue(bank_stmt_line.is_reconciled)
self.assertTrue(bank_stmt_line.to_check)
bank_stmt_line.action_checked()
self.assertTrue(bank_stmt_line.is_reconciled)
self.assertFalse(bank_stmt_line.to_check)
def test_reconcile_invoice_to_check_not_reconciled(self):
"""
We want to test the reconcile widget for bank statements on invoices.
As we use edit mode by default, we will also check what happens when
we press unreconcile
"""
bank_stmt = self.acc_bank_stmt_model.create(
{
"company_id": self.env.ref("base.main_company").id,
"journal_id": self.bank_journal_euro.id,
"date": time.strftime("%Y-07-15"),
"name": "test",
}
)
bank_stmt_line = self.acc_bank_stmt_line_model.create(
{
"name": "testLine",
"journal_id": self.bank_journal_euro.id,
"statement_id": bank_stmt.id,
"amount": 100,
"date": time.strftime("%Y-07-15"),
}
)
self.assertFalse(bank_stmt_line.is_reconciled)
self.assertFalse(bank_stmt_line.to_check)
bank_stmt_line.action_to_check()
self.assertFalse(bank_stmt_line.is_reconciled)
self.assertTrue(bank_stmt_line.to_check)
bank_stmt_line.action_checked()
self.assertFalse(bank_stmt_line.is_reconciled)
self.assertFalse(bank_stmt_line.to_check)
# Testing widget
def test_widget_invoice_clean(self):
@ -545,10 +705,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
# Testing actions
def test_bank_statement_action_to_check(self):
action = self.bank_journal_euro.action_open_reconcile_to_check()
self.assertFalse(self.env[action["res_model"]].search(action["domain"]))
def test_bank_statement_rainbowman(self):
message = self.bank_journal_euro.get_rainbowman_message()
self.assertTrue(message)

View File

@ -11,6 +11,7 @@
<field name="is_reconciled" />
<field name="currency_id" />
<field name="foreign_currency_id" />
<field name="to_check" />
<templates>
<t t-name="kanban-box">
<div
@ -43,6 +44,12 @@
<field name="payment_ref" />
</div>
<div class="col-4" style="text-align:right">
<div
t-if="record.to_check.raw_value"
class="badge text-bg-warning"
>
To check
</div>
<div
t-if="record.is_reconciled.raw_value"
class="badge text-bg-success"
@ -121,7 +128,8 @@
<button
name="reconcile_bank_line"
type="object"
string="Reconcile"
string="Validate"
accesskey="v"
class="btn btn-primary"
attrs="{'invisible': ['|', ('is_reconciled', '=', True), ('can_reconcile', '=', False)]}"
/>
@ -135,7 +143,8 @@
<button
name="unreconcile_bank_line"
type="object"
string="Unreconcile"
string="Reset"
accesskey="r"
class="btn btn-warning"
attrs="{'invisible': [('is_reconciled', '=', False)]}"
confirm="Are you sure that the move should be unreconciled?"
@ -147,9 +156,25 @@
class="btn btn-secondary"
attrs="{'invisible': [('is_reconciled', '=', True)]}"
/>
<button
name="action_to_check"
string="To Check"
class="btn btn-secondary"
accesskey="c"
type="object"
attrs="{'invisible': [('to_check', '=', True)]}"
/>
<button
name="action_checked"
string="Set as Checked"
accesskey="c"
type="object"
attrs="{'invisible': [('to_check', '=', False)]}"
/>
<button
name="action_show_move"
type="object"
accesskey="m"
string="View move"
class="btn btn-info"
/>
@ -158,6 +183,7 @@
<field name="id" invisible="1" />
<field name="name" invisible="1" />
<field name="can_reconcile" invisible="1" />
<field name="to_check" invisible="1" />
<field name="partner_id" invisible="1" />
<field name="company_id" invisible="1" />
<field name="journal_id" invisible="1" />
@ -208,6 +234,13 @@
string="Partner"
attrs="{'readonly': ['|', '|', ('manual_reference', '=', False), ('is_reconciled', '=', True), '&amp;', ('manual_line_id', '!=', False), ('manual_kind', '!=', 'liquidity')]}"
/>
<field
name="analytic_distribution"
widget="analytic_distribution"
groups="analytic.group_analytic_accounting"
options="{'account_field': 'manual_account_id', 'business_domain': 'general'}"
attrs="{'invisible': ['|', ('manual_kind', '=', 'liquidity'), ('manual_reference', '=', False)], 'readonly': [('is_reconciled', '=', True)]}"
/>
</group>
<group>
<field
@ -220,6 +253,35 @@
string="Amount"
attrs="{'readonly': ['|', ('manual_reference', '=', False), ('is_reconciled', '=', True)]}"
/>
<field name="manual_original_amount" invisible="1" />
<field name="manual_move_type" invisible="1" />
<label
for="manual_move_id"
string=""
attrs="{'invisible': ['|', ('manual_move_type', 'not in', ['in_invoice', 'in_refund', 'out_invoice', 'out_refund']), ('manual_original_amount', '=', 0)]}"
/>
<div
attrs="{'invisible': ['|', ('manual_move_type', 'not in', ['in_invoice', 'in_refund', 'out_invoice', 'out_refund']), ('manual_original_amount', '=', 0)]}"
>
Invoice <field
class="oe_inline"
name="manual_move_id"
/>
with an open amount <field
class="oe_inline"
name="manual_original_amount"
/> will be reduced by <field
class="oe_inline"
name="manual_amount"
readonly="1"
/>.
<br />
You might want to set the invoice as <button
name="button_manual_reference_full_paid"
type="object"
method_args="[1]"
>fully paid</button>.
</div>
</group>
</group>
</page>
@ -268,6 +330,28 @@
</p>
</field>
</record>
<record
id="action_bank_statement_line_reconcile_to_check"
model="ir.actions.act_window"
>
<field name="name">Reconcile bank statement lines</field>
<field name="res_model">account.bank.statement.line</field>
<field name="domain">[('journal_id', '=', active_id)]</field>
<field
name="context"
>{'default_journal_id': active_id, 'search_default_to_check': True, 'view_ref': 'account_reconcile_oca.bank_statement_line_form_reconcile_view'}</field>
<field name="view_mode">tree</field>
<field
name="view_ids"
eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('bank_statement_line_reconcile_view')})]"
/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Nothing to check
</p>
</field>
</record>
<record
id="action_bank_statement_line_move_view_reconcile"

View File

@ -44,7 +44,10 @@
<t t-if="dashboard.number_to_check > 0">
<div class="row">
<div class="col overflow-hidden text-left">
<a type="object" name="action_open_reconcile_to_check">
<a
type="action"
name="%(account_reconcile_oca.action_bank_statement_line_reconcile_to_check)s"
>
<t t-esc="dashboard.number_to_check" /> to check</a>
</div>
<div class="col-auto text-right">

View File

@ -41,7 +41,7 @@
<field name="account_id" />
<field name="account_root_id" />
<field name="account_type" />
<field name="partner_id" domain="[('parent_id', '=', False)]"/>
<field name="partner_id" domain="[('parent_id', '=', False)]" />
<field name="journal_id" />
<field
name="move_id"

View File

@ -117,6 +117,12 @@
domain="[('is_reconciled', '=', False)]"
/>
<separator />
<filter
name="to_check"
string="To check"
domain="[('to_check', '=', True)]"
/>
<separator />
<filter name="date" string="Date" date="date" />
<group name="groupby">
<filter