Skip to content

Commit

Permalink
[14.0][ADD] sale_unreconciled
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronHForgeFlow committed Dec 17, 2021
1 parent 8092171 commit 2971dcf
Show file tree
Hide file tree
Showing 18 changed files with 1,407 additions and 0 deletions.
102 changes: 102 additions & 0 deletions sale_unreconciled/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
=================
Sale Unreconciled
=================

.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--financial--tools-lightgray.png?logo=github
:target: https://github.com/OCA/account-financial-tools/tree/14.0/sale_unreconciled
:alt: OCA/account-financial-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-financial-tools-14-0/account-financial-tools-14-0-sale_unreconciled
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/92/14.0
:alt: Try me on Runbot

|badge1| |badge2| |badge3| |badge4| |badge5|

This module adds a new fields "Unreconciled" on Sales Orders, that allows
to find SO's with unreconciled journal items related.

This module allows to reconcile those SO in a single click. In accounting
settings users will be able to set up a specific account for write-off.

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_

**Table of contents**

.. contents::
:local:

Usage
=====

Accountants will be able to find a filters in Sale Orders that shows
outstanding balances in interim accounts. Also there is a link in the SO
to those outstanding journal items.

Locking the SO will automatically reconcile the outstanding balance for the
stock iterim accounts.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-financial-tools/issues>`_.
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
`feedback <https://github.com/OCA/account-financial-tools/issues/new?body=module:%20sale_unreconciled%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* ForgeFlow S.L.

Contributors
~~~~~~~~~~~~

* ForgeFlow S.L. <[email protected]>

- Aaron Henriquez <[email protected]>

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

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.

.. |maintainer-AaronHForgeFlow| image:: https://github.com/AaronHForgeFlow.png?size=40px
:target: https://github.com/AaronHForgeFlow
:alt: AaronHForgeFlow

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-AaronHForgeFlow|

This module is part of the `OCA/account-financial-tools <https://github.com/OCA/account-financial-tools/tree/14.0/sale_unreconciled>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions sale_unreconciled/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
16 changes: 16 additions & 0 deletions sale_unreconciled/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2021 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
"name": "Sale Unreconciled",
"version": "14.0.1.0.0",
"author": "ForgeFlow S.L., Odoo Community Association (OCA)",
"website": "https://github.com/OCA/account-financial-tools",
"category": "Accounting",
"depends": ["sale_mrp", "account_move_line_sale_info"],
"data": ["views/sale_order_view.xml", "views/res_config_settings_view.xml"],
"license": "AGPL-3",
"installable": True,
"development_status": "Alpha",
"maintainers": ["AaronHForgeFlow"],
}
4 changes: 4 additions & 0 deletions sale_unreconciled/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import sale_order
from . import company
from . import res_config_settings
from . import account_move_line
72 changes: 72 additions & 0 deletions sale_unreconciled/models/account_move_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2019 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from datetime import datetime

from odoo import _, models
from odoo.exceptions import ValidationError


class AccountMoveLine(models.Model):
_inherit = "account.move.line"

def _get_so_writeoff_amounts(self):
precision = self.env["decimal.precision"].precision_get("Account")
writeoff_amount = round(
sum([line["amount_residual"] for line in self]), precision
)
writeoff_amount_curr = round(
sum([line["amount_residual_currency"] for line in self]), precision
)

first_currency = self[0]["currency_id"]
if all([line["currency_id"] == first_currency for line in self]):
same_curr = True
else:
same_curr = False

return (
writeoff_amount,
writeoff_amount_curr,
same_curr,
)

def _create_so_writeoff(self, writeoff_vals):
(
amount_writeoff,
amount_writeoff_curr,
same_curr,
) = self._get_so_writeoff_amounts()
partners = self.mapped("partner_id")
write_off_vals = {
"name": _("Automatic writeoff"),
"amount_currency": same_curr and amount_writeoff_curr or amount_writeoff,
"debit": amount_writeoff > 0.0 and amount_writeoff or 0.0,
"credit": amount_writeoff < 0.0 and -amount_writeoff or 0.0,
"partner_id": len(partners) == 1 and partners.id or False,
"account_id": writeoff_vals["account_id"],
"sale_order_id": writeoff_vals["sale_order_id"],
"journal_id": writeoff_vals["journal_id"],
"currency_id": writeoff_vals["currency_id"],
}
counterpart_account = self.mapped("account_id")
if len(counterpart_account) != 1:
raise ValidationError(_("Cannot write-off more than one account"))
counter_part = write_off_vals.copy()
counter_part["debit"] = write_off_vals["credit"]
counter_part["credit"] = write_off_vals["debit"]
counter_part["amount_currency"] = -write_off_vals["amount_currency"]
counter_part["account_id"] = (counterpart_account.id,)

move = self.env["account.move"].create(
{
"date": datetime.now(),
"journal_id": writeoff_vals["journal_id"],
"currency_id": writeoff_vals["currency_id"],
"line_ids": [(0, 0, write_off_vals), (0, 0, counter_part)],
}
)
move.action_post()
return move.line_ids.filtered(
lambda l: l.account_id.id == counterpart_account.id
)
19 changes: 19 additions & 0 deletions sale_unreconciled/models/company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2021 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import fields, models


class ResCompany(models.Model):
_inherit = "res.company"

sale_reconcile_account_id = fields.Many2one(
"account.account",
domain=lambda self: [("deprecated", "=", False)],
string="Write-Off Account On Sales",
ondelete="restrict",
copy=False,
help="Write-off account to reconcile Unreconciled Sale Orders",
)

sale_reconcile_journal_id = fields.Many2one("account.journal")
15 changes: 15 additions & 0 deletions sale_unreconciled/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2021 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

sale_reconcile_account_id = fields.Many2one(
related="company_id.sale_reconcile_account_id", readonly=False
)
sale_reconcile_journal_id = fields.Many2one(
related="company_id.sale_reconcile_journal_id", readonly=False
)
133 changes: 133 additions & 0 deletions sale_unreconciled/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright 2021 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import _, api, fields, models
from odoo.osv import expression


class SaleOrder(models.Model):
_inherit = "sale.order"

unreconciled = fields.Boolean(
compute="_compute_unreconciled",
search="_search_unreconciled",
help="""Indicates that a sale Order has related Journal items not
reconciled.Note that if it is false it can be either that
everything is reconciled or that the related accounts do not
allow reconciliation""",
)

@api.model
def _get_sale_unreconciled_base_domain(self):
included_accounts = (
(
self.env["product.category"].search(
[("property_valuation", "=", "real_time")]
)
)
.mapped("property_stock_account_output_categ_id")
.ids
)
unreconciled_domain = [
("account_id.reconcile", "=", True),
("account_id", "in", included_accounts),
("move_id.state", "=", "posted"),
# for some reason when amount_residual is zero
# is marked as reconciled, this is better check
("full_reconcile_id", "=", False),
("company_id", "in", self.env.companies.ids),
]
return unreconciled_domain

def _compute_unreconciled(self):
acc_item = self.env["account.move.line"]
for rec in self:
domain = rec._get_sale_unreconciled_base_domain()
unreconciled_domain = expression.AND(
[domain, [("sale_order_id", "=", rec.id)]]
)
unreconciled_items = acc_item.search(unreconciled_domain)
rec.unreconciled = len(unreconciled_items) > 0

def _search_unreconciled(self, operator, value):
if operator != "=" or not isinstance(value, bool):
raise ValueError(_("Unsupported search operator"))
acc_item = self.env["account.move.line"]
domain = self._get_sale_unreconciled_base_domain()
unreconciled_domain = expression.AND([domain, [("sale_order_id", "!=", False)]])
unreconciled_items = acc_item.search(unreconciled_domain)
unreconciled_sos = unreconciled_items.mapped("sale_order_id")
if value:
return [("id", "in", unreconciled_sos.ids)]
else:
return [("id", "not in", unreconciled_sos.ids)]

def action_view_unreconciled(self):
self.ensure_one()
acc_item = self.env["account.move.line"]
domain = self._get_sale_unreconciled_base_domain()
unreconciled_domain = expression.AND(
[domain, [("sale_order_id", "=", self.id)]]
)
unreconciled_items = acc_item.search(unreconciled_domain)
action = self.env.ref("account.action_account_moves_all")
action_dict = action.read()[0]
action_dict["domain"] = [("id", "in", unreconciled_items.ids)]
return action_dict

def action_reconcile(self):
self.ensure_one()
acc_item = self.env["account.move.line"]
domain = self._get_sale_unreconciled_base_domain()
unreconciled_domain = expression.AND(
[domain, [("sale_order_id", "=", self.id)]]
)
unreconciled_items = acc_item.search(unreconciled_domain)
writeoff_to_reconcile = False
for account in unreconciled_items.mapped("account_id"):
acc_unrec_items = unreconciled_items.filtered(
lambda ml: ml.account_id == account
)
all_aml_share_same_currency = all(
[x.currency_id == self[0].currency_id for x in acc_unrec_items]
)
writeoff_vals = {
"account_id": self.company_id.sale_reconcile_account_id.id,
"journal_id": self.company_id.sale_reconcile_journal_id.id,
"sale_order_id": self.id,
"currency_id": self.currency_id.id,
}
if not all_aml_share_same_currency:
writeoff_vals["amount_currency"] = False
if writeoff_to_reconcile:
writeoff_to_reconcile += unreconciled_items._create_so_writeoff(
writeoff_vals
)
else:
writeoff_to_reconcile = unreconciled_items._create_so_writeoff(
writeoff_vals
)
# add writeoff line to reconcile algorithm and finish the reconciliation
if writeoff_to_reconcile:
remaining_moves = unreconciled_items + writeoff_to_reconcile
else:
remaining_moves = unreconciled_items
# Check if reconciliation is total or needs an exchange rate entry to be created
if remaining_moves:
remaining_moves.filtered(lambda l: not l.reconciled).reconcile()
return {
"name": _("Reconciled journal items"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "tree,form",
"res_model": "account.move.line",
"domain": [
("id", "in", unreconciled_items.ids + writeoff_to_reconcile.ids)
],
}

def action_done(self):
for rec in self:
if rec.unreconciled:
rec.action_reconcile()
return super(SaleOrder, self).action_done()
3 changes: 3 additions & 0 deletions sale_unreconciled/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* ForgeFlow S.L. <[email protected]>

- Aaron Henriquez <[email protected]>
5 changes: 5 additions & 0 deletions sale_unreconciled/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This module adds a new fields "Unreconciled" on Sales Orders, that allows
to find SO's with unreconciled journal items related.

This module allows to reconcile those SO in a single click. In accounting
settings users will be able to set up a specific account for write-off.
6 changes: 6 additions & 0 deletions sale_unreconciled/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Accountants will be able to find a filters in Sale Orders that shows
outstanding balances in interim accounts. Also there is a link in the SO
to those outstanding journal items.

Locking the SO will automatically reconcile the outstanding balance for the
stock iterim accounts.
Loading

0 comments on commit 2971dcf

Please sign in to comment.