Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
8
account_spread_cost_revenue/models/__init__.py
Executable file
8
account_spread_cost_revenue/models/__init__.py
Executable file
@@ -0,0 +1,8 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import account_move
|
||||
from . import account_move_line
|
||||
from . import account_spread_line
|
||||
from . import account_spread
|
||||
from . import account_spread_template
|
||||
from . import res_company
|
||||
34
account_spread_cost_revenue/models/account_move.py
Executable file
34
account_spread_cost_revenue/models/account_move.py
Executable file
@@ -0,0 +1,34 @@
|
||||
# Copyright 2016-2020 Onestein (<https://www.onestein.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.move"
|
||||
|
||||
def action_post(self):
|
||||
"""Invoked when validating the invoices."""
|
||||
self.mapped("invoice_line_ids").create_auto_spread()
|
||||
res = super().action_post()
|
||||
spreads = self.mapped("invoice_line_ids.spread_id")
|
||||
spreads.compute_spread_board()
|
||||
# On posting of spread moves. Find their related spreads to reconcile
|
||||
move_spreads = self.env["account.spread"].search(
|
||||
[("line_ids.move_id", "in", self.ids)]
|
||||
)
|
||||
spreads += move_spreads
|
||||
spreads.reconcile_spread_moves()
|
||||
return res
|
||||
|
||||
def button_cancel(self):
|
||||
"""Cancel the spread lines and their related moves when
|
||||
the invoice is canceled."""
|
||||
spread_lines = self.mapped("invoice_line_ids.spread_id.line_ids")
|
||||
moves = spread_lines.mapped("move_id")
|
||||
moves.line_ids.remove_move_reconcile()
|
||||
moves.filtered(lambda move: move.state == "posted").button_draft()
|
||||
moves.with_context(force_delete=True).unlink()
|
||||
spread_lines.unlink()
|
||||
res = super().button_cancel()
|
||||
return res
|
||||
178
account_spread_cost_revenue/models/account_move_line.py
Executable file
178
account_spread_cost_revenue/models/account_move_line.py
Executable file
@@ -0,0 +1,178 @@
|
||||
# Copyright 2016-2020 Onestein (<https://www.onestein.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
spread_id = fields.Many2one("account.spread", string="Spread Board", copy=False)
|
||||
spread_check = fields.Selection(
|
||||
[
|
||||
("linked", "Linked"),
|
||||
("unlinked", "Unlinked"),
|
||||
("unavailable", "Unavailable"),
|
||||
],
|
||||
compute="_compute_spread_check",
|
||||
)
|
||||
|
||||
@api.depends("spread_id", "move_id.state")
|
||||
def _compute_spread_check(self):
|
||||
for line in self:
|
||||
if line.spread_id:
|
||||
line.spread_check = "linked"
|
||||
elif line.move_id.state == "draft":
|
||||
line.spread_check = "unlinked"
|
||||
else:
|
||||
line.spread_check = "unavailable"
|
||||
|
||||
def spread_details(self):
|
||||
"""Button on the invoice lines tree view of the invoice
|
||||
form to show the spread form view."""
|
||||
if not self:
|
||||
# In case the widget clicked before the creation of the line
|
||||
return
|
||||
|
||||
if self.spread_id:
|
||||
return {
|
||||
"name": _("Spread Details"),
|
||||
"view_mode": "form",
|
||||
"res_model": "account.spread",
|
||||
"type": "ir.actions.act_window",
|
||||
"target": "current",
|
||||
"readonly": False,
|
||||
"res_id": self.spread_id.id,
|
||||
}
|
||||
|
||||
# In case no spread board is linked to the invoice line
|
||||
# open the wizard to link them
|
||||
ctx = dict(
|
||||
self.env.context,
|
||||
default_invoice_line_id=self.id,
|
||||
default_company_id=self.move_id.company_id.id,
|
||||
allow_spread_planning=self.move_id.company_id.allow_spread_planning,
|
||||
)
|
||||
return {
|
||||
"name": _("Link Invoice Line with Spread Board"),
|
||||
"view_mode": "form",
|
||||
"res_model": "account.spread.invoice.line.link.wizard",
|
||||
"type": "ir.actions.act_window",
|
||||
"target": "new",
|
||||
"context": ctx,
|
||||
}
|
||||
|
||||
@api.constrains("spread_id", "account_id")
|
||||
def _check_spread_account_balance_sheet(self):
|
||||
for line in self:
|
||||
if not line.spread_id:
|
||||
pass
|
||||
elif line.move_id.move_type in ("out_invoice", "in_refund"):
|
||||
if line.account_id != line.spread_id.debit_account_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The account of the invoice line does not correspond "
|
||||
"to the Balance Sheet (debit account) of the spread"
|
||||
)
|
||||
)
|
||||
elif line.move_id.move_type in ("in_invoice", "out_refund"):
|
||||
if line.account_id != line.spread_id.credit_account_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The account of the invoice line does not correspond "
|
||||
"to the Balance Sheet (credit account) of the spread"
|
||||
)
|
||||
)
|
||||
|
||||
def write(self, vals):
|
||||
if vals.get("spread_id"):
|
||||
spread = self.env["account.spread"].browse(vals.get("spread_id"))
|
||||
if spread.invoice_type in ["out_invoice", "in_refund"]:
|
||||
vals["account_id"] = spread.debit_account_id.id
|
||||
else:
|
||||
vals["account_id"] = spread.credit_account_id.id
|
||||
return super().write(vals)
|
||||
|
||||
def _check_spread_reconcile_validity(self):
|
||||
# Improve error messages of standard Odoo
|
||||
reconciled_lines = self.filtered(lambda x: x.reconciled)
|
||||
msg_line = _(
|
||||
"Move line: %(line_id)s (%(line_name)s), account code: %(account_code)s\n"
|
||||
)
|
||||
if reconciled_lines:
|
||||
msg = _("Cannot reconcile entries that are already reconciled:\n")
|
||||
for line in reconciled_lines:
|
||||
msg += msg_line % {
|
||||
"line_id": line.id,
|
||||
"line_name": line.name,
|
||||
"account_code": line.account_id.code,
|
||||
}
|
||||
raise ValidationError(msg)
|
||||
if len(self.mapped("account_id").ids) > 1:
|
||||
msg = _("Some entries are not from the same account:\n")
|
||||
for line in self:
|
||||
msg += msg_line % {
|
||||
"line_id": line.id,
|
||||
"line_name": line.name,
|
||||
"account_code": line.account_id.code,
|
||||
}
|
||||
raise ValidationError(msg)
|
||||
|
||||
def create_auto_spread(self):
|
||||
"""Create auto spread table for each invoice line, when needed"""
|
||||
|
||||
def _filter_line(aline, iline):
|
||||
"""Find matching template auto line with invoice line"""
|
||||
if aline.product_id and iline.product_id != aline.product_id:
|
||||
return False
|
||||
if aline.account_id and iline.account_id != aline.account_id:
|
||||
return False
|
||||
if (
|
||||
aline.analytic_distribution
|
||||
and iline.analytic_distribution != aline.analytic_distribution
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
# Skip create new template when create move on spread lines
|
||||
if self.env.context.get("skip_create_template"):
|
||||
return
|
||||
|
||||
for line in self:
|
||||
if line.spread_check == "linked":
|
||||
continue
|
||||
spread_type = (
|
||||
"sale"
|
||||
if line.move_id.move_type in ["out_invoice", "out_refund"]
|
||||
else "purchase"
|
||||
)
|
||||
spread_auto = self.env["account.spread.template.auto"].search(
|
||||
[
|
||||
("template_id.auto_spread", "=", True),
|
||||
("template_id.spread_type", "=", spread_type),
|
||||
]
|
||||
)
|
||||
matched = spread_auto.filtered(lambda a, i=line: _filter_line(a, i))
|
||||
template = matched.mapped("template_id")
|
||||
if not template:
|
||||
continue
|
||||
elif len(template) > 1:
|
||||
raise UserError(
|
||||
_(
|
||||
"Too many auto spread templates (%(len_template)s) "
|
||||
"matched with the "
|
||||
"invoice line, %(line_name)s"
|
||||
)
|
||||
% {"len_template": len(template), "line_name": line.display_name}
|
||||
)
|
||||
# Found auto spread template for this invoice line, create it
|
||||
wizard = self.env["account.spread.invoice.line.link.wizard"].new(
|
||||
{
|
||||
"invoice_line_id": line.id,
|
||||
"company_id": line.company_id.id,
|
||||
"spread_action_type": "template",
|
||||
"template_id": template.id,
|
||||
}
|
||||
)
|
||||
wizard.confirm()
|
||||
614
account_spread_cost_revenue/models/account_spread.py
Executable file
614
account_spread_cost_revenue/models/account_spread.py
Executable file
@@ -0,0 +1,614 @@
|
||||
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
import calendar
|
||||
import time
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import float_is_zero
|
||||
|
||||
|
||||
class AccountSpread(models.Model):
|
||||
_name = "account.spread"
|
||||
_description = "Account Spread"
|
||||
_inherit = ["mail.thread", "analytic.mixin"]
|
||||
_check_company_auto = True
|
||||
|
||||
name = fields.Char(required=True)
|
||||
template_id = fields.Many2one("account.spread.template", string="Spread Template")
|
||||
invoice_type = fields.Selection(
|
||||
[
|
||||
("out_invoice", "Customer Invoice"),
|
||||
("in_invoice", "Vendor Bill"),
|
||||
("out_refund", "Customer Credit Note"),
|
||||
("in_refund", "Vendor Credit Note"),
|
||||
],
|
||||
required=True,
|
||||
)
|
||||
spread_type = fields.Selection(
|
||||
[("sale", "Customer"), ("purchase", "Supplier")],
|
||||
compute="_compute_spread_type",
|
||||
required=True,
|
||||
)
|
||||
period_number = fields.Integer(
|
||||
string="Number of Repetitions",
|
||||
default=12,
|
||||
help="Define the number of spread lines",
|
||||
required=True,
|
||||
)
|
||||
period_type = fields.Selection(
|
||||
[("month", "Month"), ("quarter", "Quarter"), ("year", "Year")],
|
||||
default="month",
|
||||
help="Period length for the entries",
|
||||
required=True,
|
||||
)
|
||||
days_calc = fields.Boolean(
|
||||
string="Calculate by days",
|
||||
default=False,
|
||||
help="Use number of days to calculate amount",
|
||||
)
|
||||
use_invoice_line_account = fields.Boolean()
|
||||
credit_account_id = fields.Many2one(
|
||||
"account.account",
|
||||
compute="_compute_credit_account_id",
|
||||
readonly=False,
|
||||
store=True,
|
||||
required=True,
|
||||
)
|
||||
debit_account_id = fields.Many2one(
|
||||
"account.account",
|
||||
compute="_compute_debit_account_id",
|
||||
readonly=False,
|
||||
store=True,
|
||||
required=True,
|
||||
)
|
||||
is_credit_account_deprecated = fields.Boolean(
|
||||
compute="_compute_deprecated_accounts"
|
||||
)
|
||||
is_debit_account_deprecated = fields.Boolean(compute="_compute_deprecated_accounts")
|
||||
unspread_amount = fields.Float(
|
||||
digits="Account",
|
||||
compute="_compute_amounts",
|
||||
)
|
||||
unposted_amount = fields.Float(
|
||||
digits="Account",
|
||||
compute="_compute_amounts",
|
||||
)
|
||||
posted_amount = fields.Float(
|
||||
digits="Account",
|
||||
compute="_compute_amounts",
|
||||
)
|
||||
total_amount = fields.Float(
|
||||
digits="Account",
|
||||
compute="_compute_amounts",
|
||||
)
|
||||
all_posted = fields.Boolean(compute="_compute_all_posted", store=True)
|
||||
line_ids = fields.One2many(
|
||||
"account.spread.line", "spread_id", string="Spread Lines"
|
||||
)
|
||||
spread_date = fields.Date(
|
||||
string="Start Date", default=time.strftime("%Y-01-01"), required=True
|
||||
)
|
||||
journal_id = fields.Many2one(
|
||||
"account.journal",
|
||||
compute="_compute_journal_id",
|
||||
readonly=False,
|
||||
precompute=True,
|
||||
store=True,
|
||||
required=True,
|
||||
check_company=True,
|
||||
domain="[('id', 'in', suitable_journal_ids)]",
|
||||
)
|
||||
suitable_journal_ids = fields.Many2many(
|
||||
"account.journal",
|
||||
compute="_compute_suitable_journal_ids",
|
||||
)
|
||||
invoice_line_ids = fields.One2many(
|
||||
"account.move.line", "spread_id", copy=False, string="Invoice Lines"
|
||||
)
|
||||
invoice_line_id = fields.Many2one(
|
||||
"account.move.line",
|
||||
string="Invoice line",
|
||||
compute="_compute_invoice_line",
|
||||
inverse="_inverse_invoice_line",
|
||||
store=True,
|
||||
)
|
||||
invoice_id = fields.Many2one(
|
||||
related="invoice_line_id.move_id",
|
||||
store=True,
|
||||
)
|
||||
estimated_amount = fields.Float(digits="Account")
|
||||
company_id = fields.Many2one(
|
||||
"res.company", default=lambda self: self.env.company, required=True
|
||||
)
|
||||
currency_id = fields.Many2one(
|
||||
"res.currency",
|
||||
required=True,
|
||||
default=lambda self: self.env.company.currency_id.id,
|
||||
)
|
||||
move_line_auto_post = fields.Boolean("Auto-post lines", default=True)
|
||||
display_create_all_moves = fields.Boolean(
|
||||
compute="_compute_display_create_all_moves",
|
||||
)
|
||||
display_recompute_buttons = fields.Boolean(
|
||||
compute="_compute_display_recompute_buttons",
|
||||
)
|
||||
display_move_line_auto_post = fields.Boolean(
|
||||
compute="_compute_display_move_line_auto_post",
|
||||
string="Display Button Auto-post lines",
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.model
|
||||
def default_journal(self, company_id):
|
||||
domain = [("type", "=", "general"), ("company_id", "=", company_id)]
|
||||
return self.env["account.journal"].search(domain, limit=1)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super().default_get(fields)
|
||||
if "journal_id" not in res:
|
||||
company_id = res.get("company_id", self.env.company.id)
|
||||
default_journal = self.default_journal(company_id)
|
||||
if default_journal:
|
||||
res["journal_id"] = default_journal.id
|
||||
return res
|
||||
|
||||
@api.depends("company_id")
|
||||
def _compute_suitable_journal_ids(self):
|
||||
for spread in self:
|
||||
domain = [("company_id", "=", spread.company_id.id)]
|
||||
spread.suitable_journal_ids = self.env["account.journal"].search(domain)
|
||||
|
||||
@api.depends("invoice_type")
|
||||
def _compute_spread_type(self):
|
||||
for spread in self:
|
||||
if spread.invoice_type in ["out_invoice", "out_refund"]:
|
||||
spread.spread_type = "sale"
|
||||
else:
|
||||
spread.spread_type = "purchase"
|
||||
|
||||
@api.depends("invoice_line_ids", "invoice_line_ids.move_id")
|
||||
def _compute_invoice_line(self):
|
||||
for spread in self:
|
||||
invoice_lines = spread.invoice_line_ids
|
||||
spread.invoice_line_id = invoice_lines and invoice_lines[-1:] or False
|
||||
|
||||
def _inverse_invoice_line(self):
|
||||
for spread in self:
|
||||
invoice_line = spread.invoice_line_id
|
||||
spread.write({"invoice_line_ids": [(6, 0, [invoice_line.id])]})
|
||||
|
||||
@api.depends(
|
||||
"estimated_amount",
|
||||
"currency_id",
|
||||
"company_id",
|
||||
"invoice_line_id.price_subtotal",
|
||||
"invoice_line_id.currency_id",
|
||||
"line_ids.amount",
|
||||
"line_ids.move_id.state",
|
||||
)
|
||||
def _compute_amounts(self):
|
||||
for spread in self:
|
||||
lines_move = spread.line_ids.filtered(lambda x: x.move_id)
|
||||
moves_amount = sum(spread_line.amount for spread_line in lines_move)
|
||||
lines_posted = lines_move.filtered(lambda x: x.move_id.state == "posted")
|
||||
posted_amount = sum(spread_line.amount for spread_line in lines_posted)
|
||||
total_amount = spread.estimated_amount
|
||||
if spread.invoice_line_id:
|
||||
total_amount = spread.invoice_line_id.currency_id._convert(
|
||||
spread.invoice_line_id.balance,
|
||||
spread.currency_id,
|
||||
spread.company_id,
|
||||
spread.invoice_id.date,
|
||||
)
|
||||
|
||||
spread.unspread_amount = total_amount - moves_amount
|
||||
spread.unposted_amount = total_amount - posted_amount
|
||||
spread.posted_amount = posted_amount
|
||||
spread.total_amount = total_amount
|
||||
|
||||
@api.depends("unposted_amount")
|
||||
def _compute_all_posted(self):
|
||||
for spread in self:
|
||||
rounding = self.currency_id.rounding
|
||||
unposted = spread.unposted_amount
|
||||
spread.all_posted = float_is_zero(unposted, precision_rounding=rounding)
|
||||
|
||||
def _compute_display_create_all_moves(self):
|
||||
for spread in self:
|
||||
any_not_move = any(not line.move_id for line in spread.line_ids)
|
||||
spread.display_create_all_moves = any_not_move
|
||||
|
||||
def _compute_display_recompute_buttons(self):
|
||||
for spread in self:
|
||||
spread.display_recompute_buttons = True
|
||||
if not spread.company_id.allow_spread_planning:
|
||||
if spread.invoice_id.state == "draft":
|
||||
spread.display_recompute_buttons = False
|
||||
|
||||
@api.depends("company_id.force_move_auto_post")
|
||||
def _compute_display_move_line_auto_post(self):
|
||||
for spread in self:
|
||||
auto_post = spread.company_id.force_move_auto_post
|
||||
spread.display_move_line_auto_post = not auto_post
|
||||
|
||||
def _get_spread_entry_name(self, seq):
|
||||
"""Use this method to customise the name of the accounting entry."""
|
||||
self.ensure_one()
|
||||
return (self.name or "") + "/" + str(seq)
|
||||
|
||||
@api.onchange("template_id")
|
||||
def onchange_template(self):
|
||||
if self.template_id:
|
||||
if self.template_id.spread_type == "sale":
|
||||
if self.invoice_type in ["in_invoice", "in_refund"]:
|
||||
self.invoice_type = "out_invoice"
|
||||
else:
|
||||
if self.invoice_type in ["out_invoice", "out_refund"]:
|
||||
self.invoice_type = "in_invoice"
|
||||
if self.template_id.period_number:
|
||||
self.period_number = self.template_id.period_number
|
||||
if self.template_id.period_type:
|
||||
self.period_type = self.template_id.period_type
|
||||
if self.template_id.start_date:
|
||||
self.spread_date = self.template_id.start_date
|
||||
if self.template_id.analytic_distribution:
|
||||
self.analytic_distribution = self.template_id.analytic_distribution
|
||||
self.days_calc = self.template_id.days_calc
|
||||
|
||||
@api.depends("invoice_type", "company_id")
|
||||
def _compute_journal_id(self):
|
||||
if not self.env.context.get("default_journal_id"):
|
||||
for spread in self:
|
||||
journal = spread.company_id.default_spread_expense_journal_id
|
||||
if spread.invoice_type in ("out_invoice", "in_refund"):
|
||||
journal = spread.company_id.default_spread_revenue_journal_id
|
||||
if not journal:
|
||||
journal = self.default_journal(spread.company_id.id)
|
||||
spread.journal_id = journal
|
||||
|
||||
@api.depends("invoice_type", "company_id")
|
||||
def _compute_debit_account_id(self):
|
||||
if not self.env.context.get("default_debit_account_id"):
|
||||
invoice_types = ("out_invoice", "in_refund")
|
||||
for spread in self.filtered(lambda s: s.invoice_type in invoice_types):
|
||||
debit_account = spread.company_id.default_spread_revenue_account_id
|
||||
spread.debit_account_id = debit_account
|
||||
|
||||
@api.depends("invoice_type", "company_id")
|
||||
def _compute_credit_account_id(self):
|
||||
if not self.env.context.get("default_credit_account_id"):
|
||||
invoice_types = ("in_invoice", "out_refund")
|
||||
for spread in self.filtered(lambda s: s.invoice_type in invoice_types):
|
||||
credit_account = spread.company_id.default_spread_expense_account_id
|
||||
spread.credit_account_id = credit_account
|
||||
|
||||
@api.constrains("invoice_id", "invoice_type")
|
||||
def _check_invoice_type(self):
|
||||
if self.filtered(
|
||||
lambda s: s.invoice_id and s.invoice_type != s.invoice_id.move_type
|
||||
):
|
||||
raise ValidationError(
|
||||
_("The Invoice Type does not correspond to the Invoice")
|
||||
)
|
||||
|
||||
@api.constrains("journal_id")
|
||||
def _check_journal(self):
|
||||
for spread in self:
|
||||
moves = spread.mapped("line_ids.move_id").filtered("journal_id")
|
||||
if any(move.journal_id != spread.journal_id for move in moves):
|
||||
err_msg = _("The Journal is not consistent with the account moves.")
|
||||
raise ValidationError(err_msg)
|
||||
|
||||
@api.constrains("template_id", "invoice_type")
|
||||
def _check_template_invoice_type(self):
|
||||
for spread in self.filtered(lambda s: s.template_id.spread_type == "sale"):
|
||||
if spread.invoice_type in ["in_invoice", "in_refund"]:
|
||||
err_msg = _(
|
||||
"The Spread Template (Sales) is not compatible "
|
||||
"with selected invoice type"
|
||||
)
|
||||
raise ValidationError(err_msg)
|
||||
for spread in self.filtered(lambda s: s.template_id.spread_type == "purchase"):
|
||||
if spread.invoice_type in ["out_invoice", "out_refund"]:
|
||||
err_msg = _(
|
||||
"The Spread Template (Purchases) is not compatible "
|
||||
"with selected invoice type"
|
||||
)
|
||||
raise ValidationError(err_msg)
|
||||
|
||||
def _get_spread_period_duration(self):
|
||||
"""Converts the selected period_type to number of months."""
|
||||
self.ensure_one()
|
||||
if self.period_type == "year":
|
||||
return 12
|
||||
elif self.period_type == "quarter":
|
||||
return 3
|
||||
return 1
|
||||
|
||||
def _init_line_date(self, posted_line_ids):
|
||||
"""Calculates the initial spread date. This method
|
||||
is used by "def _compute_spread_board()" method.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if posted_line_ids:
|
||||
# if we already have some previous validated entries,
|
||||
# starting date is last entry + method period
|
||||
last_date = posted_line_ids[-1].date
|
||||
months = self._get_spread_period_duration()
|
||||
spread_date = last_date + relativedelta(months=months)
|
||||
else:
|
||||
spread_date = self.spread_date
|
||||
return spread_date
|
||||
|
||||
def _next_line_date(self, month_day, date):
|
||||
"""Calculates the next spread date. This method
|
||||
is used by "def _compute_spread_board()" method.
|
||||
"""
|
||||
self.ensure_one()
|
||||
months = self._get_spread_period_duration()
|
||||
date = date + relativedelta(months=months)
|
||||
# get the last day of the month
|
||||
if month_day > 28:
|
||||
max_day_in_month = calendar.monthrange(date.year, date.month)[1]
|
||||
date = date.replace(day=min(max_day_in_month, month_day))
|
||||
return date
|
||||
|
||||
def _compute_spread_board(self):
|
||||
"""Creates the spread lines. This method is highly inspired
|
||||
from method compute_depreciation_board() present in standard
|
||||
Odoo 11.0 "account_asset" module, developed by Odoo SA.
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
posted_line_ids = self.line_ids.filtered(
|
||||
lambda x: x.move_id.state == "posted"
|
||||
).sorted(key=lambda x: x.date)
|
||||
unposted_line_ids = self.line_ids.filtered(
|
||||
lambda x: not x.move_id.state == "posted"
|
||||
)
|
||||
|
||||
# Remove old unposted spread lines.
|
||||
commands = [(2, line_id.id, False) for line_id in unposted_line_ids]
|
||||
|
||||
if self.unposted_amount != 0.0:
|
||||
unposted_amount = self.unposted_amount
|
||||
|
||||
spread_date = self._init_line_date(posted_line_ids)
|
||||
|
||||
month_day = spread_date.day
|
||||
number_of_periods = self._get_number_of_periods(month_day)
|
||||
|
||||
for x in range(len(posted_line_ids), number_of_periods):
|
||||
sequence = x + 1
|
||||
date = self._get_last_day_of_month(spread_date)
|
||||
amount = self._compute_board_amount(
|
||||
sequence, unposted_amount, number_of_periods, date
|
||||
)
|
||||
amount = self.currency_id.round(amount)
|
||||
rounding = self.currency_id.rounding
|
||||
if float_is_zero(amount, precision_rounding=rounding):
|
||||
continue
|
||||
unposted_amount -= amount
|
||||
vals = {
|
||||
"amount": amount,
|
||||
"spread_id": self.id,
|
||||
"name": self._get_spread_entry_name(sequence),
|
||||
"date": date,
|
||||
}
|
||||
commands.append((0, False, vals))
|
||||
|
||||
spread_date = self._next_line_date(month_day, spread_date)
|
||||
|
||||
self.write({"line_ids": commands})
|
||||
invoice_type_selection = dict(
|
||||
self.fields_get(allfields=["invoice_type"])["invoice_type"]["selection"]
|
||||
)[self.invoice_type]
|
||||
msg_body = _("Spread table '%s' created.") % invoice_type_selection
|
||||
self.message_post(body=msg_body)
|
||||
|
||||
def _get_number_of_periods(self, month_day):
|
||||
"""Calculates the number of spread lines."""
|
||||
self.ensure_one()
|
||||
return self.period_number + 1 if month_day != 1 else self.period_number
|
||||
|
||||
@staticmethod
|
||||
def _get_first_day_of_month(spread_date):
|
||||
return spread_date + relativedelta(day=1)
|
||||
|
||||
@staticmethod
|
||||
def _get_last_day_of_month(spread_date):
|
||||
return spread_date + relativedelta(day=31)
|
||||
|
||||
def _get_spread_start_date(self, period_type, spread_end_date):
|
||||
self.ensure_one()
|
||||
spread_start_date = spread_end_date + relativedelta(days=1)
|
||||
if period_type == "month":
|
||||
spread_start_date = spread_end_date + relativedelta(day=1)
|
||||
elif period_type == "quarter":
|
||||
spread_start_date = spread_start_date - relativedelta(months=3)
|
||||
elif period_type == "year":
|
||||
spread_start_date = spread_start_date - relativedelta(years=1)
|
||||
spread_start_date = self._get_first_day_of_month(spread_start_date)
|
||||
spread_start_date = max(spread_start_date, self.spread_date)
|
||||
return spread_start_date
|
||||
|
||||
def _get_spread_end_date(self, period_type, period_number, spread_start_date):
|
||||
self.ensure_one()
|
||||
spread_end_date = spread_start_date
|
||||
number_of_periods = (
|
||||
period_number if spread_start_date.day != 1 else period_number - 1
|
||||
)
|
||||
if period_type == "month":
|
||||
spread_end_date = spread_start_date + relativedelta(
|
||||
months=number_of_periods
|
||||
)
|
||||
elif period_type == "quarter":
|
||||
months = number_of_periods * 3
|
||||
spread_end_date = spread_start_date + relativedelta(months=months)
|
||||
elif period_type == "year":
|
||||
spread_end_date = spread_start_date + relativedelta(years=number_of_periods)
|
||||
# calculate by days and not first day of month should compute residual day only
|
||||
if self.days_calc and spread_end_date.day != 1:
|
||||
spread_end_date = spread_end_date - relativedelta(days=1)
|
||||
else:
|
||||
spread_end_date = self._get_last_day_of_month(spread_end_date)
|
||||
return spread_end_date
|
||||
|
||||
def _get_amount_per_day(self, amount):
|
||||
self.ensure_one()
|
||||
spread_start_date = self.spread_date
|
||||
spread_end_date = self._get_spread_end_date(
|
||||
self.period_type, self.period_number, spread_start_date
|
||||
)
|
||||
number_of_days = (spread_end_date - spread_start_date).days + 1
|
||||
return amount / number_of_days
|
||||
|
||||
def _compute_board_amount(
|
||||
self, sequence, amount, number_of_periods, spread_end_date
|
||||
):
|
||||
"""Calculates the amount for the spread lines."""
|
||||
self.ensure_one()
|
||||
amount_to_spread = self.total_amount
|
||||
period = self.period_number
|
||||
if sequence != number_of_periods:
|
||||
amount = amount_to_spread / period
|
||||
if sequence == 1:
|
||||
date = self.spread_date
|
||||
month_days = calendar.monthrange(date.year, date.month)[1]
|
||||
days = month_days - date.day + 1
|
||||
amount = (amount_to_spread / period) / month_days * days
|
||||
if self.days_calc:
|
||||
spread_start_date = self._get_spread_start_date(
|
||||
self.period_type, spread_end_date
|
||||
)
|
||||
days = (spread_end_date - spread_start_date).days + 1
|
||||
amount = self._get_amount_per_day(amount_to_spread) * days
|
||||
return amount
|
||||
|
||||
def compute_spread_board(self):
|
||||
"""Checks whether the spread lines should be calculated.
|
||||
In case checks pass, invoke "def _compute_spread_board()" method.
|
||||
"""
|
||||
for spread in self.filtered(lambda s: s.total_amount):
|
||||
spread._compute_spread_board()
|
||||
|
||||
def action_recalculate_spread(self):
|
||||
"""Recalculate spread"""
|
||||
self.ensure_one()
|
||||
spread_lines = self.mapped("line_ids").filtered("move_id")
|
||||
spread_lines.unlink_move()
|
||||
self.compute_spread_board()
|
||||
self.env["account.spread.line"]._create_entries()
|
||||
|
||||
def action_undo_spread(self):
|
||||
"""Undo spreading: Remove all created moves"""
|
||||
self.ensure_one()
|
||||
self.mapped("line_ids").filtered("move_id").unlink_move()
|
||||
self.mapped("line_ids").unlink()
|
||||
|
||||
def action_unlink_invoice_line(self):
|
||||
"""Unlink the invoice line from the spread board"""
|
||||
self.ensure_one()
|
||||
if self.invoice_id.state != "draft":
|
||||
msg = _("Cannot unlink invoice lines if the invoice is validated")
|
||||
raise UserError(msg)
|
||||
self._action_unlink_invoice_line()
|
||||
|
||||
def _action_unlink_invoice_line(self):
|
||||
self.mapped("line_ids.move_id.line_ids").remove_move_reconcile()
|
||||
self._message_post_unlink_invoice_line()
|
||||
self.write({"invoice_line_ids": [(5, 0, 0)]})
|
||||
|
||||
def _message_post_unlink_invoice_line(self):
|
||||
for spread in self:
|
||||
inv_link = (
|
||||
"<a href=# data-oe-model=account.move "
|
||||
"data-oe-id=%d>%s</a>" % (spread.invoice_id.id, _("Invoice"))
|
||||
)
|
||||
msg_body = _(
|
||||
"Unlinked invoice line '%(spread_line_name)s' (view %(inv_link)s)."
|
||||
) % {
|
||||
"spread_line_name": spread.invoice_line_id.name,
|
||||
"inv_link": inv_link,
|
||||
}
|
||||
spread.message_post(body=Markup(msg_body))
|
||||
spread_link = (
|
||||
"<a href=# data-oe-model=account.spread "
|
||||
"data-oe-id=%d>%s</a>" % (spread.id, _("Spread"))
|
||||
)
|
||||
msg_body = _("Unlinked '%(spread_link)s' (invoice line %(inv_line)s).") % {
|
||||
"spread_link": spread_link,
|
||||
"inv_line": spread.invoice_line_id.name,
|
||||
}
|
||||
spread.invoice_id.message_post(body=Markup(msg_body))
|
||||
|
||||
def unlink(self):
|
||||
if self.filtered(lambda s: s.invoice_line_id):
|
||||
err_msg = _("Cannot delete spread(s) that are linked to an invoice line.")
|
||||
raise UserError(err_msg)
|
||||
if self.mapped("line_ids.move_id").filtered(lambda m: m.state == "posted"):
|
||||
err_msg = _("Cannot delete spread(s): there are posted Journal Entries.")
|
||||
raise ValidationError(err_msg)
|
||||
return super().unlink()
|
||||
|
||||
def reconcile_spread_moves(self):
|
||||
for spread in self:
|
||||
spread._reconcile_spread_moves()
|
||||
|
||||
def _reconcile_spread_moves(self, created_moves=False):
|
||||
"""Reconcile spread moves if possible"""
|
||||
self.ensure_one()
|
||||
|
||||
spread_mls = self.line_ids.mapped("move_id.line_ids")
|
||||
if created_moves:
|
||||
spread_mls |= created_moves.mapped("line_ids")
|
||||
|
||||
account = self.invoice_line_id.account_id
|
||||
mls_to_reconcile = spread_mls.filtered(lambda x: x.account_id == account)
|
||||
|
||||
if mls_to_reconcile:
|
||||
do_reconcile = mls_to_reconcile + self.invoice_line_ids
|
||||
do_reconcile.remove_move_reconcile()
|
||||
for line in do_reconcile:
|
||||
line.reconciled = False
|
||||
# ensure to reconcile only posted items
|
||||
do_reconcile = do_reconcile.filtered(lambda x: x.move_id.state == "posted")
|
||||
do_reconcile._check_spread_reconcile_validity()
|
||||
do_reconcile.reconcile()
|
||||
|
||||
def create_all_moves(self):
|
||||
for line in self.mapped("line_ids").filtered(lambda x: not x.move_id):
|
||||
line.create_move()
|
||||
|
||||
def _post_spread_moves(self, moves):
|
||||
self.ensure_one()
|
||||
moves = moves.filtered(lambda x: x.state != "posted")
|
||||
if not moves:
|
||||
return
|
||||
ctx = dict(self.env.context, skip_unique_sequence_number=True)
|
||||
if self.company_id.force_move_auto_post or self.move_line_auto_post:
|
||||
moves.with_context(**ctx).action_post()
|
||||
|
||||
@api.depends("debit_account_id.deprecated", "credit_account_id.deprecated")
|
||||
def _compute_deprecated_accounts(self):
|
||||
for spread in self:
|
||||
spread.is_debit_account_deprecated = spread.debit_account_id.deprecated
|
||||
spread.is_credit_account_deprecated = spread.credit_account_id.deprecated
|
||||
|
||||
def open_posted_view(self):
|
||||
action_name = "account_spread_cost_revenue.action_account_moves_all_spread"
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(action_name)
|
||||
action["domain"] = [("id", "in", [])]
|
||||
spread_mls = self.line_ids.mapped("move_id.line_ids")
|
||||
if self.env.context.get("show_reconciled_only"):
|
||||
spread_mls = spread_mls.filtered(lambda m: m.reconciled)
|
||||
if spread_mls:
|
||||
domain = [("id", "in", spread_mls.ids + [self.invoice_line_id.id])]
|
||||
action["domain"] = domain
|
||||
return action
|
||||
178
account_spread_cost_revenue/models/account_spread_line.py
Executable file
178
account_spread_cost_revenue/models/account_spread_line.py
Executable file
@@ -0,0 +1,178 @@
|
||||
# Copyright 2016-2020 Onestein (<https://www.onestein.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountInvoiceSpreadLine(models.Model):
|
||||
_name = "account.spread.line"
|
||||
_description = "Account Spread Lines"
|
||||
_order = "date"
|
||||
|
||||
name = fields.Char("Description")
|
||||
amount = fields.Float(digits="Account", required=True)
|
||||
date = fields.Date(required=True)
|
||||
spread_id = fields.Many2one("account.spread", ondelete="cascade")
|
||||
move_id = fields.Many2one("account.move", string="Journal Entry")
|
||||
|
||||
def create_and_reconcile_moves(self):
|
||||
grouped_lines = {}
|
||||
for spread_line in self:
|
||||
spread = spread_line.spread_id
|
||||
spread_line_list = grouped_lines.get(
|
||||
spread, self.env["account.spread.line"]
|
||||
)
|
||||
grouped_lines.update({spread: spread_line_list + spread_line})
|
||||
for spread in grouped_lines:
|
||||
created_moves = grouped_lines[spread]._create_moves()
|
||||
spread._post_spread_moves(created_moves)
|
||||
if created_moves:
|
||||
post_msg = _("Created move(s) ")
|
||||
post_msg += ", ".join(
|
||||
"<a href=# data-oe-model=account.move data-oe-id=%d"
|
||||
">%s</a>" % (move.id, move.name)
|
||||
for move in created_moves
|
||||
)
|
||||
spread.message_post(body=Markup(post_msg))
|
||||
|
||||
def create_move(self):
|
||||
"""Button to manually create a move from a spread line entry."""
|
||||
self.ensure_one()
|
||||
self.with_context(
|
||||
skip_create_template=True,
|
||||
).create_and_reconcile_moves()
|
||||
|
||||
def _create_moves(self):
|
||||
if self.filtered(lambda x: x.move_id):
|
||||
raise UserError(
|
||||
_(
|
||||
"This spread line is already linked to a "
|
||||
"journal entry! Please post or delete it."
|
||||
)
|
||||
)
|
||||
|
||||
created_moves = self.env["account.move"]
|
||||
for line in self:
|
||||
move_vals = line._prepare_move()
|
||||
move = (
|
||||
self.env["account.move"]
|
||||
.with_context(check_move_validity=False)
|
||||
.create(move_vals)
|
||||
)
|
||||
line.move_id = move
|
||||
created_moves += move
|
||||
return created_moves
|
||||
|
||||
def _prepare_move(self):
|
||||
self.ensure_one()
|
||||
|
||||
spread_date = self.env.context.get("spread_date") or self.date
|
||||
spread = self.spread_id
|
||||
analytic_distribution = spread.analytic_distribution
|
||||
|
||||
company_currency = spread.company_id.currency_id
|
||||
current_currency = spread.currency_id
|
||||
amount = current_currency._convert(
|
||||
self.amount, company_currency, spread.company_id, spread_date
|
||||
)
|
||||
|
||||
debit_credit = spread.invoice_type in ["in_invoice", "out_refund"]
|
||||
|
||||
line_ids = [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": spread.name,
|
||||
"account_id": spread.debit_account_id.id
|
||||
if debit_credit
|
||||
else spread.credit_account_id.id,
|
||||
"debit": amount if amount > 0.0 else 0.0,
|
||||
"credit": -amount if amount < 0.0 else 0.0,
|
||||
"partner_id": self.spread_id.invoice_id.partner_id.id,
|
||||
"journal_id": self.spread_id.journal_id.id,
|
||||
"analytic_distribution": analytic_distribution,
|
||||
"date": self.date,
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": spread.name,
|
||||
"account_id": spread.credit_account_id.id
|
||||
if debit_credit
|
||||
else spread.debit_account_id.id,
|
||||
"credit": amount if amount > 0.0 else 0.0,
|
||||
"debit": -amount if amount < 0.0 else 0.0,
|
||||
"partner_id": self.spread_id.invoice_id.partner_id.id,
|
||||
"journal_id": self.spread_id.journal_id.id,
|
||||
"analytic_distribution": analytic_distribution,
|
||||
"date": self.date,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
return {
|
||||
"name": False,
|
||||
"ref": self.name,
|
||||
"date": spread_date,
|
||||
"invoice_date": spread_date,
|
||||
"journal_id": spread.journal_id.id,
|
||||
"line_ids": line_ids,
|
||||
"company_id": spread.company_id.id,
|
||||
"partner_id": spread.invoice_id.partner_id.id,
|
||||
}
|
||||
|
||||
def open_move(self):
|
||||
"""Used by a button to manually view a move from a spread line entry."""
|
||||
self.ensure_one()
|
||||
return {
|
||||
"name": _("Journal Entry"),
|
||||
"view_mode": "form",
|
||||
"res_model": "account.move",
|
||||
"view_id": False,
|
||||
"type": "ir.actions.act_window",
|
||||
"res_id": self.move_id.id,
|
||||
}
|
||||
|
||||
def unlink_move(self):
|
||||
"""Used by a button to manually unlink a move from a spread line entry."""
|
||||
for line in self:
|
||||
move = line.move_id
|
||||
if move.state == "posted":
|
||||
move.button_cancel()
|
||||
move.line_ids.remove_move_reconcile()
|
||||
post_msg = _("Deleted move %s") % line.move_id.id
|
||||
move.with_context(force_delete=True).unlink()
|
||||
line.move_id = False
|
||||
line.spread_id.message_post(body=post_msg)
|
||||
|
||||
@api.model
|
||||
def _create_entries(self):
|
||||
"""Find spread line entries where date is in the past and
|
||||
create moves for them. Method also called by the cron job.
|
||||
"""
|
||||
lines = self.search(
|
||||
[("date", "<=", fields.Date.today()), ("move_id", "=", False)]
|
||||
)
|
||||
lines.create_and_reconcile_moves()
|
||||
|
||||
unposted_moves = (
|
||||
self.search([("move_id", "!=", False)])
|
||||
.mapped("move_id")
|
||||
.filtered(lambda m: m.state != "posted")
|
||||
)
|
||||
unposted_moves.filtered(
|
||||
lambda m: m.company_id.force_move_auto_post
|
||||
).action_post()
|
||||
|
||||
spreads_to_archive = (
|
||||
self.env["account.spread"]
|
||||
.search([("all_posted", "=", True)])
|
||||
.filtered(lambda s: s.company_id.auto_archive_spread)
|
||||
)
|
||||
spreads_to_archive.write({"active": False})
|
||||
179
account_spread_cost_revenue/models/account_spread_template.py
Executable file
179
account_spread_cost_revenue/models/account_spread_template.py
Executable file
@@ -0,0 +1,179 @@
|
||||
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountSpreadTemplate(models.Model):
|
||||
_name = "account.spread.template"
|
||||
_inherit = "analytic.mixin"
|
||||
_description = "Account Spread Template"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
spread_type = fields.Selection(
|
||||
[("sale", "Customer"), ("purchase", "Supplier")], default="sale", required=True
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
"res.company", default=lambda self: self.env.company, required=True
|
||||
)
|
||||
spread_journal_id = fields.Many2one(
|
||||
"account.journal",
|
||||
string="Journal",
|
||||
compute="_compute_spread_journal",
|
||||
readonly=False,
|
||||
store=True,
|
||||
required=True,
|
||||
)
|
||||
use_invoice_line_account = fields.Boolean(
|
||||
string="Invoice account as spread account",
|
||||
help="Use invoice line's account as Balance sheet / spread account.\n"
|
||||
"In this case, user need to select expense/revenue account too.",
|
||||
)
|
||||
spread_account_id = fields.Many2one(
|
||||
"account.account",
|
||||
string="Spread Balance Sheet Account",
|
||||
compute="_compute_spread_account",
|
||||
readonly=False,
|
||||
store=True,
|
||||
required=False,
|
||||
)
|
||||
exp_rev_account_id = fields.Many2one(
|
||||
"account.account",
|
||||
string="Expense/Revenue Account",
|
||||
help="Optional account to overwrite the existing expense/revenue account",
|
||||
)
|
||||
period_number = fields.Integer(
|
||||
string="Number of Repetitions", help="Define the number of spread lines"
|
||||
)
|
||||
period_type = fields.Selection(
|
||||
[("month", "Month"), ("quarter", "Quarter"), ("year", "Year")],
|
||||
help="Period length for the entries",
|
||||
)
|
||||
start_date = fields.Date()
|
||||
days_calc = fields.Boolean(
|
||||
string="Calculate by days",
|
||||
default=False,
|
||||
help="Use number of days to calculate amount",
|
||||
)
|
||||
auto_spread = fields.Boolean(
|
||||
string="Auto assign template on invoice validate",
|
||||
help="If checked, provide option to auto create spread during "
|
||||
"invoice validation, based on product/account/analytic in invoice line.",
|
||||
)
|
||||
auto_spread_ids = fields.One2many(
|
||||
comodel_name="account.spread.template.auto",
|
||||
string="Auto Spread On",
|
||||
inverse_name="template_id",
|
||||
)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super().default_get(fields)
|
||||
if not res.get("company_id"):
|
||||
res["company_id"] = self.env.company.id
|
||||
if "spread_journal_id" not in res:
|
||||
default_journal = self.env["account.spread"].default_journal(
|
||||
res["company_id"]
|
||||
)
|
||||
if default_journal:
|
||||
res["spread_journal_id"] = default_journal.id
|
||||
return res
|
||||
|
||||
@api.constrains("auto_spread", "auto_spread_ids")
|
||||
def _check_product_account(self):
|
||||
for rec in self.filtered("auto_spread"):
|
||||
for line in rec.auto_spread_ids:
|
||||
if not line.product_id and not line.account_id:
|
||||
raise UserError(
|
||||
_(
|
||||
"Please select product and/or account "
|
||||
"on auto spread options"
|
||||
)
|
||||
)
|
||||
|
||||
@api.depends("spread_type", "company_id")
|
||||
def _compute_spread_journal(self):
|
||||
for spread in self:
|
||||
company = spread.company_id
|
||||
if spread.spread_type == "sale":
|
||||
journal = company.default_spread_revenue_journal_id
|
||||
else:
|
||||
journal = company.default_spread_expense_journal_id
|
||||
if journal:
|
||||
spread.spread_journal_id = journal
|
||||
|
||||
@api.depends("spread_type", "company_id")
|
||||
def _compute_spread_account(self):
|
||||
for spread in self:
|
||||
company = spread.company_id
|
||||
if spread.spread_type == "sale":
|
||||
account = company.default_spread_revenue_account_id
|
||||
else:
|
||||
account = company.default_spread_expense_account_id
|
||||
if account:
|
||||
spread.spread_account_id = account
|
||||
|
||||
@api.onchange("use_invoice_line_account")
|
||||
def _onchange_user_invoice_line_account(self):
|
||||
self.exp_rev_account_id = False
|
||||
|
||||
def _prepare_spread_from_template(self, spread_account_id=False):
|
||||
self.ensure_one()
|
||||
company = self.company_id
|
||||
spread_vals = {
|
||||
"name": self.name,
|
||||
"template_id": self.id,
|
||||
"journal_id": self.spread_journal_id.id,
|
||||
"use_invoice_line_account": self.use_invoice_line_account,
|
||||
"days_calc": self.days_calc,
|
||||
"company_id": company.id,
|
||||
}
|
||||
|
||||
account_id = spread_account_id or self.spread_account_id.id
|
||||
if self.spread_type == "sale":
|
||||
invoice_type = "out_invoice"
|
||||
spread_vals["debit_account_id"] = account_id
|
||||
else:
|
||||
invoice_type = "in_invoice"
|
||||
spread_vals["credit_account_id"] = account_id
|
||||
|
||||
if self.period_number:
|
||||
spread_vals["period_number"] = self.period_number
|
||||
if self.period_type:
|
||||
spread_vals["period_type"] = self.period_type
|
||||
if self.start_date:
|
||||
spread_vals["spread_date"] = self.start_date
|
||||
|
||||
spread_vals["invoice_type"] = invoice_type
|
||||
return spread_vals
|
||||
|
||||
|
||||
class AccountSpreadTemplateAuto(models.Model):
|
||||
_name = "account.spread.template.auto"
|
||||
_inherit = "analytic.mixin"
|
||||
_description = "Auto create spread, based on product/account/analytic"
|
||||
|
||||
template_id = fields.Many2one(
|
||||
comodel_name="account.spread.template",
|
||||
string="Spread Template",
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
index=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
related="template_id.company_id",
|
||||
store=True,
|
||||
)
|
||||
name = fields.Char(
|
||||
required=True,
|
||||
default="/",
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
string="Product",
|
||||
)
|
||||
account_id = fields.Many2one(
|
||||
comodel_name="account.account",
|
||||
string="Account",
|
||||
)
|
||||
40
account_spread_cost_revenue/models/res_company.py
Executable file
40
account_spread_cost_revenue/models/res_company.py
Executable file
@@ -0,0 +1,40 @@
|
||||
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
default_spread_revenue_account_id = fields.Many2one(
|
||||
"account.account", string="Revenue Spread Account"
|
||||
)
|
||||
|
||||
default_spread_expense_account_id = fields.Many2one(
|
||||
"account.account", string="Expense Spread Account"
|
||||
)
|
||||
|
||||
default_spread_revenue_journal_id = fields.Many2one(
|
||||
"account.journal", string="Revenue Spread Journal"
|
||||
)
|
||||
|
||||
default_spread_expense_journal_id = fields.Many2one(
|
||||
"account.journal", string="Expense Spread Journal"
|
||||
)
|
||||
|
||||
allow_spread_planning = fields.Boolean(
|
||||
default=True,
|
||||
help="Disable this option if you do not want to allow the "
|
||||
"spreading before the invoice is validated.",
|
||||
)
|
||||
force_move_auto_post = fields.Boolean(
|
||||
"Auto-post spread lines",
|
||||
help="Enable this option if you want to post automatically the "
|
||||
"accounting moves of all the spreads.",
|
||||
)
|
||||
auto_archive_spread = fields.Boolean(
|
||||
"Auto-archive spread",
|
||||
help="Enable this option if you want the cron job to automatically "
|
||||
"archive the spreads when all lines are posted.",
|
||||
)
|
||||
Reference in New Issue
Block a user