19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,18 +1,31 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import Command, api, fields, models
class Expense(models.Model):
class HrExpense(models.Model):
_inherit = "hr.expense"
sale_order_id = fields.Many2one('sale.order', compute='_compute_sale_order_id', store=True, string='Customer to Reinvoice', readonly=False, tracking=True,
states={'done': [('readonly', True)], 'refused': [('readonly', True)]},
sale_order_id = fields.Many2one(
'sale.order',
string='Customer to Reinvoice',
compute='_compute_sale_order_id',
store=True,
readonly=False,
index='btree_not_null',
tracking=True,
# NOTE: only confirmed SO can be selected, but this domain in activated throught the name search with the `sale_expense_all_order`
# context key. So, this domain is not the one applied.
domain="[('state', '=', 'sale'), ('company_id', '=', company_id)]",
domain="[('state', '=', 'sale')]",
check_company=True,
help="If the category has an expense policy, it will be reinvoiced on this sales order")
sale_order_line_id = fields.Many2one(
comodel_name='sale.order.line',
compute='_compute_sale_order_id',
store=True,
readonly=True,
index='btree_not_null',
)
can_be_reinvoiced = fields.Boolean("Can be reinvoiced", compute='_compute_can_be_reinvoiced')
@api.depends('product_id.expense_policy')
@ -24,12 +37,7 @@ class Expense(models.Model):
def _compute_sale_order_id(self):
for expense in self.filtered(lambda e: not e.can_be_reinvoiced):
expense.sale_order_id = False
def _compute_analytic_distribution(self):
super(Expense, self)._compute_analytic_distribution()
for expense in self.filtered('sale_order_id'):
if expense.sale_order_id.sudo().analytic_account_id:
expense.analytic_distribution = {expense.sale_order_id.sudo().analytic_account_id.id: 100} # `sudo` required for normal employee without sale access rights
expense.sale_order_line_id = False
@api.onchange('sale_order_id')
def _onchange_sale_order_id(self):
@ -37,22 +45,47 @@ class Expense(models.Model):
to_reset.invalidate_recordset(['analytic_distribution'])
self.env.add_to_compute(self._fields['analytic_distribution'], to_reset)
def _sale_expense_reset_sol_quantities(self):
"""
Resets the quantity of a SOL created by a reinvoiced expense to 0 when the expense or its move is reset to an unfinished state
Note: Resetting the qty_delivered will raise if the product is a storable product and sale_stock is installed,
but it's fine as it doesn't make much sense to have a stored product in an expense.
"""
self.check_access('write')
# If we can edit the expense, we may not be able to edit the sol without sudoing.
self.sudo().sale_order_line_id.write({
'qty_delivered': 0.0,
'product_uom_qty': 0.0,
'expense_ids': [Command.clear()],
})
def _get_split_values(self):
vals = super(Expense, self)._get_split_values()
# EXTENDS hr_expense
vals = super()._get_split_values()
for split_value in vals:
split_value['sale_order_id'] = self.sale_order_id.id
return vals
def action_move_create(self):
""" When posting expense, if the AA is given, we will track cost in that
If a SO is set, this means we want to reinvoice the expense. But to do so, we
need the analytic entries to be generated, so a AA is required to reinvoice. So,
we ensure the AA if a SO is given.
"""
for expense in self.filtered(lambda expense: expense.sale_order_id and not expense.analytic_distribution):
if not expense.sale_order_id.analytic_account_id:
expense.sale_order_id._create_analytic_account()
expense.write({
'analytic_distribution': {expense.sale_order_id.analytic_account_id.id: 100}
})
return super(Expense, self).action_move_create()
def action_post(self):
# EXTENDS hr_expense
# When posting expense, we need the analytic entries to be generated, because reinvoicing uses analytic accounts.
# We then ensure the proper analytic acocunt is given in the distribution and if not,
# we create an account and set the distribution to it.
for expense in self:
if expense.sale_order_id and not expense.analytic_distribution:
analytic_account = self.env['account.analytic.account'].create(expense.sale_order_id._prepare_analytic_account_data())
expense.analytic_distribution = {analytic_account.id: 100}
return super().action_post()
def action_open_sale_order(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': 'sale.order',
'views': [(self.env.ref("sale.view_order_form").id, 'form')],
'view_mode': 'form',
'target': 'current',
'name': self.sale_order_id.display_name,
'res_id': self.sale_order_id.id,
}