Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] partner_name_history #1733

Open
wants to merge 1 commit into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions partner_name_history/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
====================
Partner Name History
====================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e90fd770b51475c3ace20e14fdfcb13ebacb6b1c0a92cfa3b473f4c1c500ad21
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |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%2Fpartner--contact-lightgray.png?logo=github
:target: https://github.com/OCA/partner-contact/tree/16.0/partner_name_history
:alt: OCA/partner-contact
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/partner-contact-16-0/partner-contact-16-0-partner_name_history
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/partner-contact&target_branch=16.0
:alt: Try me on Runboat

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

Save the changes to partner name.

**Table of contents**

.. contents::
:local:

Usage
=====

When a partner changes its name:

#. Open the partner and change its name as usual, a new line is automatically added in its `Name history`.
#. In the `Name history`, you can edit the date if necessary

The old name of a partner can be retrieved in many ways:

- `partner._get_name_at_date(<date>)`
- `partner.with_context(partner_name_date=<date>).name`

A more dynamic behavior is enabled when `use_partner_name_history` is in the context: a model can declare a `_partner_name_history_field_map`:

.. code-block::

class AccountMove(models.Model):
_inherit = "account.move"
_partner_name_history_field_map = {
"partner_id": "invoice_date",
}

This means that `move.partner_id.name` will actually be `move.partner_id.with_context(partner_name_date=move.invoice_date).name`.

The values of the map can also be method names:

.. code-block::

class AccountMove(models.Model):
_inherit = "account.move"
_partner_name_history_field_map = {
"partner_id": "_get_partner_name_history_date",
}

This means that `move.partner_id.name` will actually be `move.partner_id.with_context(partner_name_date=move._get_partner_name_history_date()).name`.

This can be useful in reports because it allows to show the old name of partners with no QWeb change.

The above example is extracted from `account_move_partner_name_history`.

Known issues / Roadmap
======================

Better use of Odoo's cache.

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

Bugs are tracked on `GitHub Issues <https://github.com/OCA/partner-contact/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/partner-contact/issues/new?body=module:%20partner_name_history%0Aversion:%2016.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
~~~~~~~

* Davide Corio

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

* `Aion Tech <https://aiontech.company/>`_:

* Simone Rubino <[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-SirAionTech| image:: https://github.com/SirAionTech.png?size=40px
:target: https://github.com/SirAionTech
:alt: SirAionTech

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

|maintainer-SirAionTech|

This module is part of the `OCA/partner-contact <https://github.com/OCA/partner-contact/tree/16.0/partner_name_history>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
4 changes: 4 additions & 0 deletions partner_name_history/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import fields
from . import models
23 changes: 23 additions & 0 deletions partner_name_history/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2024 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Partner Name History",
"summary": "Save the changes to partner name.",
"author": "Davide Corio, Odoo Community Association (OCA)",
"maintainers": [
"SirAionTech",
],
"website": "https://github.com/OCA/partner-contact",
"category": "Customer Relationship Management",
"version": "16.0.1.0.0",
"license": "AGPL-3",
"depends": [
"base",
],
"data": [
"security/ir.model.access.csv",
"views/partner_name_history_views.xml",
"views/partner_views.xml",
],
}
64 changes: 64 additions & 0 deletions partner_name_history/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2024 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo.api import _
from odoo.exceptions import UserError
from odoo.fields import Many2one

# Monkey patch to use a model's field
# to automatically show the partner name at a specific date
original_convert_to_record = Many2one.convert_to_record


def _get_partner_name_date(self, record):
"""Get date to be used for partner name `record`"""
model_date_field_map = getattr(record, "_partner_name_history_field_map", {})
partner_name_date_field = model_date_field_map.get(self.name)
if partner_name_date_field in record._fields:
partner_name_date = record[partner_name_date_field]
elif partner_name_date_field:
partner_name_date_method_name = partner_name_date_field

try:
partner_name_date_method = getattr(record, partner_name_date_method_name)
except AttributeError as ae:
raise UserError(
_(
"Method %(method)s not found in model %(model)s",
method=partner_name_date_method_name,
model=record._name,
)
) from ae
else:
partner_name_date = partner_name_date_method()
else:
partner_name_date = None

Check warning on line 35 in partner_name_history/fields.py

View check run for this annotation

Codecov / codecov/patch

partner_name_history/fields.py#L35

Added line #L35 was not covered by tests
return partner_name_date


def partner_name_history_convert_to_record(self, value, record):
partner = original_convert_to_record(self, value, record)
# Do this only when needed:
# - specific context key is present
# - returned record is a partner
if (
record.env.context.get("use_partner_name_history")
and partner._name == "res.partner"
):
partner_name_date = _get_partner_name_date(self, record)
if partner_name_date is not None:
# Otherwise the name of the partner
# is retrieved once and always returned,
# even if it is requested for different dates
partner.invalidate_recordset(
fnames=[
"name",
],
)
partner = partner.with_context(
partner_name_date=partner_name_date,
)
return partner


Many2one.convert_to_record = partner_name_history_convert_to_record
4 changes: 4 additions & 0 deletions partner_name_history/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import partner
from . import partner_name_history
59 changes: 59 additions & 0 deletions partner_name_history/models/partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2024 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResPartner(models.Model):
_inherit = "res.partner"

name_history_ids = fields.One2many(
comodel_name="partner.name.history",
inverse_name="partner_id",
string="Name History",
)

def _prepare_name_history_values(self):
return [
{
"partner_id": partner.id,
"old_name": partner.name,
}
for partner in self
]

def write(self, vals):
if vals.get("name"):
name_history_values = self._prepare_name_history_values()
self.env["partner.name.history"].create(name_history_values)
return super().write(vals)

def _read(self, field_names):
partner_name_date = self.env.context.get("partner_name_date")
use_history_name = partner_name_date and "name" in field_names
if use_history_name:
field_names.remove("name")

res = super()._read(field_names)

if use_history_name:
for partner in self:
history_name = partner.with_context(
partner_name_date=None
)._get_name_at_date(partner_name_date)
self.env.cache.insert_missing(
partner, partner._fields["name"], [history_name]
)
return res

def _get_name_at_date(self, date):
self.ensure_one()
history = self.env["partner.name.history"].search(
[
("partner_id", "=", self.id),
("change_date", ">=", date),
],
order="change_date asc",
limit=1,
)
return history.old_name if history else self.name
25 changes: 25 additions & 0 deletions partner_name_history/models/partner_name_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2024 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class PartnerNameHistory(models.Model):
_name = "partner.name.history"
_description = "History of partner name"

partner_id = fields.Many2one(
comodel_name="res.partner",
ondelete="cascade",
string="Partner",
readonly=True,
required=True,
)
old_name = fields.Char(
readonly=True,
required=True,
)
change_date = fields.Datetime(
default=fields.Datetime.now,
required=True,
)
3 changes: 3 additions & 0 deletions partner_name_history/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* `Aion Tech <https://aiontech.company/>`_:

* Simone Rubino <[email protected]>
1 change: 1 addition & 0 deletions partner_name_history/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Save the changes to partner name.
1 change: 1 addition & 0 deletions partner_name_history/readme/ROADMAP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Better use of Odoo's cache.
37 changes: 37 additions & 0 deletions partner_name_history/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
When a partner changes its name:

#. Open the partner and change its name as usual, a new line is automatically added in its `Name history`.
#. In the `Name history`, you can edit the date if necessary

The old name of a partner can be retrieved in many ways:

- `partner._get_name_at_date(<date>)`
- `partner.with_context(partner_name_date=<date>).name`

A more dynamic behavior is enabled when `use_partner_name_history` is in the context: a model can declare a `_partner_name_history_field_map`:

.. code-block::

class AccountMove(models.Model):
_inherit = "account.move"
_partner_name_history_field_map = {
"partner_id": "invoice_date",
}

This means that `move.partner_id.name` will actually be `move.partner_id.with_context(partner_name_date=move.invoice_date).name`.

The values of the map can also be method names:

.. code-block::

class AccountMove(models.Model):
_inherit = "account.move"
_partner_name_history_field_map = {
"partner_id": "_get_partner_name_history_date",
}

This means that `move.partner_id.name` will actually be `move.partner_id.with_context(partner_name_date=move._get_partner_name_history_date()).name`.

This can be useful in reports because it allows to show the old name of partners with no QWeb change.

The above example is extracted from `account_move_partner_name_history`.
2 changes: 2 additions & 0 deletions partner_name_history/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
partner_name_history.access_partner_name_history,Everyone can manage the History of partner name,partner_name_history.model_partner_name_history,,1,1,1,1
Loading
Loading