[12.0][ADD] create addon: account_reconcile_restrict_partner_mismatch

pull/274/head
Iryna Vyshnevska 2019-07-18 16:50:59 +03:00
parent e9c9723c21
commit 4187189c05
14 changed files with 714 additions and 0 deletions

View File

@ -0,0 +1,2 @@
from . import models
from . import report

View File

@ -0,0 +1,19 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': 'Reconcile restrict partner mismatch',
'summary': 'Restrict reconciliation on receivable '
'and payable accounts to the same partner',
'version': '12.0.1.0.0',
'depends': ['account'],
'author': 'Camptocamp, Odoo Community Association (OCA)',
'website': 'http://www.github.com/OCA/account-reconcile',
'category': 'Finance',
'license': 'AGPL-3',
'data': [
'report/account_move_lines_report.xml',
'security/ir.model.access.csv',
],
'installable': True,
}

View File

@ -0,0 +1 @@
from . import account_move_line

View File

@ -0,0 +1,29 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, models, _
from odoo.exceptions import UserError
from odoo.tools import config
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
@api.multi
def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False):
if (config['test_enable']
and not self.env.context.get('test_partner_mismatch')):
return super().reconcile(writeoff_acc_id, writeoff_journal_id)
# to be consistent with parent method
if not self:
return True
partners = set()
for line in self:
if line.account_id.internal_type in ('receivable', 'payable'):
partners.add(line.partner_id.id)
if len(partners) > 1:
raise UserError(_('The partner has to be the same on all'
' lines for receivable and payable accounts!'))
return super().reconcile(
writeoff_acc_id, writeoff_journal_id)

View File

@ -0,0 +1,10 @@
This module restricts reconciliation between journal items when:
- both items have different partners
- one item is with partner and the other without it
This rule applies only for journal items using receivable and payable account type.
As at the moment of installation some journal items could have been reconciled
using different partners, you can detect them in menu Accounting > Adviser >
Reconciled items with partner mismatch.

View File

@ -0,0 +1 @@
from . import report_reconciled_lines

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_move_reconciled_report_tree" model="ir.ui.view">
<field name="name">Reconciled items with partner mismatch</field>
<field name="model">account.reconcile.partner.mismatch.report</field>
<field name="arch" type="xml">
<tree string="Reconciled items with partner mismatch" create="false" delete="false" edit="false">
<!-- links are not clickable in tree view
only after open form view -->
<field name="partial_reconcile_id" />
<field name="full_reconcile_id" />
<field name="debit_move_id" />
<field name="debit_amount" />
<field name="debit_partner_id" />
<field name="credit_move_id" />
<field name="credit_amount" />
<field name="credit_partner_id" />
<field name="account_type_id" />
<field name="account_id" />
</tree>
</field>
</record>
<record id="account_reconcile_partner_mismatch_report_view_form" model="ir.ui.view">
<field name="name">account.reconcile.partner.mismatch.report.form</field>
<field name="model">account.reconcile.partner.mismatch.report</field>
<field name="arch" type="xml">
<form string="Reconciled items with partner mismatch" create="false" delete="false" edit="false">
<sheet>
<group>
<group>
<field name="partial_reconcile_id" />
<field name="full_reconcile_id" />
<field name="debit_amount" />
<field name="credit_amount" />
<field name="debit_partner_id" />
<field name="credit_partner_id" />
</group>
<group>
<field name="account_id" />
<field name="account_type_id" />
<field name="debit_move_id" />
<field name="credit_move_id" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_account_reconcile_partner_mismatch_report" model="ir.actions.act_window">
<field name="name">Reconciled items with partner mismatch</field>
<field name="res_model">account.reconcile.partner.mismatch.report</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_account_reconcile_partner_mismatch_report"
parent="account.menu_finance_entries" sequence="80"
action="action_account_reconcile_partner_mismatch_report"
name="Reconciled items with partner mismatch"/>
</odoo>

View File

@ -0,0 +1,65 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models, tools
class AccountReconcilePartnerMismatchReport(models.Model):
_name = 'account.reconcile.partner.mismatch.report'
_auto = False
partial_reconcile_id = fields.Many2one(
'account.partial.reconcile',
string="Partial Reconcile"
)
full_reconcile_id = fields.Many2one('account.full.reconcile')
account_id = fields.Many2one(
'account.account',
string="Account"
)
account_type_id = fields.Many2one(
'account.account.type',
string="Account type",
)
debit_move_id = fields.Many2one('account.move.line', string="Debit move")
debit_amount = fields.Float("Debit amount")
debit_partner_id = fields.Many2one('res.partner', string="Debit partner")
credit_move_id = fields.Many2one('account.move.line', string="Credit move")
credit_amount = fields.Float("Credit amount")
credit_partner_id = fields.Many2one('res.partner', string="Credit partner")
@api.model_cr
def init(self):
"""Select lines which violate defined rules"""
tools.drop_view_if_exists(self.env.cr, self._table)
self._cr.execute(
"""CREATE OR REPLACE VIEW %s AS (
SELECT pr.id id
, pr.id partial_reconcile_id
, pr.full_reconcile_id
, pr.debit_move_id
, daml.debit debit_amount
, aat.id account_type_id
, daml.partner_id debit_partner_id
, daml.account_id account_id
, pr.credit_move_id
, caml.credit credit_amount
, caml.partner_id credit_partner_id
FROM account_partial_reconcile pr
LEFT JOIN account_move_line daml
ON daml.id = pr.debit_move_id
LEFT JOIN account_move_line caml
ON caml.id = pr.credit_move_id
LEFT JOIN account_account_type aat
ON daml.user_type_id = aat.id
WHERE aat.type in ('receivable', 'payable')
AND (daml.partner_id <> caml.partner_id
OR (daml.partner_id IS NULL
AND caml.partner_id IS NOT NULL)
OR (caml.partner_id IS NULL
AND daml.partner_id IS NOT NULL))
)
"""
% self._table
)

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_reconcile_partner_mismatch_report,access_account_reconcile_partner_mismatch_report,model_account_reconcile_partner_mismatch_report,account.group_account_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_reconcile_partner_mismatch_report access_account_reconcile_partner_mismatch_report model_account_reconcile_partner_mismatch_report account.group_account_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,419 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Account Set Reconcilable</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="account-set-reconcilable">
<h1 class="title">Account Set Reconcilable</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/account-reconcile/tree/10.0/account_set_reconcilable"><img alt="OCA/account-reconcile" src="https://img.shields.io/badge/github-OCA%2Faccount--reconcile-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/account-reconcile-10-0/account-reconcile-10-0-account_set_reconcilable"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/98/10.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>Allows to set as reconcilable a non reconcilable account that already have journal items.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="id1">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id4">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id5">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id1">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/account-reconcile/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/account-reconcile/issues/new?body=module:%20account_set_reconcilable%0Aversion:%2010.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id2">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id3">Authors</a></h2>
<ul class="simple">
<li>Eficent</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id4">Contributors</a></h2>
<ul class="simple">
<li>Miquel Raïch &lt;<a class="reference external" href="mailto:miquel.raich&#64;eficent.com">miquel.raich&#64;eficent.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id5">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account-reconcile/tree/10.0/account_set_reconcilable">OCA/account-reconcile</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1 @@
from . import test_reconciliation

View File

@ -0,0 +1,104 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.exceptions import UserError
class TestReconciliation(AccountingTestCase):
def setUp(self):
super().setUp()
self.env = self.env(context=dict(
self.env.context, tracking_disable=True,
test_partner_mismatch=True)
)
self.partner = self.env.ref("base.res_partner_2")
self.partner_id = self.partner.id
rec_type = self.env['account.account'].search([
('user_type_id', '=',
self.env.ref('account.data_account_type_receivable').id)
], limit=1)
pay_type = self.env['account.account'].search([
('user_type_id', '=',
self.env.ref('account.data_account_type_payable').id)
], limit=1)
self.account_rcv = (self.partner.property_account_receivable_id
or rec_type)
self.account_rsa = self.partner.property_account_payable_id or pay_type
self.bank_journal = self.env['account.journal']. \
create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'})
self.aml = self.init_moves()
self.wizard = self.env['account.move.line.reconcile.writeoff']. \
with_context(active_ids=[x.id for x in self.aml]).create({
'journal_id': self.bank_journal.id,
'writeoff_acc_id': self.account_rsa.id
})
def create_move(self, name, amount):
debit_line_vals = {
'name': name,
'debit': amount > 0 and amount or 0.0,
'credit': amount < 0 and -amount or 0.0,
'account_id': self.account_rcv.id,
}
credit_line_vals = debit_line_vals.copy()
credit_line_vals['debit'] = debit_line_vals['credit']
credit_line_vals['credit'] = debit_line_vals['debit']
credit_line_vals['account_id'] = self.account_rsa.id
vals = {
'journal_id': self.bank_journal.id,
'line_ids': [(0, 0, debit_line_vals), (0, 0, credit_line_vals)]
}
return self.env['account.move'].create(vals).id
def init_moves(self):
move_list_vals = [
('1', -1.83),
('2', 728.35),
('3', -4.46),
('4', 0.32),
('5', 14.72),
('6', -737.10),
]
move_ids = []
for name, amount in move_list_vals:
move_ids.append(self.create_move(name, amount))
aml_recs = self.env['account.move.line'].search([
('move_id', 'in', move_ids),
('account_id', '=', self.account_rcv.id)
])
return aml_recs
def test_reconcile_no_partner(self):
self.wizard.trans_rec_reconcile()
self.assertTrue(all(self.aml.mapped('reconciled')))
def test_reconcile_partner_mismatch(self):
self.aml[0].partner_id = self.partner.id
with self.assertRaises(UserError):
self.wizard.trans_rec_reconcile()
# all lines with same partner allowed
self.aml.write({'partner_id': self.partner.id})
self.wizard.trans_rec_reconcile()
self.assertTrue(all(self.aml.mapped('reconciled')))
def test_reconcile_accounts_excluded(self):
self.aml[0].partner_id = self.partner.id
with self.assertRaises(UserError):
self.wizard.trans_rec_reconcile()
# reconciliation forbiden only for certain types of accounts
account = self.env['account.account'].search([
('user_type_id.type', '=', 'other')
], limit=1)
account.reconcile = True
self.aml[0].account_id = account.id
with self.assertRaises(UserError):
self.wizard.trans_rec_reconcile()
# reconciliation for different partners allowed
# for not forbidden types
self.aml.write({'account_id': account.id})
self.wizard.trans_rec_reconcile()
self.assertTrue(all(self.aml.mapped('reconciled')))