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

[IMP] account,hr_expense: allow ORM to prefetch attachments #117074

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
41 changes: 1 addition & 40 deletions addons/account/models/account_bank_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,7 @@ class AccountBankStatement(models.Model):
compute='_compute_problem_description',
)

attachment_ids = fields.Many2many(
comodel_name='ir.attachment',
string="Attachments",
)
linked_attachment_ids = fields.One2many(comodel_name='ir.attachment', inverse_name='res_id')

# -------------------------------------------------------------------------
# COMPUTE METHODS
Expand Down Expand Up @@ -308,39 +305,3 @@ def default_get(self, fields_list):
defaults['line_ids'] = [Command.set(lines.ids)]

return defaults

@contextmanager
def _check_attachments(self, container, values_list):
attachments_to_fix_list = []
for values in values_list:
attachment_ids = set()
for orm_command in values.get('attachment_ids', []):
if orm_command[0] == Command.LINK:
attachment_ids.add(orm_command[1])
elif orm_command[0] == Command.SET:
for attachment_id in orm_command[2]:
attachment_ids.add(attachment_id)

attachments = self.env['ir.attachment'].browse(list(attachment_ids))
attachments_to_fix_list.append(attachments)

yield

for stmt, attachments in zip(container['records'], attachments_to_fix_list):
attachments.write({'res_id': stmt.id, 'res_model': stmt._name})

@api.model_create_multi
def create(self, vals_list):
container = {'records': self.env['account.bank.statement']}
with self._check_attachments(container, vals_list):
container['records'] = stmts = super().create(vals_list)
return stmts

def write(self, values):
if len(self) != 1 and 'attachment_ids' in values:
values.pop('attachment_ids')

container = {'records': self}
with self._check_attachments(container, [values]):
result = super().write(values)
return result
4 changes: 2 additions & 2 deletions addons/account/models/account_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@ def _sequence_fixed_regex(self):
show_name_warning = fields.Boolean(store=False)
type_name = fields.Char('Type Name', compute='_compute_type_name')
country_code = fields.Char(related='company_id.account_fiscal_country_id.code', readonly=True)
attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=[('res_model', '=', 'account.move')], string='Attachments')

# === Hash Fields === #
restrict_mode_hash_table = fields.Boolean(related='journal_id.restrict_mode_hash_table')
Expand Down Expand Up @@ -558,6 +557,7 @@ def _sequence_fixed_regex(self):
help='Defines the smallest coinage of the currency that can be used to pay by cash.',
)
send_and_print_values = fields.Json(copy=False)
linked_attachment_ids = fields.One2many(comodel_name='ir.attachment', inverse_name='res_id')
invoice_pdf_report_id = fields.Many2one(
comodel_name='ir.attachment',
string="PDF Attachment",
Expand Down Expand Up @@ -1618,7 +1618,7 @@ def _fetch_duplicate_reference(self, matching_states=('draft', 'posted')):
move.ref = duplicate_move.ref
AND (move.invoice_date = duplicate_move.invoice_date OR move.state = 'draft')
)
)
)
WHERE move.id IN %(moves)s
GROUP BY move.id
""", {
Expand Down
19 changes: 7 additions & 12 deletions addons/account/models/account_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -3078,18 +3078,13 @@ def _all_reconciled_lines(self):
"""Get all the the lines matched with the lines in self."""
return self._filter_reconciled_by_number(self._reconciled_by_number())

def _get_attachment_domains(self):
self.ensure_one()
domains = [[
('res_model', '=', 'account.move'),
('res_id', '=', self.move_id.id),
('res_field', 'in', (False, 'invoice_pdf_report_file')),
]]
if self.statement_id:
domains.append([('res_model', '=', 'account.bank.statement'), ('res_id', '=', self.statement_id.id)])
if self.payment_id:
domains.append([('res_model', '=', 'account.payment'), ('res_id', '=', self.payment_id.id)])
return domains
def _get_attachments(self):
return (
self.move_id.linked_attachment_ids
+ self.move_id.invoice_pdf_report_id
+ self.payment_id.linked_attachment_ids
+ self.statement_id.linked_attachment_ids
)

@api.model
def _get_tax_exigible_domain(self):
Expand Down
1 change: 1 addition & 0 deletions addons/account/models/account_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class AccountPayment(models.Model):
)

# == Display purpose fields ==
linked_attachment_ids = fields.One2many(comodel_name='ir.attachment', inverse_name='res_id')
payment_method_code = fields.Char(
related='payment_method_line_id.code')
payment_receipt_title = fields.Char(
Expand Down
6 changes: 3 additions & 3 deletions addons/account/tests/test_account_bank_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -1412,14 +1412,14 @@ def test_statement_attachments(self):

statement = self.env['account.bank.statement'].create({
'name': 'test_statement',
'attachment_ids': [Command.set(attachment.ids)],
'linked_attachment_ids': [Command.set(attachment.ids)],
})

attachment = self.env['ir.attachment'].create(attachment_vals)

statement.write({'attachment_ids': [Command.link(attachment.id)]})
statement.write({'linked_attachment_ids': [Command.link(attachment.id)]})

self.assertRecordValues(statement.attachment_ids, [
self.assertRecordValues(statement.linked_attachment_ids, [
{'res_id': statement.id, 'res_model': 'account.bank.statement'},
{'res_id': statement.id, 'res_model': 'account.bank.statement'},
])
Expand Down
7 changes: 2 additions & 5 deletions addons/account/wizard/account_tour_upload_bill.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ class AccountTourUploadBill(models.TransientModel):
_name = 'account.tour.upload.bill'
_description = 'Account tour upload bill'

attachment_ids = fields.Many2many(
comodel_name='ir.attachment',
relation='account_tour_upload_bill_ir_attachments_rel',
string='Attachments')
linked_attachment_ids = fields.One2many(comodel_name='ir.attachment', inverse_name='res_id')

selection = fields.Selection(
selection=lambda self: self._selection_values(),
Expand Down Expand Up @@ -79,7 +76,7 @@ def apply(self):
purchase_journal = self.env['account.journal'].search([('type', '=', 'purchase')], limit=1)

if self.selection == 'upload':
return purchase_journal.with_context(default_journal_id=purchase_journal.id, default_move_type='in_invoice').create_document_from_attachment(attachment_ids=self.attachment_ids.ids)
return purchase_journal.with_context(default_journal_id=purchase_journal.id, default_move_type='in_invoice').create_document_from_attachment(attachment_ids=self.linked_attachment_ids.ids)
elif self.selection == 'sample':
invoice_date = fields.Date.today() - timedelta(days=12)
partner = self.env['res.partner'].search([('name', '=', 'Deco Addict')], limit=1)
Expand Down
2 changes: 1 addition & 1 deletion addons/account/wizard/account_tour_upload_bill.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<div class="grid">
<field name="selection" widget="radio" class="g-col-4"/>
<field name="preview_invoice" widget="html" invisible="selection != 'sample'" class="g-col-8"/>
<field name="attachment_ids" widget="many2many_binary" string="Attach a file" invisible="selection != 'upload'" class="g-col-8"/>
<field name="linked_attachment_ids" widget="many2many_binary" string="Attach a file" invisible="selection != 'upload'" class="g-col-8"/>
</div>
</sheet>
<footer>
Expand Down
9 changes: 2 additions & 7 deletions addons/crm/models/crm_lead.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class Lead(models.Model):
meeting_display_date = fields.Date(compute="_compute_meeting_display")
meeting_display_label = fields.Char(compute="_compute_meeting_display")
# UX
linked_attachment_ids = fields.One2many(comodel_name='ir.attachment', inverse_name='res_id')
partner_email_update = fields.Boolean('Partner Email will Update', compute='_compute_partner_email_update')
partner_phone_update = fields.Boolean('Partner Phone will Update', compute='_compute_partner_phone_update')
is_partner_visible = fields.Boolean('Is Partner Visible', compute='_compute_is_partner_visible')
Expand Down Expand Up @@ -1521,14 +1522,8 @@ def _merge_dependences_attachments(self, opportunities):
"""
self.ensure_one()

all_attachments = self.env['ir.attachment'].search([
('res_model', '=', self._name),
('res_id', 'in', opportunities.ids)
])

for opportunity in opportunities:
attachments = all_attachments.filtered(lambda attach: attach.res_id == opportunity.id)
for attachment in attachments:
for attachment in opportunity.linked_attachment_ids:
attachment.write({
'res_id': self.id,
'name': _("%(attach_name)s (from %(lead_name)s)",
Expand Down
1 change: 0 additions & 1 deletion addons/hr_expense/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from . import hr_department
from . import hr_expense
from . import hr_expense_sheet
from . import ir_attachment
from . import product_product
from . import product_template
from . import res_config_settings
Expand Down
7 changes: 2 additions & 5 deletions addons/hr_expense/models/account_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ class AccountMoveLine(models.Model):
def _check_payable_receivable(self):
super(AccountMoveLine, self.filtered(lambda line: line.move_id.expense_sheet_id.payment_mode != 'company_account'))._check_payable_receivable()

def _get_attachment_domains(self):
attachment_domains = super(AccountMoveLine, self)._get_attachment_domains()
if self.expense_id:
attachment_domains.append([('res_model', '=', 'hr.expense'), ('res_id', '=', self.expense_id.id)])
return attachment_domains
def _get_attachments(self):
return super()._get_attachments() + self.expense_id.linked_attachment_ids

def _compute_tax_key(self):
super()._compute_tax_key()
Expand Down
32 changes: 1 addition & 31 deletions addons/hr_expense/models/hr_expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def _default_employee_id(self):
quantity = fields.Float(required=True, digits='Product Unit of Measure', default=1)
description = fields.Text(string="Internal Notes")
nb_attachment = fields.Integer(string="Number of Attachments", compute='_compute_nb_attachment')
attachment_ids = fields.One2many(
linked_attachment_ids = fields.One2many(
comodel_name='ir.attachment',
inverse_name='res_id',
domain="[('res_model', '=', 'hr.expense')]",
Expand Down Expand Up @@ -548,11 +548,8 @@ def _unlink_except_posted_or_approved(self):
raise UserError(_('You cannot delete a posted or approved expense.'))

def write(self, vals):
expense_to_previous_sheet = {}
if 'sheet_id' in vals:
self.env['hr.expense.sheet'].browse(vals['sheet_id']).check_access_rule('write')
for expense in self:
expense_to_previous_sheet[expense] = expense.sheet_id
if 'tax_ids' in vals or 'analytic_distribution' in vals or 'account_id' in vals:
if any(not expense.is_editable for expense in self):
raise UserError(_('You are not authorized to edit this expense report.'))
Expand All @@ -568,35 +565,8 @@ def write(self, vals):
self.sheet_id.write({'employee_id': vals['employee_id']})
elif len(employees) > 1:
self.sheet_id = False
if 'sheet_id' in vals:
# The sheet_id has been modified, either by an explicit write on sheet_id of the expense,
# or by processing a command on the sheet's expense_line_ids.
# We need to delete the attachments on the previous sheet coming from the expenses that were modified,
# and copy the attachments of the expenses to the new sheet,
# if it's a no-op (writing same sheet_id as the current sheet_id of the expense),
# nothing should be done (no unlink then copy of the same attachments)
attachments_to_unlink = self.env['ir.attachment']
for expense in self:
previous_sheet = expense_to_previous_sheet[expense]
checksums = set((expense.attachment_ids - previous_sheet.expense_line_ids.attachment_ids).mapped('checksum'))
attachments_to_unlink += previous_sheet.attachment_ids.filtered(lambda att: att.checksum in checksums)
if vals['sheet_id'] and expense.sheet_id != previous_sheet:
for attachment in expense.attachment_ids.with_context(sync_attachment=False):
attachment.copy({
'res_model': 'hr.expense.sheet',
'res_id': vals['sheet_id'],
})
attachments_to_unlink.with_context(sync_attachment=False).unlink()
return res

def unlink(self):
attachments_to_unlink = self.env['ir.attachment']
for sheet in self.sheet_id:
checksums = set((sheet.expense_line_ids.attachment_ids & self.attachment_ids).mapped('checksum'))
attachments_to_unlink += sheet.attachment_ids.filtered(lambda att: att.checksum in checksums)
attachments_to_unlink.with_context(sync_attachment=False).unlink()
return super().unlink()

@api.model
def get_empty_list_help(self, help_message):
return super().get_empty_list_help((help_message or '') + self._get_empty_list_mail_alias())
Expand Down
29 changes: 15 additions & 14 deletions addons/hr_expense/models/hr_expense_sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,12 @@ def _default_journal_id(self):
domain="[('id', 'in', selectable_payment_method_line_ids)]",
help="The payment method used when the expense is paid by the company.",
)
attachment_ids = fields.One2many(
linked_attachment_ids = fields.One2many(
comodel_name='ir.attachment',
inverse_name='res_id',
domain="[('res_model', '=', 'hr.expense.sheet')]",
string='Attachments of expenses',
)
message_main_attachment_id = fields.Many2one(compute='_compute_main_attachment', store=True)
accounting_date = fields.Date(string="Accounting Date", compute='_compute_accounting_date', store=True)
account_move_ids = fields.One2many(
string="Journal Entries",
Expand Down Expand Up @@ -301,17 +300,6 @@ def _compute_state(self):
else:
sheet.state = 'draft'

@api.depends('expense_line_ids.attachment_ids')
def _compute_main_attachment(self):
for sheet in self:
attachments = sheet.attachment_ids
if not sheet.message_main_attachment_id or sheet.message_main_attachment_id not in attachments:
expenses = sheet.expense_line_ids
expenses_mma_checksums = expenses.message_main_attachment_id.mapped('checksum')
sheet.message_main_attachment_id = attachments.filtered(
lambda att: att.checksum in expenses_mma_checksums
)[:1] or attachments[:1]

@api.depends('expense_line_ids.currency_id', 'company_currency_id')
def _compute_currency_id(self):
for sheet in self:
Expand Down Expand Up @@ -474,6 +462,19 @@ def _unlink_except_posted_or_paid(self):
# Mail Thread
# --------------------------------------------

def _get_mail_thread_data_attachments(self):
"""
In order to see in the sheet attachment preview the corresponding
expenses' attachments, the latter attachments are added to the fetched data for the sheet record.
"""
self.ensure_one()
res = super()._get_mail_thread_data_attachments()
expense_attachments = self.env['ir.attachment'].search(
[('res_id', 'in', self.expense_line_ids.ids), ('res_model', '=', 'hr.expense')],
order='id desc',
)
return res | expense_attachments

def _track_subtype(self, init_values):
self.ensure_one()
if 'state' in init_values and self.state == 'draft':
Expand Down Expand Up @@ -681,7 +682,7 @@ def _do_create_moves(self):
# Set the main attachment on the moves directly to avoid recomputing the
# `register_as_main_attachment` on the moves which triggers the OCR again
for move in moves:
move.message_main_attachment_id = move.attachment_ids[0] if move.attachment_ids else None
move.message_main_attachment_id = move.linked_attachment_ids[0] if move.linked_attachment_ids else None
payments = self.env['account.payment'].with_context(**skip_context).create([
expense._prepare_payments_vals() for expense in company_account_sheets.expense_line_ids
])
Expand Down
39 changes: 0 additions & 39 deletions addons/hr_expense/models/ir_attachment.py

This file was deleted.