177 lines
8.0 KiB
Python
177 lines
8.0 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.tools import ormcache
|
|
|
|
|
|
class IrActionsServer(models.Model):
|
|
_inherit = 'ir.actions.server'
|
|
|
|
state = fields.Selection(
|
|
selection_add=[
|
|
('documents_account_record_create', 'New Journal Entry'),
|
|
],
|
|
ondelete={
|
|
'documents_account_record_create': 'cascade',
|
|
}
|
|
)
|
|
documents_account_create_model = fields.Selection([
|
|
('account.move.in_invoice', 'Vendor Bill'),
|
|
('account.move.out_invoice', 'Customer Invoice'),
|
|
('account.move.in_refund', 'Vendor Credit Note'),
|
|
('account.move.out_refund', 'Credit Note'),
|
|
('account.move.entry', 'Miscellaneous Operations'),
|
|
('account.bank.statement', 'Bank Statement'),
|
|
('account.move.in_receipt', 'Purchase Receipt'),
|
|
])
|
|
documents_account_journal_id = fields.Many2one(
|
|
comodel_name='account.journal',
|
|
domain="['|', ('id', 'in', documents_account_suitable_journal_ids), ('id', '=', False)]",
|
|
compute="_compute_documents_account_journal_id", store=True, readonly=False,
|
|
)
|
|
documents_account_suitable_journal_ids = fields.Many2many(
|
|
'account.journal', compute='_compute_documents_account_suitable_journal_ids')
|
|
documents_account_move_type = fields.Char(compute='_compute_documents_account_move_type')
|
|
|
|
@api.constrains('model_id', 'state')
|
|
def _check_document_account_check_model(self):
|
|
document_model_id = self.env['ir.model']._get('documents.document')
|
|
for action in self:
|
|
if action.state == 'documents_account_record_create' and action.model_id != document_model_id:
|
|
raise ValidationError(_('"New Journal Entry" can only be applied to Document.'))
|
|
|
|
@api.depends('model_id')
|
|
def _compute_allowed_states(self):
|
|
super()._compute_allowed_states()
|
|
document_model_id = self.env['ir.model']._get('documents.document')
|
|
for action in self:
|
|
if action.model_id != document_model_id:
|
|
action.allowed_states = [
|
|
state
|
|
for state in action.allowed_states
|
|
if state != 'documents_account_record_create']
|
|
|
|
@api.depends('documents_account_create_model')
|
|
def _compute_documents_account_journal_id(self):
|
|
for action in self:
|
|
if (action.documents_account_journal_id
|
|
and action.documents_account_journal_id not in action.documents_account_suitable_journal_ids):
|
|
action.documents_account_journal_id = (
|
|
action.documents_account_suitable_journal_ids[0] if action.documents_account_suitable_journal_ids
|
|
else False)
|
|
|
|
@api.depends('documents_account_create_model')
|
|
@api.depends_context('company')
|
|
def _compute_documents_account_suitable_journal_ids(self):
|
|
company_journals = self.env['account.journal'].search(
|
|
self.env['account.journal']._check_company_domain(self.env.company))
|
|
if not company_journals:
|
|
self.documents_account_suitable_journal_ids = False
|
|
return
|
|
bank_journals = company_journals.filtered(lambda journal: journal.type in ('bank', 'credit'))
|
|
self.documents_account_suitable_journal_ids = False
|
|
for action in self:
|
|
if action.documents_account_move_type == 'statement':
|
|
action.documents_account_suitable_journal_ids = bank_journals
|
|
elif action.documents_account_move_type:
|
|
action.documents_account_suitable_journal_ids = (
|
|
self.env['account.move']._get_suitable_journal_ids(action.documents_account_move_type))
|
|
|
|
@api.depends('documents_account_create_model')
|
|
def _compute_documents_account_move_type(self):
|
|
self.documents_account_move_type = False
|
|
for action in self:
|
|
create_model = action.documents_account_create_model or ''
|
|
if create_model.startswith(('account.move', 'account.bank.statement')):
|
|
action.documents_account_move_type = create_model.split('.')[-1]
|
|
|
|
def _generate_action_name(self):
|
|
self.ensure_one()
|
|
if self.state != 'documents_account_record_create':
|
|
return super()._generate_action_name()
|
|
options = dict(self._fields["documents_account_create_model"]._description_selection(self.env))
|
|
translated_model_name = options.get(self.documents_account_create_model, "")
|
|
if self.documents_account_journal_id:
|
|
return _('Create %(model_name)s (%(journal_name)s)',
|
|
model_name=translated_model_name,
|
|
journal_name=self.documents_account_journal_id.name or '')
|
|
return _('Create %(model_name)s', model_name=translated_model_name)
|
|
|
|
def _name_depends(self):
|
|
return super()._name_depends() + ["documents_account_create_model", "documents_account_journal_id.name"]
|
|
|
|
def _run_action_documents_account_record_create_multi(self, eval_context=None):
|
|
documents = eval_context.get('records') or eval_context.get('record')
|
|
if not documents:
|
|
return False
|
|
|
|
journal_id = self.documents_account_journal_id or None
|
|
if self.documents_account_create_model.startswith('account.move'):
|
|
return documents.account_create_account_move(self.documents_account_move_type, journal_id=journal_id)
|
|
elif self.documents_account_create_model == 'account.bank.statement':
|
|
return documents.account_create_account_bank_statement(journal_id=journal_id)
|
|
raise NotImplementedError
|
|
|
|
def _run_action_multi(self, eval_context=None):
|
|
"""Override to enforce visual execution order for specific accounting actions.
|
|
|
|
This is a stable-proof temporary solution to ensure that sub-actions
|
|
(e.g., 'Move to Folder' followed by 'Create Invoice') execute in the same
|
|
order as visually shown in the frontend.
|
|
"""
|
|
account_actions = self._get_documents_account_actions_map()
|
|
if self.id not in account_actions:
|
|
return super()._run_action_multi(eval_context=eval_context)
|
|
|
|
res = False
|
|
# Order as viewed in action's form view, simplest contained change for stable.
|
|
for act in self.child_ids.sorted('sequence,id'):
|
|
res = act.run() or res
|
|
|
|
return res
|
|
|
|
@ormcache()
|
|
@api.model
|
|
def _get_documents_account_actions_map(self):
|
|
"""Map actions that need custom handling to ensure correct syncing behaviour.
|
|
|
|
Returns a dictionary mapping Action IDs to their target Folder IDs.
|
|
|
|
- Keys: Actions that must execute in visual order.
|
|
- Values: The target folder for the move. (Note: These values were previously used
|
|
for temporary permission elevation but are currently unused as the security
|
|
logic has been relaxed).
|
|
|
|
It is cached because the mapping relies on global data (XML IDs) and is
|
|
independent of the current user.
|
|
"""
|
|
taxes_folder = self.env.ref("documents.document_finance_taxes_folder", raise_if_not_found=False)
|
|
annual_closing_folder = self.env.ref("documents.document_finance_annual_closing_year_current_folder", raise_if_not_found=False)
|
|
|
|
allowed_actions_per_folder = {
|
|
taxes_folder: [
|
|
'create_vendor_bill',
|
|
'create_vendor_refund',
|
|
'create_customer_invoice',
|
|
'create_credit_note',
|
|
],
|
|
annual_closing_folder: [
|
|
'create_misc_entry',
|
|
'bank_statement',
|
|
],
|
|
}
|
|
|
|
return {
|
|
action.id: folder.id
|
|
for folder, actions_xml_ids in allowed_actions_per_folder.items()
|
|
for action_xmlid in actions_xml_ids
|
|
if folder
|
|
and (
|
|
action := self.env.ref(
|
|
f"documents_account.ir_actions_server_{action_xmlid}",
|
|
raise_if_not_found=False,
|
|
)
|
|
)
|
|
}
|