summaryrefslogtreecommitdiff
path: root/addons/sale/wizard
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/sale/wizard
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/sale/wizard')
-rw-r--r--addons/sale/wizard/__init__.py7
-rw-r--r--addons/sale/wizard/payment_acquirer_onboarding_wizard.py37
-rw-r--r--addons/sale/wizard/sale_make_invoice_advance.py197
-rw-r--r--addons/sale/wizard/sale_make_invoice_advance_views.xml64
-rw-r--r--addons/sale/wizard/sale_order_cancel.py20
-rw-r--r--addons/sale/wizard/sale_order_cancel_views.xml20
-rw-r--r--addons/sale/wizard/sale_payment_link.py49
-rw-r--r--addons/sale/wizard/sale_payment_link_views.xml14
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','&gt;',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&currency_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>