mirror of
https://github.com/OCA/account-financial-tools.git
synced 2025-02-02 12:47:26 +02:00
@@ -1,5 +1,5 @@
|
||||
# Do NOT update manually; changes here will be overwritten by Copier
|
||||
_commit: v1.6.1
|
||||
_commit: v1.7.0
|
||||
_src_path: gh:oca/oca-addons-repo-template
|
||||
ci: GitHub
|
||||
dependency_installation_mode: PIP
|
||||
@@ -8,11 +8,12 @@ include_wkhtmltopdf: false
|
||||
odoo_version: 14.0
|
||||
org_name: Odoo Community Association (OCA)
|
||||
org_slug: OCA
|
||||
rebel_module_groups: []
|
||||
rebel_module_groups:
|
||||
- account_move_name_sequence
|
||||
- account_sequence_option
|
||||
repo_description: 'TODO: add repo description.'
|
||||
repo_name: Account financial Tools for Odoo
|
||||
repo_slug: account-financial-tools
|
||||
repo_website: https://github.com/OCA/account-financial-tools
|
||||
travis_apt_packages: []
|
||||
travis_apt_sources: []
|
||||
|
||||
|
||||
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
@@ -36,9 +36,25 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- container: ghcr.io/oca/oca-ci/py3.6-odoo14.0:latest
|
||||
include: "account_move_name_sequence"
|
||||
makepot: "true"
|
||||
name: test with Odoo
|
||||
- container: ghcr.io/oca/oca-ci/py3.6-ocb14.0:latest
|
||||
include: "account_move_name_sequence"
|
||||
name: test with OCB
|
||||
- container: ghcr.io/oca/oca-ci/py3.6-odoo14.0:latest
|
||||
include: "account_sequence_option"
|
||||
makepot: "true"
|
||||
name: test with Odoo
|
||||
- container: ghcr.io/oca/oca-ci/py3.6-ocb14.0:latest
|
||||
include: "account_sequence_option"
|
||||
name: test with OCB
|
||||
- container: ghcr.io/oca/oca-ci/py3.6-odoo14.0:latest
|
||||
exclude: "account_move_name_sequence,account_sequence_option"
|
||||
makepot: "true"
|
||||
name: test with Odoo
|
||||
- container: ghcr.io/oca/oca-ci/py3.6-ocb14.0:latest
|
||||
exclude: "account_move_name_sequence,account_sequence_option"
|
||||
name: test with OCB
|
||||
services:
|
||||
postgres:
|
||||
@@ -49,6 +65,9 @@ jobs:
|
||||
POSTGRES_DB: odoo
|
||||
ports:
|
||||
- 5432:5432
|
||||
env:
|
||||
INCLUDE: "${{ matrix.include }}"
|
||||
EXCLUDE: "${{ matrix.exclude }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
[MASTER]
|
||||
load-plugins=pylint_odoo
|
||||
score=n
|
||||
|
||||
1
account_move_name_sequence/README.rst
Normal file
1
account_move_name_sequence/README.rst
Normal file
@@ -0,0 +1 @@
|
||||
Will be auto-generated from the readme subdir
|
||||
2
account_move_name_sequence/__init__.py
Normal file
2
account_move_name_sequence/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .post_install import create_journal_sequences
|
||||
from . import models
|
||||
24
account_move_name_sequence/__manifest__.py
Normal file
24
account_move_name_sequence/__manifest__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
# Copyright 2022 Vauxoo (https://www.vauxoo.com/)
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# @author: Moisés López <moylop260@vauxoo.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
"name": "Account Move Number Sequence",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Accounting",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Generate journal entry number from sequence",
|
||||
"author": "Akretion,Vauxoo,Odoo Community Association (OCA)",
|
||||
"maintainers": ["alexis-via"],
|
||||
"website": "https://github.com/OCA/account-financial-tools",
|
||||
"depends": ["account"],
|
||||
"data": [
|
||||
"views/account_journal.xml",
|
||||
"views/account_move.xml",
|
||||
"security/ir.model.access.csv",
|
||||
],
|
||||
"post_init_hook": "create_journal_sequences",
|
||||
"installable": True,
|
||||
}
|
||||
3
account_move_name_sequence/models/__init__.py
Normal file
3
account_move_name_sequence/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import account_journal
|
||||
from . import account_move
|
||||
from . import ir_sequence
|
||||
243
account_move_name_sequence/models/account_journal.py
Normal file
243
account_move_name_sequence/models/account_journal.py
Normal file
@@ -0,0 +1,243 @@
|
||||
# Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
# Copyright 2022 Vauxoo (https://www.vauxoo.com/)
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# @author: Moisés López <moylop260@vauxoo.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_inherit = "account.journal"
|
||||
|
||||
sequence_id = fields.Many2one(
|
||||
"ir.sequence",
|
||||
string="Entry Sequence",
|
||||
copy=False,
|
||||
check_company=True,
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
help="This sequence will be used to generate the journal entry number.",
|
||||
)
|
||||
refund_sequence_id = fields.Many2one(
|
||||
"ir.sequence",
|
||||
string="Credit Note Entry Sequence",
|
||||
copy=False,
|
||||
check_company=True,
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
help="This sequence will be used to generate the journal entry number for refunds.",
|
||||
)
|
||||
# Redefine the default to True as <=v13.0
|
||||
refund_sequence = fields.Boolean(default=True)
|
||||
|
||||
@api.constrains("refund_sequence_id", "sequence_id")
|
||||
def _check_journal_sequence(self):
|
||||
for journal in self:
|
||||
if (
|
||||
journal.refund_sequence_id
|
||||
and journal.sequence_id
|
||||
and journal.refund_sequence_id == journal.sequence_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"On journal '%s', the same sequence is used as "
|
||||
"Entry Sequence and Credit Note Entry Sequence."
|
||||
)
|
||||
% journal.display_name
|
||||
)
|
||||
if journal.sequence_id and not journal.sequence_id.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The company is not set on sequence '%s' configured on "
|
||||
"journal '%s'."
|
||||
)
|
||||
% (journal.sequence_id.display_name, journal.display_name)
|
||||
)
|
||||
if journal.refund_sequence_id and not journal.refund_sequence_id.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The company is not set on sequence '%s' configured as "
|
||||
"credit note sequence of journal '%s'."
|
||||
)
|
||||
% (journal.refund_sequence_id.display_name, journal.display_name)
|
||||
)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if not vals.get("sequence_id"):
|
||||
vals["sequence_id"] = self._create_sequence(vals).id
|
||||
if (
|
||||
vals.get("type") in ("sale", "purchase")
|
||||
and vals.get("refund_sequence")
|
||||
and not vals.get("refund_sequence_id")
|
||||
):
|
||||
vals["refund_sequence_id"] = self._create_sequence(vals, refund=True).id
|
||||
return super().create(vals)
|
||||
|
||||
@api.model
|
||||
def _prepare_sequence(self, vals, refund=False):
|
||||
code = vals.get("code") and vals["code"].upper() or ""
|
||||
prefix = "%s%s/%%(range_year)s/" % (refund and "R" or "", code)
|
||||
seq_vals = {
|
||||
"name": "%s%s"
|
||||
% (vals.get("name", _("Sequence")), refund and _("Refund") + " " or ""),
|
||||
"company_id": vals.get("company_id") or self.env.company.id,
|
||||
"implementation": "no_gap",
|
||||
"prefix": prefix,
|
||||
"padding": 4,
|
||||
"use_date_range": True,
|
||||
}
|
||||
return seq_vals
|
||||
|
||||
@api.model
|
||||
def _create_sequence(self, vals, refund=False):
|
||||
seq_vals = self._prepare_sequence(vals, refund=refund)
|
||||
return self.env["ir.sequence"].sudo().create(seq_vals)
|
||||
|
||||
def _prepare_sequence_current_moves(self, refund=False):
|
||||
"""Get sequence dict values the journal based on current moves"""
|
||||
self.ensure_one()
|
||||
move_domain = [
|
||||
("journal_id", "=", self.id),
|
||||
("name", "!=", "/"),
|
||||
]
|
||||
if self.refund_sequence:
|
||||
# Based on original Odoo behavior
|
||||
if refund:
|
||||
move_domain.append(("move_type", "in", ("out_refund", "in_refund")))
|
||||
else:
|
||||
move_domain.append(("move_type", "not in", ("out_refund", "in_refund")))
|
||||
last_move = self.env["account.move"].search(
|
||||
move_domain, limit=1, order="id DESC"
|
||||
)
|
||||
msg_err = (
|
||||
"Journal %s could not get sequence %s values based on current moves. "
|
||||
"Using default values." % (self.id, refund and "refund" or "")
|
||||
)
|
||||
if not last_move:
|
||||
_logger.warning("%s %s", msg_err, "No moves found")
|
||||
return {}
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
# get the current sequence values could be buggy to get
|
||||
# But even we can use the default values
|
||||
# or do manual changes instead of raising errors
|
||||
last_sequence = last_move._get_last_sequence()
|
||||
if not last_sequence:
|
||||
last_sequence = (
|
||||
last_move._get_last_sequence(relaxed=True)
|
||||
or last_move._get_starting_sequence()
|
||||
)
|
||||
|
||||
__, seq_format_values = last_move._get_sequence_format_param(
|
||||
last_sequence
|
||||
)
|
||||
prefix1 = seq_format_values["prefix1"]
|
||||
prefix = prefix1
|
||||
if seq_format_values["year_length"] == 4:
|
||||
prefix += "%(range_year)s"
|
||||
elif seq_format_values["year_length"] == 2:
|
||||
prefix += "%(range_y)s"
|
||||
else:
|
||||
# If there is not year so current values are valid
|
||||
seq_vals = {
|
||||
"padding": seq_format_values["seq_length"],
|
||||
"suffix": seq_format_values["suffix"],
|
||||
"prefix": prefix,
|
||||
"date_range_ids": [],
|
||||
"use_date_range": False,
|
||||
"number_next_actual": seq_format_values["seq"] + 1,
|
||||
}
|
||||
return seq_vals
|
||||
prefix2 = seq_format_values.get("prefix2") or ""
|
||||
prefix += prefix2
|
||||
month = seq_format_values.get("month") # It is 0 if only have year
|
||||
if month:
|
||||
prefix += "%(range_month)s"
|
||||
prefix3 = seq_format_values.get("prefix3") or ""
|
||||
where_name_value = "%s%s%s%s%s%%" % (
|
||||
prefix1,
|
||||
"_" * seq_format_values["year_length"],
|
||||
prefix2,
|
||||
"_" * bool(month) * 2,
|
||||
prefix3,
|
||||
)
|
||||
prefixes = prefix1 + prefix2
|
||||
select_year = (
|
||||
"split_part(name, '%s', %d)" % (prefix2, prefixes.count(prefix2))
|
||||
if prefix2
|
||||
else "''"
|
||||
)
|
||||
prefixes += prefix3
|
||||
select_month = (
|
||||
"split_part(name, '%s', %d)" % (prefix3, prefixes.count(prefix3))
|
||||
if prefix3
|
||||
else "''"
|
||||
)
|
||||
select_max_number = (
|
||||
"MAX(split_part(name, '%s', %d)::INTEGER) AS max_number"
|
||||
% (prefixes[-1], prefixes.count(prefixes[-1]) + 1)
|
||||
)
|
||||
query = (
|
||||
"SELECT %s, %s, %s FROM account_move "
|
||||
"WHERE name LIKE %%s AND journal_id=%%s GROUP BY 1,2"
|
||||
) % (
|
||||
select_year,
|
||||
select_month,
|
||||
select_max_number,
|
||||
)
|
||||
# It is not using user input
|
||||
# pylint: disable=sql-injection
|
||||
self.env.cr.execute(query, (where_name_value, self.id))
|
||||
res = self.env.cr.fetchall()
|
||||
prefix += prefix3
|
||||
seq_vals = {
|
||||
"padding": seq_format_values["seq_length"],
|
||||
"suffix": seq_format_values["suffix"],
|
||||
"prefix": prefix,
|
||||
"date_range_ids": [],
|
||||
"use_date_range": True,
|
||||
}
|
||||
for year, month, max_number in res:
|
||||
if not year and not month:
|
||||
seq_vals.update(
|
||||
{
|
||||
"use_date_range": False,
|
||||
"number_next_actual": max_number + 1,
|
||||
}
|
||||
)
|
||||
continue
|
||||
if len(year) == 2:
|
||||
# Year >=50 will be considered as last century 1950
|
||||
# Year <=49 will be considered as current century 2049
|
||||
if int(year) >= 50:
|
||||
year = "19" + year
|
||||
else:
|
||||
year = "20" + year
|
||||
if month:
|
||||
date_from = fields.Date.to_date("%s-%s-1" % (year, month))
|
||||
date_to = date_from + relativedelta(day=31)
|
||||
else:
|
||||
date_from = fields.Date.to_date("%s-1-1" % year)
|
||||
date_to = fields.Date.to_date("%s-12-31" % year)
|
||||
seq_vals["date_range_ids"].append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"date_from": date_from,
|
||||
"date_to": date_to,
|
||||
"number_next_actual": max_number + 1,
|
||||
},
|
||||
)
|
||||
)
|
||||
return seq_vals
|
||||
except Exception as e:
|
||||
_logger.warning("%s %s", msg_err, e)
|
||||
return {}
|
||||
45
account_move_name_sequence/models/account_move.py
Normal file
45
account_move_name_sequence/models/account_move.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.move"
|
||||
|
||||
name = fields.Char(compute="_compute_name_by_sequence")
|
||||
# highest_name, sequence_prefix and sequence_number are not needed any more
|
||||
# -> compute=False to improve perf
|
||||
highest_name = fields.Char(compute=False)
|
||||
sequence_prefix = fields.Char(compute=False)
|
||||
sequence_number = fields.Integer(compute=False)
|
||||
|
||||
@api.depends("state", "journal_id", "date")
|
||||
def _compute_name_by_sequence(self):
|
||||
for move in self:
|
||||
name = move.name or "/"
|
||||
# I can't use posted_before in this IF because
|
||||
# posted_before is set to True in _post() at the same
|
||||
# time as state is set to "posted"
|
||||
if (
|
||||
move.state == "posted"
|
||||
and (not move.name or move.name == "/")
|
||||
and move.journal_id
|
||||
and move.journal_id.sequence_id
|
||||
):
|
||||
if (
|
||||
move.move_type in ("out_refund", "in_refund")
|
||||
and move.journal_id.type in ("sale", "purchase")
|
||||
and move.journal_id.refund_sequence
|
||||
and move.journal_id.refund_sequence_id
|
||||
):
|
||||
seq = move.journal_id.refund_sequence_id
|
||||
else:
|
||||
seq = move.journal_id.sequence_id
|
||||
name = seq.next_by_id(sequence_date=move.date)
|
||||
move.name = name
|
||||
|
||||
# We must by-pass this constraint of sequence.mixin
|
||||
def _constrains_date_sequence(self):
|
||||
return True
|
||||
58
account_move_name_sequence/models/ir_sequence.py
Normal file
58
account_move_name_sequence/models/ir_sequence.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class IrSequence(models.Model):
|
||||
_inherit = "ir.sequence"
|
||||
|
||||
def _create_date_range_seq(self, date):
|
||||
# Fix issue creating new date range for future dates
|
||||
# It assigns more than one month
|
||||
# TODO: Remove if odoo merge the following PR:
|
||||
# https://github.com/odoo/odoo/pull/91019
|
||||
date_obj = fields.Date.from_string(date)
|
||||
prefix_suffix = "%s %s" % (self.prefix, self.suffix)
|
||||
if "%(range_day)s" in prefix_suffix:
|
||||
date_from = date_obj
|
||||
date_to = date_obj
|
||||
elif "%(range_month)s" in prefix_suffix:
|
||||
date_from = date_obj + relativedelta(day=1)
|
||||
date_to = date_obj + relativedelta(day=31)
|
||||
else:
|
||||
date_from = date_obj + relativedelta(day=1, month=1)
|
||||
date_to = date_obj + relativedelta(day=31, month=12)
|
||||
date_range = self.env["ir.sequence.date_range"].search(
|
||||
[
|
||||
("sequence_id", "=", self.id),
|
||||
("date_from", ">=", date),
|
||||
("date_from", "<=", date_to),
|
||||
],
|
||||
order="date_from desc",
|
||||
limit=1,
|
||||
)
|
||||
if date_range:
|
||||
date_to = date_range.date_from + relativedelta(days=-1)
|
||||
date_range = self.env["ir.sequence.date_range"].search(
|
||||
[
|
||||
("sequence_id", "=", self.id),
|
||||
("date_to", ">=", date_from),
|
||||
("date_to", "<=", date),
|
||||
],
|
||||
order="date_to desc",
|
||||
limit=1,
|
||||
)
|
||||
if date_range:
|
||||
date_from = date_range.date_to + relativedelta(days=1)
|
||||
seq_date_range = (
|
||||
self.env["ir.sequence.date_range"]
|
||||
.sudo()
|
||||
.create(
|
||||
{
|
||||
"date_from": date_from,
|
||||
"date_to": date_to,
|
||||
"sequence_id": self.id,
|
||||
}
|
||||
)
|
||||
)
|
||||
return seq_date_range
|
||||
32
account_move_name_sequence/post_install.py
Normal file
32
account_move_name_sequence/post_install.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
# Copyright 2022 Vauxoo (https://www.vauxoo.com/)
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# @author: Moisés López <moylop260@vauxoo.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import SUPERUSER_ID, api
|
||||
|
||||
|
||||
def create_journal_sequences(cr, registry):
|
||||
with api.Environment.manage():
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
journals = (
|
||||
env["account.journal"]
|
||||
.with_context(active_test=False)
|
||||
.search([("sequence_id", "!=", False)])
|
||||
)
|
||||
for journal in journals:
|
||||
journal_vals = {
|
||||
"code": journal.code,
|
||||
"name": journal.name,
|
||||
"company_id": journal.company_id.id,
|
||||
}
|
||||
seq_vals = journal._prepare_sequence(journal_vals)
|
||||
seq_vals.update(journal._prepare_sequence_current_moves())
|
||||
vals = {"sequence_id": env["ir.sequence"].create(seq_vals).id}
|
||||
if journal.type in ("sale", "purchase") and journal.refund_sequence:
|
||||
rseq_vals = journal._prepare_sequence(journal_vals, refund=True)
|
||||
rseq_vals.update(journal._prepare_sequence_current_moves(refund=True))
|
||||
vals["refund_sequence_id"] = env["ir.sequence"].create(rseq_vals).id
|
||||
journal.write(vals)
|
||||
return
|
||||
5
account_move_name_sequence/readme/CONFIGURE.rst
Normal file
5
account_move_name_sequence/readme/CONFIGURE.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
On the form view of an account journal, in the first tab, there is a many2one link to the sequence. When you create a new journal, you can keep this field empty and a new sequence will be automatically created when you save the journal.
|
||||
|
||||
On sale and purchase journals, you have an additionnal option to have another sequence dedicated to refunds.
|
||||
|
||||
Upon module installation, all existing journals will be updated with a journal entry sequence (and also a credit note sequence for sale and purchase journals). You should update the configuration of the sequences to fit your needs. You can uncheck the option *Dedicated Credit Note Sequence* on existing sale and purchase journals if you don't want it. For the journals which already have journal entries, you should update the sequence configuration to avoid a discontinuity in the numbering for the next journal entry.
|
||||
2
account_move_name_sequence/readme/CONTRIBUTORS.rst
Normal file
2
account_move_name_sequence/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,2 @@
|
||||
* Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
* Moisés López <moylop260@vauxoo.com>
|
||||
14
account_move_name_sequence/readme/DESCRIPTION.rst
Normal file
14
account_move_name_sequence/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
In Odoo version 13.0 and previous versions, the number of journal entries was generated from a sequence configured on the journal.
|
||||
|
||||
In Odoo version 14.0, the number of journal entries can be manually set by the user. Then, the number attributed for next journal entries in the same journal is computed by a complex piece of code that guesses the format of the journal entry number from the number of the journal entry which was manually entered by the user. It has several drawbacks:
|
||||
|
||||
* the available options for the sequence are limited,
|
||||
* it is not possible to configure the sequence in advance before the deployment in production,
|
||||
* as it is error-prone, they added a *Resequence* wizard to re-generate the journal entry numbers, which can be considered as illegal in many countries,
|
||||
* the `piece of code <https://github.com/odoo/odoo/blob/14.0/addons/account/models/sequence_mixin.py>`_ that handle this is not easy to understand and quite difficult to debug.
|
||||
|
||||
For those like me who think that the implementation before Odoo v14.0 was much better, for the accountants who think it should not be possible to manually enter the sequence of a customer invoice, for the auditor who consider that resequencing journal entries is prohibited by law, this module may be a solution to get out of the nightmare.
|
||||
|
||||
The field names used in this module to configure the sequence on the journal are exactly the same as in Odoo version 13.0 and previous versions. That way, if you migrate to Odoo version 14.0 and you install this module immediately after the migration, you should keep the previous behavior and the same sequences will continue to be used.
|
||||
|
||||
The module removes access to the *Resequence* wizard on journal entries.
|
||||
2
account_move_name_sequence/security/ir.model.access.csv
Normal file
2
account_move_name_sequence/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
account.access_account_resequence,Remove rights on account.resequence.wizard,account.model_account_resequence_wizard,account.group_account_manager,0,0,0,0
|
||||
|
1
account_move_name_sequence/tests/__init__.py
Normal file
1
account_move_name_sequence/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_account_move_name_seq
|
||||
113
account_move_name_sequence/tests/test_account_move_name_seq.py
Normal file
113
account_move_name_sequence/tests/test_account_move_name_seq.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestAccountMoveNameSequence(TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.company = self.env.ref("base.main_company")
|
||||
self.misc_journal = self.env["account.journal"].create(
|
||||
{
|
||||
"name": "Test Journal Move name seq",
|
||||
"code": "ADLM",
|
||||
"type": "general",
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
self.purchase_journal = self.env["account.journal"].create(
|
||||
{
|
||||
"name": "Test Purchase Journal Move name seq",
|
||||
"code": "ADLP",
|
||||
"type": "purchase",
|
||||
"company_id": self.company.id,
|
||||
"refund_sequence": True,
|
||||
}
|
||||
)
|
||||
self.accounts = self.env["account.account"].search(
|
||||
[("company_id", "=", self.company.id)], limit=2
|
||||
)
|
||||
self.account1 = self.accounts[0]
|
||||
self.account2 = self.accounts[1]
|
||||
self.date = datetime.now()
|
||||
|
||||
def test_seq_creation(self):
|
||||
self.assertTrue(self.misc_journal.sequence_id)
|
||||
seq = self.misc_journal.sequence_id
|
||||
self.assertEqual(seq.company_id, self.company)
|
||||
self.assertEqual(seq.implementation, "no_gap")
|
||||
self.assertEqual(seq.padding, 4)
|
||||
self.assertTrue(seq.use_date_range)
|
||||
self.assertTrue(self.purchase_journal.sequence_id)
|
||||
self.assertTrue(self.purchase_journal.refund_sequence_id)
|
||||
seq = self.purchase_journal.refund_sequence_id
|
||||
self.assertEqual(seq.company_id, self.company)
|
||||
self.assertEqual(seq.implementation, "no_gap")
|
||||
self.assertEqual(seq.padding, 4)
|
||||
self.assertTrue(seq.use_date_range)
|
||||
|
||||
def test_misc_move_name(self):
|
||||
move = self.env["account.move"].create(
|
||||
{
|
||||
"date": self.date,
|
||||
"journal_id": self.misc_journal.id,
|
||||
"line_ids": [
|
||||
(0, 0, {"account_id": self.account1.id, "debit": 10}),
|
||||
(0, 0, {"account_id": self.account2.id, "credit": 10}),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(move.name, "/")
|
||||
move.action_post()
|
||||
seq = self.misc_journal.sequence_id
|
||||
move_name = "%s%s" % (seq.prefix, "1".zfill(seq.padding))
|
||||
move_name = move_name.replace("%(range_year)s", str(self.date.year))
|
||||
self.assertEqual(move.name, move_name)
|
||||
self.assertTrue(seq.date_range_ids)
|
||||
drange_count = self.env["ir.sequence.date_range"].search_count(
|
||||
[
|
||||
("sequence_id", "=", seq.id),
|
||||
("date_from", "=", fields.Date.add(self.date, month=1, day=1)),
|
||||
]
|
||||
)
|
||||
self.assertEqual(drange_count, 1)
|
||||
move.button_draft()
|
||||
move.action_post()
|
||||
self.assertEqual(move.name, move_name)
|
||||
|
||||
def test_in_refund(self):
|
||||
in_refund_invoice = self.env["account.move"].create(
|
||||
{
|
||||
"journal_id": self.purchase_journal.id,
|
||||
"invoice_date": self.date,
|
||||
"partner_id": self.env.ref("base.res_partner_3").id,
|
||||
"move_type": "in_refund",
|
||||
"invoice_line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"account_id": self.account1.id,
|
||||
"price_unit": 42.0,
|
||||
"quantity": 12,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(in_refund_invoice.name, "/")
|
||||
in_refund_invoice.action_post()
|
||||
seq = self.purchase_journal.refund_sequence_id
|
||||
move_name = "%s%s" % (seq.prefix, "1".zfill(seq.padding))
|
||||
move_name = move_name.replace("%(range_year)s", str(self.date.year))
|
||||
self.assertEqual(in_refund_invoice.name, move_name)
|
||||
in_refund_invoice.button_draft()
|
||||
in_refund_invoice.action_post()
|
||||
self.assertEqual(in_refund_invoice.name, move_name)
|
||||
31
account_move_name_sequence/views/account_journal.xml
Normal file
31
account_move_name_sequence/views/account_journal.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_journal_form" model="ir.ui.view">
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.view_account_journal_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="refund_sequence" position="before">
|
||||
<field
|
||||
name="sequence_id"
|
||||
required="0"
|
||||
context="{'default_name': name, 'default_company_id': company_id, 'default_implementation': 'no_gap', 'default_padding': 4, 'default_use_date_range': True, 'default_prefix': (code or 'UNKNOWN') + '/%%(range_year)s/'}"
|
||||
/>
|
||||
</field>
|
||||
<field name="refund_sequence" position="after">
|
||||
<field
|
||||
name="refund_sequence_id"
|
||||
attrs="{'invisible': ['|', ('type', 'not in', ('sale', 'purchase')), ('refund_sequence', '=', False)]}"
|
||||
required="1"
|
||||
context="{'default_name': name, 'default_company_id': company_id, 'default_implementation': 'no_gap', 'default_padding': 4, 'default_use_date_range': True, 'default_prefix': 'R' + (code or 'UNKNOWN') + '/%%(range_year)s/'}"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
33
account_move_name_sequence/views/account_move.xml
Normal file
33
account_move_name_sequence/views/account_move.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="view_move_form" model="ir.ui.view">
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath
|
||||
expr="//div[hasclass('oe_title')]/h1[hasclass('mt0')]"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'invisible': [('name', '=', '/')]}</attribute>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//div[hasclass('oe_title')]//field[@name='name']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="attrs">{'readonly': 1}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='highest_name']/.." position="attributes">
|
||||
<attribute name="attrs">{'invisible': 1}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
../../../../account_move_name_sequence
|
||||
6
setup/account_move_name_sequence/setup.py
Normal file
6
setup/account_move_name_sequence/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
Reference in New Issue
Block a user