1. Anuncie Aqui ! Entre em contato fdantas@4each.com.br

[Python] Odoo-Adding an approval workflow to an Odoo payment registered from a wizard throught...

Discussão em 'Python' iniciado por Stack, Setembro 11, 2024.

  1. Stack

    Stack Membro Participativo

    I want to implement an payment process when the cashier try to register a payment from a purchase invoice for that I have implemented an approbation process for in the payment module excepting if the payment is initiated it will go through that process

    [​IMG]

    But i have notice that Odoo creates a Transient Model called "account.payment.register " to handle all the process of the completion of that payment being registered, it just create a payment and posted it on the same time here is the odoo code that handle that

    def _init_payments(self, to_process, edit_mode=False):
    """ Create the payments.

    :param to_process: A list of python dictionary, one for each payment to create, containing:
    * create_vals: The values used for the 'create' method.
    * to_reconcile: The journal items to perform the reconciliation.
    * batch: A python dict containing everything you want about the source journal items
    to which a payment will be created (see '_get_batches').
    :param edit_mode: Is the wizard in edition mode.
    """

    payments = self.env['account.payment']\
    .with_context(skip_invoice_sync=True)\
    .create([x['create_vals'] for x in to_process])

    for payment, vals in zip(payments, to_process):
    vals['payment'] = payment

    # If payments are made using a currency different than the source one, ensure the balance match exactly in
    # order to fully paid the source journal items.
    # For example, suppose a new currency B having a rate 100:1 regarding the company currency A.
    # If you try to pay 12.15A using 0.12B, the computed balance will be 12.00A for the payment instead of 12.15A.
    if edit_mode:
    lines = vals['to_reconcile']

    # Batches are made using the same currency so making 'lines.currency_id' is ok.
    if payment.currency_id != lines.currency_id:
    liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines()
    source_balance = abs(sum(lines.mapped('amount_residual')))
    if liquidity_lines[0].balance:
    payment_rate = liquidity_lines[0].amount_currency / liquidity_lines[0].balance
    else:
    payment_rate = 0.0
    source_balance_converted = abs(source_balance) * payment_rate

    # Translate the balance into the payment currency is order to be able to compare them.
    # In case in both have the same value (12.15 * 0.01 ~= 0.12 in our example), it means the user
    # attempt to fully paid the source lines and then, we need to manually fix them to get a perfect
    # match.
    payment_balance = abs(sum(counterpart_lines.mapped('balance')))
    payment_amount_currency = abs(sum(counterpart_lines.mapped('amount_currency')))
    if not payment.currency_id.is_zero(source_balance_converted - payment_amount_currency):
    continue

    delta_balance = source_balance - payment_balance

    # Balance are already the same.
    if self.company_currency_id.is_zero(delta_balance):
    continue

    # Fix the balance but make sure to peek the liquidity and counterpart lines first.
    debit_lines = (liquidity_lines + counterpart_lines).filtered('debit')
    credit_lines = (liquidity_lines + counterpart_lines).filtered('credit')

    if debit_lines and credit_lines:
    payment.move_id.write({'line_ids': [
    (1, debit_lines[0].id, {'debit': debit_lines[0].debit + delta_balance}),
    (1, credit_lines[0].id, {'credit': credit_lines[0].credit + delta_balance}),
    ]})
    return payments

    def _post_payments(self, to_process, edit_mode=False):
    """ Post the newly created payments.

    :param to_process: A list of python dictionary, one for each payment to create, containing:
    * create_vals: The values used for the 'create' method.
    * to_reconcile: The journal items to perform the reconciliation.
    * batch: A python dict containing everything you want about the source journal items
    to which a payment will be created (see '_get_batches').
    :param edit_mode: Is the wizard in edition mode.
    """
    payments = self.env['account.payment']
    for vals in to_process:
    payments |= vals['payment']
    payments.action_post()

    def _reconcile_payments(self, to_process, edit_mode=False):
    """ Reconcile the payments.

    :param to_process: A list of python dictionary, one for each payment to create, containing:
    * create_vals: The values used for the 'create' method.
    * to_reconcile: The journal items to perform the reconciliation.
    * batch: A python dict containing everything you want about the source journal items
    to which a payment will be created (see '_get_batches').
    :param edit_mode: Is the wizard in edition mode.
    """
    domain = [
    ('parent_state', '=', 'posted'),
    ('account_type', 'in', ('asset_receivable', 'liability_payable')),
    ('reconciled', '=', False),
    ]
    for vals in to_process:
    payment_lines = vals['payment'].line_ids.filtered_domain(domain)
    lines = vals['to_reconcile']

    for account in payment_lines.account_id:
    (payment_lines + lines)\
    .filtered_domain([('account_id', '=', account.id), ('reconciled', '=', False)])\
    .reconcile()

    def _create_payments(self):
    self.ensure_one()
    batches = self._get_batches()
    first_batch_result = batches[0]
    edit_mode = self.can_edit_wizard and (len(first_batch_result['lines']) == 1 or self.group_payment)
    to_process = []

    if edit_mode:
    payment_vals = self._create_payment_vals_from_wizard(first_batch_result)
    to_process.append({
    'create_vals': payment_vals,
    'to_reconcile': first_batch_result['lines'],
    'batch': first_batch_result,
    })
    else:
    # Don't group payments: Create one batch per move.
    if not self.group_payment:
    new_batches = []
    for batch_result in batches:
    for line in batch_result['lines']:
    new_batches.append({
    **batch_result,
    'payment_values': {
    **batch_result['payment_values'],
    'payment_type': 'inbound' if line.balance > 0 else 'outbound'
    },
    'lines': line,
    })
    batches = new_batches

    for batch_result in batches:
    to_process.append({
    'create_vals': self._create_payment_vals_from_batch(batch_result),
    'to_reconcile': batch_result['lines'],
    'batch': batch_result,
    })

    payments = self._init_payments(to_process, edit_mode=edit_mode)
    self._post_payments(to_process, edit_mode=edit_mode)
    self._reconcile_payments(to_process, edit_mode=edit_mode)
    return payments

    def action_create_payments(self):
    payments = self._create_payments()

    if self._context.get('dont_redirect_to_payments'):
    return True

    action = {
    'name': _('Payments'),
    'type': 'ir.actions.act_window',
    'res_model': 'account.payment',
    'context': {'create': False},
    }
    if len(payments) == 1:
    action.update({
    'view_mode': 'form',
    'res_id': payments.id,
    })
    else:
    action.update({
    'view_mode': 'tree,form',
    'domain': [('id', 'in', payments.ids)],
    })
    return action


    here is my code that I wrote trying to intercept the created payment by odoo and make it pass by my approval workflow before posting it

    # -*- coding: utf-8 -*-

    from odoo import models, fields, api, _
    from odoo.exceptions import ValidationError, UserError
    import re
    import json
    import pickle


    class nkap_custom_paiement(models.Model):
    # _inherit = 'account.payment'
    _inherit = ['account.payment']

    to_process_data = fields.Text(string='To Process Data')
    related_payment_id = fields.Many2one('account.payment.register', string='Related Wizard')
    state = fields.Selection([
    ('draft', 'Drafted'),
    ('waiting_approval', 'Waiting Approval'),
    ('approved', 'Approved'),
    ('rejected', 'Rejected'),
    ('posted', 'Posted'),
    ], string="Approval Status")

    DFC_approver_sign = fields.Binary('DFC Signature')
    DG_approver_sign = fields.Binary('DG Signature')
    current_approval = fields.Selection([('1', '1'), ('2', '2'), ('3', '3')], string="Is Current Approver")


    def action_submit_for_approval(self):
    company_id=self.env.company
    self.write({'state': 'waiting_approval'})
    message = "Vous avez un paiement pour la 1ere approbation"
    self.current_approval = '1'
    self.activity_schedule('purchase_order_approval.mail_activity_data_approval', user_id=company_id.po_third_approver_ids.id, note=message)
    self.env['bus.bus']._sendone(company_id.po_third_approver_ids.partner_id, 'simple_notification', {'title': _("Information"), 'message': message})


    def action_DFC_approval(self):
    company_id=self.env.company
    if self.env.user.id in company_id.po_third_approver_ids.ids:

    self.current_approval = '2'
    self.write({'state': 'waiting_approval'})
    self.DFC_approver_sign = self.env.user.user_signature
    message = "Vous avez un paiement pour la 2ere approbation "
    self.activity_schedule('purchase_order_approval.mail_activity_data_approval', user_id=company_id.po_DG_approver_ids.id, note=message)
    else:
    raise ValidationError(_("Seul %s peut approver !"% company_id.po_third_approver_ids.id ))

    def action_DG_approval(self):
    company_id=self.env.company
    if self.env.user.id in company_id.po_DG_approver_ids.ids:

    self.write({'state': 'approved'})
    self.current_approval = '3'
    self.DG_approver_sign = self.env.user.user_signature
    message = "Vous avez un paiement pour la validation"
    self.activity_schedule('purchase_order_approval.mail_activity_data_approval', user_id=company_id.po_fourth_approver_ids.id, note=message)
    else:
    raise ValidationError(_("Seul %s peut approver !"% company_id.po_DG_approver_ids.id ))

    def action_cashier_approval(self):
    company_id=self.env.company
    if self.env.user.id in company_id.po_fourth_approver_ids.ids:
    if self.related_payment_id:

    related_payment = self.env['account.payment.register'].browse(related_payment_id)
    if related_payment.exists():
    # Call a method on the related payment record
    related_payment.is_confirmed(payments=self)
    else:
    _logger.error("Related payment record with ID %s does not exist", self.related_payment_id.id)
    else:
    self.write({'state': 'posted'})
    self.action_post()

    else:
    raise ValidationError(_("Seul %s peut approver !"% company_id.po_fourth_approver_ids.id ))

    def action_reject(self):
    company_id=self.env.company
    current_approver = None
    if self.current_approval =='1':
    current_approver=company_id.po_third_approver_ids
    elif self.current_approval =='2':
    current_approver=company_id.po_DG_approver_ids
    else:
    raise UserError(_(f"{self.current_approval},{type(self.current_approval)}"))
    if self.env.user.id in current_approver.ids:
    self.write({'state': 'rejected'})

    else:
    raise ValidationError(_("Seul %s peut refuser cette DA !" % current_approver.name))

    def rollback(self):
    '''to cancel all the signature already done to false'''
    self.write({
    'DFC_approver_sign': False,
    'DG_approver_sign': False,
    'current_approval': False,
    })




    class CustomPaymentRegister(models.TransientModel):
    _inherit = 'account.payment.register'
    # payment_id = fields.Many2one('account.payment', string='Created Payment')
    to_process_data = fields.Binary(string="To Process Data")

    def _create_payments(self):
    self.ensure_one()
    batches = self._get_batches()
    first_batch_result = batches[0]
    edit_mode = self.can_edit_wizard and (len(first_batch_result['lines']) == 1 or self.group_payment)
    to_process = []

    if edit_mode:
    payment_vals = self._create_payment_vals_from_wizard(first_batch_result)
    to_process.append({
    'create_vals': payment_vals,
    'to_reconcile': first_batch_result['lines'],
    'batch': first_batch_result,
    })
    else:
    # Don't group payments: Create one batch per move.
    if not self.group_payment:
    new_batches = []
    for batch_result in batches:
    for line in batch_result['lines']:
    new_batches.append({
    **batch_result,
    'payment_values': {
    **batch_result['payment_values'],
    'payment_type': 'inbound' if line.balance > 0 else 'outbound'
    },
    'lines': line,
    })
    batches = new_batches

    for batch_result in batches:
    to_process.append({
    'create_vals': self._create_payment_vals_from_batch(batch_result),
    'to_reconcile': batch_result['lines'],
    'batch': batch_result,
    })

    payments = self._init_payments(to_process, edit_mode=edit_mode)
    self.to_process_data = pickle.dumps(to_process)

    return payments

    def _convert_to_process_data(self):
    if self.to_process_data:
    return pickle.loads(self.to_process_data)
    return []

    def is_confirmed(self,payments):
    to_process = self._convert_to_process_data()
    if payments.state=='approved' and to_process:
    self._post_payments(self.to_process,edit_mode=False)
    self._reconcile_payments(self.to_process,edit_mode=False)
    if self._context.get('dont_redirect_to_payments'):
    return True

    action = {
    'name': _('Payments'),
    'type': 'ir.actions.act_window',
    'res_model': 'account.payment',
    'context': {'create': False},
    }
    if len(payments) == 1:
    action.update({
    'view_mode': 'form',
    'res_id': payments.id,
    })
    else:
    action.update({
    'view_mode': 'tree,form',
    'domain': [('id', 'in', payments.ids)],
    })
    return action
    def action_create_payments(self):
    payments = self._create_payments()
    payments.related_payment_id=self.id

    payments.action_submit_for_approval()



    the problem I am enquiring is related to the to_process in the variable "_create_payments" i have tried creating a global variable but a field can not contain a list of dictionaries when i try using pickle.dumps and loads it throw an error because the to_process variable contains some elements like odjects which can not be dumped Now i have no idea how to solve this problem and make sure Odoo waits for the payment to be validated by the cashier then to process the rest

    Continue reading...

Compartilhe esta Página