mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 04:32:00 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
9
odoo-bringout-oca-ocb-sale/sale/wizard/__init__.py
Normal file
9
odoo-bringout-oca-ocb-sale/sale/wizard/__init__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_payment_register
|
||||
from . import mail_compose_message
|
||||
from . import payment_provider_onboarding_wizard
|
||||
from . import payment_link_wizard
|
||||
from . import sale_make_invoice_advance
|
||||
from . import sale_order_cancel
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="action_accrued_revenue_entry" model="ir.actions.act_window">
|
||||
<field name="name">Accrued Revenue Entry</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.accrued.orders.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="binding_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="groups_id" eval="[(4, ref('account.group_account_user'))]"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountPaymentRegister(models.TransientModel):
|
||||
_inherit = 'account.payment.register'
|
||||
|
||||
def _create_payment_vals_from_wizard(self, batch_result):
|
||||
vals = super()._create_payment_vals_from_wizard(batch_result)
|
||||
# Make sure the account move linked to generated payment
|
||||
# belongs to the expected sales team
|
||||
# team_id field on account.payment comes from the `_inherits` on account.move model
|
||||
vals.update({'team_id': self.line_ids.move_id[0].team_id.id})
|
||||
return vals
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class MailComposeMessage(models.TransientModel):
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
def _action_send_mail(self, auto_commit=False):
|
||||
if self.model == 'sale.order':
|
||||
self = self.with_context(mailing_document_based=True)
|
||||
if self.env.context.get('mark_so_as_sent'):
|
||||
self = self.with_context(mail_notify_author=self.env.user.partner_id in self.partner_ids)
|
||||
return super(MailComposeMessage, self)._action_send_mail(auto_commit=auto_commit)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class PaymentLinkWizard(models.TransientModel):
|
||||
_inherit = 'payment.link.wizard'
|
||||
_description = 'Generate Sales Payment Link'
|
||||
|
||||
def _get_payment_provider_available(self, res_model, res_id, **kwargs):
|
||||
""" Select and return the providers matching the criteria.
|
||||
|
||||
:param str res_model: active model
|
||||
:param int res_id: id of 'active_model' record
|
||||
:return: The compatible providers
|
||||
:rtype: recordset of `payment.provider`
|
||||
"""
|
||||
if res_model == 'sale.order':
|
||||
kwargs['sale_order_id'] = res_id
|
||||
return super()._get_payment_provider_available(**kwargs)
|
||||
|
||||
def _get_additional_link_values(self):
|
||||
""" Override of `payment` to add `sale_order_id` to the payment link values.
|
||||
|
||||
The other values related to the sales order are directly read from the sales order.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: The additional payment link values.
|
||||
:rtype: dict
|
||||
"""
|
||||
res = super()._get_additional_link_values()
|
||||
if self.res_model != 'sale.order':
|
||||
return res
|
||||
|
||||
# Order-related fields are retrieved in the controller
|
||||
return {
|
||||
'sale_order_id': self.res_id,
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="action_sale_order_generate_link" model="ir.actions.act_window">
|
||||
<field name="name">Generate a Payment Link</field>
|
||||
<field name="res_model">payment.link.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="payment.payment_link_wizard_view_form"/>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="model_sale_order"/>
|
||||
<field name="binding_view_types">form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class PaymentWizard(models.TransientModel):
|
||||
""" Override for the sale quotation onboarding panel. """
|
||||
|
||||
_inherit = 'payment.provider.onboarding.wizard'
|
||||
_name = 'sale.payment.provider.onboarding.wizard'
|
||||
_description = 'Sale Payment provider onboarding wizard'
|
||||
|
||||
def _get_default_payment_method(self):
|
||||
return self.env.company.sale_onboarding_payment_method or 'digital_signature'
|
||||
|
||||
payment_method = fields.Selection(selection_add=[
|
||||
('digital_signature', "Electronic signature"),
|
||||
('stripe', "Credit & Debit card (via Stripe)"),
|
||||
('paypal', "PayPal"),
|
||||
('manual', "Custom payment instructions"),
|
||||
], default=_get_default_payment_method)
|
||||
#
|
||||
|
||||
def _set_payment_provider_onboarding_step_done(self):
|
||||
""" Override. """
|
||||
self.env.company.sudo().set_onboarding_step_done('sale_onboarding_order_confirmation_state')
|
||||
|
||||
def add_payment_methods(self):
|
||||
self.env.company.sale_onboarding_payment_method = self.payment_method
|
||||
if self.payment_method == 'digital_signature':
|
||||
self.env.company.portal_confirmation_sign = True
|
||||
if self.payment_method in ('paypal', 'stripe', 'other', 'manual'):
|
||||
self.env.company.portal_confirmation_pay = True
|
||||
|
||||
return super().add_payment_methods()
|
||||
|
||||
def _start_stripe_onboarding(self):
|
||||
""" Override of payment to set the sale menu as start menu of the payment onboarding. """
|
||||
menu_id = self.env.ref('sale.sale_menu_root').id
|
||||
return self.env.company._run_payment_onboarding_step(menu_id)
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Command
|
||||
from odoo.tools import float_is_zero
|
||||
|
||||
|
||||
class SaleAdvancePaymentInv(models.TransientModel):
|
||||
_name = 'sale.advance.payment.inv'
|
||||
_description = "Sales Advance Payment Invoice"
|
||||
|
||||
advance_payment_method = fields.Selection(
|
||||
selection=[
|
||||
('delivered', "Regular invoice"),
|
||||
('percentage', "Down payment (percentage)"),
|
||||
('fixed', "Down payment (fixed amount)"),
|
||||
],
|
||||
string="Create Invoice",
|
||||
default='delivered',
|
||||
required=True,
|
||||
help="A standard invoice is issued with all the order lines ready for invoicing,"
|
||||
"according to their invoicing policy (based on ordered or delivered quantity).")
|
||||
count = fields.Integer(string="Order Count", compute='_compute_count')
|
||||
sale_order_ids = fields.Many2many(
|
||||
'sale.order', default=lambda self: self.env.context.get('active_ids'))
|
||||
|
||||
# Down Payment logic
|
||||
has_down_payments = fields.Boolean(
|
||||
string="Has down payments", compute="_compute_has_down_payments")
|
||||
deduct_down_payments = fields.Boolean(string="Deduct down payments", default=True)
|
||||
|
||||
# New Down Payment
|
||||
product_id = fields.Many2one(
|
||||
comodel_name='product.product',
|
||||
string="Down Payment Product",
|
||||
domain=[('type', '=', 'service')],
|
||||
compute='_compute_product_id',
|
||||
readonly=False,
|
||||
store=True)
|
||||
amount = fields.Float(
|
||||
string="Down Payment Amount",
|
||||
help="The percentage of amount to be invoiced in advance, taxes excluded.")
|
||||
fixed_amount = fields.Monetary(
|
||||
string="Down Payment Amount (Fixed)",
|
||||
help="The fixed amount to be invoiced in advance, taxes excluded.")
|
||||
currency_id = fields.Many2one(
|
||||
comodel_name='res.currency',
|
||||
compute='_compute_currency_id',
|
||||
store=True)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company',
|
||||
compute='_compute_company_id',
|
||||
store=True)
|
||||
|
||||
# Only used when there is no down payment product available
|
||||
# to setup the down payment product
|
||||
deposit_account_id = fields.Many2one(
|
||||
comodel_name='account.account',
|
||||
string="Income Account",
|
||||
domain=[('deprecated', '=', False)],
|
||||
help="Account used for deposits")
|
||||
deposit_taxes_id = fields.Many2many(
|
||||
comodel_name='account.tax',
|
||||
string="Customer Taxes",
|
||||
domain=[('type_tax_use', '=', 'sale')],
|
||||
help="Taxes used for deposits")
|
||||
|
||||
#=== COMPUTE METHODS ===#
|
||||
|
||||
@api.depends('sale_order_ids')
|
||||
def _compute_count(self):
|
||||
for wizard in self:
|
||||
wizard.count = len(wizard.sale_order_ids)
|
||||
|
||||
@api.depends('sale_order_ids')
|
||||
def _compute_has_down_payments(self):
|
||||
for wizard in self:
|
||||
wizard.has_down_payments = bool(
|
||||
wizard.sale_order_ids.order_line.filtered('is_downpayment')
|
||||
)
|
||||
|
||||
# next computed fields are only used for down payments invoices and therefore should only
|
||||
# have a value when 1 unique SO is invoiced through the wizard
|
||||
@api.depends('sale_order_ids')
|
||||
def _compute_currency_id(self):
|
||||
self.currency_id = False
|
||||
for wizard in self:
|
||||
if wizard.count == 1:
|
||||
wizard.currency_id = wizard.sale_order_ids.currency_id
|
||||
|
||||
@api.depends('sale_order_ids')
|
||||
def _compute_company_id(self):
|
||||
self.company_id = False
|
||||
for wizard in self:
|
||||
if wizard.count == 1:
|
||||
wizard.company_id = wizard.sale_order_ids.company_id
|
||||
|
||||
@api.depends('company_id') # 'dumb' depends to trigger the computation
|
||||
def _compute_product_id(self):
|
||||
self.product_id = False
|
||||
dp_product_id = int(self.env['ir.config_parameter'].sudo().get_param(
|
||||
'sale.default_deposit_product_id'))
|
||||
if not dp_product_id:
|
||||
return
|
||||
for wizard in self:
|
||||
if wizard.count == 1:
|
||||
wizard.product_id = dp_product_id
|
||||
|
||||
#=== ONCHANGE METHODS ===#
|
||||
|
||||
@api.onchange('advance_payment_method')
|
||||
def _onchange_advance_payment_method(self):
|
||||
if self.advance_payment_method == 'percentage':
|
||||
amount = self.default_get(['amount']).get('amount')
|
||||
return {'value': {'amount': amount}}
|
||||
|
||||
#=== CONSTRAINT METHODS ===#
|
||||
|
||||
@api.constrains('advance_payment_method', 'amount', 'fixed_amount')
|
||||
def _check_amount_is_positive(self):
|
||||
for wizard in self:
|
||||
if wizard.advance_payment_method == 'percentage' and wizard.amount <= 0.00:
|
||||
raise UserError(_('The value of the down payment amount must be positive.'))
|
||||
elif wizard.advance_payment_method == 'fixed' and wizard.fixed_amount <= 0.00:
|
||||
raise UserError(_('The value of the down payment amount must be positive.'))
|
||||
|
||||
@api.constrains('product_id')
|
||||
def _check_down_payment_product_is_valid(self):
|
||||
for wizard in self:
|
||||
if wizard.count > 1 or not wizard.product_id:
|
||||
continue
|
||||
if wizard.product_id.invoice_policy != 'order':
|
||||
raise UserError(_(
|
||||
"The product used to invoice a down payment should have an invoice policy"
|
||||
"set to \"Ordered quantities\"."
|
||||
" Please update your deposit product to be able to create a deposit invoice."))
|
||||
if wizard.product_id.type != 'service':
|
||||
raise UserError(_(
|
||||
"The product used to invoice a down payment should be of type 'Service'."
|
||||
" Please use another product or update this product."))
|
||||
|
||||
#=== ACTION METHODS ===#
|
||||
|
||||
def create_invoices(self):
|
||||
self._create_invoices(self.sale_order_ids)
|
||||
|
||||
if self.env.context.get('open_invoices'):
|
||||
return self.sale_order_ids.action_view_invoice()
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
#=== BUSINESS METHODS ===#
|
||||
|
||||
def _create_invoices(self, sale_orders):
|
||||
self.ensure_one()
|
||||
if self.advance_payment_method == 'delivered':
|
||||
return sale_orders._create_invoices(final=self.deduct_down_payments)
|
||||
else:
|
||||
self.sale_order_ids.ensure_one()
|
||||
self = self.with_company(self.company_id)
|
||||
order = self.sale_order_ids
|
||||
|
||||
# Create deposit product if necessary
|
||||
if not self.product_id:
|
||||
self.product_id = self.env['product.product'].create(
|
||||
self._prepare_down_payment_product_values()
|
||||
)
|
||||
self.env['ir.config_parameter'].sudo().set_param(
|
||||
'sale.default_deposit_product_id', self.product_id.id)
|
||||
|
||||
# Create down payment section if necessary
|
||||
if not any(line.display_type and line.is_downpayment for line in order.order_line):
|
||||
self.env['sale.order.line'].create(
|
||||
self._prepare_down_payment_section_values(order)
|
||||
)
|
||||
|
||||
down_payment_so_line = self.env['sale.order.line'].create(
|
||||
self._prepare_so_line_values(order)
|
||||
)
|
||||
|
||||
invoice = self.env['account.move'].sudo().create(
|
||||
self._prepare_invoice_values(order, down_payment_so_line)
|
||||
).with_user(self.env.uid) # Unsudo the invoice after creation
|
||||
|
||||
invoice.message_post_with_view(
|
||||
'mail.message_origin_link',
|
||||
values={'self': invoice, 'origin': order},
|
||||
subtype_id=self.env.ref('mail.mt_note').id)
|
||||
|
||||
return invoice
|
||||
|
||||
def _prepare_down_payment_product_values(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Down payment'),
|
||||
'type': 'service',
|
||||
'invoice_policy': 'order',
|
||||
'company_id': False,
|
||||
'property_account_income_id': self.deposit_account_id.id,
|
||||
'taxes_id': [Command.set(self.deposit_taxes_id.ids)],
|
||||
}
|
||||
|
||||
def _prepare_down_payment_section_values(self, order):
|
||||
context = {'lang': order.partner_id.lang}
|
||||
|
||||
so_values = {
|
||||
'name': _('Down Payments'),
|
||||
'product_uom_qty': 0.0,
|
||||
'order_id': order.id,
|
||||
'display_type': 'line_section',
|
||||
'is_downpayment': True,
|
||||
'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10,
|
||||
}
|
||||
|
||||
del context
|
||||
return so_values
|
||||
|
||||
def _prepare_so_line_values(self, order):
|
||||
self.ensure_one()
|
||||
analytic_distribution = {}
|
||||
amount_total = sum(order.order_line.mapped("price_total"))
|
||||
if not float_is_zero(amount_total, precision_rounding=self.currency_id.rounding):
|
||||
for line in order.order_line:
|
||||
distrib_dict = line.analytic_distribution or {}
|
||||
for account, distribution in distrib_dict.items():
|
||||
analytic_distribution[account] = distribution * line.price_total + analytic_distribution.get(account, 0)
|
||||
for account, distribution_amount in analytic_distribution.items():
|
||||
analytic_distribution[account] = distribution_amount/amount_total
|
||||
context = {'lang': order.partner_id.lang}
|
||||
so_values = {
|
||||
'name': _('Down Payment: %s (Draft)', time.strftime('%m %Y')),
|
||||
'price_unit': self._get_down_payment_amount(order),
|
||||
'product_uom_qty': 0.0,
|
||||
'order_id': order.id,
|
||||
'discount': 0.0,
|
||||
'product_id': self.product_id.id,
|
||||
'analytic_distribution': analytic_distribution,
|
||||
'is_downpayment': True,
|
||||
'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10,
|
||||
}
|
||||
del context
|
||||
return so_values
|
||||
|
||||
def _get_down_payment_amount(self, order):
|
||||
self.ensure_one()
|
||||
if self.advance_payment_method == 'percentage':
|
||||
advance_product_taxes = self.product_id.taxes_id.filtered(lambda tax: tax.company_id == order.company_id)
|
||||
if all(order.fiscal_position_id.map_tax(advance_product_taxes).mapped('price_include')):
|
||||
amount = order.amount_total * self.amount / 100
|
||||
else:
|
||||
amount = order.amount_untaxed * self.amount / 100
|
||||
else: # Fixed amount
|
||||
amount = self.fixed_amount
|
||||
return amount
|
||||
|
||||
def _prepare_invoice_values(self, order, so_line):
|
||||
self.ensure_one()
|
||||
return {
|
||||
**order._prepare_invoice(),
|
||||
'invoice_line_ids': [
|
||||
Command.create(
|
||||
so_line._prepare_invoice_line(
|
||||
name=self._get_down_payment_description(order),
|
||||
quantity=1.0,
|
||||
)
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
def _get_down_payment_description(self, order):
|
||||
self.ensure_one()
|
||||
context = {'lang': order.partner_id.lang}
|
||||
if self.advance_payment_method == 'percentage':
|
||||
name = _("Down payment of %s%%", self.amount)
|
||||
else:
|
||||
name = _('Down Payment')
|
||||
del context
|
||||
|
||||
return name
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_sale_advance_payment_inv" model="ir.ui.view">
|
||||
<field name="name">Invoice Orders</field>
|
||||
<field name="model">sale.advance.payment.inv</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Invoice Sales Order">
|
||||
<p class="oe_grey">
|
||||
Invoices will be created in draft so that you can review
|
||||
them before validation.
|
||||
</p>
|
||||
<group>
|
||||
<field name="sale_order_ids" invisible="1"/>
|
||||
<field name="has_down_payments" invisible="1"/>
|
||||
<field name="count" attrs="{'invisible': [('count', '=', 1)]}"/>
|
||||
<field name="advance_payment_method" class="oe_inline"
|
||||
widget="radio"
|
||||
attrs="{'invisible': [('count', '>', 1)]}"/>
|
||||
<label for="deduct_down_payments"
|
||||
string=""
|
||||
attrs="{'invisible': ['|', ('has_down_payments', '=', False), ('advance_payment_method', '!=', 'delivered')]}"/>
|
||||
<div attrs="{'invisible': ['|', ('has_down_payments', '=', False), ('advance_payment_method', '!=', 'delivered')]}"
|
||||
id="down_payment_details">
|
||||
<field name="deduct_down_payments" nolabel="1"/>
|
||||
<label for="deduct_down_payments"/>
|
||||
</div>
|
||||
</group>
|
||||
<group name="down_payment_specification"
|
||||
attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed', 'percentage'))]}">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_id" invisible="1"/>
|
||||
<label for="fixed_amount" attrs="{'invisible': [('advance_payment_method', '!=', 'fixed')]}"/>
|
||||
<label for="amount" attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}"/>
|
||||
<div id="payment_method_details">
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="fixed_amount"
|
||||
attrs="{'required': [('advance_payment_method', '=', 'fixed')],
|
||||
'invisible': [('advance_payment_method', '!=', 'fixed')]}"
|
||||
class="oe_inline"/>
|
||||
<field name="amount"
|
||||
attrs="{'required': [('advance_payment_method', '=', 'percentage')],
|
||||
'invisible': [('advance_payment_method', '!=', 'percentage')]}"
|
||||
class="oe_inline"/>
|
||||
<span attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}"
|
||||
class="oe_inline">%</span>
|
||||
</div>
|
||||
<field name="deposit_account_id"
|
||||
options="{'no_create': True}"
|
||||
attrs="{'invisible': [('product_id', '!=', False)]}"
|
||||
groups="account.group_account_manager"/>
|
||||
<field name="deposit_taxes_id"
|
||||
widget="many2many_tags"
|
||||
attrs="{'invisible': [('product_id', '!=', False)]}"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="create_invoices" type="object"
|
||||
id="create_invoice_open"
|
||||
string="Create and View Invoice"
|
||||
context="{'open_invoices': True}"
|
||||
class="btn-primary" data-hotkey="q"/>
|
||||
<button name="create_invoices" type="object"
|
||||
id="create_invoice"
|
||||
string="Create Invoice"
|
||||
data-hotkey="w"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_sale_advance_payment_inv" model="ir.actions.act_window">
|
||||
<field name="name">Create invoices</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.advance.payment.inv</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="binding_view_types">list</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
96
odoo-bringout-oca-ocb-sale/sale/wizard/sale_order_cancel.py
Normal file
96
odoo-bringout-oca-ocb-sale/sale/wizard/sale_order_cancel.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class SaleOrderCancel(models.TransientModel):
|
||||
_name = 'sale.order.cancel'
|
||||
_inherit = 'mail.composer.mixin'
|
||||
_description = "Sales Order Cancel"
|
||||
|
||||
@api.model
|
||||
def _default_email_from(self):
|
||||
if self.env.user.email:
|
||||
return self.env.user.email_formatted
|
||||
raise UserError(_("Unable to post message, please configure the sender's email address."))
|
||||
|
||||
@api.model
|
||||
def _default_author_id(self):
|
||||
return self.env.user.partner_id
|
||||
|
||||
# origin
|
||||
email_from = fields.Char(string="From", default=_default_email_from)
|
||||
author_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string="Author",
|
||||
index=True,
|
||||
ondelete='set null',
|
||||
default=_default_author_id,
|
||||
)
|
||||
|
||||
# recipients
|
||||
recipient_ids = fields.Many2many(
|
||||
'res.partner',
|
||||
string="Recipients",
|
||||
compute='_compute_recipient_ids',
|
||||
readonly=False,
|
||||
)
|
||||
order_id = fields.Many2one('sale.order', string="Sale Order", required=True, ondelete='cascade')
|
||||
display_invoice_alert = fields.Boolean(
|
||||
string="Invoice Alert",
|
||||
compute='_compute_display_invoice_alert',
|
||||
compute_sudo=True,
|
||||
)
|
||||
|
||||
@api.depends('order_id')
|
||||
def _compute_recipient_ids(self):
|
||||
for wizard in self:
|
||||
wizard.recipient_ids = wizard.order_id.partner_id \
|
||||
| wizard.order_id.message_partner_ids \
|
||||
- wizard.author_id
|
||||
|
||||
@api.depends('order_id')
|
||||
def _compute_display_invoice_alert(self):
|
||||
for wizard in self:
|
||||
wizard.display_invoice_alert = bool(
|
||||
wizard.order_id.invoice_ids.filtered(lambda inv: inv.state == 'draft')
|
||||
)
|
||||
|
||||
@api.depends('order_id')
|
||||
def _compute_subject(self):
|
||||
for wizard in self:
|
||||
if wizard.template_id:
|
||||
wizard.subject = wizard.template_id._render_field(
|
||||
'subject',
|
||||
wizard.order_id.ids,
|
||||
post_process=True,
|
||||
compute_lang=True,
|
||||
)[wizard.order_id.id]
|
||||
|
||||
@api.depends('order_id')
|
||||
def _compute_body(self):
|
||||
for wizard in self:
|
||||
if wizard.template_id:
|
||||
wizard.body = wizard.template_id._render_field(
|
||||
'body_html',
|
||||
wizard.order_id.ids,
|
||||
post_process=True,
|
||||
compute_lang=True,
|
||||
)[wizard.order_id.id]
|
||||
|
||||
def action_send_mail_and_cancel(self):
|
||||
self.ensure_one()
|
||||
self.order_id.message_post(
|
||||
subject=self.subject,
|
||||
body=self.body,
|
||||
message_type='comment',
|
||||
email_from=self.email_from,
|
||||
email_layout_xmlid='mail.mail_notification_light',
|
||||
partner_ids=self.recipient_ids.ids,
|
||||
)
|
||||
return self.action_cancel()
|
||||
|
||||
def action_cancel(self):
|
||||
return self.order_id.with_context(disable_cancel_warning=True).action_cancel()
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="sale_order_cancel_view_form" model="ir.ui.view">
|
||||
<field name="name">sale.order.cancel.form</field>
|
||||
<field name="model">sale.order.cancel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group col="1">
|
||||
<field name="render_model" invisible="1"/>
|
||||
<field name="order_id" invisible="1"/>
|
||||
<field name="template_id" invisible="1"/>
|
||||
<field name="display_invoice_alert" invisible="1"/>
|
||||
<div col="2"
|
||||
class="alert alert-warning"
|
||||
role="alert">
|
||||
<span>Are you sure you want to cancel this order? <br/></span>
|
||||
<span id="display_invoice_alert"
|
||||
attrs="{'invisible': [('display_invoice_alert', '=', False)]}">
|
||||
Draft invoices for this order will be cancelled. <br/>
|
||||
</span>
|
||||
</div>
|
||||
<group col="2">
|
||||
<field name="recipient_ids"
|
||||
widget="many2many_tags_email"
|
||||
context="{'force_email': True,
|
||||
'show_email': True,
|
||||
'no_create_edit': True}"/>
|
||||
</group>
|
||||
<group col="2">
|
||||
<field name="subject" placeholder="Subject"/>
|
||||
</group>
|
||||
<field name="body"
|
||||
class="oe-bordered-editor"
|
||||
options="{'style-inline': true}"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Send and cancel"
|
||||
name="action_send_mail_and_cancel"
|
||||
type="object"
|
||||
class="btn-primary"/>
|
||||
<button string="Cancel"
|
||||
name="action_cancel"
|
||||
type="object"
|
||||
class="btn-primary mx-1"/>
|
||||
<button string="Discard"
|
||||
class="btn-secondary"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue