# -*- 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, ValidationError class SaleOrderTemplate(models.Model): _name = "sale.order.template" _description = "Quotation Template" def _get_default_require_signature(self): return self.env.company.portal_confirmation_sign def _get_default_require_payment(self): return self.env.company.portal_confirmation_pay name = fields.Char('Quotation Template', required=True) sale_order_template_line_ids = fields.One2many('sale.order.template.line', 'sale_order_template_id', 'Lines', copy=True) note = fields.Text('Terms and conditions', translate=True) sale_order_template_option_ids = fields.One2many('sale.order.template.option', 'sale_order_template_id', 'Optional Products', copy=True) number_of_days = fields.Integer('Quotation Duration', help='Number of days for the validity date computation of the quotation') require_signature = fields.Boolean('Online Signature', default=_get_default_require_signature, help='Request a online signature to the customer in order to confirm orders automatically.') require_payment = fields.Boolean('Online Payment', default=_get_default_require_payment, help='Request an online payment to the customer in order to confirm orders automatically.') mail_template_id = fields.Many2one( 'mail.template', 'Confirmation Mail', domain=[('model', '=', 'sale.order')], help="This e-mail template will be sent on confirmation. Leave empty to send nothing.") active = fields.Boolean(default=True, help="If unchecked, it will allow you to hide the quotation template without removing it.") company_id = fields.Many2one('res.company', string='Company') @api.constrains('company_id', 'sale_order_template_line_ids', 'sale_order_template_option_ids') def _check_company_id(self): for template in self: companies = template.mapped('sale_order_template_line_ids.product_id.company_id') | template.mapped('sale_order_template_option_ids.product_id.company_id') if len(companies) > 1: raise ValidationError(_("Your template cannot contain products from multiple companies.")) elif companies and companies != template.company_id: raise ValidationError(_( "Your template contains products from company %(product_company)s whereas your template belongs to company %(template_company)s. \n Please change the company of your template or remove the products from other companies.", product_company=', '.join(companies.mapped('display_name')), template_company=template.company_id.display_name, )) @api.onchange('sale_order_template_line_ids', 'sale_order_template_option_ids') def _onchange_template_line_ids(self): companies = self.mapped('sale_order_template_option_ids.product_id.company_id') | self.mapped('sale_order_template_line_ids.product_id.company_id') if companies and self.company_id not in companies: self.company_id = companies[0] @api.model_create_multi def create(self, vals_list): records = super(SaleOrderTemplate, self).create(vals_list) records._update_product_translations() return records def write(self, vals): if 'active' in vals and not vals.get('active'): companies = self.env['res.company'].sudo().search([('sale_order_template_id', 'in', self.ids)]) companies.sale_order_template_id = None result = super(SaleOrderTemplate, self).write(vals) self._update_product_translations() return result def _update_product_translations(self): languages = self.env['res.lang'].search([('active', '=', 'true')]) for lang in languages: for line in self.sale_order_template_line_ids: if line.name == line.product_id.get_product_multiline_description_sale(): self.create_or_update_translations(model_name='sale.order.template.line,name', lang_code=lang.code, res_id=line.id,src=line.name, value=line.product_id.with_context(lang=lang.code).get_product_multiline_description_sale()) for option in self.sale_order_template_option_ids: if option.name == option.product_id.get_product_multiline_description_sale(): self.create_or_update_translations(model_name='sale.order.template.option,name', lang_code=lang.code, res_id=option.id,src=option.name, value=option.product_id.with_context(lang=lang.code).get_product_multiline_description_sale()) def create_or_update_translations(self, model_name, lang_code, res_id, src, value): data = { 'type': 'model', 'name': model_name, 'lang': lang_code, 'res_id': res_id, 'src': src, 'value': value, 'state': 'inprogress', } existing_trans = self.env['ir.translation'].search([('name', '=', model_name), ('res_id', '=', res_id), ('lang', '=', lang_code)]) if not existing_trans: self.env['ir.translation'].create(data) else: existing_trans.write(data) class SaleOrderTemplateLine(models.Model): _name = "sale.order.template.line" _description = "Quotation Template Line" _order = 'sale_order_template_id, sequence, id' sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of sale quote lines.", default=10) sale_order_template_id = fields.Many2one( 'sale.order.template', 'Quotation Template Reference', required=True, ondelete='cascade', index=True) company_id = fields.Many2one('res.company', related='sale_order_template_id.company_id', store=True, index=True) name = fields.Text('Description', required=True, translate=True) product_id = fields.Many2one( 'product.product', 'Product', check_company=True, domain=[('sale_ok', '=', True)]) product_uom_qty = fields.Float('Quantity', required=True, digits='Product Unit of Measure', default=1) product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]") product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id', readonly=True) display_type = fields.Selection([ ('line_section', "Section"), ('line_note', "Note")], default=False, help="Technical field for UX purpose.") @api.onchange('product_id') def _onchange_product_id(self): self.ensure_one() if self.product_id: self.product_uom_id = self.product_id.uom_id.id self.name = self.product_id.get_product_multiline_description_sale() @api.model def create(self, values): if values.get('display_type', self.default_get(['display_type'])['display_type']): values.update(product_id=False, product_uom_qty=0, product_uom_id=False) return super(SaleOrderTemplateLine, self).create(values) def write(self, values): if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')): raise UserError(_("You cannot change the type of a sale quote line. Instead you should delete the current line and create a new line of the proper type.")) return super(SaleOrderTemplateLine, self).write(values) _sql_constraints = [ ('accountable_product_id_required', "CHECK(display_type IS NOT NULL OR (product_id IS NOT NULL AND product_uom_id IS NOT NULL))", "Missing required product and UoM on accountable sale quote line."), ('non_accountable_fields_null', "CHECK(display_type IS NULL OR (product_id IS NULL AND product_uom_qty = 0 AND product_uom_id IS NULL))", "Forbidden product, unit price, quantity, and UoM on non-accountable sale quote line"), ] class SaleOrderTemplateOption(models.Model): _name = "sale.order.template.option" _description = "Quotation Template Option" _check_company_auto = True sale_order_template_id = fields.Many2one('sale.order.template', 'Quotation Template Reference', ondelete='cascade', index=True, required=True) company_id = fields.Many2one('res.company', related='sale_order_template_id.company_id', store=True, index=True) name = fields.Text('Description', required=True, translate=True) product_id = fields.Many2one( 'product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, check_company=True) uom_id = fields.Many2one('uom.uom', 'Unit of Measure ', required=True, domain="[('category_id', '=', product_uom_category_id)]") product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id', readonly=True) quantity = fields.Float('Quantity', required=True, digits='Product Unit of Measure', default=1) @api.onchange('product_id') def _onchange_product_id(self): if not self.product_id: return self.uom_id = self.product_id.uom_id self.name = self.product_id.get_product_multiline_description_sale()