diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/sale/wizard | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/sale/wizard')
| -rw-r--r-- | addons/sale/wizard/__init__.py | 7 | ||||
| -rw-r--r-- | addons/sale/wizard/payment_acquirer_onboarding_wizard.py | 37 | ||||
| -rw-r--r-- | addons/sale/wizard/sale_make_invoice_advance.py | 197 | ||||
| -rw-r--r-- | addons/sale/wizard/sale_make_invoice_advance_views.xml | 64 | ||||
| -rw-r--r-- | addons/sale/wizard/sale_order_cancel.py | 20 | ||||
| -rw-r--r-- | addons/sale/wizard/sale_order_cancel_views.xml | 20 | ||||
| -rw-r--r-- | addons/sale/wizard/sale_payment_link.py | 49 | ||||
| -rw-r--r-- | addons/sale/wizard/sale_payment_link_views.xml | 14 |
8 files changed, 408 insertions, 0 deletions
diff --git a/addons/sale/wizard/__init__.py b/addons/sale/wizard/__init__.py new file mode 100644 index 00000000..b0b5d571 --- /dev/null +++ b/addons/sale/wizard/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import payment_acquirer_onboarding_wizard +from . import sale_make_invoice_advance +from . import sale_order_cancel +from . import sale_payment_link diff --git a/addons/sale/wizard/payment_acquirer_onboarding_wizard.py b/addons/sale/wizard/payment_acquirer_onboarding_wizard.py new file mode 100644 index 00000000..6a7f3b37 --- /dev/null +++ b/addons/sale/wizard/payment_acquirer_onboarding_wizard.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class PaymentWizard(models.TransientModel): + """ Override for the sale quotation onboarding panel. """ + + _inherit = 'payment.acquirer.onboarding.wizard' + _name = 'sale.payment.acquirer.onboarding.wizard' + _description = 'Sale Payment acquire 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"), + ('paypal', "PayPal"), + ('stripe', "Credit card (via Stripe)"), + ('other', "Other payment acquirer"), + ('manual', "Custom payment instructions"), + ], default=_get_default_payment_method) + # + + def _set_payment_acquirer_onboarding_step_done(self): + """ Override. """ + self.env.company.sudo().set_onboarding_step_done('sale_onboarding_order_confirmation_state') + + def add_payment_methods(self, *args, **kwargs): + 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(PaymentWizard, self).add_payment_methods(*args, **kwargs) diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py new file mode 100644 index 00000000..ce9cd47f --- /dev/null +++ b/addons/sale/wizard/sale_make_invoice_advance.py @@ -0,0 +1,197 @@ +# -*- 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 + + +class SaleAdvancePaymentInv(models.TransientModel): + _name = "sale.advance.payment.inv" + _description = "Sales Advance Payment Invoice" + + @api.model + def _count(self): + return len(self._context.get('active_ids', [])) + + @api.model + def _default_product_id(self): + product_id = self.env['ir.config_parameter'].sudo().get_param('sale.default_deposit_product_id') + return self.env['product.product'].browse(int(product_id)).exists() + + @api.model + def _default_deposit_account_id(self): + return self._default_product_id()._get_product_accounts()['income'] + + @api.model + def _default_deposit_taxes_id(self): + return self._default_product_id().taxes_id + + @api.model + def _default_has_down_payment(self): + if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False): + sale_order = self.env['sale.order'].browse(self._context.get('active_id')) + return sale_order.order_line.filtered( + lambda sale_order_line: sale_order_line.is_downpayment + ) + + return False + + @api.model + def _default_currency_id(self): + if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False): + sale_order = self.env['sale.order'].browse(self._context.get('active_id')) + return sale_order.currency_id + + advance_payment_method = fields.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).") + deduct_down_payments = fields.Boolean('Deduct down payments', default=True) + has_down_payments = fields.Boolean('Has down payments', default=_default_has_down_payment, readonly=True) + product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')], + default=_default_product_id) + count = fields.Integer(default=_count, string='Order Count') + amount = fields.Float('Down Payment Amount', digits='Account', help="The percentage of amount to be invoiced in advance, taxes excluded.") + currency_id = fields.Many2one('res.currency', string='Currency', default=_default_currency_id) + fixed_amount = fields.Monetary('Down Payment Amount (Fixed)', help="The fixed amount to be invoiced in advance, taxes excluded.") + deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)], + help="Account used for deposits", default=_default_deposit_account_id) + deposit_taxes_id = fields.Many2many("account.tax", string="Customer Taxes", help="Taxes used for deposits", default=_default_deposit_taxes_id) + + @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}} + return {} + + def _prepare_invoice_values(self, order, name, amount, so_line): + invoice_vals = { + 'ref': order.client_order_ref, + 'move_type': 'out_invoice', + 'invoice_origin': order.name, + 'invoice_user_id': order.user_id.id, + 'narration': order.note, + 'partner_id': order.partner_invoice_id.id, + 'fiscal_position_id': (order.fiscal_position_id or order.fiscal_position_id.get_fiscal_position(order.partner_id.id)).id, + 'partner_shipping_id': order.partner_shipping_id.id, + 'currency_id': order.pricelist_id.currency_id.id, + 'payment_reference': order.reference, + 'invoice_payment_term_id': order.payment_term_id.id, + 'partner_bank_id': order.company_id.partner_id.bank_ids[:1].id, + 'team_id': order.team_id.id, + 'campaign_id': order.campaign_id.id, + 'medium_id': order.medium_id.id, + 'source_id': order.source_id.id, + 'invoice_line_ids': [(0, 0, { + 'name': name, + 'price_unit': amount, + 'quantity': 1.0, + 'product_id': self.product_id.id, + 'product_uom_id': so_line.product_uom.id, + 'tax_ids': [(6, 0, so_line.tax_id.ids)], + 'sale_line_ids': [(6, 0, [so_line.id])], + 'analytic_tag_ids': [(6, 0, so_line.analytic_tag_ids.ids)], + 'analytic_account_id': order.analytic_account_id.id or False, + })], + } + + return invoice_vals + + def _get_advance_details(self, order): + context = {'lang': order.partner_id.lang} + if self.advance_payment_method == 'percentage': + if all(self.product_id.taxes_id.mapped('price_include')): + amount = order.amount_total * self.amount / 100 + else: + amount = order.amount_untaxed * self.amount / 100 + name = _("Down payment of %s%%") % (self.amount) + else: + amount = self.fixed_amount + name = _('Down Payment') + del context + + return amount, name + + def _create_invoice(self, order, so_line, amount): + if (self.advance_payment_method == 'percentage' and self.amount <= 0.00) or (self.advance_payment_method == 'fixed' and self.fixed_amount <= 0.00): + raise UserError(_('The value of the down payment amount must be positive.')) + + amount, name = self._get_advance_details(order) + + invoice_vals = self._prepare_invoice_values(order, name, amount, so_line) + + if order.fiscal_position_id: + invoice_vals['fiscal_position_id'] = order.fiscal_position_id.id + invoice = self.env['account.move'].sudo().create(invoice_vals).with_user(self.env.uid) + 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_so_line(self, order, analytic_tag_ids, tax_ids, amount): + context = {'lang': order.partner_id.lang} + so_values = { + 'name': _('Down Payment: %s') % (time.strftime('%m %Y'),), + 'price_unit': amount, + 'product_uom_qty': 0.0, + 'order_id': order.id, + 'discount': 0.0, + 'product_uom': self.product_id.uom_id.id, + 'product_id': self.product_id.id, + 'analytic_tag_ids': analytic_tag_ids, + 'tax_id': [(6, 0, tax_ids)], + 'is_downpayment': True, + 'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10, + } + del context + return so_values + + def create_invoices(self): + sale_orders = self.env['sale.order'].browse(self._context.get('active_ids', [])) + + if self.advance_payment_method == 'delivered': + sale_orders._create_invoices(final=self.deduct_down_payments) + else: + # Create deposit product if necessary + if not self.product_id: + vals = self._prepare_deposit_product() + self.product_id = self.env['product.product'].create(vals) + self.env['ir.config_parameter'].sudo().set_param('sale.default_deposit_product_id', self.product_id.id) + + sale_line_obj = self.env['sale.order.line'] + for order in sale_orders: + amount, name = self._get_advance_details(order) + + if self.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 self.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.")) + taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id) + tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id, order.partner_shipping_id).ids + analytic_tag_ids = [] + for line in order.order_line: + analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids] + + so_line_values = self._prepare_so_line(order, analytic_tag_ids, tax_ids, amount) + so_line = sale_line_obj.create(so_line_values) + self._create_invoice(order, so_line, amount) + if self._context.get('open_invoices', False): + return sale_orders.action_view_invoice() + return {'type': 'ir.actions.act_window_close'} + + def _prepare_deposit_product(self): + return { + 'name': 'Down payment', + 'type': 'service', + 'invoice_policy': 'order', + 'property_account_income_id': self.deposit_account_id.id, + 'taxes_id': [(6, 0, self.deposit_taxes_id.ids)], + 'company_id': False, + } + diff --git a/addons/sale/wizard/sale_make_invoice_advance_views.xml b/addons/sale/wizard/sale_make_invoice_advance_views.xml new file mode 100644 index 00000000..3dd0a99c --- /dev/null +++ b/addons/sale/wizard/sale_make_invoice_advance_views.xml @@ -0,0 +1,64 @@ +<?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="count" attrs="{'invisible': [('count','=', 1)]}" readonly="True"/> + <field name="advance_payment_method" class="oe_inline" widget="radio" + attrs="{'invisible': [('count','>',1)]}"/> + <field name="has_down_payments" invisible="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> + <field name="product_id" + context="{'default_invoice_policy': 'order'}" class="oe_inline" + invisible="1"/> + <label for="amount" attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"/> + <div attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}" + 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}" class="oe_inline" + attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}" groups="account.group_account_manager"/> + <field name="deposit_taxes_id" class="oe_inline" widget="many2many_tags" + domain="[('type_tax_use','=','sale')]" + attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"/> + </group> + <footer> + <button name="create_invoices" id="create_invoice_open" string="Create and View Invoice" type="object" + context="{'open_invoices': True}" class="btn-primary"/> + <button name="create_invoices" id="create_invoice" string="Create Invoice" type="object"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </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> + <!-- TODO: check if we need this --> + <field name="binding_model_id" ref="sale.model_sale_order" /> + <field name="binding_view_types">list</field> + </record> + +</odoo> diff --git a/addons/sale/wizard/sale_order_cancel.py b/addons/sale/wizard/sale_order_cancel.py new file mode 100644 index 00000000..b2ac441a --- /dev/null +++ b/addons/sale/wizard/sale_order_cancel.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class SaleOrderCancel(models.TransientModel): + _name = 'sale.order.cancel' + _description = "Sales Order Cancel" + + order_id = fields.Many2one('sale.order', string='Sale Order', required=True, ondelete='cascade') + display_invoice_alert = fields.Boolean('Invoice Alert', compute='_compute_display_invoice_alert') + + @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')) + + def action_cancel(self): + return self.order_id.with_context({'disable_cancel_warning': True}).action_cancel() diff --git a/addons/sale/wizard/sale_order_cancel_views.xml b/addons/sale/wizard/sale_order_cancel_views.xml new file mode 100644 index 00000000..722d2797 --- /dev/null +++ b/addons/sale/wizard/sale_order_cancel_views.xml @@ -0,0 +1,20 @@ +<?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> + <field name="order_id" invisible="1"/> + <field name="display_invoice_alert" invisible="1"/> + <div attrs="{'invisible': [('display_invoice_alert', '=', False)]}"> + Draft invoices for this order will be cancelled. + </div> + <footer> + <button string="Confirm" name="action_cancel" type="object" class="oe_highlight"/> + <button string="Cancel" class="btn btn-default" special="cancel"/> + </footer> + </form> + </field> + </record> +</odoo> diff --git a/addons/sale/wizard/sale_payment_link.py b/addons/sale/wizard/sale_payment_link.py new file mode 100644 index 00000000..823a2e11 --- /dev/null +++ b/addons/sale/wizard/sale_payment_link.py @@ -0,0 +1,49 @@ +# -*- 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 SalePaymentLink(models.TransientModel): + _inherit = "payment.link.wizard" + _description = "Generate Sales Payment Link" + + @api.model + def default_get(self, fields): + res = super(SalePaymentLink, self).default_get(fields) + if res['res_id'] and res['res_model'] == 'sale.order': + record = self.env[res['res_model']].browse(res['res_id']) + res.update({ + 'description': record.name, + 'amount': record.amount_total - sum(record.invoice_ids.filtered(lambda x: x.state != 'cancel').mapped('amount_total')), + 'currency_id': record.currency_id.id, + 'partner_id': record.partner_id.id, + 'amount_max': record.amount_total + }) + return res + + def _generate_link(self): + """ Override of the base method to add the order_id in the link. """ + for payment_link in self: + # only add order_id for SOs, + # otherwise the controller might try to link it with an unrelated record + # NOTE: company_id is not necessary here, we have it in order_id + # however, should parsing of the id fail in the controller, let's include + # it anyway + if payment_link.res_model == 'sale.order': + record = self.env[payment_link.res_model].browse(payment_link.res_id) + payment_link.link = ('%s/website_payment/pay?reference=%s&amount=%s¤cy_id=%s' + '&partner_id=%s&order_id=%s&company_id=%s&access_token=%s') % ( + record.get_base_url(), + urls.url_quote_plus(payment_link.description), + payment_link.amount, + payment_link.currency_id.id, + payment_link.partner_id.id, + payment_link.res_id, + payment_link.company_id.id, + payment_link.access_token + ) + else: + super(SalePaymentLink, payment_link)._generate_link() diff --git a/addons/sale/wizard/sale_payment_link_views.xml b/addons/sale/wizard/sale_payment_link_views.xml new file mode 100644 index 00000000..836d1b90 --- /dev/null +++ b/addons/sale/wizard/sale_payment_link_views.xml @@ -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> |
