Odoo18-Base/addons/sale_expense/models/hr_expense_sheet.py

115 lines
5.6 KiB
Python
Raw Permalink Normal View History

2025-01-06 10:57:38 +07:00
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import Counter
from odoo import fields, models, _
class HrExpenseSheet(models.Model):
_inherit = "hr.expense.sheet"
sale_order_count = fields.Integer(compute='_compute_sale_order_count')
def _compute_sale_order_count(self):
for sheet in self:
sheet.sale_order_count = len(sheet.expense_line_ids.sale_order_id)
def _get_sale_order_lines(self):
"""
This method is used to try to find the sale order lines created by expense sheets.
It is used to reset the quantities of the sale order lines when the expense sheet is reset.
It uses several shared fields to try to find the sale order lines:
- order_id
- product_id
- product_uom_qty
- sale order line's price_unit (computed from the product_id, then rounded to the currency's rounding)
- name
"""
# Get the product account move lines created by an expense
expensed_amls = self.account_move_ids.line_ids.filtered(lambda aml: aml.expense_id.sale_order_id and aml.balance >= 0 and not aml.tax_line_id)
if not expensed_amls:
return self.env['sale.order.line']
# Get the sale orders linked to the related expenses
aml_to_so_map = expensed_amls._sale_determine_order()
self.env['sale.order.line'].flush_model(['order_id', 'product_id', 'product_uom_qty', 'price_unit', 'name'])
self.env['res.company'].flush_model(['currency_id'])
self.env['res.currency'].flush_model(['rounding'])
query = """
WITH aml(key_id, key_count, order_id, product_id, product_uom_qty, price_unit, name) AS (VALUES %s)
SELECT ARRAY_AGG(sol.id ORDER BY sol.id), aml.key_count
FROM aml,
sale_order_line AS sol
JOIN res_company AS company ON sol.company_id = company.id
JOIN res_currency AS company_currency ON company.currency_id = company_currency.id
LEFT JOIN res_currency AS currency ON sol.currency_id = currency.id
WHERE sol.is_expense = TRUE
AND sol.order_id = aml.order_id
AND sol.product_id = aml.product_id
AND sol.product_uom_qty = aml.product_uom_qty
AND sol.name = aml.name
AND ROUND(sol.price_unit::numeric, COALESCE(currency.rounding, company_currency.rounding)::int)
= ROUND(aml.price_unit::numeric, COALESCE(currency.rounding, company_currency.rounding)::int)
GROUP BY aml.key_id, aml.key_count
"""
# Get the keys used to fetch the corresponding sale order lines, and the number of times they are used
# We need the occurrences count to filter out the sale order lines so that we keep exactly one per expense
expense_keys_counter = Counter(expensed_amls.mapped(lambda aml: (
aml.expense_id.sale_order_id.id,
aml.product_id.id,
aml.quantity,
aml.currency_id.round(aml._sale_get_invoice_price(aml_to_so_map[aml.id])),
aml.name,
)))
expensed_amls_keys_and_count = tuple(
(key_id, key_count, *key) for key_id, (key, key_count) in enumerate(expense_keys_counter.items())
)
self.env.cr.execute_values(query, expensed_amls_keys_and_count)
# Filters out the sale order lines so that we only keep one per expense
sol_ids = []
for all_sol_ids_per_key, expense_count_per_key in self.env.cr.fetchall():
sol_ids += all_sol_ids_per_key[:expense_count_per_key]
return self.env['sale.order.line'].browse(sol_ids)
def _sale_expense_reset_sol_quantities(self):
sale_order_lines = self._get_sale_order_lines()
sale_order_lines.write({'qty_delivered': 0.0, 'product_uom_qty': 0.0})
def action_reset_expense_sheets(self):
super().action_reset_expense_sheets()
self.sudo()._sale_expense_reset_sol_quantities()
def action_open_sale_orders(self):
self.ensure_one()
if self.sale_order_count == 1:
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.expense_line_ids.sale_order_id.display_name,
'res_id': self.expense_line_ids.sale_order_id.id,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'sale.order',
'views': [(self.env.ref('sale.view_order_tree').id, 'list'), (self.env.ref("sale.view_order_form").id, 'form')],
'view_mode': 'list,form',
'target': 'current',
'name': _('Reinvoiced Sales Orders'),
'domain': [('id', 'in', self.expense_line_ids.sale_order_id.ids)],
}
def _do_create_moves(self):
""" When posting expense, we need the analytic entries to be generated, so a AA is required to reinvoice.
We then ensure a AA is given in the distribution and if not, we create a AA et set the distribution to it.
"""
for expense in self.expense_line_ids:
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()._do_create_moves()