[IMP] product,(website_)sale: Allow users to fully configure products
Purpose
=======
- Configure a product from a sales order as easily as in the ecommerce
The backend uses the same template as the frontend to configure a products and its options
- Display optionnal products of optionnal products in the "sale options" section
- Allow adding exlusions to some combinations of the product and/or
to some combinations of the optionnal and accessory products
- Product attributes can now have custom values.
These values are specified by the user during the SO creation.
They are also available on the webshop frontend.
- Product attributes can be configured to create variants "on demand".
This means that product.templates having these attributes will only create product.product variant
when that combination is actually selected in a sale order/in the web shop.
Product attribute values are also improved to handle extra prices and exclusions
in all three modes ("never", "dynamic, "always")
Specification
=============
1. Improve the "Add to cart" with optional products
- When you add an option to cart, move the product to the cart & show the options of this option
- Don't open the option wizard if no option available (in case this module becomes generic,
it's better to skip this step when it's not needed)
- Display the options right under their related product in the cart
2. Do not allow to add the product as an optional product for itself
Why ?
- No functional sense
- Creates issues in the add to cart wizard
3. Exclude some attribute combinations within the product or with related options and accessory products
- Rename VARIANT PRICES button -> CONFIGURE VARIANTS
- When you click this button and select a value, open form view rather than inline edition
- Form view of variant values:
- Attribute
- Value
- HTML Color Index
- Attribute Price Extra
- Not Compatible with: o2m tab with 2 fields: Product AND Attribute Value (of this product)
[m2m tag selection]
- in any new line, autocomplete the current product by default
- Tooltip: A list of product and attribute values that you want to exclude for
this product's attribue value. Also applies on optionnal and accessory products.
4. New option to configure a product in a sales order line
- This option is only visible if the corresponding option is activated in the sales settings
- It opens a wizard to select a product template, configure related attribute values
and select optional products (based on frontend view)
- Attributes not compatible with other selected attributes should not be selectable
- Must work like in the frontend
5. Create variants on demand
- add a new mode to generate variants as they are added to the order/cart
(needed for big # of combinations managed in the inventory and/or produced in mrp with specific boms)
- replace the boolean by a selection radio
tooltip for this new field:
Always:
Variants are generated straight away when
the attribute values are combined in the product configuration.
Only when the product is added to a sales order:
Variants are generated once the combination is selected in the sales order.
This is advised when variants are customized on demand and manufactured as such in Odoo.
Never:
Advised when variants are not needed in Odoo.
The selected values just show up in the description of sales order lines.
- ability to define attribute extra prices & exclude combinations in all variant modes
- if at least one attribute of the product in set to "on demand" mode, generate all the variants "on demand"
This commit is contained in:
@@ -942,11 +942,6 @@ file_filter = addons/website_sale_management/i18n/<lang>.po
|
||||
source_file = addons/website_sale_management/i18n/website_sale_management.pot
|
||||
source_lang = en
|
||||
|
||||
[odoo-12.website_sale_options]
|
||||
file_filter = addons/website_sale_options/i18n/<lang>.po
|
||||
source_file = addons/website_sale_options/i18n/website_sale_options.pot
|
||||
source_lang = en
|
||||
|
||||
[odoo-12.website_sale_stock]
|
||||
file_filter = addons/website_sale_stock/i18n/<lang>.po
|
||||
source_file = addons/website_sale_stock/i18n/website_sale_stock.pot
|
||||
|
||||
@@ -234,6 +234,7 @@ class MrpBomLine(models.Model):
|
||||
bom_id = fields.Many2one(
|
||||
'mrp.bom', 'Parent BoM',
|
||||
index=True, ondelete='cascade', required=True)
|
||||
parent_product_tmpl_id = fields.Many2one('product.template', 'Parent Product Template', related='bom_id.product_tmpl_id')
|
||||
attribute_value_ids = fields.Many2many(
|
||||
'product.attribute.value', string='Apply on Variants',
|
||||
help="BOM Product Variants needed form apply this line.")
|
||||
@@ -294,6 +295,13 @@ class MrpBomLine(models.Model):
|
||||
if self.product_id:
|
||||
self.product_uom_id = self.product_id.uom_id.id
|
||||
|
||||
@api.onchange('parent_product_tmpl_id')
|
||||
def onchange_parent_product(self):
|
||||
return {'domain': {'attribute_value_ids': [
|
||||
('id', 'in', self.parent_product_tmpl_id.mapped('attribute_line_ids.value_ids.id')),
|
||||
('attribute_id.create_variant', '!=', 'never')
|
||||
]}}
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for values in vals_list:
|
||||
|
||||
@@ -24,6 +24,14 @@ class ReportBomStructure(models.AbstractModel):
|
||||
doc['report_type'] = 'pdf'
|
||||
doc['report_structure'] = data and data.get('report_type') or 'all'
|
||||
docs.append(doc)
|
||||
if not candidates:
|
||||
if data and data.get('childs'):
|
||||
doc = self._get_pdf_line(bom_id, qty=int(data.get('quantity')), child_bom_ids=json.loads(data.get('childs')))
|
||||
else:
|
||||
doc = self._get_pdf_line(bom_id, unfolded=True)
|
||||
doc['report_type'] = 'pdf'
|
||||
doc['report_structure'] = data and data.get('report_type') or 'all'
|
||||
docs.append(doc)
|
||||
return {
|
||||
'doc_ids': docids,
|
||||
'doc_model': 'mrp.bom',
|
||||
@@ -95,6 +103,12 @@ class ReportBomStructure(models.AbstractModel):
|
||||
product = self.env['product.product'].browse(int(product_id))
|
||||
else:
|
||||
product = bom.product_id or bom.product_tmpl_id.product_variant_id
|
||||
if product:
|
||||
attachments = self.env['mrp.document'].search(['|', '&', ('res_model', '=', 'product.product'),
|
||||
('res_id', '=', product.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', product.product_tmpl_id.id)])
|
||||
else:
|
||||
product = bom.product_tmpl_id
|
||||
attachments = self.env['mrp.document'].search([('res_model', '=', 'product.template'), ('res_id', '=', product.id)])
|
||||
operations = self._get_operation_line(bom.routing_id, (bom_quantity / bom.product_qty), 0)
|
||||
lines = {
|
||||
'bom': bom,
|
||||
@@ -108,8 +122,7 @@ class ReportBomStructure(models.AbstractModel):
|
||||
'level': level or 0,
|
||||
'operations': operations,
|
||||
'operations_cost': sum([op['total'] for op in operations]),
|
||||
'attachments': self.env['mrp.document'].search(['|', '&',
|
||||
('res_model', '=', 'product.product'), ('res_id', '=', product.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', product.product_tmpl_id.id)]),
|
||||
'attachments': attachments,
|
||||
'operations_time': sum([op['duration_expected'] for op in operations])
|
||||
}
|
||||
components, total = self._get_bom_lines(bom, bom_quantity, product, line_id, level)
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Components">
|
||||
<field name="bom_line_ids" widget="one2many_list">
|
||||
<field name="bom_line_ids" widget="one2many_list" context="{'default_parent_product_tmpl_id': product_tmpl_id}">
|
||||
<tree string="Components" editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="product_id" context="{'default_type': 'product'}"/>
|
||||
@@ -65,8 +65,9 @@
|
||||
<button name="action_see_attachments" type="object" icon="fa-files-o" attrs="{'invisible': [('has_attachments', '=', False)]}" aria-label="No attachment" title="No attachment"/>
|
||||
<button name="action_see_attachments" type="object" icon="fa-file" attrs="{'invisible': [('has_attachments', '=', True)]}" aria-label="Attachments" title="Attachments"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="parent_product_tmpl_id" invisible="1" />
|
||||
<field name="product_uom_id" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
|
||||
<field name="attribute_value_ids" widget="many2many_tags" options="{'no_create': True}" domain="[('product_ids.product_tmpl_id', '=', parent.product_tmpl_id)]" attrs="{'column_invisible': [('parent.product_id', '!=', False)]}" groups="product.group_product_variant"/>
|
||||
<field name="attribute_value_ids" widget="many2many_tags" options="{'no_create': True}" attrs="{'column_invisible': [('parent.product_id', '!=', False)]}" groups="product.group_product_variant"/>
|
||||
<field name="operation_id" groups="mrp.group_mrp_routings" domain="[('routing_id', '=', parent.routing_id)]" options="{'no_quick_create':True,'no_create_edit':True}" attrs="{'column_invisible': [('parent.type', '=', 'phantom')]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
@@ -31,15 +31,14 @@ class TestUi(odoo.tests.HttpCase):
|
||||
pear = env.ref('point_of_sale.whiteboard')
|
||||
attribute_value = env['product.attribute.value'].create({
|
||||
'name': 'add 2',
|
||||
'product_ids': [(6, 0, [pear.id])],
|
||||
'attribute_id': env['product.attribute'].create({
|
||||
'name': 'add 2',
|
||||
}).id,
|
||||
})
|
||||
env['product.attribute.price'].create({
|
||||
env['product.product.attribute.value'].create({
|
||||
'product_tmpl_id': pear.product_tmpl_id.id,
|
||||
'price_extra': 2,
|
||||
'value_id': attribute_value.id,
|
||||
'product_attribute_value_id': attribute_value.id,
|
||||
})
|
||||
|
||||
fixed_pricelist = env['product.pricelist'].create({
|
||||
|
||||
@@ -212,16 +212,6 @@
|
||||
<field name="attribute_line_ids" eval="[(6,0,[ref('product.product_attribute_line_1'), ref('product.product_attribute_line_2')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_4d" model="product.product">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_price_1" model="product.attribute.price">
|
||||
<field name="product_tmpl_id" ref="product_product_4_product_template"/>
|
||||
<field name="value_id" ref="product_attribute_value_2"/>
|
||||
<field name="price_extra">50.40</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_5" model="product.product">
|
||||
<field name="name">Corner Desk Right Sit</field>
|
||||
<field name="categ_id" ref="product_category_5"/>
|
||||
@@ -320,14 +310,34 @@
|
||||
<field name="attribute_id" ref="product_attribute_1"/>
|
||||
<field name="value_ids" eval="[(6,0,[ref('product.product_attribute_value_1'), ref('product.product_attribute_value_2')])]"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="product_product_11_product_template" model="product.template">
|
||||
<field name="attribute_line_ids" eval="[(6,0,[ref('product.product_attribute_line_4')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_price_2" model="product.attribute.price">
|
||||
<field name="product_tmpl_id" ref="product_product_11_product_template"/>
|
||||
<field name="value_id" ref="product_attribute_value_2"/>
|
||||
<!--
|
||||
Handle automatically created product.product.attribute.value.
|
||||
Meaning that the combination between the "customizable desk" and the attribute value "black" will be materialized
|
||||
into a "product.product.attribute.value" with the ref "product.product_product_attribute_value_1".
|
||||
This will allow setting fields like "price_extra" and "exclude_for"
|
||||
-->
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'product.product_product_attribute_value_1',
|
||||
'record': obj().env.ref('product.product_attribute_line_1').product_value_ids[1],
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'product.product_product_attribute_value_2',
|
||||
'record': obj().env.ref('product.product_attribute_line_4').product_value_ids[1],
|
||||
'noupdate': True,
|
||||
}]"/>
|
||||
</function>
|
||||
|
||||
<record id="product_product_attribute_value_1" model="product.product.attribute.value">
|
||||
<field name="price_extra">50.40</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_attribute_value_2" model="product.product.attribute.value">
|
||||
<field name="price_extra">6.40</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class ProductProduct(models.Model):
|
||||
lst_price = fields.Float(
|
||||
'Sale Price', compute='_compute_product_lst_price',
|
||||
digits=dp.get_precision('Product Price'), inverse='_set_product_lst_price',
|
||||
help="The sale price is managed from the product template. Click on the 'Variant Prices' button to set the extra attribute prices.")
|
||||
help="The sale price is managed from the product template. Click on the 'Configure Variants' button to set the extra attribute prices.")
|
||||
|
||||
default_code = fields.Char('Internal Reference', index=True)
|
||||
code = fields.Char('Reference', compute='_compute_product_code')
|
||||
@@ -110,6 +110,8 @@ class ProductProduct(models.Model):
|
||||
help="International Article Number used for product identification.")
|
||||
attribute_value_ids = fields.Many2many(
|
||||
'product.attribute.value', string='Attributes', ondelete='restrict')
|
||||
product_attribute_value_ids = fields.Many2many(
|
||||
'product.product.attribute.value', string='Attribute Values', compute="_compute_product_attribute_value_ids")
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image_variant = fields.Binary(
|
||||
"Variant Image", attachment=True,
|
||||
@@ -193,15 +195,10 @@ class ProductProduct(models.Model):
|
||||
value -= product.price_extra
|
||||
product.write({'list_price': value})
|
||||
|
||||
@api.depends('attribute_value_ids.price_ids.price_extra', 'attribute_value_ids.price_ids.product_tmpl_id')
|
||||
@api.depends('product_attribute_value_ids.price_extra')
|
||||
def _compute_product_price_extra(self):
|
||||
# TDE FIXME: do a real multi and optimize a bit ?
|
||||
for product in self:
|
||||
price_extra = 0.0
|
||||
for attribute_price in product.mapped('attribute_value_ids.price_ids'):
|
||||
if attribute_price.product_tmpl_id == product.product_tmpl_id:
|
||||
price_extra += attribute_price.price_extra
|
||||
product.price_extra = price_extra
|
||||
product.price_extra = sum(product.mapped('product_attribute_value_ids.price_extra'))
|
||||
|
||||
@api.depends('list_price', 'price_extra')
|
||||
def _compute_product_lst_price(self):
|
||||
@@ -276,6 +273,12 @@ class ProductProduct(models.Model):
|
||||
else:
|
||||
self.product_tmpl_id.image = image
|
||||
|
||||
def _compute_product_attribute_value_ids(self):
|
||||
for product in self:
|
||||
product.product_attribute_value_ids = self.env['product.product.attribute.value']._search([
|
||||
('product_tmpl_id', '=', product.product_tmpl_id.id),
|
||||
('product_attribute_value_id', 'in', product.attribute_value_ids.ids)])
|
||||
|
||||
@api.one
|
||||
def _get_pricelist_items(self):
|
||||
self.pricelist_item_ids = self.env['product.pricelist.item'].search([
|
||||
@@ -290,7 +293,7 @@ class ProductProduct(models.Model):
|
||||
for value in product.attribute_value_ids:
|
||||
if value.attribute_id in attributes:
|
||||
raise ValidationError(_('Error! It is not allowed to choose more than one value for a given attribute.'))
|
||||
if value.attribute_id.create_variant:
|
||||
if value.attribute_id.create_variant == 'always':
|
||||
attributes |= value.attribute_id
|
||||
return True
|
||||
|
||||
@@ -324,9 +327,10 @@ class ProductProduct(models.Model):
|
||||
# Check if product still exists, in case it has been unlinked by unlinking its template
|
||||
if not product.exists():
|
||||
continue
|
||||
# Check if the product is last product of this template
|
||||
# Check if the product is last product of this template...
|
||||
other_products = self.search([('product_tmpl_id', '=', product.product_tmpl_id.id), ('id', '!=', product.id)])
|
||||
if not other_products:
|
||||
# ... and do not delete product template if it's configured to be created "on demand"
|
||||
if not other_products and not product.product_tmpl_id.has_dynamic_attributes():
|
||||
unlink_templates |= product.product_tmpl_id
|
||||
unlink_products |= product
|
||||
res = super(ProductProduct, unlink_products).unlink()
|
||||
@@ -529,6 +533,11 @@ class ProductProduct(models.Model):
|
||||
prices[product.id] = product[price_type] or 0.0
|
||||
if price_type == 'list_price':
|
||||
prices[product.id] += product.price_extra
|
||||
# we need to add the price from the attributes that do not generate variants
|
||||
# (see field product.attribute create_variant)
|
||||
if self._context.get('no_variant_attributes_price_extra'):
|
||||
# we have a list of price_extra that comes from the attribute values, we need to sum all that
|
||||
prices[product.id] += sum(self._context.get('no_variant_attributes_price_extra'))
|
||||
|
||||
if uom:
|
||||
prices[product.id] = product.uom_id._compute_price(prices[product.id], uom)
|
||||
@@ -581,6 +590,7 @@ class ProductProduct(models.Model):
|
||||
name = self.display_name
|
||||
if self.description_sale:
|
||||
name += '\n' + self.description_sale
|
||||
|
||||
return name
|
||||
|
||||
|
||||
|
||||
@@ -16,78 +16,78 @@ class ProductAttribute(models.Model):
|
||||
value_ids = fields.One2many('product.attribute.value', 'attribute_id', 'Values', copy=True)
|
||||
sequence = fields.Integer('Sequence', help="Determine the display order")
|
||||
attribute_line_ids = fields.One2many('product.attribute.line', 'attribute_id', 'Lines')
|
||||
create_variant = fields.Boolean(default=True, help="Check this if you want to create multiple variants for this attribute.")
|
||||
create_variant = fields.Selection([
|
||||
('never', 'Never'),
|
||||
('always', 'Always'),
|
||||
('dynamic', 'Only when the product is added to a sales order')],
|
||||
default='always',
|
||||
string="Create Variants",
|
||||
help="Check this if you want to create multiple variants for this attribute.", required=True)
|
||||
|
||||
|
||||
class ProductAttributevalue(models.Model):
|
||||
class ProductAttributeValue(models.Model):
|
||||
_name = "product.attribute.value"
|
||||
_order = 'attribute_id, sequence, id'
|
||||
_description = 'Product Attribute Value'
|
||||
_description = 'Attribute Value'
|
||||
|
||||
name = fields.Char('Attribute Value', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', help="Determine the display order")
|
||||
attribute_id = fields.Many2one('product.attribute', 'Attribute', ondelete='cascade', required=True)
|
||||
product_ids = fields.Many2many('product.product', string='Variants', readonly=True)
|
||||
price_extra = fields.Float(
|
||||
'Attribute Price Extra', compute='_compute_price_extra', inverse='_set_price_extra',
|
||||
default=0.0, digits=dp.get_precision('Product Price'),
|
||||
help="Price Extra: Extra price for the variant with this attribute value on sale price. eg. 200 price extra, 1000 + 200 = 1200.")
|
||||
price_ids = fields.One2many('product.attribute.price', 'value_id', 'Attribute Prices', readonly=True)
|
||||
name = fields.Char(string='Value', required=True, translate=True)
|
||||
sequence = fields.Integer(string='Sequence', help="Determine the display order")
|
||||
attribute_id = fields.Many2one('product.attribute', string='Attribute', ondelete='cascade', required=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('value_company_uniq', 'unique (name,attribute_id)', 'This attribute value already exists !')
|
||||
('value_company_uniq', 'unique (name, attribute_id)', 'This attribute value already exists !')
|
||||
]
|
||||
|
||||
@api.one
|
||||
def _compute_price_extra(self):
|
||||
if self._context.get('active_id'):
|
||||
price = self.price_ids.filtered(lambda price: price.product_tmpl_id.id == self._context['active_id'])
|
||||
self.price_extra = price.price_extra
|
||||
else:
|
||||
self.price_extra = 0.0
|
||||
|
||||
def _set_price_extra(self):
|
||||
if not self._context.get('active_id'):
|
||||
return
|
||||
|
||||
AttributePrice = self.env['product.attribute.price']
|
||||
prices = AttributePrice.search([('value_id', 'in', self.ids), ('product_tmpl_id', '=', self._context['active_id'])])
|
||||
updated = prices.mapped('value_id')
|
||||
if prices:
|
||||
prices.write({'price_extra': self.price_extra})
|
||||
else:
|
||||
for value in self - updated:
|
||||
AttributePrice.create({
|
||||
'product_tmpl_id': self._context['active_id'],
|
||||
'value_id': value.id,
|
||||
'price_extra': self.price_extra,
|
||||
})
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
if not self._context.get('show_attribute', True): # TDE FIXME: not used
|
||||
return super(ProductAttributevalue, self).name_get()
|
||||
return super(ProductAttributeValue, self).name_get()
|
||||
return [(value.id, "%s: %s" % (value.attribute_id.name, value.name)) for value in self]
|
||||
|
||||
@api.multi
|
||||
def _variant_name(self, variable_attributes):
|
||||
return ", ".join([v.name for v in self if v.attribute_id in variable_attributes])
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
linked_products = self.env['product.product'].with_context(active_test=False).search([('attribute_value_ids', 'in', self.ids)])
|
||||
if linked_products:
|
||||
raise UserError(_('The operation cannot be completed:\nYou are trying to delete an attribute value with a reference on a product variant.'))
|
||||
return super(ProductAttributevalue, self).unlink()
|
||||
return super(ProductAttributeValue, self).unlink()
|
||||
|
||||
|
||||
class ProductProductAttributeValue(models.Model):
|
||||
_name = "product.product.attribute.value"
|
||||
_order = 'sequence, attribute_id, id'
|
||||
_description = 'Product Attribute Value'
|
||||
|
||||
name = fields.Char('Value', related="product_attribute_value_id.name")
|
||||
product_attribute_value_id = fields.Many2one('product.attribute.value', string='Attribute Value', required=True, ondelete='cascade', index=True)
|
||||
product_tmpl_id = fields.Many2one('product.template', string='Product Template', required=True, ondelete='cascade', index=True)
|
||||
attribute_id = fields.Many2one('product.attribute', string='Attribute', related="product_attribute_value_id.attribute_id")
|
||||
sequence = fields.Integer('Sequence', related="product_attribute_value_id.sequence")
|
||||
price_extra = fields.Float(
|
||||
string='Attribute Price Extra', default=0.0, digits=dp.get_precision('Product Price'),
|
||||
help="Price Extra: Extra price for the variant with this attribute value on sale price. eg. 200 price extra, 1000 + 200 = 1200.")
|
||||
exclude_for = fields.Many2many(
|
||||
'product.attribute.filter.line', string="Exclude for", relation="product_attribute_value_exclusion",
|
||||
help="Make this attribute value not compatible with other values of the product or some attribute values of optional and accessory products.")
|
||||
|
||||
@api.multi
|
||||
def _variant_name(self, variable_attributes):
|
||||
return ", ".join([v.name for v in self if v.attribute_id in variable_attributes])
|
||||
def name_get(self):
|
||||
if not self._context.get('show_attribute', True): # TDE FIXME: not used
|
||||
return super(ProductAttributeValue, self).name_get()
|
||||
return [(value.id, "%s: %s" % (value.attribute_id.name, value.name)) for value in self]
|
||||
|
||||
|
||||
class ProductAttributePrice(models.Model):
|
||||
_name = "product.attribute.price"
|
||||
_description = 'Product Attribute Price'
|
||||
class ProductAttributeFilterLine(models.Model):
|
||||
_name = "product.attribute.filter.line"
|
||||
_description = 'Product Attribute Filter Line'
|
||||
|
||||
product_tmpl_id = fields.Many2one('product.template', 'Product Template', ondelete='cascade', required=True)
|
||||
value_id = fields.Many2one('product.attribute.value', 'Product Attribute Value', ondelete='cascade', required=True)
|
||||
price_extra = fields.Float('Price Extra', digits=dp.get_precision('Product Price'))
|
||||
product_tmpl_id = fields.Many2one('product.template', string='Product Template', ondelete='cascade', required=True)
|
||||
value_ids = fields.Many2many(
|
||||
'product.product.attribute.value', relation="product_attr_filter_line_value_ids_rel",
|
||||
string='Attribute Values', domain="[('product_tmpl_id', '=', product_tmpl_id)]")
|
||||
|
||||
|
||||
class ProductAttributeLine(models.Model):
|
||||
@@ -95,9 +95,15 @@ class ProductAttributeLine(models.Model):
|
||||
_rec_name = 'attribute_id'
|
||||
_description = 'Product Attribute Line'
|
||||
|
||||
product_tmpl_id = fields.Many2one('product.template', 'Product Template', ondelete='cascade', required=True)
|
||||
attribute_id = fields.Many2one('product.attribute', 'Attribute', ondelete='restrict', required=True)
|
||||
product_tmpl_id = fields.Many2one('product.template', string='Product Template', ondelete='cascade', required=True)
|
||||
attribute_id = fields.Many2one('product.attribute', string='Attribute', ondelete='restrict', required=True)
|
||||
value_ids = fields.Many2many('product.attribute.value', string='Attribute Values')
|
||||
product_value_ids = fields.Many2many(
|
||||
'product.product.attribute.value',
|
||||
string='Product Attribute Values',
|
||||
compute="_set_product_value_ids",
|
||||
store=False
|
||||
)
|
||||
|
||||
@api.constrains('value_ids', 'attribute_id')
|
||||
def _check_valid_attribute(self):
|
||||
@@ -105,6 +111,66 @@ class ProductAttributeLine(models.Model):
|
||||
raise ValidationError(_('You cannot use this attribute with the following value.'))
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
res = super(ProductAttributeLine, self).create(values)
|
||||
res._update_product_product_attribute_values()
|
||||
return res
|
||||
|
||||
def write(self, values):
|
||||
res = super(ProductAttributeLine, self).write(values)
|
||||
self._update_product_product_attribute_values()
|
||||
return res
|
||||
|
||||
@api.depends('value_ids')
|
||||
def _set_product_value_ids(self):
|
||||
for product_attribute_line in self:
|
||||
product_attribute_line.product_value_ids = self.env['product.product.attribute.value'].search([
|
||||
('product_tmpl_id', 'in', product_attribute_line.product_tmpl_id.ids),
|
||||
('product_attribute_value_id.attribute_id', 'in', product_attribute_line.value_ids.mapped('attribute_id').ids)]
|
||||
).sorted(lambda product_product_attribute: product_product_attribute.sequence)
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for product_attribute_line in self:
|
||||
self.env['product.product.attribute.value'].search([
|
||||
('product_tmpl_id', 'in', product_attribute_line.product_tmpl_id.ids),
|
||||
('product_attribute_value_id.attribute_id', 'in', product_attribute_line.value_ids.mapped('attribute_id').ids)]).unlink()
|
||||
|
||||
return super(ProductAttributeLine, self).unlink()
|
||||
|
||||
def _update_product_product_attribute_values(self):
|
||||
"""
|
||||
Create or unlink product.product.attribute.value based on the attribute lines.
|
||||
If the product.attribute.value is removed, remove the corresponding product.product.attribute.value
|
||||
If no product.product.attribute.value exists for the newly added product.attribute.value, create it.
|
||||
"""
|
||||
for attribute_line in self:
|
||||
# All existing product.product.attribute.value for this template
|
||||
product_product_attribute_values_to_remove = self.env['product.product.attribute.value'].search([
|
||||
('product_tmpl_id', '=', attribute_line.product_tmpl_id.id),
|
||||
('product_attribute_value_id.attribute_id', 'in', attribute_line.value_ids.mapped('attribute_id').ids)])
|
||||
# All existing product.attribute.value shared by all products
|
||||
# eg (Yellow, Red, Blue, Small, Large)
|
||||
existing_product_attribute_values = product_product_attribute_values_to_remove.mapped('product_attribute_value_id')
|
||||
|
||||
# Loop on product.attribute.values for the line (eg: Yellow, Red, Blue)
|
||||
for product_attribute_value in attribute_line.value_ids:
|
||||
if product_attribute_value in existing_product_attribute_values:
|
||||
# property is already existing: don't touch, remove it from list to avoid unlinking it
|
||||
product_product_attribute_values_to_remove = product_product_attribute_values_to_remove.filtered(
|
||||
lambda value: product_attribute_value not in value.mapped('product_attribute_value_id')
|
||||
)
|
||||
else:
|
||||
# property does not exist: create it
|
||||
self.env['product.product.attribute.value'].create({
|
||||
'product_attribute_value_id': product_attribute_value.id,
|
||||
'product_tmpl_id': attribute_line.product_tmpl_id.id})
|
||||
|
||||
# at this point, existing properties can be removed to reflect the modifications on value_ids
|
||||
if product_product_attribute_values_to_remove:
|
||||
product_product_attribute_values_to_remove.unlink()
|
||||
|
||||
@api.model
|
||||
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
||||
# TDE FIXME: currently overriding the domain; however as it includes a
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import itertools
|
||||
import operator
|
||||
import psycopg2
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
@@ -9,6 +10,7 @@ from odoo.addons import decimal_precision as dp
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError, RedirectWarning, except_orm
|
||||
from odoo.tools import pycompat
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
@@ -159,6 +161,15 @@ class ProductTemplate(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _compute_template_price(self):
|
||||
prices = self._compute_template_price_no_inverse()
|
||||
for template in self:
|
||||
template.price = prices.get(template.id, 0.0)
|
||||
|
||||
@api.multi
|
||||
def _compute_template_price_no_inverse(self):
|
||||
"""The _compute_template_price writes the 'list_price' field with an inverse method
|
||||
This method allows computing the price without writing the 'list_price'
|
||||
"""
|
||||
prices = {}
|
||||
pricelist_id_or_name = self._context.get('pricelist')
|
||||
if pricelist_id_or_name:
|
||||
@@ -179,8 +190,7 @@ class ProductTemplate(models.Model):
|
||||
partners = [partner] * len(self)
|
||||
prices = pricelist.get_products_price(self, quantities, partners)
|
||||
|
||||
for template in self:
|
||||
template.price = prices.get(template.id, 0.0)
|
||||
return prices
|
||||
|
||||
@api.multi
|
||||
def _set_template_price(self):
|
||||
@@ -403,6 +413,11 @@ class ProductTemplate(models.Model):
|
||||
prices = dict.fromkeys(self.ids, 0.0)
|
||||
for template in templates:
|
||||
prices[template.id] = template[price_type] or 0.0
|
||||
# yes, there can be attribute values for product template if it's not a variant YET
|
||||
# (see field product.attribute create_variant)
|
||||
if price_type == 'list_price' and self._context.get('current_attributes_price_extra'):
|
||||
# we have a list of price_extra that comes from the attribute values, we need to sum all that
|
||||
prices[template.id] += sum(self._context.get('current_attributes_price_extra'))
|
||||
|
||||
if uom:
|
||||
prices[template.id] = template.uom_id._compute_price(prices[template.id], uom)
|
||||
@@ -431,7 +446,7 @@ class ProductTemplate(models.Model):
|
||||
for tmpl_id in self.with_context(active_test=False):
|
||||
# adding an attribute with only one value should not recreate product
|
||||
# write this attribute on every product to make sure we don't lose them
|
||||
variant_alone = tmpl_id.attribute_line_ids.filtered(lambda line: line.attribute_id.create_variant and len(line.value_ids) == 1).mapped('value_ids')
|
||||
variant_alone = tmpl_id.attribute_line_ids.filtered(lambda line: line.attribute_id.create_variant == 'always' and len(line.value_ids) == 1).mapped('value_ids')
|
||||
for value_id in variant_alone:
|
||||
updated_products = tmpl_id.product_variant_ids.filtered(lambda product: value_id.attribute_id not in product.mapped('attribute_value_ids.attribute_id'))
|
||||
updated_products.write({'attribute_value_ids': [(4, value_id.id)]})
|
||||
@@ -439,25 +454,31 @@ class ProductTemplate(models.Model):
|
||||
# iterator of n-uple of product.attribute.value *ids*
|
||||
variant_matrix = [
|
||||
AttributeValues.browse(value_ids)
|
||||
for value_ids in itertools.product(*(line.value_ids.ids for line in tmpl_id.attribute_line_ids if line.value_ids[:1].attribute_id.create_variant))
|
||||
for value_ids in itertools.product(*(line.value_ids.ids for line in tmpl_id.attribute_line_ids if line.value_ids[:1].attribute_id.create_variant != 'never'))
|
||||
]
|
||||
|
||||
# get the value (id) sets of existing variants
|
||||
existing_variants = {frozenset(variant.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant).ids) for variant in tmpl_id.product_variant_ids}
|
||||
existing_variants = {frozenset(variant.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant != 'never').ids) for variant in tmpl_id.product_variant_ids}
|
||||
# -> for each value set, create a recordset of values to create a
|
||||
# variant for if the value set isn't already a variant
|
||||
for value_ids in variant_matrix:
|
||||
if set(value_ids.ids) not in existing_variants:
|
||||
if set(value_ids.ids) not in existing_variants and not any(value_id.attribute_id.create_variant == 'dynamic' for value_id in value_ids):
|
||||
variants_to_create.append({
|
||||
'product_tmpl_id': tmpl_id.id,
|
||||
'attribute_value_ids': [(6, 0, value_ids.ids)]
|
||||
})
|
||||
|
||||
if len(variants_to_create) > 1000:
|
||||
raise UserError(_("""
|
||||
The number of variants to generate is too high.
|
||||
You should either not generate variants for each combination or generate them on demand from the sales order.
|
||||
To do so, open the form view of attributes and change the mode of *Create Variants*."""))
|
||||
|
||||
# check product
|
||||
for product_id in tmpl_id.product_variant_ids:
|
||||
if not product_id.active and product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) in variant_matrix:
|
||||
if not product_id.active and product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant != 'never') in variant_matrix:
|
||||
variants_to_activate.append(product_id)
|
||||
elif product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) not in variant_matrix:
|
||||
elif product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant != 'never') not in variant_matrix:
|
||||
variants_to_unlink.append(product_id)
|
||||
|
||||
if variants_to_activate:
|
||||
@@ -479,6 +500,65 @@ class ProductTemplate(models.Model):
|
||||
|
||||
return True
|
||||
|
||||
def has_dynamic_attributes(self):
|
||||
return self.attribute_line_ids and \
|
||||
any(create_variant == 'dynamic'
|
||||
for create_variant in self.mapped('attribute_line_ids.value_ids.attribute_id.create_variant'))
|
||||
|
||||
@api.multi
|
||||
def get_filtered_variants(self, reference_product=None):
|
||||
"""
|
||||
Will filter availability (excluded) for the product
|
||||
combinations (ex: color: white excludes size: large).
|
||||
|
||||
Will also filter availability (excluded) for the parent
|
||||
product if specified (meaning that this product is a an optionnal or
|
||||
accessory product of the reference_product).
|
||||
Args:
|
||||
reference_product (product.product): The reference product that has
|
||||
the current product as an option or accessory product.
|
||||
Returns:
|
||||
The filtered list of product variants
|
||||
"""
|
||||
self.ensure_one()
|
||||
product_product_attribute_values = self.env['product.product.attribute.value'].search([('product_tmpl_id', '=', self.id)])
|
||||
if reference_product:
|
||||
# append the reference_product if provided
|
||||
product_product_attribute_values |= reference_product.product_attribute_value_ids
|
||||
product_variants = self.product_variant_ids
|
||||
|
||||
for product_product_attribute_value in product_product_attribute_values:
|
||||
# CASE 1: The whole product is excluded when no attribute values are selected in the parent product
|
||||
# returns empty recordset of product.product if so. What is checked is:
|
||||
# If the product_attribute value doesn't belong to self (i.e. belongs to the reference product)
|
||||
# and self is the excluded product template on the exclusion lines
|
||||
# and the exclusions is on the product without specified product attribute values (i.e. the whole product is excluded)
|
||||
if product_product_attribute_value.product_tmpl_id != self \
|
||||
and self in product_product_attribute_value.exclude_for.mapped('product_tmpl_id') \
|
||||
and any(not exclude_for.value_ids
|
||||
for exclude_for in product_product_attribute_value.exclude_for.filtered(
|
||||
lambda excluded_product_attribute_value: excluded_product_attribute_value.product_tmpl_id == self)):
|
||||
return self.env['product.product']
|
||||
|
||||
# CASE 2: Check if some of the product.product.attribute.value of the product are excluded
|
||||
# for this prodcut. A variant could be excluded:
|
||||
# - Either by itself (eg: The office chair with iron legs excludes the color white)
|
||||
# - Or by the reference product (eg: The customizable desk with iron legs excludes the office chair with aluminium legs)
|
||||
for excluded in product_product_attribute_value.exclude_for.filtered(
|
||||
lambda excluded_product_attribute_value: excluded_product_attribute_value.product_tmpl_id == self):
|
||||
product_variants -= product_variants.filtered(
|
||||
lambda variant:
|
||||
# 1/ Check the applicability of the exclusion
|
||||
# i.e: the restriction comes from the parent
|
||||
# OR the restriction is on a product_attribute_value that this variant has, eg:
|
||||
# if the office chair with iron legs excludes the color white, we must check
|
||||
# that this variant has iron legs to check the exclusion
|
||||
(product_product_attribute_value.product_tmpl_id != self or product_product_attribute_value in variant.product_attribute_value_ids) and
|
||||
# 2/ Check the variant has one of the excluded attribute values
|
||||
any(attribute_value in excluded.value_ids for attribute_value in variant.product_attribute_value_ids))
|
||||
|
||||
return product_variants
|
||||
|
||||
@api.model
|
||||
def get_empty_list_help(self, help):
|
||||
self = self.with_context(
|
||||
|
||||
@@ -13,7 +13,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
" * Checked : Product are visible for every company, even if a company is defined on the partner.\n"
|
||||
" * Unchecked : Each company can see only its product (product where company is defined). Product not related to a company are visible for all companies.")
|
||||
group_uom = fields.Boolean("Units of Measure", implied_group='uom.group_uom')
|
||||
group_product_variant = fields.Boolean("Attributes and Variants", implied_group='product.group_product_variant')
|
||||
group_product_variant = fields.Boolean("Variants and Options", implied_group='product.group_product_variant')
|
||||
group_stock_packaging = fields.Boolean('Product Packagings',
|
||||
implied_group='product.group_stock_packaging')
|
||||
group_sale_pricelist = fields.Boolean("Use pricelists to adapt your price per customers",
|
||||
|
||||
@@ -10,7 +10,8 @@ access_product_product_employee,product.product employee,model_product_product,b
|
||||
access_product_price_history_employee,prices.history employee,model_product_price_history,base.group_user,1,0,0,0
|
||||
access_product_attribute,product.attribute,model_product_attribute,base.group_user,1,0,0,0
|
||||
access_product_attribute_value,product.attribute value,model_product_attribute_value,base.group_user,1,0,0,0
|
||||
access_product_attribute_price,product.attribute price,model_product_attribute_price,base.group_user,1,0,0,0
|
||||
access_product_product_attribute,product.product.attribute value,model_product_product_attribute_value,base.group_user,1,0,0,0
|
||||
access_product_attribute_filter_line,product.attribute filter line,model_product_attribute_filter_line,base.group_user,1,0,0,0
|
||||
access_product_attribute_line,product.attribute line,model_product_attribute_line,base.group_user,1,0,0,0
|
||||
access_product_category_manager,product.category.manager,model_product_category,base.group_system,1,1,1,1
|
||||
access_product_template_manager,product.template.manager,model_product_template,base.group_system,1,1,1,1
|
||||
@@ -22,5 +23,6 @@ access_product_product_manager,product.product manager,model_product_product,bas
|
||||
access_product_price_history_manager,prices.history manager,model_product_price_history,base.group_system,1,1,1,1
|
||||
access_product_attribute_manager,product.attribute manager,model_product_attribute,base.group_system,1,1,1,1
|
||||
access_product_attribute_value_manager,product.attribute value manager,model_product_attribute_value,base.group_system,1,1,1,1
|
||||
access_product_attribute_price_manager,product.attribute price manager,model_product_attribute_price,base.group_system,1,1,1,1
|
||||
access_product_product_attribute_manager,product.product.attribute value manager,model_product_product_attribute_value,base.group_system,1,1,1,1
|
||||
access_product_attribute_filter_line_manager,product.attribute filter line manager,model_product_attribute_filter_line,base.group_system,1,1,1,1
|
||||
access_product_attribute_line_manager,product.attribute line manager,model_product_attribute_line,base.group_system,1,1,1,1
|
||||
|
||||
|
@@ -1,4 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_variants, test_pricelist, test_product_pricelist, test_seller
|
||||
from . import test_variants
|
||||
from . import test_pricelist
|
||||
from . import test_product_pricelist
|
||||
from . import test_seller
|
||||
from . import test_product_attribute_value_config
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestProductAttributeValueConfig(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestProductAttributeValueConfig, self).setUp()
|
||||
|
||||
self.computer = self.env['product.template'].create({
|
||||
'name': 'Super Computer'
|
||||
})
|
||||
|
||||
self._add_ssd_attribute()
|
||||
self._add_ram_attribute()
|
||||
self._add_hdd_attribute()
|
||||
|
||||
self.computer.create_variant_ids()
|
||||
|
||||
self.computer_case = self.env['product.template'].create({
|
||||
'name': 'Super Computer Case'
|
||||
})
|
||||
|
||||
self._add_size_attribute()
|
||||
|
||||
self.computer_case.create_variant_ids()
|
||||
|
||||
def test_product_product_attribute_values_creation(self):
|
||||
self.assertEqual(len(self.computer_ssd_attribute_lines.product_value_ids), 2, 'Product attribute values (ssd) were not automatically created')
|
||||
self.assertEqual(len(self.computer_ram_attribute_lines.product_value_ids), 3, 'Product attribute values (ram) were not automatically created')
|
||||
self.assertEqual(len(self.computer_hdd_attribute_lines.product_value_ids), 3, 'Product attribute values (hdd) were not automatically created')
|
||||
self.assertEqual(len(self.computer_case_size_attribute_lines.product_value_ids), 3, 'Product attribute values (size) were not automatically created')
|
||||
|
||||
def test_product_filtered_exclude_for(self):
|
||||
"""
|
||||
Super Computer has 18 variants total (2 ssd * 3 ram * 3 hdd)
|
||||
RAM 16 excudes HDD 1, that matches 2 variants:
|
||||
- SSD 256 RAM 16 HDD 1
|
||||
- SSD 512 RAM 16 HDD 1
|
||||
|
||||
=> There has to be 16 variants left when filtered
|
||||
"""
|
||||
self._add_ram_exclude_for()
|
||||
self.assertEqual(len(self.computer.get_filtered_variants()), 16)
|
||||
self.assertFalse(self._get_variant_for_attribute_values(self.computer, [self.ssd_256, self.ram_16, self.hdd_1]))
|
||||
self.assertFalse(self._get_variant_for_attribute_values(self.computer, [self.ssd_512, self.ram_16, self.hdd_1]))
|
||||
|
||||
def test_children_product_filtered_exclude_for(self):
|
||||
"""
|
||||
Super Computer Case has 3 variants total (3 size)
|
||||
Reference product Computer with HDD 4 excludes Size M
|
||||
The following variant will be excluded:
|
||||
- Size M
|
||||
|
||||
=> There has to be 2 variants left when filtered
|
||||
"""
|
||||
|
||||
self._add_hdd_excludes_computer_case()
|
||||
hdd_4_variant = self.computer.product_variant_ids.filtered(
|
||||
lambda variant: self.hdd_4 in variant.product_attribute_value_ids.mapped('product_attribute_value_id'))[0]
|
||||
self.assertEqual(len(self.computer_case.get_filtered_variants(hdd_4_variant)), 2)
|
||||
self.assertFalse(self._get_variant_for_attribute_values(self.computer_case, [self.size_m], hdd_4_variant))
|
||||
|
||||
def _add_ssd_attribute(self):
|
||||
self.ssd_attribute = self.env['product.attribute'].create({'name': 'Memory'})
|
||||
self.ssd_256 = self.env['product.attribute.value'].create({
|
||||
'name': '256 GB',
|
||||
'attribute_id': self.ssd_attribute.id
|
||||
})
|
||||
self.ssd_512 = self.env['product.attribute.value'].create({
|
||||
'name': '512 GB',
|
||||
'attribute_id': self.ssd_attribute.id
|
||||
})
|
||||
self.computer_ssd_attribute_lines = self.env['product.attribute.line'].create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'attribute_id': self.ssd_attribute.id,
|
||||
'value_ids': [(6, 0, [self.ssd_256.id, self.ssd_512.id])]
|
||||
})
|
||||
|
||||
def _add_ram_attribute(self):
|
||||
self.ram_attribute = self.env['product.attribute'].create({'name': 'RAM'})
|
||||
self.ram_8 = self.env['product.attribute.value'].create({
|
||||
'name': '8 GB',
|
||||
'attribute_id': self.ram_attribute.id
|
||||
})
|
||||
self.ram_16 = self.env['product.attribute.value'].create({
|
||||
'name': '16 GB',
|
||||
'attribute_id': self.ram_attribute.id
|
||||
})
|
||||
self.ram_32 = self.env['product.attribute.value'].create({
|
||||
'name': '32 GB',
|
||||
'attribute_id': self.ram_attribute.id
|
||||
})
|
||||
self.computer_ram_attribute_lines = self.env['product.attribute.line'].create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'attribute_id': self.ram_attribute.id,
|
||||
'value_ids': [(6, 0, [self.ram_8.id, self.ram_16.id, self.ram_32.id])]
|
||||
})
|
||||
|
||||
def _add_hdd_attribute(self):
|
||||
self.hdd_attribute = self.env['product.attribute'].create({'name': 'HDD'})
|
||||
self.hdd_1 = self.env['product.attribute.value'].create({
|
||||
'name': '1 To',
|
||||
'attribute_id': self.hdd_attribute.id
|
||||
})
|
||||
self.hdd_2 = self.env['product.attribute.value'].create({
|
||||
'name': '2 To',
|
||||
'attribute_id': self.hdd_attribute.id
|
||||
})
|
||||
self.hdd_4 = self.env['product.attribute.value'].create({
|
||||
'name': '4 To',
|
||||
'attribute_id': self.hdd_attribute.id
|
||||
})
|
||||
self.computer_hdd_attribute_lines = self.env['product.attribute.line'].create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'attribute_id': self.hdd_attribute.id,
|
||||
'value_ids': [(6, 0, [self.hdd_1.id, self.hdd_2.id, self.hdd_4.id])]
|
||||
})
|
||||
|
||||
def _add_ram_exclude_for(self):
|
||||
self.ram_16_excludes_hdd_1 = self.env['product.attribute.filter.line'].create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'value_ids': [(6, 0, [self._get_product_value_id(self.computer_hdd_attribute_lines, self.hdd_1).id])]
|
||||
})
|
||||
self._get_product_value_id(self.computer_ram_attribute_lines, self.ram_16).update({
|
||||
'exclude_for': [(6, 0, [self.ram_16_excludes_hdd_1.id])]
|
||||
})
|
||||
|
||||
def _add_size_attribute(self):
|
||||
self.size_attribute = self.env['product.attribute'].create({'name': 'Size'})
|
||||
self.size_m = self.env['product.attribute.value'].create({
|
||||
'name': 'M',
|
||||
'attribute_id': self.size_attribute.id
|
||||
})
|
||||
self.size_l = self.env['product.attribute.value'].create({
|
||||
'name': 'L',
|
||||
'attribute_id': self.size_attribute.id
|
||||
})
|
||||
self.size_xl = self.env['product.attribute.value'].create({
|
||||
'name': 'XL',
|
||||
'attribute_id': self.size_attribute.id
|
||||
})
|
||||
self.computer_case_size_attribute_lines = self.env['product.attribute.line'].create({
|
||||
'product_tmpl_id': self.computer_case.id,
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [(6, 0, [self.size_m.id, self.size_l.id, self.size_xl.id])]
|
||||
})
|
||||
|
||||
def _add_hdd_excludes_computer_case(self):
|
||||
self.hdd_4_excludes_computer_case_m = self.env['product.attribute.filter.line'].create({
|
||||
'product_tmpl_id': self.computer_case.id,
|
||||
'value_ids': [(6, 0, [self._get_product_value_id(self.computer_case_size_attribute_lines, self.size_m).id])]
|
||||
})
|
||||
self._get_product_value_id(self.computer_hdd_attribute_lines, self.hdd_4).update({
|
||||
'exclude_for': [(6, 0, [self.hdd_4_excludes_computer_case_m.id])]
|
||||
})
|
||||
|
||||
def _get_product_value_id(self, product_attribute_lines, product_attribute_value):
|
||||
return product_attribute_lines.product_value_ids.filtered(
|
||||
lambda product_value_id: product_value_id.product_attribute_value_id == product_attribute_value)[0]
|
||||
|
||||
def _get_variant_for_attribute_values(self, product, attribute_values, reference_product=None):
|
||||
return product.get_filtered_variants(reference_product).filtered(
|
||||
lambda variant:
|
||||
all(attribute_value in variant.attribute_value_ids for attribute_value in attribute_values))
|
||||
@@ -169,7 +169,7 @@ class TestVariantsNoCreate(common.TestProductCommon):
|
||||
super(TestVariantsNoCreate, self).setUp()
|
||||
self.size = self.env['product.attribute'].create({
|
||||
'name': 'Size',
|
||||
'create_variant': False,
|
||||
'create_variant': 'never',
|
||||
'value_ids': [(0, 0, {'name': 'S'}), (0, 0, {'name': 'M'}), (0, 0, {'name': 'L'})],
|
||||
})
|
||||
self.size_S = self.size.value_ids[0]
|
||||
|
||||
@@ -1,96 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="product_attribute_value_view_tree" model="ir.ui.view">
|
||||
<field name="name">product.attribute.value.view.tree</field>
|
||||
<field name="model">product.attribute.value</field>
|
||||
<record id="product_product_attribute_value_view_tree" model="ir.ui.view">
|
||||
<field name="name">product.product.attribute.value.view.tree</field>
|
||||
<field name="model">product.product.attribute.value</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Attributes" editable="top">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<tree string="Attributes" create="0" delete="0">
|
||||
<field name="attribute_id"/>
|
||||
<field name="name"/>
|
||||
<field name="price_extra"/>
|
||||
<field name="price_extra" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_attribute_value_view_form" model="ir.ui.view">
|
||||
<field name="name">product.product.attribute.value.view.form.</field>
|
||||
<field name="model">product.product.attribute.value</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Product Attribute" create="0" delete="0">
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="price_extra" />
|
||||
<field name="exclude_for" widget="one2many_list" mode="tree">
|
||||
<tree editable="top">
|
||||
<field name="product_tmpl_id" />
|
||||
<field name="value_ids" widget="many2many_tags" options="{'no_create': true}" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_value_action" model="ir.actions.act_window">
|
||||
<field name="name">Variant Values</field>
|
||||
<field name="name">Product Variant Values</field>
|
||||
<field name="res_model">product.product.attribute.value</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('product_tmpl_id', '=', active_id)]</field>
|
||||
<field name="view_ids"
|
||||
eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('product.product_product_attribute_value_view_tree')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('product.product_product_attribute_value_view_form')})]" />
|
||||
<field name="context">{
|
||||
'default_product_tmpl_id': active_id
|
||||
}</field>
|
||||
</record>
|
||||
|
||||
<record id="attribute_tree_view" model="ir.ui.view">
|
||||
<field name="name">product.attribute.tree</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Variant Values" editable="top">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="create_variant"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_view_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.form</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Product Attribute">
|
||||
<group>
|
||||
<group name="main_fields">
|
||||
<label for="name" string="Attribute Name"/>
|
||||
<field name="name" nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group name="values_ids">
|
||||
<label for="value_ids" string="Attribute Values"/>
|
||||
<field name="value_ids" widget="one2many_list" nolabel="1">
|
||||
<tree string="Values" editable="bottom">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
<form string="Values">
|
||||
<field name="name"/>
|
||||
</form>
|
||||
</field>
|
||||
<field name="create_variant"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="attribute_action" model="ir.actions.act_window">
|
||||
<field name="name">Attributes</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">product.attribute</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
|
||||
<record id="variants_tree_view" model="ir.ui.view">
|
||||
<field name="name">product.attribute.value.tree</field>
|
||||
<field name="model">product.attribute.value</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Variant Values" editable="top">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="attribute_id"/>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="variants_action" model="ir.actions.act_window">
|
||||
<field name="name">Attribute Values</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">product.attribute.value</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="product_attribute_value_view_tree"/>
|
||||
<field name="domain">[('product_ids.product_tmpl_id', '=', active_id)]</field>
|
||||
<field name="context">{'default_product_tmpl_id': active_id}</field>
|
||||
</record>
|
||||
|
||||
<record id="attribute_tree_view" model="ir.ui.view">
|
||||
<field name="name">product.attribute.tree</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Variant Values" editable="top">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="create_variant"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_view_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.form</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Product Attribute">
|
||||
<group>
|
||||
<group name="main_fields">
|
||||
<label for="name" string="Attribute Name"/>
|
||||
<field name="name" nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group name="values_ids">
|
||||
<label for="value_ids" string="Attribute Values"/>
|
||||
<field name="value_ids" widget="one2many_list" nolabel="1">
|
||||
<tree string="Values" editable="bottom">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
<form string="Values">
|
||||
<field name="name"/>
|
||||
</form>
|
||||
</field>
|
||||
<field name="create_variant"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="attribute_action" model="ir.actions.act_window">
|
||||
<field name="name">Attributes</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">product.attribute</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
|
||||
<record id="variants_tree_view" model="ir.ui.view">
|
||||
<field name="name">product.attribute.value.tree</field>
|
||||
<field name="model">product.attribute.value</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Variant Values" editable="top">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="attribute_id"/>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="variants_action" model="ir.actions.act_window">
|
||||
<field name="name">Attribute Values</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">product.attribute.value</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_line_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.line.form</field>
|
||||
<field name="model">product.attribute.line</field>
|
||||
|
||||
@@ -9,14 +9,15 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Product">
|
||||
<header>
|
||||
<button string="Variant Prices" type="action"
|
||||
<button string="Configure Variants" type="action"
|
||||
name="%(product_attribute_value_action)d"
|
||||
attrs="{'invisible': [('product_variant_count', '<=', 1)]}"
|
||||
attrs="{'invisible': [('attribute_line_ids', '=', False)]}"
|
||||
groups="product.group_product_variant"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<field name='product_variant_count' invisible='1'/>
|
||||
<field name='is_product_variant' invisible='1'/>
|
||||
<field name='attribute_line_ids' invisible='1'/>
|
||||
<field name="id" invisible="True"/>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
|
||||
@@ -28,9 +28,12 @@ This module contains all the common features of Sales Management and eCommerce.
|
||||
'views/mail_activity_views.xml',
|
||||
'views/assets.xml',
|
||||
'views/sale_portal_templates.xml',
|
||||
'views/sale_product_configurator_templates.xml',
|
||||
'views/sale_onboarding_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/payment_views.xml',
|
||||
'views/product_attribute_views.xml',
|
||||
'wizard/sale_product_configurator_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/sale_demo.xml',
|
||||
@@ -38,5 +41,5 @@ This module contains all the common features of Sales Management and eCommerce.
|
||||
],
|
||||
'uninstall_hook': "uninstall_hook",
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'auto_install': False
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import onboarding
|
||||
from . import portal
|
||||
from . import product_configurator
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http, fields
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class ProductConfiguratorController(http.Controller):
|
||||
@http.route(['/product_configurator/configure'], type='json', auth="user", methods=['POST'])
|
||||
def configure(self, product_id, pricelist_id, **kw):
|
||||
product_template = request.env['product.template'].browse(int(product_id))
|
||||
to_currency = product_template.currency_id
|
||||
pricelist = self._get_pricelist(pricelist_id)
|
||||
if pricelist:
|
||||
product_template = product_template.with_context(pricelist=pricelist.id, partner=request.env.user.partner_id)
|
||||
to_currency = pricelist.currency_id
|
||||
|
||||
return request.env['ir.ui.view'].render_template("sale.product_configurator_configure", {
|
||||
'product': product_template,
|
||||
'to_currency': to_currency,
|
||||
'pricelist': pricelist,
|
||||
'get_attribute_exclusions': self._get_attribute_exclusions
|
||||
})
|
||||
|
||||
@http.route(['/product_configurator/show_optional_products'], type='json', auth="user", methods=['POST'])
|
||||
def show_optional_products(self, product_id, variant_values, pricelist_id, **kw):
|
||||
return self._show_optional_products(product_id, variant_values, self._get_pricelist(pricelist_id), False, **kw)
|
||||
|
||||
@http.route(['/product_configurator/optional_product_items'], type='json', auth="user", methods=['POST'])
|
||||
def optional_product_items(self, product_id, pricelist_id, **kw):
|
||||
return self._optional_product_items(product_id, self._get_pricelist(pricelist_id), **kw)
|
||||
|
||||
@http.route(['/product_configurator/get_combination_info'], type='json', auth="user", methods=['POST'])
|
||||
def get_combination_info(self, product_template_id, product_id, combination, add_qty, pricelist_id, **kw):
|
||||
return self._get_combination_info(product_template_id, product_id, combination, add_qty, self._get_pricelist(pricelist_id))
|
||||
|
||||
def _optional_product_items(self, product_id, pricelist, **kw):
|
||||
product = request.env['product.product'].with_context(self._get_product_context(pricelist, **kw)).browse(int(product_id))
|
||||
to_currency = product.currency_id
|
||||
if pricelist:
|
||||
to_currency = pricelist.currency_id
|
||||
|
||||
return request.env['ir.ui.view'].render_template("sale.optional_product_items", {
|
||||
'product': product,
|
||||
'reference_product': product,
|
||||
'pricelist': pricelist,
|
||||
'to_currency': to_currency,
|
||||
'get_attribute_exclusions': self._get_attribute_exclusions,
|
||||
})
|
||||
|
||||
def _show_optional_products(self, product_id, variant_values, pricelist, handle_stock, **kw):
|
||||
product = request.env['product.product'].browse(int(product_id))
|
||||
from_currency = request.env.user.company_id.currency_id
|
||||
to_currency = pricelist.currency_id if pricelist else product.currency_id
|
||||
company = request.env['res.company'].browse(request.env.context.get('company_id')) or request.env['res.users']._get_company()
|
||||
date = request.env.context.get('date') or fields.Date.today()
|
||||
compute_currency = lambda price: from_currency._convert(price, to_currency, company, date)
|
||||
|
||||
product = product.with_context(self._get_product_context(pricelist, **kw))
|
||||
|
||||
has_optional_products = False
|
||||
for optional_product in product.optional_product_ids:
|
||||
if optional_product.get_filtered_variants(product):
|
||||
has_optional_products = True
|
||||
break
|
||||
|
||||
if not has_optional_products:
|
||||
return False
|
||||
else:
|
||||
return request.env['ir.ui.view'].render_template("sale.optional_products_modal", {
|
||||
'product': product,
|
||||
'reference_product': product,
|
||||
'variant_values': variant_values,
|
||||
'pricelist': pricelist,
|
||||
'compute_currency': compute_currency,
|
||||
'to_currency': to_currency,
|
||||
'handle_stock': handle_stock,
|
||||
'get_attribute_exclusions': self._get_attribute_exclusions,
|
||||
})
|
||||
|
||||
def _get_attribute_exclusions(self, product, reference_product=None):
|
||||
""" list of attribute exclusions of a product
|
||||
|
||||
Args:
|
||||
- product (product.template): The base product template
|
||||
- reference_product (product.product): The reference product from which 'product' is an optional or accessory product
|
||||
|
||||
:return: dict of exclusions
|
||||
exclusions.exclusions: exclusions within this product
|
||||
exclusions.parent_exclusions: exclusions coming from the reference_product
|
||||
"""
|
||||
|
||||
product_attribute_values = request.env['product.product.attribute.value'].search([
|
||||
('product_tmpl_id', '=', product.id),
|
||||
('product_attribute_value_id', 'in', product.attribute_line_ids.mapped('value_ids').ids),
|
||||
])
|
||||
|
||||
# array of all the excluded value_ids of all the filter lines for this product
|
||||
mapped_exclusions = {
|
||||
product_attribute_value.id: [
|
||||
value_id
|
||||
for filter_line in product_attribute_value.exclude_for.filtered(
|
||||
lambda filter_line: filter_line.product_tmpl_id == product
|
||||
) for value_id in filter_line.value_ids.ids
|
||||
]
|
||||
for product_attribute_value in product_attribute_values
|
||||
}
|
||||
|
||||
parent_exclusions = []
|
||||
if reference_product:
|
||||
parent_exclusions = [
|
||||
value_id
|
||||
for filter_line in reference_product.mapped('product_attribute_value_ids.exclude_for').filtered(
|
||||
lambda filter_line: filter_line.product_tmpl_id == product
|
||||
) for value_id in filter_line.value_ids.ids]
|
||||
|
||||
return {
|
||||
'exclusions': mapped_exclusions,
|
||||
'parent_exclusions': parent_exclusions
|
||||
}
|
||||
|
||||
def _get_product_context(self, pricelist=None, **kw):
|
||||
product_context = dict(request.context)
|
||||
if pricelist:
|
||||
if not product_context.get('pricelist'):
|
||||
product_context['pricelist'] = pricelist.id
|
||||
product_context.update(kw.get('kwargs', {}).get('context', {}))
|
||||
|
||||
return product_context
|
||||
|
||||
def _get_combination_info(self, product_template_id, product_id, combination, add_qty, pricelist, **kw):
|
||||
product_attribute_values = request.env['product.product.attribute.value'].browse(combination)
|
||||
filtered_product_attribute_values = product_attribute_values.filtered(
|
||||
lambda product_attribute_value: product_attribute_value.attribute_id.create_variant != 'never'
|
||||
)
|
||||
context = {
|
||||
'quantity': add_qty,
|
||||
'pricelist': pricelist.id if pricelist else None,
|
||||
'current_attributes_price_extra': [product_attribute_value.price_extra or 0.0 for product_attribute_value in product_attribute_values]
|
||||
}
|
||||
|
||||
product_template = request.env['product.template'].with_context(context).browse(product_template_id)
|
||||
|
||||
Product = request.env['product.product'].with_context(context)
|
||||
if product_id and not combination:
|
||||
product = Product.browse(product_id)
|
||||
else:
|
||||
products = Product.search([
|
||||
('product_tmpl_id', '=', product_template_id)
|
||||
])
|
||||
product = products.filtered(
|
||||
lambda product: all(product_attribute_value in product.product_attribute_value_ids
|
||||
for product_attribute_value in filtered_product_attribute_values)
|
||||
)
|
||||
|
||||
product_id = None
|
||||
list_price = product_template.price_compute('list_price')[product_template.id]
|
||||
price = product_template.price or list_price
|
||||
if(product):
|
||||
product = product.with_context(
|
||||
no_variant_attributes_price_extra=[product_attribute_value.price_extra or 0.0
|
||||
for product_attribute_value in product_attribute_values.filtered(
|
||||
lambda product_attribute_value: product_attribute_value.attribute_id.create_variant == 'never'
|
||||
)
|
||||
]
|
||||
)
|
||||
product_id = product.id
|
||||
list_price = product.price_compute('list_price')[product.id]
|
||||
price = product.price or list_price
|
||||
|
||||
display_name = [product_template.name]
|
||||
if filtered_product_attribute_values:
|
||||
display_name.append(' (')
|
||||
display_name.append(', '.join(filtered_product_attribute_values.mapped('name')))
|
||||
display_name.append(')')
|
||||
|
||||
if pricelist and pricelist.currency_id != product_template.currency_id:
|
||||
list_price = product_template.currency_id._convert(
|
||||
list_price, pricelist.currency_id,
|
||||
request.env.user.company_id, fields.Date.today()
|
||||
)
|
||||
|
||||
return {
|
||||
'product_id': product_id,
|
||||
'product_template_id': product_template.id,
|
||||
'display_name': ''.join(display_name),
|
||||
'price': price,
|
||||
'list_price': list_price
|
||||
}
|
||||
|
||||
def _get_pricelist(self, pricelist_id):
|
||||
return request.env['product.pricelist'].browse(int(pricelist_id)) if pricelist_id and pricelist_id != '0' else None
|
||||
@@ -691,6 +691,19 @@ Thanks!</field>
|
||||
<field name="price_unit">38.25</field>
|
||||
</record>
|
||||
|
||||
<record id="product.product_attribute_2" model="product.attribute">
|
||||
<field name="type">color</field>
|
||||
</record>
|
||||
<record id="product.product_attribute_3" model="product.attribute">
|
||||
<field name="type">select</field>
|
||||
</record>
|
||||
|
||||
<record id="product.product_attribute_value_3" model="product.attribute.value">
|
||||
<field name="html_color">#FFFFFF</field>
|
||||
</record>
|
||||
<record id="product.product_attribute_value_4" model="product.attribute.value">
|
||||
<field name="html_color">#000000</field>
|
||||
</record>
|
||||
|
||||
<!-- Confirm some Sales Orders-->
|
||||
<function model="sale.order" name="action_confirm" eval="[[ref('portal_sale_order_2')]]"/>
|
||||
@@ -700,5 +713,94 @@ Thanks!</field>
|
||||
<function model="crm.team" name="message_subscribe"
|
||||
eval="[ref('sales_team.team_sales_department'),ref('sales_team.salesteam_website_sales')], [ref('base.partner_root')]"/>
|
||||
|
||||
<record id="product_product_1_product_template" model="product.template">
|
||||
<field name="name">Chair floor protection</field>
|
||||
<field name="categ_id" ref="product.product_category_5"/>
|
||||
<field name="list_price">12.0</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Office chairs can harm your floor: protect it.</field>
|
||||
<field name="image" type="base64" file="sale/static/img/floor_protection-image.png"/>
|
||||
</record>
|
||||
<record id="product.product_product_4_product_template" model="product.template">
|
||||
<field name="optional_product_ids" eval="[(6,0,[ref('product.product_product_11_product_template')])]"/>
|
||||
</record>
|
||||
<record id="product.product_product_11_product_template" model="product.template">
|
||||
<field name="optional_product_ids" eval="[(6,0,[ref('product_product_1_product_template')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_value_7" model="product.attribute.value">
|
||||
<field name="name">Custom</field>
|
||||
<field name="attribute_id" ref="product.product_attribute_1"/>
|
||||
<field name="is_custom">True</field>
|
||||
</record>
|
||||
|
||||
<record id="product.product_attribute_line_1" model="product.attribute.line">
|
||||
<field name="value_ids" eval="[(4,ref('product_attribute_value_7'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_4e" model="product.product">
|
||||
<field name="default_code">DESK0005</field>
|
||||
<field name="product_tmpl_id" ref="product.product_product_4_product_template"/>
|
||||
<field name="attribute_value_ids" eval="[(6,0,[ref('product_attribute_value_7'), ref('product.product_attribute_value_3')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_4f" model="product.product">
|
||||
<field name="default_code">DESK0006</field>
|
||||
<field name="product_tmpl_id" ref="product.product_product_4_product_template"/>
|
||||
<field name="attribute_value_ids" eval="[(6,0,[ref('product_attribute_value_7'), ref('product.product_attribute_value_4')])]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Handle automatically created product.product.attribute.value.
|
||||
Check "product.product_product_attribute_value_1" for more information about this
|
||||
-->
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'sale.product_product_attribute_value_3',
|
||||
'record': obj().env.ref('product.product_attribute_line_1').product_value_ids[0],
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'sale.product_product_attribute_value_4',
|
||||
'record': obj().env.ref('product.product_attribute_line_4').product_value_ids[0],
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'sale.product_product_attribute_value_5',
|
||||
'record': obj().env.ref('product.product_attribute_line_2').product_value_ids[1],
|
||||
'noupdate': True,
|
||||
}]"/>
|
||||
</function>
|
||||
|
||||
<record id="product_attribute_filter_line_1" model="product.attribute.filter.line">
|
||||
<field name="product_tmpl_id" ref="product.product_product_4_product_template" />
|
||||
<field name="value_ids" eval="[(6,0,[ref('sale.product_product_attribute_value_5')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_filter_line_2" model="product.attribute.filter.line">
|
||||
<field name="product_tmpl_id" ref="product.product_product_11_product_template" />
|
||||
<field name="value_ids" eval="[(6,0,[ref('sale.product_product_attribute_value_4')])]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
The "Customizable Desk's Aluminium" attribute value will excude:
|
||||
- The "Customizable Desk's Black" attribute
|
||||
- The "Office Chair's Steel" attribute
|
||||
-->
|
||||
<record id="product.product_product_attribute_value_1" model="product.product.attribute.value">
|
||||
<field name="exclude_for" eval="[(6,0,[ref('sale.product_attribute_filter_line_1') ,ref('sale.product_attribute_filter_line_2')])]" />
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_filter_line_3" model="product.attribute.filter.line">
|
||||
<field name="product_tmpl_id" ref="product.product_product_11_product_template" />
|
||||
<field name="value_ids" eval="[(6,0,[ref('product.product_product_attribute_value_2')])]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
The "Customizable Desk's Steel" attribute value will excude:
|
||||
- The "Office Chair's Aluminium" attribute
|
||||
-->
|
||||
<record id="product_product_attribute_value_3" model="product.product.attribute.value">
|
||||
<field name="exclude_for" eval="[(6,0,[ref('sale.product_attribute_filter_line_3')])]" />
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -6,6 +6,16 @@ from odoo import api, fields, models
|
||||
from odoo.tools.float_utils import float_round
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
optional_product_ids = fields.Many2many(
|
||||
'product.template', 'product_optional_rel', 'src_id', 'dest_id',
|
||||
string='Optional Products', help="Optional Products are suggested "
|
||||
"whenever the customer hits *Add to Cart* (cross-sell strategy, "
|
||||
"e.g. for computers: warranty, software, etc.).")
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
@@ -42,3 +52,41 @@ class ProductProduct(models.Model):
|
||||
|
||||
def _get_invoice_policy(self):
|
||||
return self.invoice_policy
|
||||
|
||||
|
||||
class ProductAttribute(models.Model):
|
||||
_inherit = "product.attribute"
|
||||
|
||||
# YTI FIX ME: PLEASE RENAME ME INTO attribute_type
|
||||
type = fields.Selection([
|
||||
('radio', 'Radio'),
|
||||
('select', 'Select'),
|
||||
('color', 'Color')], default='radio')
|
||||
|
||||
|
||||
class ProductAttributeValue(models.Model):
|
||||
_inherit = "product.attribute.value"
|
||||
|
||||
is_custom = fields.Boolean('Is custom value', help="Allow users to input custom values for this attribute value")
|
||||
html_color = fields.Char(
|
||||
string='HTML Color Index', oldname='color',
|
||||
help="""Here you can set a
|
||||
specific HTML color index (e.g. #ff0000) to display the color if the
|
||||
attribute type is 'Color'.""")
|
||||
|
||||
|
||||
class ProductProductAttributeValue(models.Model):
|
||||
_inherit = "product.product.attribute.value"
|
||||
|
||||
html_color = fields.Char('HTML Color Index', related="product_attribute_value_id.html_color")
|
||||
is_custom = fields.Boolean('Is custom value', related="product_attribute_value_id.is_custom")
|
||||
|
||||
|
||||
class ProductAttributeCustomValue(models.Model):
|
||||
_name = "product.attribute.custom.value"
|
||||
_rec_name = 'custom_value'
|
||||
_description = 'Product Attribute Custom Value'
|
||||
|
||||
attribute_value_id = fields.Many2one('product.attribute.value', string='Attribute')
|
||||
sale_order_line_id = fields.Many2one('sale.order.line', string='Sale order line')
|
||||
custom_value = fields.Char('Custom value')
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.base.models.res_partner import WARNING_MESSAGE, WARNING_HELP
|
||||
from odoo.tools.float_utils import float_round
|
||||
import json
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
@@ -54,6 +55,22 @@ class ProductTemplate(models.Model):
|
||||
}
|
||||
return action
|
||||
|
||||
def create_product_variant(self, product_attribute_value_ids):
|
||||
self.ensure_one()
|
||||
|
||||
attribute_value_ids = \
|
||||
self.env['product.product.attribute.value'] \
|
||||
.browse(json.loads(product_attribute_value_ids)) \
|
||||
.mapped('product_attribute_value_id') \
|
||||
.filtered(lambda attribute_value_id: attribute_value_id.attribute_id.create_variant != 'never')
|
||||
|
||||
product_variant = self.env['product.product'].create({
|
||||
'product_tmpl_id': self.id,
|
||||
'attribute_value_ids': [(6, 0, attribute_value_ids.ids)]
|
||||
})
|
||||
|
||||
return product_variant.id
|
||||
|
||||
@api.onchange('type')
|
||||
def _onchange_type(self):
|
||||
""" Force values to stay consistent with integrity constraints """
|
||||
|
||||
@@ -159,6 +159,7 @@ class SaleOrder(models.Model):
|
||||
partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Delivery address for current sales order.")
|
||||
|
||||
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current sales order.")
|
||||
pricelist_id_id = fields.Integer(string="Pricelist ID", related='pricelist_id.id', readonly=True)
|
||||
currency_id = fields.Many2one("res.currency", related='pricelist_id.currency_id', string="Currency", readonly=True, required=True)
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order.", copy=False, oldname='project_id')
|
||||
|
||||
@@ -928,6 +929,21 @@ class SaleOrderLine(models.Model):
|
||||
else:
|
||||
line.invoice_status = 'no'
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(SaleOrderLine, self).default_get(fields)
|
||||
if self._context.get('product_id'):
|
||||
res['product_id'] = int(self._context.get('product_id'))
|
||||
if self._context.get('quantity'):
|
||||
res['product_uom_qty'] = float(self._context.get('quantity'))
|
||||
if self._context.get('no_variant_attribute_values'):
|
||||
attributes = self._context.get('no_variant_attribute_values')
|
||||
res['product_no_variant_attribute_values'] = [(6, 0, [int(attribute['value']) for attribute in attributes])]
|
||||
if self._context.get('product_custom_variant_values'):
|
||||
values = self._context.get('product_custom_variant_values')
|
||||
res['product_custom_variant_values'] = [(0, 0, value) for value in values]
|
||||
return res
|
||||
|
||||
@api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
|
||||
def _compute_amount(self):
|
||||
"""
|
||||
@@ -1115,6 +1131,10 @@ class SaleOrderLine(models.Model):
|
||||
product_updatable = fields.Boolean(compute='_compute_product_updatable', string='Can Edit Product', readonly=True, default=True)
|
||||
product_uom_qty = fields.Float(string='Ordered Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
|
||||
product_uom = fields.Many2one('uom.uom', string='Unit of Measure')
|
||||
product_custom_variant_values = fields.One2many('product.attribute.custom.value', 'sale_order_line_id', string='User entered custom product attribute values')
|
||||
# M2M holding the values of product.attribute with create_variant field set to 'never'
|
||||
# It allows keeping track of the extra_price associated to those attribute values and add them to the SO line description
|
||||
product_no_variant_attribute_values = fields.Many2many('product.product.attribute.value', string='Product attribute values that do not create variants')
|
||||
# Non-stored related field to allow portal user to see the image of the product he has ordered
|
||||
product_image = fields.Binary('Product Image', related="product_id.image", store=False)
|
||||
|
||||
@@ -1362,9 +1382,18 @@ class SaleOrderLine(models.Model):
|
||||
@api.multi
|
||||
def _get_display_price(self, product):
|
||||
# TO DO: move me in master/saas-16 on sale.order
|
||||
# awa: don't know it it's still the case since we need the "product_no_variant_attribute_values" field now
|
||||
# to be able to compute the full price
|
||||
if self.product_no_variant_attribute_values:
|
||||
product = product.with_context(no_variant_attributes_price_extra=[
|
||||
no_variant_attribute_value.price_extra or 0
|
||||
for no_variant_attribute_value in self.product_no_variant_attribute_values
|
||||
])
|
||||
|
||||
if self.order_id.pricelist_id.discount_policy == 'with_discount':
|
||||
return product.with_context(pricelist=self.order_id.pricelist_id.id).price
|
||||
product_context = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order, uom=self.product_uom.id)
|
||||
|
||||
final_price, rule_id = self.order_id.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
|
||||
base_price, currency = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id)
|
||||
if currency != self.order_id.pricelist_id.currency_id:
|
||||
@@ -1384,7 +1413,7 @@ class SaleOrderLine(models.Model):
|
||||
domain = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
|
||||
if not self.product_uom or (self.product_id.uom_id.id != self.product_uom.id):
|
||||
vals['product_uom'] = self.product_id.uom_id
|
||||
vals['product_uom_qty'] = 1.0
|
||||
vals['product_uom_qty'] = self.product_uom_qty or 1.0
|
||||
|
||||
product = self.product_id.with_context(
|
||||
lang=self.order_id.partner_id.lang,
|
||||
@@ -1410,7 +1439,23 @@ class SaleOrderLine(models.Model):
|
||||
self.product_id = False
|
||||
return result
|
||||
|
||||
vals.update(name=self.get_sale_order_line_multiline_description_sale(product))
|
||||
name = self.get_sale_order_line_multiline_description_sale(product)
|
||||
|
||||
if self.product_custom_variant_values or self.product_no_variant_attribute_values:
|
||||
name += '\n'
|
||||
|
||||
if self.product_custom_variant_values:
|
||||
for product_custom_variant_value in self.product_custom_variant_values:
|
||||
if product_custom_variant_value.custom_value and product_custom_variant_value.custom_value.strip():
|
||||
name += '\n' + product_custom_variant_value.attribute_value_id.name + ': ' + product_custom_variant_value.custom_value.strip()
|
||||
|
||||
if self.product_no_variant_attribute_values:
|
||||
for no_variant_attribute_value in self.product_no_variant_attribute_values.filtered(
|
||||
lambda product_attribute_value: not product_attribute_value.is_custom
|
||||
):
|
||||
name += '\n' + no_variant_attribute_value.attribute_id.name + ': ' + no_variant_attribute_value.name
|
||||
|
||||
vals.update(name=name)
|
||||
|
||||
self._compute_tax_id()
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ access_product_template_sale_manager,product.template salemanager,model_product_
|
||||
access_product_product_sale_manager,product.product salemanager,model_product_product,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_attribute_sale_manager,product.attribute manager,product.model_product_attribute,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_attribute_value_sale_manager,product.attribute manager value,product.model_product_attribute_value,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_attribute_price_sale_manager,product.attribute manager price,product.model_product_attribute_price,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_product_attribute_sale_manager,product.product.attribute manager value,product.model_product_product_attribute_value,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_product_attribute_custom_value,product.attribute.custom value manager,model_product_attribute_custom_value,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_attribute_filter_line_sale_manager,product.attribute manager filter line,product.model_product_attribute_filter_line,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_attribute_line_sale_manager,product.attribute manager line,product.model_product_attribute_line,sales_team.group_sale_manager,1,1,1,1
|
||||
access_account_tax_sale_manager,account.tax sale manager,account.model_account_tax,sales_team.group_sale_salesman,1,0,0,0
|
||||
access_account_journal_sale_manager,account.journal sale manager,account.model_account_journal,sales_team.group_sale_salesman,1,0,0,0
|
||||
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* This adds the possibility to open the product configurator when the control
|
||||
* "Configure a product" is clicked
|
||||
*/
|
||||
odoo.define('sale.ProductConfiguratorEditableListRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var _t = core._t;
|
||||
var ListRenderer = require('web.ListRenderer');
|
||||
|
||||
ListRenderer.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onAddRecord: function (ev) {
|
||||
// we don't want the browser to navigate to a the # url
|
||||
ev.preventDefault();
|
||||
|
||||
// we don't want the click to cause other effects, such as unselecting
|
||||
// the row that we are creating, because it counts as a click on a tr
|
||||
ev.stopPropagation();
|
||||
|
||||
// but we do want to unselect current row
|
||||
var self = this;
|
||||
this.unselectRow().then(function () {
|
||||
var open_product_configurator = false;
|
||||
var context = ev.currentTarget.dataset.context;
|
||||
|
||||
var context_json = false;
|
||||
if(context){
|
||||
context = context.replace(/\'/g, '"');
|
||||
context_json = JSON.parse(context);
|
||||
|
||||
open_product_configurator = context_json.open_product_configurator;
|
||||
}
|
||||
|
||||
if(open_product_configurator){
|
||||
self._rpc({
|
||||
model: 'ir.model.data',
|
||||
method: 'xmlid_to_res_id',
|
||||
kwargs: {xmlid: 'sale.sale_product_configurator_view_form'},
|
||||
}).then(function(res_id) {
|
||||
self.do_action({
|
||||
name: _t('Configure a product'),
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'sale.product.configurator',
|
||||
views: [[res_id, 'form']],
|
||||
target: 'new'
|
||||
}, {
|
||||
on_close: function (products) {
|
||||
if(products && products !== 'special'){
|
||||
self.trigger_up('add_record', {
|
||||
context: products,
|
||||
forceEditable: "bottom" ,
|
||||
allowWarning: true,
|
||||
onSuccess: function(){
|
||||
self.unselectRow();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.trigger_up('add_record', {context: context && [context]}); // TODO write a test, the deferred was not considered
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
odoo.define('sale.ProductConfiguratorFormController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var _t = core._t;
|
||||
var FormController = require('web.FormController');
|
||||
var OptionalProductsModal = require('sale.OptionalProductsModal');
|
||||
|
||||
var ProductConfiguratorFormController = FormController.extend({
|
||||
custom_events: _.extend({}, FormController.prototype.custom_events, {
|
||||
field_changed: '_onFieldChanged'
|
||||
}),
|
||||
className: 'o_product_configurator',
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function (){
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* We need to override the default click behavior for our "Add" button
|
||||
* because there is a possibility that this product has optional products.
|
||||
* If so, we need to display an extra modal to choose the options.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_onButtonClicked: function (event) {
|
||||
if (event.stopPropagation){
|
||||
event.stopPropagation();
|
||||
}
|
||||
var attrs = event.data.attrs;
|
||||
if (attrs.special === 'cancel') {
|
||||
this._super.apply(this, arguments);
|
||||
} else {
|
||||
if (!this.$el
|
||||
.parents('.modal')
|
||||
.find('.o_sale_product_configurator_add')
|
||||
.hasClass('disabled')){
|
||||
this._handleAdd(this.$el);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* This is overridden to allow catching the "select" event on our product template select field.
|
||||
* This will not work anymore if more fields are added to the form.
|
||||
* TODO awa: Find a better way to catch that event.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_onFieldChanged: function (event) {
|
||||
var self = this;
|
||||
|
||||
this.$el.parents('.modal').find('.o_sale_product_configurator_add').removeClass('disabled');
|
||||
|
||||
this._rpc({
|
||||
route: '/product_configurator/configure',
|
||||
params: {
|
||||
product_id: event.data.changes.product_template_id.id,
|
||||
pricelist_id: $('.js_sale_order_pricelist_id').html()
|
||||
}
|
||||
}).then(function (configurator) {
|
||||
self.renderer.renderConfigurator(configurator);
|
||||
});
|
||||
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* When the user adds a product that has optional products, we need to display
|
||||
* a window to allow the user to choose these extra options.
|
||||
*
|
||||
* This will also create the product if it's in "dynamic" mode
|
||||
* (see product_attribute.create_variant)
|
||||
*
|
||||
* @private
|
||||
* @param {$.Element} $modal
|
||||
*/
|
||||
_handleAdd: function ($modal) {
|
||||
var self = this;
|
||||
var productSelector = [
|
||||
'input[type="hidden"][name="product_id"]',
|
||||
'input[type="radio"][name="product_id"]:checked'
|
||||
];
|
||||
|
||||
var productId = parseInt($modal.find(productSelector.join(', ')).first().val(), 10);
|
||||
var productReady = $.Deferred();
|
||||
if (productId){
|
||||
productReady.resolve(productId);
|
||||
} else {
|
||||
productReady = this._rpc({
|
||||
model: 'product.template',
|
||||
method: 'create_product_variant',
|
||||
args: [
|
||||
$modal.find('.product_template_id').val(),
|
||||
JSON.stringify(self.renderer.getSelectedVariantValues($modal))
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
productReady.done(function (productId){
|
||||
$modal.find(productSelector.join(', ')).val(productId);
|
||||
|
||||
var variantValues = self
|
||||
.renderer
|
||||
.getSelectedVariantValues($modal.find('.js_product'));
|
||||
|
||||
var productCustomVariantValues = self
|
||||
.renderer
|
||||
.getCustomVariantValues($modal.find('.js_product'));
|
||||
|
||||
var noVariantAttributeValues = self
|
||||
.renderer
|
||||
.getNoVariantAttributeValues($modal.find('.js_product'));
|
||||
|
||||
self.rootProduct = {
|
||||
product_id: productId,
|
||||
quantity: parseFloat($modal.find('input[name="add_qty"]').val() || 1),
|
||||
variant_values: variantValues,
|
||||
product_custom_variant_values: productCustomVariantValues,
|
||||
no_variant_attribute_values: noVariantAttributeValues
|
||||
};
|
||||
|
||||
self.optionalProductsModal = new OptionalProductsModal($('body'), {
|
||||
rootProduct: self.rootProduct,
|
||||
pricelistId: $('.js_sale_order_pricelist_id').html(),
|
||||
okButtonText: _t('Confirm'),
|
||||
cancelButtonText: _t('Back'),
|
||||
title: _t('Configure')
|
||||
}).open();
|
||||
|
||||
self.optionalProductsModal.on('options_empty', null,
|
||||
self._onModalOptionsEmpty.bind(self));
|
||||
|
||||
self.optionalProductsModal.on('update_quantity', null,
|
||||
self._onOptionsUpdateQuantity.bind(self));
|
||||
|
||||
self.optionalProductsModal.on('confirm', null,
|
||||
self._onModalConfirm.bind(self));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* No optional products found for this product, only add the root product
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onModalOptionsEmpty: function () {
|
||||
this._addProducts([this.rootProduct]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add all selected products
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onModalConfirm: function () {
|
||||
this._addProducts(this.optionalProductsModal.getSelectedProducts());
|
||||
},
|
||||
|
||||
/**
|
||||
* Update product configurator form
|
||||
* when quantity is updated in the optional products window
|
||||
*
|
||||
* @private
|
||||
* @param {integer} quantity
|
||||
*/
|
||||
_onOptionsUpdateQuantity: function (quantity) {
|
||||
this.$el
|
||||
.find('input[name="add_qty"]')
|
||||
.val(quantity)
|
||||
.trigger('change');
|
||||
},
|
||||
|
||||
/**
|
||||
* This triggers the close action for the window and
|
||||
* adds the product as the "infos" parameter.
|
||||
* It will allow the caller (typically the SO line form) of this window
|
||||
* to handle the added products.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} products the list of added products
|
||||
* {integer} products.product_id: the id of the product
|
||||
* {integer} products.quantity: the added quantity for this product
|
||||
* {Array} products.product_custom_variant_values:
|
||||
* see product_configurator_mixin.getCustomVariantValues
|
||||
* {Array} products.no_variant_attribute_values:
|
||||
* see product_configurator_mixin.getNoVariantAttributeValues
|
||||
*/
|
||||
_addProducts: function (products) {
|
||||
this.do_action({type: 'ir.actions.act_window_close', infos: products});
|
||||
}
|
||||
});
|
||||
|
||||
return ProductConfiguratorFormController;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,510 @@
|
||||
odoo.define('sale.ProductConfiguratorMixin', function (require) {
|
||||
'use strict';
|
||||
|
||||
var core = require('web.core');
|
||||
var utils = require('web.utils');
|
||||
var ajax = require('web.ajax');
|
||||
var _t = core._t;
|
||||
|
||||
var ProductConfiguratorMixin = {
|
||||
events: {
|
||||
'click .css_attribute_color input': '_onChangeColorAttribute',
|
||||
'change .main_product:not(.in_cart) input.js_quantity': 'onChangeAddQuantity',
|
||||
'click button.js_add_cart_json': 'onClickAddCartJSON',
|
||||
'change [data-attribute_exclusions]': 'onChangeVariant'
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* When a product is added or when the quantity is changed,
|
||||
* we need to refresh the total price row
|
||||
* TODO awa: add a container context to avoid global selectors ?
|
||||
*
|
||||
*/
|
||||
computePriceTotal: function () {
|
||||
if ($('.js_price_total').length){
|
||||
var price = 0;
|
||||
$('.js_product.in_cart').each(function (){
|
||||
var quantity = parseInt($('input[name="add_qty"]').first().val());
|
||||
price += parseFloat($(this).find('.js_raw_price').html()) * quantity;
|
||||
});
|
||||
|
||||
$('.js_price_total .oe_currency_value').html(
|
||||
this._priceToStr(parseFloat(price))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When a variant is changed, this will check:
|
||||
* - If the selected combination is available or not
|
||||
* - The extra price if applicable
|
||||
* - The display name of the product ("Customizable desk (White, Steel)")
|
||||
* - The new total price
|
||||
* - The need of adding a "custom value" input
|
||||
*
|
||||
* @param {MouseEvent}
|
||||
*/
|
||||
onChangeVariant: function (ev) {
|
||||
var self = this;
|
||||
|
||||
var $component;
|
||||
if ($(ev.currentTarget).closest('form').length > 0){
|
||||
$component = $(ev.currentTarget).closest('form');
|
||||
} else if ($(ev.currentTarget).closest('.oe_optional_products_modal').length > 0){
|
||||
$component = $(ev.currentTarget).closest('.oe_optional_products_modal');
|
||||
} else {
|
||||
$component = $(ev.currentTarget).closest('.o_product_configurator');
|
||||
}
|
||||
var qty = $component.find('input[name="add_qty"]').val();
|
||||
|
||||
var $parent = $(ev.target).closest('.js_product');
|
||||
var combination = this.getSelectedVariantValues($parent);
|
||||
|
||||
self._checkExclusions($parent, combination);
|
||||
|
||||
ajax.jsonRpc(this._getUri('/product_configurator/get_combination_info'), 'call', {
|
||||
product_template_id: parseInt($parent.find('.product_template_id').val()),
|
||||
product_id: parseInt($parent.find('.product_id').val()),
|
||||
combination: combination,
|
||||
add_qty: parseInt(qty),
|
||||
pricelist_id: this.pricelistId
|
||||
}).then(function (combinationData) {
|
||||
self._onChangeCombination(ev, $parent, combinationData);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Will add the "custom value" input for this attribute value if
|
||||
* the attribute value is configured as "custom" (see product_attribute_value.is_custom)
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
handleCustomValues: function ($target){
|
||||
var $variantContainer;
|
||||
var $customInput = false;
|
||||
if ($target.is('input[type=radio]') && $target.is(':checked')) {
|
||||
$variantContainer = $target.closest('ul').closest('li');
|
||||
$customInput = $target;
|
||||
} else if ($target.is('select')){
|
||||
$variantContainer = $target.closest('li');
|
||||
$customInput = $target
|
||||
.find('option[value="' + $target.val() + '"]');
|
||||
}
|
||||
|
||||
if ($variantContainer) {
|
||||
if ($customInput && $customInput.data('is_custom')) {
|
||||
var attributeValueId = $customInput.data('value_id');
|
||||
var attributeValueName = $customInput.data('value_name');
|
||||
|
||||
if ($variantContainer.find('.variant_custom_value').length === 0
|
||||
|| $variantContainer
|
||||
.find('.variant_custom_value')
|
||||
.data('attribute_value_id') !== parseInt(attributeValueId)){
|
||||
$variantContainer.find('.variant_custom_value_label').remove();
|
||||
$variantContainer.find('.variant_custom_value').remove();
|
||||
|
||||
var $label = $('<label>', {
|
||||
html: attributeValueName + ': ',
|
||||
class: 'variant_custom_value_label'
|
||||
});
|
||||
|
||||
var $input = $('<input>', {
|
||||
type: 'text',
|
||||
'data-attribute_value_id': attributeValueId,
|
||||
'data-attribute_value_name': attributeValueName,
|
||||
class: 'variant_custom_value form-control'
|
||||
});
|
||||
$variantContainer.append($label).append($input);
|
||||
}
|
||||
} else {
|
||||
$variantContainer.find('.variant_custom_value_label').remove();
|
||||
$variantContainer.find('.variant_custom_value').remove();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hack to add and remove from cart with json
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickAddCartJSON: function (ev) {
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
var $input = $link.closest('.input-group').find("input");
|
||||
var min = parseFloat($input.data("min") || 0);
|
||||
var max = parseFloat($input.data("max") || Infinity);
|
||||
var quantity = ($link.has(".fa-minus").length ? -1 : 1) + parseFloat($input.val() || 0, 10);
|
||||
var newQty = quantity > min ? (quantity < max ? quantity : max) : min;
|
||||
|
||||
$input.val(newQty).trigger('change');
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* When the quantity is changed, we need to query the new price of the product.
|
||||
* Based on the price list, the price might change when quantity exceeds X
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onChangeAddQuantity: function (ev) {
|
||||
var $parent;
|
||||
|
||||
if ($(ev.currentTarget).closest('.oe_optional_products_modal').length > 0){
|
||||
$parent = $(ev.currentTarget).closest('.oe_optional_products_modal');
|
||||
} else if ($(ev.currentTarget).closest('form').length > 0){
|
||||
$parent = $(ev.currentTarget).closest('form');
|
||||
} else {
|
||||
$parent = $(ev.currentTarget).closest('.o_product_configurator');
|
||||
}
|
||||
|
||||
this.triggerVariantChange($parent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers the price computation and other variant specific changes
|
||||
*
|
||||
* @param {$.Element} $container
|
||||
*/
|
||||
triggerVariantChange: function ($container) {
|
||||
var self = this;
|
||||
$container.find('ul[data-attribute_exclusions]').trigger('change');
|
||||
$container.find('input.js_variant_change:checked, select.js_variant_change').each(function () {
|
||||
self.handleCustomValues($(this));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Will look for user custom attribute values
|
||||
* in the provided container
|
||||
*
|
||||
* @param {$.Element} $container
|
||||
* @returns {Array} array of custom values with the following format
|
||||
* {integer} attribute_value_id
|
||||
* {string} attribute_value_name
|
||||
* {string} custom_value
|
||||
*/
|
||||
getCustomVariantValues: function ($container) {
|
||||
var variantCustomValues = [];
|
||||
$container.find('.variant_custom_value').each(function (){
|
||||
var $variantCustomValueInput = $(this);
|
||||
if ($variantCustomValueInput.length !== 0){
|
||||
variantCustomValues.push({
|
||||
'attribute_value_id': $variantCustomValueInput.data('attribute_value_id'),
|
||||
'attribute_value_name': $variantCustomValueInput.data('attribute_value_name'),
|
||||
'custom_value': $variantCustomValueInput.val(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return variantCustomValues;
|
||||
},
|
||||
|
||||
/**
|
||||
* Will look for attribute values that do not create product variant
|
||||
* (see product_attribute.create_variant "dynamic")
|
||||
*
|
||||
* @param {$.Element} $container
|
||||
* @returns {Array} array of attribute values with the following format
|
||||
* {integer} attribute_value_id
|
||||
* {string} attribute_value_name
|
||||
* {integer} value
|
||||
* {string} attribute_name
|
||||
* {boolean} is_custom
|
||||
*/
|
||||
getNoVariantAttributeValues: function ($container) {
|
||||
var noVariantAttributeValues = [];
|
||||
var variantsValuesSelectors = [
|
||||
'input.never.js_variant_change:checked',
|
||||
'select.never.js_variant_change'
|
||||
];
|
||||
|
||||
$container.find(variantsValuesSelectors.join(',')).each(function (){
|
||||
var $variantValueInput = $(this);
|
||||
|
||||
if ($variantValueInput.is('select')){
|
||||
$variantValueInput = $variantValueInput.find('option[value=' + $variantValueInput.val() + ']');
|
||||
}
|
||||
|
||||
if ($variantValueInput.length !== 0){
|
||||
noVariantAttributeValues.push({
|
||||
'attribute_value_id': $variantValueInput.data('value_id'),
|
||||
'attribute_value_name': $variantValueInput.data('value_name'),
|
||||
'value': $variantValueInput.val(),
|
||||
'attribute_name': $variantValueInput.data('attribute_name'),
|
||||
'is_custom': $variantValueInput.data('is_custom')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return noVariantAttributeValues;
|
||||
},
|
||||
|
||||
/**
|
||||
* Will return the list of selected product.product.attribute.value ids
|
||||
* For the modal, the "main product"'s attribute values are stored in the
|
||||
* "unchanged_value_ids" data
|
||||
*
|
||||
* @param {$.Element} $container the container to look into
|
||||
*/
|
||||
getSelectedVariantValues: function ($container) {
|
||||
var values = [];
|
||||
var unchangedValues = $container
|
||||
.find('div.oe_unchanged_value_ids')
|
||||
.data('unchanged_value_ids') || [];
|
||||
|
||||
var variantsValuesSelectors = [
|
||||
'input.js_variant_change:checked',
|
||||
'select.js_variant_change'
|
||||
];
|
||||
_.each($container.find(variantsValuesSelectors.join(', ')), function (el) {
|
||||
values.push(+$(el).val());
|
||||
});
|
||||
|
||||
return values.concat(unchangedValues);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Will disable attribute value's inputs based on combination exclusions
|
||||
* and will disable the "add" button if the selected combination
|
||||
* is not available
|
||||
*
|
||||
* This will check both the exclusions within the product itself and
|
||||
* the exclusions coming from the parent product (meaning that this product
|
||||
* is an option of the parent product)
|
||||
*
|
||||
* @private
|
||||
* @param {$.Element} $parent the parent container to apply exclusions
|
||||
* @param {Array} combination the selected combination of product attribute values
|
||||
*/
|
||||
_checkExclusions: function ($parent, combination) {
|
||||
var self = this;
|
||||
var combinationData = $parent
|
||||
.find('ul[data-attribute_exclusions]')
|
||||
.data('attribute_exclusions');
|
||||
|
||||
$parent.find('option, input, label').removeClass('css_not_available');
|
||||
|
||||
var disable = false;
|
||||
if (combinationData.exclusions) {
|
||||
_.each(combination, function (combinationValue){
|
||||
if (combinationData.exclusions &&
|
||||
combinationData.exclusions.hasOwnProperty(combinationValue)){
|
||||
// check that the selected combination is in the exclusions
|
||||
_.each(combinationData.exclusions[combinationValue], function (exclusion) {
|
||||
if (!disable && combination.indexOf(exclusion) > -1) {
|
||||
disable = true;
|
||||
}
|
||||
|
||||
self._disableInput($parent, exclusion);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (combinationData.parent_exclusions){
|
||||
_.each(combinationData.parent_exclusions, function (exclusion){
|
||||
if (!disable && combination.indexOf(exclusion) > -1) {
|
||||
disable = true;
|
||||
}
|
||||
self._disableInput($parent, exclusion);
|
||||
});
|
||||
}
|
||||
|
||||
$parent.toggleClass('css_not_available', disable);
|
||||
$parent.find("#add_to_cart").toggleClass('disabled', disable);
|
||||
$parent
|
||||
.parents('.modal')
|
||||
.find('.o_sale_product_configurator_add')
|
||||
.toggleClass('disabled', disable);
|
||||
},
|
||||
|
||||
/**
|
||||
* Will disable the input/option that refers to the passed attributeValueId.
|
||||
* This is used for showing the user that some combinations are not available.
|
||||
*
|
||||
* @private
|
||||
* @param {$.Element} $parent
|
||||
* @param {integer} attributeValueId
|
||||
*/
|
||||
_disableInput: function ($parent, attributeValueId) {
|
||||
var $input = $parent
|
||||
.find('option[value=' + attributeValueId + '], input[value=' + attributeValueId + ']');
|
||||
$input.addClass('css_not_available');
|
||||
$input.closest('label').addClass('css_not_available');
|
||||
},
|
||||
|
||||
/**
|
||||
* @see onChangeVariant
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
* @param {$.Element} $parent
|
||||
* @param {Array} combination
|
||||
*/
|
||||
_onChangeCombination: function (ev, $parent, combination) {
|
||||
var self = this;
|
||||
var $price = $parent.find(".oe_price:first .oe_currency_value");
|
||||
var $default_price = $parent.find(".oe_default_price:first .oe_currency_value");
|
||||
var $optional_price = $parent.find(".oe_optional:first .oe_currency_value");
|
||||
$price.html(self._priceToStr(combination.price));
|
||||
$default_price.html(self._priceToStr(combination.list_price));
|
||||
if (combination.list_price - combination.price > 0.01) {
|
||||
$default_price
|
||||
.closest('.oe_website_sale')
|
||||
.addClass("discount");
|
||||
$optional_price
|
||||
.closest('.oe_optional')
|
||||
.removeClass('d-none')
|
||||
.css('text-decoration', 'line-through');
|
||||
$default_price.parent().removeClass('d-none');
|
||||
} else {
|
||||
$optional_price.closest('.oe_optional').addClass('d-none');
|
||||
$default_price.parent().addClass('d-none');
|
||||
}
|
||||
|
||||
var rootComponentSelectors = [
|
||||
'tr.js_product',
|
||||
'.oe_website_sale',
|
||||
'.o_product_configurator'
|
||||
];
|
||||
|
||||
self._updateProductImage(
|
||||
$parent.closest(rootComponentSelectors.join(', ')),
|
||||
combination.product_id,
|
||||
combination.product_template_id
|
||||
);
|
||||
|
||||
$parent
|
||||
.find('.product_id')
|
||||
.first()
|
||||
.val(combination.product_id || 0)
|
||||
.trigger('change');
|
||||
|
||||
$parent
|
||||
.find('.product_display_name')
|
||||
.first()
|
||||
.val(combination.display_name);
|
||||
|
||||
$parent
|
||||
.find('.js_raw_price')
|
||||
.first()
|
||||
.html(combination.price);
|
||||
|
||||
this.computePriceTotal();
|
||||
this.handleCustomValues($(ev.target));
|
||||
},
|
||||
|
||||
/**
|
||||
* returns the formatted price
|
||||
*
|
||||
* @private
|
||||
* @param {float} price
|
||||
*/
|
||||
_priceToStr: function (price) {
|
||||
var l10n = _t.database.parameters;
|
||||
var precision = 2;
|
||||
|
||||
if ($('.decimal_precision').length) {
|
||||
precision = parseInt($('.decimal_precision').last().data('precision'));
|
||||
}
|
||||
var formatted = _.str.sprintf('%.' + precision + 'f', price).split('.');
|
||||
formatted[0] = utils.insert_thousand_seps(formatted[0]);
|
||||
return formatted.join(l10n.decimal_point);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the product image.
|
||||
* This will use the productId if available or will fallback to the productTemplateId.
|
||||
*
|
||||
* @private
|
||||
* @param {$.Element} $productContainer
|
||||
* @param {integer} product_id
|
||||
* @param {integer} productTemplateId
|
||||
*/
|
||||
_updateProductImage: function ($productContainer, productId, productTemplateId) {
|
||||
var $img;
|
||||
var model = productId ? 'product.product' : 'product.template';
|
||||
var modelId = productId || productTemplateId;
|
||||
var imageSrc = '/web/image?model={0}&id={1}&field=image'
|
||||
.replace("{0}", model)
|
||||
.replace("{1}", modelId);
|
||||
|
||||
if ($productContainer.find('#o-carousel-product').length) {
|
||||
$img = $productContainer.find('img.js_variant_img');
|
||||
$img.attr("src", imageSrc);
|
||||
$img.parent().attr('data-oe-model', model).attr('data-oe-id', modelId)
|
||||
.data('oe-model', model).data('oe-id', modelId);
|
||||
|
||||
var $thumbnail = $productContainer.find('img.js_variant_img_small');
|
||||
if ($thumbnail.length !== 0) { // if only one, thumbnails are not displayed
|
||||
$thumbnail.attr("src", "/web/image/{0}/{1}/image/90x90"
|
||||
.replace('{0}', model)
|
||||
.replace('{1}', modelId));
|
||||
$('.carousel').carousel(0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var imagesSelectors = [
|
||||
'span[data-oe-model^="product."][data-oe-type="image"] img:first',
|
||||
'img.product_detail_img',
|
||||
'span.variant_image img'
|
||||
];
|
||||
|
||||
$img = $productContainer.find(imagesSelectors.join(', '));
|
||||
$img.attr('src', imageSrc);
|
||||
$img.parent()
|
||||
.attr('data-oe-model', model)
|
||||
.attr('data-oe-id', modelId)
|
||||
.data('oe-model', model)
|
||||
.data('oe-id', modelId);
|
||||
}
|
||||
// reset zooming constructs
|
||||
$img.filter('[data-zoom-image]').attr('data-zoom-image', $img.attr('src'));
|
||||
if ($img.data('zoomOdoo') !== undefined) {
|
||||
$img.data('zoomOdoo').isReady = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight selected color
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onChangeColorAttribute: function (ev) {
|
||||
var $parent = $(ev.target).closest('.js_product');
|
||||
$parent.find('.css_attribute_color')
|
||||
.removeClass("active")
|
||||
.filter(':has(input:checked)')
|
||||
.addClass("active");
|
||||
},
|
||||
|
||||
/**
|
||||
* Website behavior is slightly different from backend so we append
|
||||
* "_website" to URLs to lead to a different route
|
||||
*
|
||||
* @private
|
||||
* @param {string} uri The uri to adapt
|
||||
*/
|
||||
_getUri: function (uri) {
|
||||
if (this.isWebsite){
|
||||
return uri + '_website';
|
||||
} else {
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return ProductConfiguratorMixin;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,472 @@
|
||||
odoo.define('sale.OptionalProductsModal', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var Dialog = require('web.Dialog');
|
||||
var ServicesMixin = require('web.ServicesMixin');
|
||||
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
|
||||
var weContext = require('web_editor.context');
|
||||
|
||||
var productNameMap = {};
|
||||
var optionalProductsMap = {};
|
||||
|
||||
var OptionalProductsModal = Dialog.extend(ServicesMixin, ProductConfiguratorMixin, {
|
||||
events: _.extend({}, Dialog.prototype.events, ProductConfiguratorMixin.events, {
|
||||
'click a.js_add, a.js_remove': '_onAddOrRemoveOption',
|
||||
'change .in_cart.main_product input.js_quantity': '_onChangeQuantity'
|
||||
}),
|
||||
/**
|
||||
* Initializes the optional products modal
|
||||
*
|
||||
* If the "isWebsite" param is true, will also disable the following events:
|
||||
* - change [data-attribute_exclusions]
|
||||
* - click button.js_add_cart_json
|
||||
*
|
||||
* This has to be done because those events are already registered at the "website_sale"
|
||||
* component level.
|
||||
* This modal is part of the form that has these events registered and we
|
||||
* want to avoid duplicates.
|
||||
*
|
||||
* @override
|
||||
* @param {$.Element} parent The parent container
|
||||
* @param {Object} params
|
||||
* @param {integer} params.pricelistId
|
||||
* @param {boolean} params.isWebsite If we're on a web shop page, we need some
|
||||
* custom behavior
|
||||
* @param {string} okButtonText The text to apply on the "ok" button, typically
|
||||
* "Add" for the sale order and "Proceed to checkout" on the web shop
|
||||
* @param {string} cancelButtonText same as "params.okButtonText" but
|
||||
* for the cancel button
|
||||
* @param {Object} rootProduct The root product of the optional products window
|
||||
* @param {integer} rootProduct.product_id
|
||||
* @param {integer} rootProduct.quantity
|
||||
* @param {Array} rootProduct.variant_values
|
||||
* @param {Array} rootProduct.product_custom_variant_values
|
||||
* @param {Array} rootProduct.no_variant_attribute_values
|
||||
*/
|
||||
init: function (parent, params) {
|
||||
var self = this;
|
||||
|
||||
this._super(parent, {
|
||||
size: 'large',
|
||||
buttons: [{
|
||||
text: params.okButtonText,
|
||||
click: this._onConfirmButtonClick,
|
||||
classes: 'btn-primary'
|
||||
}, {
|
||||
text: params.cancelButtonText,
|
||||
click: this._onCancelButtonClick
|
||||
}],
|
||||
title: params.title
|
||||
});
|
||||
|
||||
this.rootProduct = params.rootProduct;
|
||||
this.container = parent;
|
||||
this.pricelistId = params.pricelistId;
|
||||
this.isWebsite = params.isWebsite;
|
||||
this.dialogClass = 'oe_optional_products_modal' + (params.isWebsite ? ' oe_website_sale' : '');
|
||||
|
||||
if (this.isWebsite) {
|
||||
delete this.events['change [data-attribute_exclusions]'];
|
||||
delete this.events['click button.js_add_cart_json'];
|
||||
}
|
||||
|
||||
this._opened.then(function () {
|
||||
self.triggerVariantChange(self.$el);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
|
||||
var uri = this._getUri("/product_configurator/show_optional_products");
|
||||
var getModalContent = ajax.jsonRpc(uri, 'call', {
|
||||
product_id: self.rootProduct.product_id,
|
||||
variant_values: self.rootProduct.variant_values,
|
||||
pricelist_id: self.pricelistId,
|
||||
kwargs: {
|
||||
context: _.extend({
|
||||
'quantity': self.rootProduct.quantity
|
||||
}, weContext.get()),
|
||||
}
|
||||
})
|
||||
.then(function (modalContent) {
|
||||
if (modalContent){
|
||||
var $modalContent = $(modalContent);
|
||||
$modalContent = self._postProcessContent($modalContent);
|
||||
self.$content = $modalContent;
|
||||
} else {
|
||||
self.trigger('options_empty');
|
||||
self.preventOpening = true;
|
||||
}
|
||||
});
|
||||
|
||||
var parentInit = self._super.apply(self, arguments);
|
||||
return $.when(getModalContent, parentInit);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is overridden to append the modal to the provided container (see init("parent")).
|
||||
* We need this to have the modal contained in the web shop product form.
|
||||
* The additional products data will then be contained in the form and sent on submit.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
open: function (options) {
|
||||
$('.tooltip').remove(); // remove open tooltip if any to prevent them staying when modal is opened
|
||||
|
||||
var self = this;
|
||||
this.appendTo($('<div/>')).then(function () {
|
||||
if (!self.preventOpening){
|
||||
self.$modal.find(".modal-body").replaceWith(self.$el);
|
||||
self.$modal.attr('open', true);
|
||||
self.$modal.removeAttr("aria-hidden");
|
||||
self.$modal.modal().appendTo(self.container);
|
||||
self._opened.resolve();
|
||||
}
|
||||
});
|
||||
if (options && options.shouldFocusButtons) {
|
||||
self._onFocusControlButton();
|
||||
}
|
||||
|
||||
return self;
|
||||
},
|
||||
/**
|
||||
* Will update quantity input to synchronize with previous window
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
start: function (){
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.$el.find('input[name="add_qty"]').val(this.rootProduct.quantity);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the list of selected products.
|
||||
* The root product is added on top of the list.
|
||||
*
|
||||
* @public
|
||||
* @returns {Array} products
|
||||
* @returns {integer} products.product_id
|
||||
* @returns {integer} products.quantity
|
||||
* @returns {Array} products.product_custom_variant_values
|
||||
* @returns {Array} products.no_variant_attribute_values
|
||||
*/
|
||||
getSelectedProducts: function () {
|
||||
var self = this;
|
||||
var products = [this.rootProduct];
|
||||
this.$modal.find('.js_product.in_cart:not(.main_product)').each(function (){
|
||||
var quantity = 0;
|
||||
var $item = $(this);
|
||||
if ($item.find('input[name="add_qty"]').length){
|
||||
quantity = $item.find('input[name="add_qty"]').val();
|
||||
} else {
|
||||
quantity = parseInt(
|
||||
$item
|
||||
.find('.optional_product_quantity span.add_qty')
|
||||
.html()
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
var productCustomVariantValues = self.getCustomVariantValues($(this));
|
||||
var noVariantAttributeValues = self.getNoVariantAttributeValues($(this));
|
||||
products.push({
|
||||
product_id: parseInt($item.find('input.product_id').val()),
|
||||
quantity: quantity,
|
||||
product_custom_variant_values: productCustomVariantValues,
|
||||
no_variant_attribute_values: noVariantAttributeValues
|
||||
});
|
||||
});
|
||||
|
||||
return products;
|
||||
},
|
||||
|
||||
// ------------------------------------------
|
||||
// Private
|
||||
// ------------------------------------------
|
||||
|
||||
/**
|
||||
* Adds the product image and updates the product description
|
||||
* based on attribute values that are either "no variant" or "custom".
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_postProcessContent: function ($modalContent) {
|
||||
var productId = this.rootProduct.product_id;
|
||||
$modalContent
|
||||
.find('img:first')
|
||||
.attr("src", "/web/image/product.product/" + productId + "/image_medium");
|
||||
|
||||
if (this.rootProduct &&
|
||||
(this.rootProduct.product_custom_variant_values ||
|
||||
this.rootProduct.no_variant_attribute_values)) {
|
||||
var $productDescription = $modalContent
|
||||
.find('.main_product')
|
||||
.find('td.td-product_name div.text-muted.small');
|
||||
var description = $productDescription.html();
|
||||
|
||||
$.each(this.rootProduct.product_custom_variant_values, function (){
|
||||
description += '<br/>' + this.attribute_value_name + ': ' + this.custom_value;
|
||||
});
|
||||
|
||||
$.each(this.rootProduct.no_variant_attribute_values, function (){
|
||||
if (this.is_custom !== 'True'){
|
||||
description += '<br/>' + this.attribute_name + ': ' + this.attribute_value_name;
|
||||
}
|
||||
});
|
||||
|
||||
$productDescription.html(description);
|
||||
}
|
||||
|
||||
return $modalContent;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onConfirmButtonClick: function () {
|
||||
this.trigger('confirm');
|
||||
this.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onCancelButtonClick: function () {
|
||||
this.trigger('back');
|
||||
this.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Will add/remove the option, that includes:
|
||||
* - Moving it to the correct DOM section
|
||||
* and possibly under its parent product
|
||||
* - Hiding attribute values selection and showing the quantity
|
||||
* - Creating the product if it's in "dynamic" mode (see product_attribute.create_variant)
|
||||
* - Updating the description based on custom/no_create attribute values
|
||||
* - Removing optional products if parent product is removed
|
||||
* - Computing the total price
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onAddOrRemoveOption: function (ev){
|
||||
ev.preventDefault();
|
||||
var self = this;
|
||||
var $target = $(ev.currentTarget);
|
||||
var $modal = $target.parents('.oe_optional_products_modal');
|
||||
var $parent = $target.parents('.js_product:first');
|
||||
$parent.find("a.js_add, span.js_remove").toggleClass('d-none');
|
||||
$parent.find("input.js_optional_same_quantity").val($target.hasClass("js_add") ? 1 : 0 );
|
||||
$parent.find(".js_remove");
|
||||
|
||||
var productTemplateId = $parent.find(".product_template_id").val();
|
||||
if ($target.hasClass('js_add')) {
|
||||
self._onAddOption($modal, $parent ,productTemplateId);
|
||||
} else {
|
||||
self._onRemoveOption($modal, $parent ,productTemplateId);
|
||||
}
|
||||
|
||||
self.computePriceTotal();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @see _onAddOrRemoveOption
|
||||
* @param {$.Element} $modal
|
||||
* @param {$.Element} $parent
|
||||
* @param {integer} productTemplateId
|
||||
*/
|
||||
_onAddOption: function ($modal, $parent, productTemplateId) {
|
||||
var self = this;
|
||||
var $main_product = $modal.find('.js_product:first');
|
||||
var $selectOptionsText = $modal.find('.o_select_options');
|
||||
|
||||
// remove attribute values selection and move quantity to the correct table spot (".td-qty"))
|
||||
$parent.addClass('in_cart');
|
||||
$parent.find('.td-qty').addClass('text-center');
|
||||
$parent.find('.td-qty').append($parent.find('.optional_product_quantity'));
|
||||
|
||||
// change product name to display_name (containing attribute values) when it's added to cart
|
||||
// the "simple" name is kept into a map to revert to it if the product is removed from the cart
|
||||
productNameMap[productTemplateId] = $parent.find(".product-name").html();
|
||||
$parent.find(".product-name").html($parent.find(".product_display_name").val());
|
||||
|
||||
var productCustomVariantValues = self.getCustomVariantValues($parent);
|
||||
var noVariantAttributeValues = self.getNoVariantAttributeValues($parent);
|
||||
if (productCustomVariantValues || noVariantAttributeValues) {
|
||||
var $productDescription = $parent
|
||||
.find('td.td-product_name div.float-left');
|
||||
|
||||
var description = '';
|
||||
$.each(productCustomVariantValues, function (){
|
||||
description += '<br/>' + this.attribute_value_name + ': ' + this.custom_value;
|
||||
});
|
||||
|
||||
$.each(noVariantAttributeValues, function (){
|
||||
if (this.is_custom !== 'True'){
|
||||
description += '<br/>' + this.attribute_name + ': ' + this.attribute_value_name;
|
||||
}
|
||||
});
|
||||
|
||||
var $customAttributeValuesDescription = $('<div>', {
|
||||
class: 'custom_attribute_values_description text-muted small',
|
||||
html: description
|
||||
});
|
||||
|
||||
$productDescription.append($customAttributeValuesDescription);
|
||||
}
|
||||
|
||||
// if it's an optional product of an optional product, place it after it's parent
|
||||
var parentProductId = null;
|
||||
for (var productIdKey in optionalProductsMap) {
|
||||
if (optionalProductsMap[productIdKey].indexOf(productTemplateId) !== -1) {
|
||||
parentProductId = productIdKey;
|
||||
break;
|
||||
}
|
||||
|
||||
$('tr:last').after($parent);
|
||||
}
|
||||
|
||||
if (parentProductId) {
|
||||
$modal.find('.product_template_id').filter(function () {
|
||||
return parentProductId === $(this).val();
|
||||
}).parents('.js_product:first').after($parent);
|
||||
} else {
|
||||
// else, place it after the main product
|
||||
$main_product.after($parent);
|
||||
}
|
||||
|
||||
var productId = $parent.find('.product_id').val();
|
||||
var productReady = $.Deferred();
|
||||
if (productId && productId !== '0'){
|
||||
productReady.resolve(productId);
|
||||
} else {
|
||||
productReady = ajax.jsonRpc('/web/dataset/call', 'call', {
|
||||
model: 'product.template',
|
||||
method: 'create_product_variant',
|
||||
args: [
|
||||
productTemplateId,
|
||||
JSON.stringify(self.getSelectedVariantValues($parent))
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
productReady.done(function (productId){
|
||||
$parent.find('.product_id').val(productId);
|
||||
|
||||
ajax.jsonRpc(self._getUri("/product_configurator/optional_product_items"), 'call', {
|
||||
'product_id': productId,
|
||||
'pricelist_id': self.pricelistId
|
||||
}).then(function (addedItem) {
|
||||
var $addedItem = $(addedItem);
|
||||
$modal.find('tr:last').after($addedItem);
|
||||
|
||||
self.$el.find('input[name="add_qty"]').trigger('change');
|
||||
self.triggerVariantChange($addedItem);
|
||||
|
||||
if ($addedItem.find(".product_template_id").length > 0) {
|
||||
// we need to map this product with it's optional products to be able to:
|
||||
// - remove them from the cart if the parent product is removed
|
||||
// - place the optional products under their parent in the interface when they're added to the cart
|
||||
optionalProductsMap[productTemplateId] = $addedItem.find(".product_template_id").map(function () {
|
||||
return $(this).val();
|
||||
}).get();
|
||||
}
|
||||
|
||||
if ($selectOptionsText.nextAll('.js_product').length === 0){
|
||||
// no more optional products to select -> hide the header
|
||||
$selectOptionsText.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @see _onAddOrRemoveOption
|
||||
* @param {$.Element} $modal
|
||||
* @param {$.Element} $parent
|
||||
* @param {integer} productTemplateId
|
||||
*/
|
||||
_onRemoveOption: function ($modal, $parent, productTemplateId){
|
||||
// restore attribute values selection
|
||||
$parent.removeClass('in_cart');
|
||||
$parent.find('.td-qty').removeClass('text-center');
|
||||
$parent.find('.js_remove.d-none').prepend($parent.find('.optional_product_quantity'));
|
||||
$parent.find('.custom_attribute_values_description').remove();
|
||||
|
||||
var $select_options_text = $modal.find('.o_select_options');
|
||||
$select_options_text.show();
|
||||
|
||||
// revert back to original product name and clean the entry
|
||||
$parent.find(".product-name").html(productNameMap[productTemplateId]);
|
||||
delete productNameMap[productTemplateId];
|
||||
|
||||
this._removeOptionOption($modal, productTemplateId);
|
||||
|
||||
$('tr:last').after($parent);
|
||||
},
|
||||
|
||||
/**
|
||||
* If the removed product had optional products, remove them as well
|
||||
*
|
||||
* @private
|
||||
* @param {$.Element} $modal
|
||||
* @param {integer} optionId The removed optional product id
|
||||
*/
|
||||
_removeOptionOption: function ($modal, optionId) {
|
||||
var self = this;
|
||||
if (optionalProductsMap[optionId]) {
|
||||
optionalProductsMap[optionId].forEach(function (productId) {
|
||||
$modal.find('.product_template_id').filter(function () {
|
||||
return productId === $(this).val();
|
||||
}).parents('.js_product:first').remove();
|
||||
|
||||
self._removeOptionOption($modal, productId);
|
||||
});
|
||||
|
||||
delete optionalProductsMap[optionId];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When the quantity of the root product is updated, we need to update
|
||||
* the quantity of all the selected optional products.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onChangeQuantity: function (ev){
|
||||
var $quantity = $(ev.currentTarget);
|
||||
var qty = parseFloat($quantity.val());
|
||||
var $modal = $quantity.parents('.oe_optional_products_modal');
|
||||
if (qty === 1) {
|
||||
$modal.find(".js_items").addClass('d-none').removeClass('add_qty');
|
||||
$modal.find(".js_item").removeClass('d-none').addClass('add_qty');
|
||||
} else {
|
||||
$modal.find(".js_items").removeClass('d-none').addClass('add_qty').text(qty);
|
||||
$modal.find(".js_item").addClass('d-none').removeClass('add_qty');
|
||||
}
|
||||
|
||||
this.rootProduct.quantity = qty;
|
||||
|
||||
if (!this.isWebsite){
|
||||
this.onChangeAddQuantity(ev);
|
||||
}
|
||||
|
||||
this.trigger('update_quantity', qty);
|
||||
}
|
||||
});
|
||||
|
||||
return OptionalProductsModal;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
odoo.define('sale.ProductConfiguratorFormRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var FormRenderer = require('web.FormRenderer');
|
||||
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
|
||||
|
||||
var ProductConfiguratorFormRenderer = FormRenderer.extend(ProductConfiguratorMixin ,{
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function (){
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.pricelistId = $('.js_sale_order_pricelist_id').html();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.append($('<div>', {class: 'configurator_container'}));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Renders the product configurator within the form
|
||||
*
|
||||
* Will also:
|
||||
* - add events handling for variant changes
|
||||
* - trigger variant change to compute the price and other
|
||||
* variant specific changes
|
||||
*
|
||||
* @param {string} configuratorHtml the evaluated template of
|
||||
* the product configurator
|
||||
*/
|
||||
renderConfigurator: function (configuratorHtml) {
|
||||
var $configuratorContainer = this.$('.configurator_container');
|
||||
$configuratorContainer.empty();
|
||||
|
||||
var $configuratorHtml = $(configuratorHtml);
|
||||
$configuratorHtml.appendTo($configuratorContainer);
|
||||
|
||||
this.triggerVariantChange($configuratorContainer);
|
||||
}
|
||||
});
|
||||
|
||||
return ProductConfiguratorFormRenderer;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
odoo.define('sale.ProductConfiguratorFormView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ProductConfiguratorFormController = require('sale.ProductConfiguratorFormController');
|
||||
var ProductConfiguratorFormRenderer = require('sale.ProductConfiguratorFormRenderer');
|
||||
var FormView = require('web.FormView');
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
var ProductConfiguratorFormView = FormView.extend({
|
||||
config: _.extend({}, FormView.prototype.config, {
|
||||
Controller: ProductConfiguratorFormController,
|
||||
Renderer: ProductConfiguratorFormRenderer,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('product_configurator_form', ProductConfiguratorFormView);
|
||||
|
||||
return ProductConfiguratorFormView;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
.css_attribute_color {
|
||||
display: inline-block;
|
||||
border: 1px solid #999999;
|
||||
text-align: center;
|
||||
|
||||
input {
|
||||
margin: 8px;
|
||||
height: 13px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border: 3px ridge #66ee66;
|
||||
}
|
||||
|
||||
&.active input {
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
&.custom_value {
|
||||
background-image: linear-gradient(to bottom right, #FF0000, #FFF200, #1E9600);
|
||||
}
|
||||
}
|
||||
|
||||
.css_not_available_msg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css_not_available.js_product {
|
||||
> *:nth-child(4) > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css_quantity,
|
||||
.product_price {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css_not_available_msg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.css_quantity {
|
||||
max-width: 125px;
|
||||
|
||||
input[name="add_qty"] {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
option.css_not_available {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
label.css_not_available {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
label.css_attribute_color.css_not_available {
|
||||
opacity: 1;
|
||||
background-image: url("/website_sale/static/src/img/redcross.png");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.o_product_configurator {
|
||||
.product_detail_img {
|
||||
max-height: 240px;
|
||||
}
|
||||
|
||||
.variant_custom_value {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.variant_custom_value_label {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.configurator_container {
|
||||
ul ul {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.oe_optional_products_modal {
|
||||
.table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
|
||||
.o_total_row {
|
||||
h5 {
|
||||
padding-top: 0.4rem;
|
||||
}
|
||||
.js_price_total {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal.o_technical_modal .oe_optional_products_modal .btn.js_add_cart_json {
|
||||
padding: 0.075rem 0.75rem;
|
||||
}
|
||||
|
||||
.js_product {
|
||||
&.in_cart {
|
||||
.js_add_cart_variants {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
-webkit-appearance: menulist;
|
||||
-moz-appearance: menulist;
|
||||
appearance: menulist;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.remove-optionnal-item, .add-optionnal-item {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.td-product_name {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.td-product_name {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.td-img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.td-qty {
|
||||
width: 130px;
|
||||
a.input-group-addon {
|
||||
background-color: transparent;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
.td-action {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.td-price,
|
||||
.td-price-total {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.td-img,
|
||||
.td-price-total {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.td-qty {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.td-price {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 476px) {
|
||||
.td-qty {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
#modal_optional_products table thead,
|
||||
.oe_cart table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#modal_optional_products table td.td-img,
|
||||
.oe_cart table td.td-img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_total_row {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.oe_striked_price {
|
||||
text-decoration: line-through;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
odoo.define('sale.product.configurator.tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var FormView = require('web.FormView');
|
||||
var ProductConfiguratorFormView = require('sale.ProductConfiguratorFormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
var ajax = require('web.ajax');
|
||||
var createView = testUtils.createView;
|
||||
|
||||
var getArch = function (){
|
||||
return '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="sale_order_line">' +
|
||||
'<tree editable="top"><control>' +
|
||||
'<create string="Add a product"/>' +
|
||||
'<create string="Configure a product" context="{\'open_product_configurator\': \'true\'}"/>' +
|
||||
'<create string="Add a section" context="{\'default_display_type\': \'line_section\'}"/>' +
|
||||
'<create string="Add a note" context="{\'default_display_type\': \'line_note\'}"/>' +
|
||||
'</control>' +
|
||||
'<field name="product_id"/><field name="quantity"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>';
|
||||
};
|
||||
|
||||
QUnit.module('Product Configurator', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
product_template: {
|
||||
fields: {
|
||||
id: {type: 'integer'}
|
||||
},
|
||||
records: [{
|
||||
id: 42,
|
||||
display_name: "Customizable Desk"
|
||||
}]
|
||||
},
|
||||
product: {
|
||||
fields: {
|
||||
id: {type: 'integer'}
|
||||
}
|
||||
},
|
||||
sale_order: {
|
||||
fields: {
|
||||
id: {type: 'integer'},
|
||||
sale_order_line: {
|
||||
string: 'lines',
|
||||
type: 'one2many',
|
||||
relation: 'sale_order_line'
|
||||
},
|
||||
}
|
||||
},
|
||||
sale_order_line: {
|
||||
fields: {
|
||||
product_id: {
|
||||
string: 'product',
|
||||
type: 'many2one',
|
||||
relation: 'product'
|
||||
},
|
||||
quantity: {type: 'integer'}
|
||||
}
|
||||
},
|
||||
sale_product_configurator: {
|
||||
fields: {
|
||||
product_template_id: {
|
||||
string: 'product',
|
||||
type: 'many2one',
|
||||
relation: 'product_template'
|
||||
}
|
||||
},
|
||||
records: [{
|
||||
product_template_id: 42
|
||||
}]
|
||||
},
|
||||
};
|
||||
}
|
||||
}, function (){
|
||||
QUnit.test('click on "Configure a product" and check for form loading', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'sale_order',
|
||||
data: this.data,
|
||||
arch: getArch(),
|
||||
mockRPC: function (route) {
|
||||
if (route === '/web/dataset/call_kw/ir.model.data/xmlid_to_res_id') {
|
||||
assert.ok(true);
|
||||
return $.Deferred().then(_.constant(1));
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$("a:contains('Configure a product')").length, 1);
|
||||
|
||||
form.$("a:contains('Configure a product')").click();
|
||||
});
|
||||
|
||||
QUnit.test('trigger_up the "add_record" event and checks that rows are correctly added to the list', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'sale_order',
|
||||
data: this.data,
|
||||
arch: getArch()
|
||||
});
|
||||
|
||||
var list = form.renderer.allFieldWidgets[form.handle][0];
|
||||
|
||||
list.trigger_up('add_record', {
|
||||
context: [{product_id: 1, quantity: 2}, {product_id: 2, quantity: 3}],
|
||||
forceEditable: "bottom" ,
|
||||
allowWarning: true
|
||||
});
|
||||
|
||||
assert.strictEqual(list.$("tr.o_data_row").length, 2);
|
||||
});
|
||||
|
||||
QUnit.test('Select a product in the list and check for template loading', function (assert){
|
||||
assert.expect(1);
|
||||
|
||||
var product_configurator_form = createView({
|
||||
View: ProductConfiguratorFormView,
|
||||
model: 'sale_product_configurator',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<form js_class="product_configurator_form">' +
|
||||
'<group>' +
|
||||
'<field name="product_template_id" class="oe_product_configurator_product_template_id" />' +
|
||||
'</group>' +
|
||||
'<footer>' +
|
||||
'<button string="Add" class="btn-primary o_sale_product_configurator_add disabled"/>' +
|
||||
'<button string="Cancel" class="btn-secondary" special="cancel"/>' +
|
||||
'</footer>' +
|
||||
'</form>',
|
||||
mockRPC: function (route) {
|
||||
if (route === '/product_configurator/configure') {
|
||||
assert.ok(true);
|
||||
return $.Deferred().then(_.constant(1));
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
var div = '<div>';
|
||||
var $div = $(div).addClass('js_sale_order_pricelist_id').html(43);
|
||||
product_configurator_form.$('.o_input').after($div);
|
||||
product_configurator_form.$('.o_input').click();
|
||||
$("ul.ui-autocomplete li a:contains('Customizable Desk')").mouseenter().click();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,10 +3,17 @@
|
||||
<template id="assets_backend_inherit_sale" inherit_id="web.assets_backend" name="Sale backend assets">
|
||||
<xpath expr="link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/sale/static/src/scss/sale_onboarding.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/sale/static/src/scss/product_configurator.scss"/>
|
||||
</xpath>
|
||||
<xpath expr="script[last()]" position="after">
|
||||
<script type="text/javascript" src="/sale/static/src/js/sale.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/tour.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/list_editable_renderer.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/product_configurator_mixin.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/product_configurator_renderer.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/product_configurator_controller.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/product_configurator_view.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/product_configurator_modal.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="assets_frontend_inherit_sale" inherit_id="web.assets_frontend" name="Sale frontend assets">
|
||||
@@ -17,4 +24,9 @@
|
||||
<script type="text/javascript" src="/sale/static/src/js/sale_portal_sidebar.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="qunit_suite" name="product_configurator_tests" inherit_id="web.qunit_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="text/javascript" src="/sale/static/tests/product_configurator.test.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Product attribute -->
|
||||
|
||||
<record id="attribute_tree_view" model="ir.ui.view">
|
||||
<field name="name">product.attribute.tree.type</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="inherit_id" ref="product.attribute_tree_view"></field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="type"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_line_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.line.form.inherit</field>
|
||||
<field name="model">product.attribute.line</field>
|
||||
<field name="inherit_id" ref="product.product_attribute_line_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='value_ids']/tree/field[@name='name']" position="after">
|
||||
<field name="html_color"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_view_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.form.inherit</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="inherit_id" ref="product.product_attribute_view_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='main_fields']" position="after">
|
||||
<group>
|
||||
<field name="type"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='value_ids']/tree/field[@name='name']" position="after">
|
||||
<field name="html_color"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_attribute_value_view_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">product.product.attribute.value.view.tree.inherit</field>
|
||||
<field name="model">product.product.attribute.value</field>
|
||||
<field name="inherit_id" ref="product.product_product_attribute_value_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="html_color"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_attribute_value_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">product.product.attribute.value.view.form.inherit</field>
|
||||
<field name="model">product.product.attribute.value</field>
|
||||
<field name="inherit_id" ref="product.product_product_attribute_value_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="html_color" readonly="True" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="variants_tree_view" model="ir.ui.view">
|
||||
<field name="name">variants.template.tree</field>
|
||||
<field name="model">product.attribute.value</field>
|
||||
<field name="inherit_id" ref="product.variants_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="is_custom" groups="product.group_product_variant" />
|
||||
<field name="html_color"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="group_product_variant"/>
|
||||
<div class="text-muted">
|
||||
Set product attributes (e.g. color, size) to sell variants
|
||||
Configure your products with variants and select optional products
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('group_product_variant','=',False)]}">
|
||||
<div class="mt16">
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="optional_products_modal" name="Optional Products">
|
||||
<main class="modal-body">
|
||||
<t t-call="sale.product_configurator_configure_optional_products" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<template id="product_configurator_configure" name="Configure">
|
||||
<div class="js_product main_product">
|
||||
<input type="hidden" class="product_template_id" t-att-value="product.id"/>
|
||||
<input type="hidden" class="product_id" t-attf-name="product_id" t-att-value="product.product_variant_id.id if len(product.product_variant_ids) == 1 else '0'"/>
|
||||
<div class="col-lg-12 text-center mt-5">
|
||||
<div class="col-lg-5 d-inline-block align-top text-left">
|
||||
<span t-field="product.image" t-options='{"widget": "image", "class": "d-block product_detail_img"}'/>
|
||||
</div>
|
||||
<div class="col-lg-1 d-inline-block"></div>
|
||||
<div class="col-lg-5 d-inline-block text-left">
|
||||
<t t-call="sale.variants" />
|
||||
<h2 class="oe_price product_id mt-3" style="white-space: nowrap;"
|
||||
t-att-data-product-id="product.id"
|
||||
t-esc="product.price if product.price else product.lst_price"
|
||||
t-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": to_currency
|
||||
}'/>
|
||||
<div class="css_quantity input-group oe_website_spinner">
|
||||
<div class="input-group-prepend">
|
||||
<button t-attf-href="#" class="btn btn-primary js_add_cart_json d-none d-md-inline-block" aria-label="Remove one" title="Remove one">
|
||||
<i class="fa fa-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input type="text" class="js_quantity form-control quantity" data-min="1" name="add_qty" value="1"/>
|
||||
<div class="input-group-append">
|
||||
<button t-attf-href="#" class="btn btn-primary float_left js_add_cart_json d-none d-md-inline-block" aria-label="Add one" title="Add one">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="product_configurator_configure_optional_products">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="td-img">Product</th>
|
||||
<th></th>
|
||||
<th class="text-center td-qty">Quantity</th>
|
||||
<th class="text-center td-price">Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="js_product in_cart main_product">
|
||||
<input type="hidden" class="product_template_id" t-att-value="product.product_tmpl_id.id"/>
|
||||
<input type="hidden" class="product_id" t-att-value="product.id"/>
|
||||
<td class='td-img'>
|
||||
<span t-field="product.image_medium" t-options='{"widget": "image" }'/>
|
||||
</td>
|
||||
<td class='td-product_name'>
|
||||
<strong t-field="product.display_name"/>
|
||||
<div class="text-muted small">
|
||||
<div t-field="product.description_sale"/>
|
||||
<div class="js_attributes"/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center td-qty">
|
||||
<div class="css_quantity input-group oe_website_spinner">
|
||||
<div class="input-group-prepend">
|
||||
<button t-attf-href="#" class="btn btn-primary js_add_cart_json d-none d-md-inline-block" aria-label="Remove one" title="Remove one">
|
||||
<i class="fa fa-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input type="text" class="js_quantity form-control quantity" data-min="1" name="add_qty" value="1"/>
|
||||
<div class="input-group-append">
|
||||
<button t-attf-href="#" class="btn btn-primary float_left js_add_cart_json d-none d-md-inline-block" aria-label="Add one" title="Add one">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center td-price" name="price">
|
||||
<ul class="d-none js_add_cart_variants" t-att-data-attribute_exclusions="{'exclusions: []'}"></ul>
|
||||
<div class="d-none oe_unchanged_value_ids" t-att-data-unchanged_value_ids="variant_values" ></div>
|
||||
<div t-attf-class="text-danger oe_default_price oe_striked_price {{ (compute_currency(product.lst_price) - product.price) < 0.01 and 'd-none' }}"
|
||||
t-field="product.lst_price"
|
||||
t-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": product.currency_id,
|
||||
"display_currency": to_currency
|
||||
}'/>
|
||||
<span class="oe_price product_id" style="white-space: nowrap;"
|
||||
t-att-data-product-id="product.id"
|
||||
t-esc="product.price"
|
||||
t-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": to_currency
|
||||
}'/>
|
||||
<span class="js_raw_price d-none" t-esc="product.price" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="o_total_row">
|
||||
<td colspan="2"></td>
|
||||
<td class="text-right"><h5>Total:</h5></td>
|
||||
<td class="text-center">
|
||||
<span class="js_price_total font-weight-bold" style="white-space: nowrap;"
|
||||
t-att-data-product-id="product.id"
|
||||
t-esc="product.price"
|
||||
t-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": to_currency
|
||||
}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-if="product.optional_product_ids" class="o_select_options"><td colspan="4"><h4>Available Options:</h4></td></tr>
|
||||
<t t-call="sale.optional_product_items" />
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<template id="optional_product_items">
|
||||
<t t-foreach="product.optional_product_ids" t-as="product">
|
||||
<t t-if="len(product.get_filtered_variants(reference_product)) > 0">
|
||||
<tr class="js_product">
|
||||
<td class="td-img">
|
||||
<input type="hidden" class="product_template_id" t-att-value="product.id"/>
|
||||
<input type="hidden" class="product_id" t-attf-name="optional-product-#{product.id}" t-att-value="product.product_variant_id.id if len(product.product_variant_ids) == 1 else '0'"/>
|
||||
<input type="hidden" class="product_display_name" t-attf-name="optional-productname-#{product.id}" t-att-value="product.product_variant_id.display_name if len(product.product_variant_ids) == 1 else ''"/>
|
||||
<span t-field="product.image_small" t-options='{"widget": "image"}' class="variant_image"/>
|
||||
</td>
|
||||
<td class='td-product_name'>
|
||||
<div class="float-left">
|
||||
<strong class="product-name" t-field="product.name"/>
|
||||
<div class="text-muted small" t-field="product.description_sale"/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="td-qty">
|
||||
<div class="float-right">
|
||||
<t t-call="sale.variants"/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center td-price">
|
||||
<span class="text-danger oe_default_price oe_optional"
|
||||
t-field="product.lst_price"
|
||||
t-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": product.currency_id,
|
||||
"display_currency": to_currency
|
||||
}'/>
|
||||
<span class="oe_price" style="white-space: nowrap;"
|
||||
t-field="product.price"
|
||||
t-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": to_currency
|
||||
}'/>
|
||||
<span class="js_raw_price d-none" t-esc="product.price" />
|
||||
<p class="css_not_available_msg bg-danger" style="padding: 5px;">Option not available</p>
|
||||
|
||||
<input type="hidden" class="js_optional_same_quantity" t-attf-name="optional-add-#{product.id}" value="0"/>
|
||||
<a role="button" href="#" class="js_add"><i class="fa fa-shopping-cart add-optionnal-item"></i></a>
|
||||
<span class="js_remove d-none">
|
||||
<span class="optional_product_quantity"><span class="js_item add_qty">1 </span><span class="js_items d-none">5 </span> <span t-field="product.uom_id.sudo().name"/><br/></span>
|
||||
<a role="button" href="#" class="js_remove"><i class="fa fa-trash-o remove-optionnal-item"></i></a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<template id="variants">
|
||||
<t t-set="attribute_exclusions" t-value="get_attribute_exclusions(product, reference_product)"/>
|
||||
<ul t-attf-class="list-unstyled js_add_cart_variants #{ul_class}" t-att-data-attribute_exclusions="json.dumps(attribute_exclusions)">
|
||||
<t t-foreach="product.attribute_line_ids.sorted(key=lambda x: x.attribute_id.sequence)" t-as="variant_id">
|
||||
<li t-att-data-attribute_id="variant_id.attribute_id.id" t-att-data-attribute_name="variant_id.attribute_id.name">
|
||||
|
||||
<strong t-field="variant_id.attribute_id.name"/>
|
||||
|
||||
<t t-if="variant_id.attribute_id.type == 'select'">
|
||||
<select
|
||||
t-att-data-attribute_id="variant_id.attribute_id.id"
|
||||
t-attf-class="form-control js_variant_change #{variant_id.attribute_id.create_variant}"
|
||||
t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)">
|
||||
<t t-foreach="variant_id.product_value_ids" t-as="value_id">
|
||||
<option t-att-value="value_id.id"
|
||||
t-att-data-value_id="value_id.product_attribute_value_id.id"
|
||||
t-att-data-value_name="value_id.name"
|
||||
t-att-data-attribute_name="value_id.attribute_id.name"
|
||||
t-att-data-is_custom="value_id.is_custom">
|
||||
<span t-field="value_id.name"/>
|
||||
<span t-if="value_id.price_extra">
|
||||
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" class="variant_price_extra" style="white-space: nowrap;" t-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": product.currency_id,
|
||||
"display_currency": to_currency or website.currency_id
|
||||
}'/>
|
||||
</span>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
|
||||
<t t-if="variant_id.attribute_id.type == 'radio'">
|
||||
<ul t-att-data-attribute_id="variant_id.attribute_id.id" class="list-unstyled">
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<t t-foreach="variant_id.product_value_ids" t-as="value_id">
|
||||
<li class="form-group js_attribute_value" style="margin: 0;">
|
||||
<label class="col-form-label" style="margin: 0 20px;">
|
||||
<input type="radio"
|
||||
t-attf-class="js_variant_change #{variant_id.attribute_id.create_variant}"
|
||||
t-att-checked="'checked' if not inc else None"
|
||||
t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)"
|
||||
t-att-value="value_id.id"
|
||||
t-att-data-value_id="value_id.product_attribute_value_id.id"
|
||||
t-att-data-value_name="value_id.name"
|
||||
t-att-data-attribute_name="value_id.attribute_id.name"
|
||||
t-att-data-is_custom="value_id.is_custom"
|
||||
style="vertical-align: top; margin-right: 10px;"/>
|
||||
<span t-field="value_id.name"/>
|
||||
<span class="badge badge-pill" t-if="value_id.price_extra">
|
||||
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" class="variant_price_extra" style="white-space: nowrap;" t-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": product.currency_id,
|
||||
"display_currency": to_currency or website.currency_id
|
||||
}'/>
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
|
||||
<t t-if="variant_id.attribute_id.type == 'color'">
|
||||
<ul t-att-data-attribute_id="variant_id.attribute_id.id" class="list-inline">
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<li t-foreach="variant_id.product_value_ids" t-as="value_id" class="list-inline-item">
|
||||
<label t-attf-style="background-color:#{value_id.html_color or value_id.name if not value_id.is_custom else ''}"
|
||||
t-attf-class="css_attribute_color #{'active' if not inc else ''} #{'custom_value' if value_id.is_custom else ''}">
|
||||
<input type="radio"
|
||||
t-attf-class="js_variant_change #{variant_id.attribute_id.create_variant}"
|
||||
t-att-checked="'checked' if not inc else None"
|
||||
t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)"
|
||||
t-att-value="value_id.id"
|
||||
t-att-title="value_id.name"
|
||||
t-att-data-value_id="value_id.product_attribute_value_id.id"
|
||||
t-att-data-value_name="value_id.name"
|
||||
t-att-data-attribute_name="value_id.attribute_id.name"
|
||||
t-att-data-is_custom="value_id.is_custom"/>
|
||||
</label>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -72,6 +72,24 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_form_view" model="ir.ui.view">
|
||||
<field name="name">product.template.product.website.form</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='sales']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='sale']" position="after">
|
||||
<group name="options" groups="product.group_product_variant">
|
||||
<group string="Options">
|
||||
<field name="optional_product_ids" widget="many2many_tags" options="{'color_field': 'color'}" domain="[('id', '!=', active_id)]" />
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="product_menu_catalog" name="Products" action="product_template_action" parent="sale_menu_root" sequence="4" groups="sales_team.group_sale_salesman"/>
|
||||
<menuitem id="menu_product" name="Product Variants" parent="product_menu_catalog" sequence="2" groups="base.group_no_one" active="False"/>
|
||||
<menuitem action="product_template_action" id="menu_product_template_action" parent="product_menu_catalog" sequence="1" active="False"/>
|
||||
@@ -272,6 +290,7 @@
|
||||
<field name="validity_date" attrs="{'invisible': [('state', 'in', ['sale', 'done'])]}"/>
|
||||
<field name="confirmation_date" attrs="{'invisible': [('state', 'in', ['draft', 'sent', 'cancel'])]}"/>
|
||||
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
|
||||
<field name="pricelist_id_id" invisible="1" class="js_sale_order_pricelist_id" />
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="payment_term_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
@@ -377,6 +396,7 @@
|
||||
>
|
||||
<control>
|
||||
<create string="Add a product"/>
|
||||
<create string="Configure a product" context="{'open_product_configurator': 'true'}" groups="product.group_product_variant"/>
|
||||
<create string="Add a section" context="{'default_display_type': 'line_section'}"/>
|
||||
<create string="Add a note" context="{'default_display_type': 'line_note'}"/>
|
||||
</control>
|
||||
@@ -403,6 +423,8 @@
|
||||
'default_description_sale': name
|
||||
}"
|
||||
/>
|
||||
<field name="product_custom_variant_values" invisible="1" />
|
||||
<field name="product_no_variant_attribute_values" invisible="1" />
|
||||
<field name="name" widget="section_and_note_text"/>
|
||||
<field
|
||||
name="product_uom_qty"
|
||||
@@ -1001,7 +1023,7 @@
|
||||
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='email_template_and_project']" position="before">
|
||||
<group string="Invoicing" name="invoicing">
|
||||
<group string="Invoicing" name="invoicing" invisible="1">
|
||||
<field name="invoice_policy" widget="radio"/>
|
||||
<field name="service_type" widget="radio" invisible="True"/>
|
||||
<field name="hide_expense_policy" invisible="1"/>
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import payment_acquirer_onboarding_wizard
|
||||
from . import sale_make_invoice_advance
|
||||
from . import sale_product_configurator
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class SaleProductConfigurator(models.TransientModel):
|
||||
_name = 'sale.product.configurator'
|
||||
_description = 'Sale Product Configurator'
|
||||
|
||||
product_template_id = fields.Many2one(
|
||||
'product.template', string="Product",
|
||||
required=True, domain=['|', ('attribute_line_ids', '!=', False), ('optional_product_ids', '!=', False)])
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="sale_product_configurator_view_form" model="ir.ui.view">
|
||||
<field name="name">sale.product.configurator.view.form</field>
|
||||
<field name="model">sale.product.configurator</field>
|
||||
<field name="arch" type="xml">
|
||||
<form js_class="product_configurator_form">
|
||||
<group>
|
||||
<field name="product_template_id" class="oe_product_configurator_product_template_id" />
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Add" class="btn-primary o_sale_product_configurator_add disabled"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -47,6 +47,7 @@ The Dashboard for the Sales Manager will include
|
||||
'views/sale_management_views.xml',
|
||||
'views/digest_views.xml',
|
||||
'views/sale_order_views.xml',
|
||||
'views/assets.xml',
|
||||
'report/sale_report_templates.xml',
|
||||
],
|
||||
'demo': [
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
odoo.define('sale.sale_product_configurator_advanced_tour', function (require) {
|
||||
"use strict";
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
tour.register('sale_product_configurator_advanced_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [tour.STEPS.SHOW_APPS_MENU_ITEM, {
|
||||
trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]',
|
||||
edition: 'community'
|
||||
}, {
|
||||
trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]',
|
||||
edition: 'enterprise'
|
||||
}, {
|
||||
trigger: ".o_list_button_add",
|
||||
extra_trigger: ".o_sale_order"
|
||||
}, {
|
||||
trigger: "a:contains('Configure a product')"
|
||||
}, {
|
||||
trigger: '.o_product_configurator .o_input_dropdown input',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'li a:contains("Customizable Desk")',
|
||||
in_modal: false,
|
||||
extra_trigger: 'ul.ui-autocomplete',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'span:contains("Custom")',
|
||||
extra_trigger: '.o_product_configurator',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(1) .variant_custom_value',
|
||||
extra_trigger: '.o_product_configurator',
|
||||
run: 'text Custom 1'
|
||||
}, {
|
||||
trigger: '.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(3) span:contains("PAV9")',
|
||||
extra_trigger: '.o_product_configurator',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(3) .variant_custom_value',
|
||||
extra_trigger: '.o_product_configurator',
|
||||
run: 'text Custom 2'
|
||||
}, {
|
||||
trigger: '.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(4) span:contains("PAV5")',
|
||||
extra_trigger: '.o_product_configurator',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(6) select ',
|
||||
extra_trigger: '.o_product_configurator',
|
||||
run: function (){
|
||||
var inputValue = $('.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(6) option[data-value_name="PAV9"]').val();
|
||||
$('.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(6) select').val(inputValue);
|
||||
$('.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(6) select').trigger('change');
|
||||
}
|
||||
}, {
|
||||
trigger: '.o_product_configurator ul.js_add_cart_variants li[data-attribute_id]:nth-child(6) .variant_custom_value',
|
||||
extra_trigger: '.o_product_configurator',
|
||||
run: 'text Custom 3'
|
||||
}, {
|
||||
trigger: ".o_sale_product_configurator_add",
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.main_product strong:contains("White, Custom, PAV9, PAV5, PAV1")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: function () {} //check
|
||||
}, {
|
||||
trigger: '.main_product div:contains("Custom: Custom 1")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: function () {} //check
|
||||
}, {
|
||||
trigger: '.main_product div:contains("PAV9: Custom 2")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: function () {} //check
|
||||
}, {
|
||||
trigger: '.main_product div:contains("PAV9: Custom 3")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: function () {} //check
|
||||
}, {
|
||||
trigger: '.main_product div:contains("PA5: PAV1")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: function () {} //check
|
||||
}, {
|
||||
trigger: '.main_product div:contains("PA5: PAV1")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: function () {} //check
|
||||
}, {
|
||||
trigger: '.main_product div:contains("PA8: PAV1")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: function () {} //check
|
||||
}, {
|
||||
trigger: 'button span:contains(Confirm)',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("Customizable Desk (White, Custom, PAV9, PAV5, PAV1)")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){} //check
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("Custom: Custom 1")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){} //check
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("PAV9: Custom 2")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){} //check
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("PAV9: Custom 3")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){} //check
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("PA5: PAV1")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){} //check
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("PA5: PAV1")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){} //check
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("PA8: PAV1")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){} //check
|
||||
}]);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
odoo.define('sale.product_configurator_tour', function (require) {
|
||||
"use strict";
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
tour.register('sale_product_configurator_tour', {
|
||||
url: "/web",
|
||||
test: true,
|
||||
}, [tour.STEPS.SHOW_APPS_MENU_ITEM, {
|
||||
trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]',
|
||||
edition: 'community'
|
||||
}, {
|
||||
trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]',
|
||||
edition: 'enterprise'
|
||||
}, {
|
||||
trigger: ".o_list_button_add",
|
||||
extra_trigger: ".o_sale_order"
|
||||
}, {
|
||||
trigger: "a:contains('Configure a product')"
|
||||
}, {
|
||||
trigger: '.o_product_configurator .o_input_dropdown input',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'li a:contains("Customizable Desk")',
|
||||
in_modal: false,
|
||||
extra_trigger: 'ul.ui-autocomplete',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.configurator_container span:contains("Steel")',
|
||||
run: function () {
|
||||
$('input.product_id').change(function () {
|
||||
var request_count = 0;
|
||||
if ($('.o_sale_product_configurator_add').attr('request_count')) {
|
||||
request_count = parseInt($('.o_sale_product_configurator_add').attr('request_count'));
|
||||
}
|
||||
request_count++;
|
||||
$('.o_sale_product_configurator_add').attr('request_count', request_count);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
trigger: '.configurator_container span:contains("Aluminium")',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'span.oe_currency_value:contains("800")',
|
||||
run: function (){} // check updated price
|
||||
}, {
|
||||
trigger: 'input[data-value_name="Black"]'
|
||||
}, {
|
||||
trigger: 'input[data-value_name="White"]'
|
||||
}, {
|
||||
trigger: '.o_sale_product_configurator_add[request_count="3"]',
|
||||
run: function (){} // used to sync with "get_combination_info" completion
|
||||
}, {
|
||||
trigger: '.o_sale_product_configurator_add:not(.disabled)'
|
||||
}, {
|
||||
trigger: 'span:contains("Aluminium")',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.js_product:has(strong:contains(Conference Chair)) .js_add',
|
||||
extra_trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Conference Chair))',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.js_product:has(strong:contains(Chair floor protection)) .js_add',
|
||||
extra_trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Chair floor protection))',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'button span:contains(Confirm)',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: 'click'
|
||||
},
|
||||
// check that 3 products were added to the SO
|
||||
{
|
||||
trigger: 'td.o_data_cell:contains("Customizable Desk (White, Aluminium)")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){}
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("Conference Chair (Aluminium)")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){}
|
||||
}, {
|
||||
trigger: 'td.o_data_cell:contains("Chair floor protection")',
|
||||
extra_trigger: 'div[name="order_line"]',
|
||||
in_modal: false,
|
||||
run: function (){}
|
||||
}]);
|
||||
|
||||
});
|
||||
@@ -6,3 +6,65 @@ class TestUi(odoo.tests.HttpCase):
|
||||
|
||||
def test_01_sale_tour(self):
|
||||
self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_tour.ready", login="admin")
|
||||
|
||||
def test_02_product_configurator(self):
|
||||
# To be able to test the product configurator, admin user must have access to "variants and options" feature, so we give him the right group for that
|
||||
self.env.ref('base.user_admin').write({'groups_id': [(4, self.env.ref('product.group_product_variant').id)]})
|
||||
|
||||
self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_tour.ready", login="admin")
|
||||
|
||||
def test_03_product_configurator_advanced(self):
|
||||
# To be able to test the product configurator, admin user must have access to "variants and options" feature, so we give him the right group for that
|
||||
self.env.ref('base.user_admin').write({'groups_id': [(4, self.env.ref('product.group_product_variant').id)]})
|
||||
|
||||
# Prepare relevant test data
|
||||
# This is not included in demo data to avoid useless noise
|
||||
product_attributes = self.env['product.attribute'].create([{
|
||||
'name': 'PA1',
|
||||
'type': 'radio',
|
||||
'create_variant': 'dynamic'
|
||||
}, {
|
||||
'name': 'PA2',
|
||||
'type': 'radio',
|
||||
'create_variant': 'always'
|
||||
}, {
|
||||
'name': 'PA3',
|
||||
'type': 'radio',
|
||||
'create_variant': 'dynamic'
|
||||
}, {
|
||||
'name': 'PA4',
|
||||
'type': 'select',
|
||||
'create_variant': 'never'
|
||||
}, {
|
||||
'name': 'PA5',
|
||||
'type': 'select',
|
||||
'create_variant': 'never'
|
||||
}, {
|
||||
'name': 'PA7',
|
||||
'type': 'color',
|
||||
'create_variant': 'never'
|
||||
}, {
|
||||
'name': 'PA8',
|
||||
'type': 'radio',
|
||||
'create_variant': 'never'
|
||||
}])
|
||||
|
||||
product_attribute_values = self.env['product.attribute.value'].create([{
|
||||
'name': 'PAV' + str(i),
|
||||
'is_custom': i == 9,
|
||||
'attribute_id': product_attribute.id
|
||||
} for i in range(1, 11) for product_attribute in product_attributes])
|
||||
|
||||
product_attribute_lines = self.env['product.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': self.env.ref("product.product_product_4").id,
|
||||
'value_ids': [(6, 0, product_attribute_values.filtered(
|
||||
lambda product_attribute_value: product_attribute_value.attribute_id == product_attribute
|
||||
).ids)]
|
||||
} for product_attribute in product_attributes])
|
||||
|
||||
self.env.ref("product.product_product_4").update({
|
||||
'attribute_line_ids': [(4, product_attribute_line.id) for product_attribute_line in product_attribute_lines]
|
||||
})
|
||||
|
||||
self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_advanced_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_advanced_tour.ready", login="admin")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="assets_backend_inherit_sale_management" inherit_id="web.assets_backend" name="Sale management backend assets">
|
||||
<xpath expr="script[last()]" position="after">
|
||||
<script type="text/javascript" src="/sale_management/static/tests/product_configurator_test_ui.js"/>
|
||||
<script type="text/javascript" src="/sale_management/static/tests/product_configurator_advanced_test_ui.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -108,6 +108,15 @@
|
||||
<xpath expr="//group[@name='description']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='sale']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='invoicing']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='description']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -46,4 +46,13 @@
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_product_settings" name="Products" sequence="80"
|
||||
parent="sale.menu_sale_config"/>
|
||||
<menuitem id="menu_product_attribute_action"
|
||||
action="product.attribute_action"
|
||||
parent="menu_product_settings" groups="product.group_product_variant" sequence="1"/>
|
||||
<menuitem id="menu_catalog_variants_action"
|
||||
action="product.variants_action"
|
||||
parent="menu_product_settings" groups="product.group_product_variant" sequence="2"/>
|
||||
</odoo>
|
||||
|
||||
@@ -19,7 +19,7 @@ Preferences
|
||||
* Incoterms: International Commercial terms
|
||||
|
||||
""",
|
||||
'depends': ['sale_management', 'stock_account'],
|
||||
'depends': ['sale', 'stock_account'],
|
||||
'data': [
|
||||
'security/sale_stock_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
@@ -262,8 +262,8 @@ class SaleOrderLine(models.Model):
|
||||
if float_compare(product.virtual_available, product_qty, precision_digits=precision) == -1:
|
||||
is_available = self._check_routing()
|
||||
if not is_available:
|
||||
message = _('You plan to sell %s %s but you only have %s %s available in %s warehouse.') % \
|
||||
(self.product_uom_qty, self.product_uom.name, product.virtual_available, product.uom_id.name, self.order_id.warehouse_id.name)
|
||||
message = _('You plan to sell %s %s of %s but you only have %s %s available in %s warehouse.') % \
|
||||
(self.product_uom_qty, self.product_uom.name, self.product_id.name, product.virtual_available, product.uom_id.name, self.order_id.warehouse_id.name)
|
||||
# We check if some products are available in other warehouses.
|
||||
if float_compare(product.virtual_available, self.product_id.virtual_available, precision_digits=precision) == -1:
|
||||
message += _('\nThere are %s %s available across all warehouses.\n\n') % \
|
||||
|
||||
@@ -73,5 +73,6 @@ access_stock_forecast_manager,report.stock.forecast.manager,model_report_stock_f
|
||||
access_stock_scrap_user,stock.scrap.user,model_stock_scrap,stock.group_stock_user,1,1,1,0
|
||||
access_product_attribute_manager,product.attribute manager,product.model_product_attribute,stock.group_stock_manager,1,1,1,1
|
||||
access_product_attribute_value_manager,product.attribute manager value,product.model_product_attribute_value,stock.group_stock_manager,1,1,1,1
|
||||
access_product_attribute_price_manager,product.attribute manager price,product.model_product_attribute_price,stock.group_stock_manager,1,1,1,1
|
||||
access_product_product_attribute_manager,product.product.attribute manager value,product.model_product_product_attribute_value,stock.group_stock_manager,1,1,1,1
|
||||
access_product_attribute_filter_line_manager,product.attribute manager filter line,product.model_product_attribute_filter_line,stock.group_stock_manager,1,1,1,1
|
||||
access_product_attribute_line_manager,product.attribute manager line,product.model_product_attribute_line,stock.group_stock_manager,1,1,1,1
|
||||
|
||||
|
@@ -496,6 +496,7 @@ var AbstractField = Widget.extend({
|
||||
viewType: this.viewType,
|
||||
doNotSetDirty: options && options.doNotSetDirty,
|
||||
notifyChange: !options || options.notifyChange !== false,
|
||||
allowWarning: options && options.allowWarning,
|
||||
onSuccess: def.resolve.bind(def),
|
||||
onFailure: def.reject.bind(def),
|
||||
});
|
||||
|
||||
@@ -1453,6 +1453,15 @@ var FieldOne2Many = FieldX2Many.extend({
|
||||
* @param {OdooEvent|MouseEvent} ev this event comes either from the 'Add
|
||||
* record' link in the list editable renderer, or from the 'Create' button
|
||||
* in the kanban view
|
||||
* @param {Array} ev.data.context additional context for the added records,
|
||||
* if several contexts are provided, multiple records will be added
|
||||
* (form dialog will only use the context at index 0 if provided)
|
||||
* @param {boolean} ev.data.forceEditable this is used to bypass the dialog opening
|
||||
* in case you want to add record(s) to a list
|
||||
* @param {function} ev.data.onSuccess called when the records are correctly created
|
||||
* (not supported by form dialog)
|
||||
* @param {boolean} ev.data.allowWarning defines if the records can be added
|
||||
* to the list even if warnings are triggered (e.g: stock warning for product availability)
|
||||
*/
|
||||
_onAddRecord: function (ev) {
|
||||
var self = this;
|
||||
@@ -1461,7 +1470,7 @@ var FieldOne2Many = FieldX2Many.extend({
|
||||
// we don't want interference with the components upstream.
|
||||
ev.stopPropagation();
|
||||
|
||||
if (this.editable) {
|
||||
if (this.editable || data.forceEditable) {
|
||||
if (!this.activeActions.create) {
|
||||
if (data.onFail) {
|
||||
data.onFail();
|
||||
@@ -1471,15 +1480,21 @@ var FieldOne2Many = FieldX2Many.extend({
|
||||
this.trigger_up('edited_list', { id: this.value.id });
|
||||
this._setValue({
|
||||
operation: 'CREATE',
|
||||
position: this.editable,
|
||||
position: this.editable || data.forceEditable,
|
||||
context: data.context,
|
||||
}, {
|
||||
allowWarning: data.allowWarning
|
||||
}).always(function () {
|
||||
self.creatingRecord = false;
|
||||
}).done(function(){
|
||||
if(data.onSuccess){
|
||||
data.onSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._openFormDialog({
|
||||
context: data.context,
|
||||
context: data.context && data.context[0],
|
||||
on_saved: function (record) {
|
||||
self._setValue({ operation: 'ADD', id: record.id });
|
||||
},
|
||||
|
||||
@@ -1328,34 +1328,54 @@ var BasicModel = AbstractModel.extend({
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.position=top] if the new record should be added
|
||||
* on top or on bottom of the list
|
||||
* @param {Object} [options.context] additional context to be merged before
|
||||
* calling the default_get (eg. to set default values)
|
||||
* @returns {Deferred<string>} resolves to the new record id
|
||||
* @param {Object} [options.[context]] additional context to be merged before
|
||||
* calling the default_get (eg. to set default values).
|
||||
* If several contexts are found, multiple records are added
|
||||
* @param {boolean} [params.allowWarning=false] if true, the default record
|
||||
* operation can complete, even if a warning is raised
|
||||
* @returns {Deferred<[string]>} resolves to the new records ids
|
||||
*/
|
||||
_addX2ManyDefaultRecord: function (list, options) {
|
||||
var self = this;
|
||||
var position = options && options.position || 'top';
|
||||
var additionalContext = options && options.context;
|
||||
var context = this._getContext(list, {additionalContext: additionalContext});
|
||||
var params = {
|
||||
context: context,
|
||||
fields: list.fields,
|
||||
fieldsInfo: list.fieldsInfo,
|
||||
parentID: list.id,
|
||||
position: position,
|
||||
viewType: list.viewType,
|
||||
allowWarning: options && options.allowWarning
|
||||
};
|
||||
return this._makeDefaultRecord(list.model, params).then(function (id) {
|
||||
list._changes.push({operation: 'ADD', id: id, position: position, isNew: true});
|
||||
var record = self.localData[id];
|
||||
list._cache[record.res_id] = id;
|
||||
if (list.orderedResIDs) {
|
||||
var index = list.offset + (position !== 'top' ? list.limit : 0);
|
||||
list.orderedResIDs.splice(index, 0, record.res_id);
|
||||
// list could be a copy of the original one
|
||||
self.localData[list.id].orderedResIDs = list.orderedResIDs;
|
||||
}
|
||||
return id;
|
||||
|
||||
var additionalContexts = options && options.context;
|
||||
var makeDefaultRecords = [];
|
||||
if(additionalContexts){
|
||||
_.each(additionalContexts, function (context) {
|
||||
params.context = self._getContext(list, {additionalContext: context});
|
||||
makeDefaultRecords.push(self._makeDefaultRecord(list.model, params));
|
||||
});
|
||||
} else {
|
||||
params.context = self._getContext(list);
|
||||
makeDefaultRecords.push(self._makeDefaultRecord(list.model, params));
|
||||
}
|
||||
|
||||
return $.when.apply($, makeDefaultRecords).then(function(){
|
||||
var ids = [];
|
||||
_.each(arguments, function(id){
|
||||
ids.push(id);
|
||||
|
||||
list._changes.push({operation: 'ADD', id: id, position: position, isNew: true});
|
||||
var record = self.localData[id];
|
||||
list._cache[record.res_id] = id;
|
||||
if (list.orderedResIDs) {
|
||||
var index = list.offset + (position !== 'top' ? list.limit : 0);
|
||||
list.orderedResIDs.splice(index, 0, record.res_id);
|
||||
// list could be a copy of the original one
|
||||
self.localData[list.id].orderedResIDs = list.orderedResIDs;
|
||||
}
|
||||
});
|
||||
|
||||
return ids;
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -1374,6 +1394,9 @@ var BasicModel = AbstractModel.extend({
|
||||
* it was changed.
|
||||
* @param {string} [options.viewType] current viewType. If not set, we will assume
|
||||
* main viewType from the record
|
||||
* @param {boolean} [params.allowWarning=false] if true, change
|
||||
* operation can complete, even if a warning is raised
|
||||
* (only supported by X2ManyChange)
|
||||
* @returns {Deferred} list of changed fields
|
||||
*/
|
||||
_applyChange: function (recordID, changes, options) {
|
||||
@@ -1395,7 +1418,7 @@ var BasicModel = AbstractModel.extend({
|
||||
for (var fieldName in changes) {
|
||||
field = record.fields[fieldName];
|
||||
if (field.type === 'one2many' || field.type === 'many2many') {
|
||||
defs.push(this._applyX2ManyChange(record, fieldName, changes[fieldName], options.viewType));
|
||||
defs.push(this._applyX2ManyChange(record, fieldName, changes[fieldName], options.viewType, options.allowWarning));
|
||||
} else if (field.type === 'many2one' || field.type === 'reference') {
|
||||
defs.push(this._applyX2OneChange(record, fieldName, changes[fieldName]));
|
||||
} else {
|
||||
@@ -1722,9 +1745,12 @@ var BasicModel = AbstractModel.extend({
|
||||
* key. For example, it looks like {operation: ADD, id: 'partner_1'}
|
||||
* @param {string} [viewType] current viewType. If not set, we will assume
|
||||
* main viewType from the record
|
||||
* @param {boolean} [params.allowWarning=false] if true, change
|
||||
* operation can complete, even if a warning is raised
|
||||
* (only supported by the 'CREATE' command.operation)
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_applyX2ManyChange: function (record, fieldName, command, viewType) {
|
||||
_applyX2ManyChange: function (record, fieldName, command, viewType, allowWarning) {
|
||||
if (command.operation === 'TRIGGER_ONCHANGE') {
|
||||
// the purpose of this operation is to trigger an onchange RPC, so
|
||||
// there is no need to apply any change on the record (the changes
|
||||
@@ -1819,16 +1845,19 @@ var BasicModel = AbstractModel.extend({
|
||||
var options = {
|
||||
context: command.context,
|
||||
position: command.position,
|
||||
allowWarning: allowWarning
|
||||
};
|
||||
def = this._addX2ManyDefaultRecord(list, options).then(function (id) {
|
||||
if (command.position === 'bottom' && list.orderedResIDs && list.orderedResIDs.length >= list.limit) {
|
||||
list.tempLimitIncrement = (list.tempLimitIncrement || 0) + 1;
|
||||
list.limit += 1;
|
||||
}
|
||||
// FIXME: hack for lunch widget, which does useless default_get and onchange
|
||||
if (command.data) {
|
||||
return self._applyChange(id, command.data);
|
||||
}
|
||||
def = this._addX2ManyDefaultRecord(list, options).then(function (ids) {
|
||||
_.each(ids, function(id){
|
||||
if (command.position === 'bottom' && list.orderedResIDs && list.orderedResIDs.length >= list.limit) {
|
||||
list.tempLimitIncrement = (list.tempLimitIncrement || 0) + 1;
|
||||
list.limit += 1;
|
||||
}
|
||||
// FIXME: hack for lunch widget, which does useless default_get and onchange
|
||||
if (command.data) {
|
||||
return self._applyChange(id, command.data);
|
||||
}
|
||||
});
|
||||
});
|
||||
defs.push(def);
|
||||
break;
|
||||
|
||||
@@ -46,7 +46,7 @@ var FieldManagerMixin = {
|
||||
*/
|
||||
_applyChanges: function (dataPointID, changes, event) {
|
||||
var self = this;
|
||||
var options = _.pick(event.data, 'context', 'doNotSetDirty', 'notifyChange', 'viewType');
|
||||
var options = _.pick(event.data, 'context', 'doNotSetDirty', 'notifyChange', 'viewType', 'allowWarning');
|
||||
return this.model.notifyChanges(dataPointID, changes, options)
|
||||
.then(function (result) {
|
||||
if (event.data.force_save) {
|
||||
|
||||
@@ -61,7 +61,7 @@ ListRenderer.include({
|
||||
}
|
||||
|
||||
_.each(child.children, function (child) {
|
||||
if (child.tag !== 'create') {
|
||||
if (child.tag !== 'create' || child.attrs.invisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -778,7 +778,7 @@ ListRenderer.include({
|
||||
// but we do want to unselect current row
|
||||
var self = this;
|
||||
this.unselectRow().then(function () {
|
||||
self.trigger_up('add_record', {context: ev.currentTarget.dataset.context}); // TODO write a test, the deferred was not considered
|
||||
self.trigger_up('add_record', {context: ev.currentTarget.dataset.context && [ev.currentTarget.dataset.context]}); // TODO write a test, the deferred was not considered
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ from odoo.addons.payment.controllers.portal import PaymentProcessing
|
||||
from odoo.addons.website.controllers.main import QueryURL
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.website.controllers.main import Website
|
||||
from odoo.addons.sale.controllers.product_configurator import ProductConfiguratorController
|
||||
from odoo.addons.website_form.controllers.main import WebsiteForm
|
||||
from odoo.osv import expression
|
||||
|
||||
@@ -130,7 +131,7 @@ class Website(Website):
|
||||
return views
|
||||
|
||||
|
||||
class WebsiteSale(http.Controller):
|
||||
class WebsiteSale(ProductConfiguratorController):
|
||||
|
||||
def _get_compute_currency_and_context(self):
|
||||
pricelist_context = dict(request.env.context)
|
||||
@@ -147,31 +148,6 @@ class WebsiteSale(http.Controller):
|
||||
|
||||
return compute_currency, pricelist_context, pricelist
|
||||
|
||||
def get_attribute_value_ids(self, product):
|
||||
""" list of selectable attributes of a product
|
||||
|
||||
:return: list of product variant description
|
||||
(variant id, [visible attribute ids], variant price, variant sale price)
|
||||
"""
|
||||
# product attributes with at least two choices
|
||||
quantity = product._context.get('quantity') or 1
|
||||
product = product.with_context(quantity=quantity)
|
||||
|
||||
visible_attrs_ids = product.attribute_line_ids.filtered(lambda l: len(l.value_ids) > 1).mapped('attribute_id').ids
|
||||
to_currency = request.website.get_current_pricelist().currency_id
|
||||
attribute_value_ids = []
|
||||
for variant in product.product_variant_ids:
|
||||
if to_currency != product.currency_id:
|
||||
price = variant.currency_id._convert(
|
||||
variant.website_public_price, to_currency,
|
||||
request.env.user.company_id, fields.Date.today()
|
||||
) / quantity
|
||||
else:
|
||||
price = variant.website_public_price / quantity
|
||||
visible_attribute_ids = [v.id for v in variant.attribute_value_ids if v.attribute_id.id in visible_attrs_ids]
|
||||
attribute_value_ids.append([variant.id, visible_attribute_ids, variant.website_price / quantity, price])
|
||||
return attribute_value_ids
|
||||
|
||||
def _get_search_order(self, post):
|
||||
# OrderBy will be parsed in orm and so no direct sql injection
|
||||
# id is added to be sure that order is a unique sort key
|
||||
@@ -344,7 +320,8 @@ class WebsiteSale(http.Controller):
|
||||
'categories': categs,
|
||||
'main_object': product,
|
||||
'product': product,
|
||||
'get_attribute_value_ids': self.get_attribute_value_ids,
|
||||
'optional_product_ids': [p.with_context({'active_id': p.id}) for p in product.optional_product_ids],
|
||||
'get_attribute_exclusions': self._get_attribute_exclusions,
|
||||
}
|
||||
return request.render("website_sale.product", values)
|
||||
|
||||
@@ -421,13 +398,10 @@ class WebsiteSale(http.Controller):
|
||||
product_id=int(product_id),
|
||||
add_qty=add_qty,
|
||||
set_qty=set_qty,
|
||||
attributes=self._filter_attributes(**kw),
|
||||
product_custom_variant_values=json.loads(kw.get('product_custom_variant_values'))
|
||||
)
|
||||
return request.redirect("/shop/cart")
|
||||
|
||||
def _filter_attributes(self, **kw):
|
||||
return {k: v for k, v in kw.items() if "attribute" in k}
|
||||
|
||||
@http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True, csrf=False)
|
||||
def cart_update_json(self, product_id, line_id=None, add_qty=None, set_qty=None, display=True):
|
||||
order = request.website.sale_get_order(force_create=1)
|
||||
@@ -1005,11 +979,6 @@ class WebsiteSale(http.Controller):
|
||||
ret = self.order_2_return_dict(order)
|
||||
return ret
|
||||
|
||||
@http.route(['/shop/get_unit_price'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def get_unit_price(self, product_ids, add_qty, **kw):
|
||||
products = request.env['product.product'].with_context({'quantity': add_qty}).browse(product_ids)
|
||||
return {product.id: product.website_price / add_qty for product in products}
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Edit
|
||||
# ------------------------------------------------------
|
||||
@@ -1097,3 +1066,75 @@ class WebsiteSale(http.Controller):
|
||||
states=[(st.id, st.name, st.code) for st in country.get_website_sale_states(mode=mode)],
|
||||
phone_code=country.phone_code
|
||||
)
|
||||
|
||||
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True, multilang=False)
|
||||
def cart_options_update_json(self, product_id, add_qty=1, set_qty=0, goto_shop=None, lang=None, **kw):
|
||||
if lang:
|
||||
request.website = request.website.with_context(lang=lang)
|
||||
|
||||
order = request.website.sale_get_order(force_create=1)
|
||||
optional_product_ids = []
|
||||
for k, v in kw.items():
|
||||
if "optional-product-" in k and int(kw.get(k.replace("product", "add"))):
|
||||
optional_product_ids.append(int(v))
|
||||
|
||||
custom_values = json.loads(kw.get('custom_values'))
|
||||
|
||||
value = {}
|
||||
if add_qty or set_qty:
|
||||
value = order._cart_update(
|
||||
product_id=int(product_id),
|
||||
add_qty=add_qty,
|
||||
set_qty=set_qty,
|
||||
optional_product_ids=optional_product_ids,
|
||||
product_custom_variant_values=self._get_product_custom_value(
|
||||
int(product_id),
|
||||
custom_values,
|
||||
'product_custom_variant_values'
|
||||
),
|
||||
no_variant_attribute_values=self._get_product_custom_value(
|
||||
int(product_id),
|
||||
custom_values,
|
||||
'no_variant_attribute_values'
|
||||
)
|
||||
)
|
||||
|
||||
# options have all time the same quantity
|
||||
for option_id in optional_product_ids:
|
||||
order._cart_update(
|
||||
product_id=option_id,
|
||||
set_qty=value.get('quantity'),
|
||||
linked_line_id=value.get('line_id'),
|
||||
product_custom_variant_values=self._get_product_custom_value(
|
||||
option_id,
|
||||
custom_values,
|
||||
'product_custom_variant_values'
|
||||
),
|
||||
no_variant_attribute_values=self._get_product_custom_value(
|
||||
option_id,
|
||||
custom_values,
|
||||
'no_variant_attribute_values'
|
||||
)
|
||||
)
|
||||
|
||||
return str(order.cart_quantity)
|
||||
|
||||
def _get_product_custom_value(self, product_id, custom_values, field):
|
||||
if custom_values:
|
||||
for custom_value in custom_values:
|
||||
if custom_value['product_id'] == product_id:
|
||||
return custom_value[field]
|
||||
|
||||
return None
|
||||
|
||||
@http.route(['/product_configurator/show_optional_products_website'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def show_optional_products_website(self, product_id, variant_values, **kw):
|
||||
return self._show_optional_products(product_id, variant_values, request.website.get_current_pricelist(), True, **kw)
|
||||
|
||||
@http.route(['/product_configurator/optional_product_items_website'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def optional_product_items_website(self, product_id, **kw):
|
||||
return self._optional_product_items(product_id, request.website.get_current_pricelist(), **kw)
|
||||
|
||||
@http.route(['/product_configurator/get_combination_info_website'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def get_combination_info_website(self, product_template_id, product_id, combination, add_qty, **kw):
|
||||
return self._get_combination_info(product_template_id, product_id, combination, add_qty, request.website.get_current_pricelist())
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
<field name="salesteam_id" ref="sales_team.salesteam_website_sales"/>
|
||||
</record>
|
||||
|
||||
<record id="product.product_attribute_2" model="product.attribute">
|
||||
<field name="type">color</field>
|
||||
</record>
|
||||
<record id="product.product_attribute_3" model="product.attribute">
|
||||
<field name="type">select</field>
|
||||
<record id="sale.product_product_1_product_template" model="product.template">
|
||||
<field name="website_sequence">100</field>
|
||||
<field name="website_published" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="product.product_product_24" model="product.product">
|
||||
@@ -223,12 +221,6 @@
|
||||
<record id="product.product_delivery_02_product_template" model="product.template">
|
||||
<field name="public_categ_ids" eval="[(6,0,[ref('public_category_desks')])]"/>
|
||||
</record>
|
||||
<record id="product.product_attribute_value_3" model="product.attribute.value">
|
||||
<field name="html_color">#FFFFFF</field>
|
||||
</record>
|
||||
<record id="product.product_attribute_value_4" model="product.attribute.value">
|
||||
<field name="html_color">#000000</field>
|
||||
</record>
|
||||
|
||||
<record model="product.pricelist" id="product.list0">
|
||||
<field name="selectable" eval="True" />
|
||||
@@ -582,4 +574,43 @@
|
||||
<!-- action_confirm for confirmation date -->
|
||||
<function model="sale.order" name="action_confirm" eval="[[ref('website_sale_order_14')]]"/>
|
||||
|
||||
<!-- Moved from deleted module "website_sale_option" -->
|
||||
<record id="product_product_1" model="product.product">
|
||||
<field name="name">Warranty</field>
|
||||
<field name="default_code">SERV_125889</field>
|
||||
<field name="list_price">20.0</field>
|
||||
<field name="website_sequence">100</field>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="type">service</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Warranty, issued to the purchaser of an article by its manufacturer, promising to repair or replace it if necessary within a specified period of time.</field>
|
||||
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_5')])]"/>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
</record>
|
||||
<record id="product_attribute_line_1" model="product.attribute.line">
|
||||
<field name="product_tmpl_id" ref="product_product_1_product_template"/>
|
||||
<field name="attribute_id" ref="product.product_attribute_3"/>
|
||||
<field name="value_ids" eval="[(6,0,[ref('product.product_attribute_value_5'), ref('product.product_attribute_value_6')])]"/>
|
||||
</record>
|
||||
<record id="product_product_1_product_template" model="product.template">
|
||||
<field name="attribute_line_ids" eval="[(6,0,[ref('product_attribute_line_1')])]"/>
|
||||
<field name="public_categ_ids" eval="[(6,0,[ref('website_sale.services')])]"/>
|
||||
</record>
|
||||
<record id="product.product_product_4_product_template" model="product.template">
|
||||
<field name="optional_product_ids" eval="[(6,0,[ref('product.product_product_11_product_template'), ref('product_product_1_product_template')])]"/>
|
||||
</record>
|
||||
|
||||
<!-- Handle automatically created product.product.attribute.value -->
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'website_sale.product_product_attribute_value_1',
|
||||
'record': obj().env.ref('website_sale.product_attribute_line_1').product_value_ids[1],
|
||||
'noupdate': True,
|
||||
}]"/>
|
||||
</function>
|
||||
|
||||
<record id="product_product_attribute_value_1" model="product.product.attribute.value">
|
||||
<field name="price_extra">18.00</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -217,20 +217,6 @@ class Product(models.Model):
|
||||
return self.product_tmpl_id.website_publish_button()
|
||||
|
||||
|
||||
class ProductAttribute(models.Model):
|
||||
_inherit = "product.attribute"
|
||||
|
||||
type = fields.Selection([('radio', 'Radio'), ('select', 'Select'), ('color', 'Color')], default='radio')
|
||||
|
||||
|
||||
class ProductAttributeValue(models.Model):
|
||||
_inherit = "product.attribute.value"
|
||||
|
||||
html_color = fields.Char(string='HTML Color Index', oldname='color', help="Here you can set a "
|
||||
"specific HTML color index (e.g. #ff0000) to display the color on the website if the "
|
||||
"attibute type is 'Color'.")
|
||||
|
||||
|
||||
class ProductImage(models.Model):
|
||||
_name = 'product.image'
|
||||
_description = 'Product Image'
|
||||
|
||||
@@ -27,7 +27,6 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
group_delivery_invoice_address = fields.Boolean(string="Shipping Address", implied_group='sale.group_delivery_invoice_address', group='base.group_portal,base.group_user,base.group_public')
|
||||
|
||||
module_website_sale_options = fields.Boolean("Optional Products")
|
||||
module_website_sale_digital = fields.Boolean("Digital Content")
|
||||
module_website_sale_wishlist = fields.Boolean("Wishlists")
|
||||
module_website_sale_comparison = fields.Boolean("Product Comparison Tool")
|
||||
|
||||
@@ -66,13 +66,30 @@ class SaleOrder(models.Model):
|
||||
product = self.env['product.product'].browse(product_id)
|
||||
|
||||
# split lines with the same product if it has untracked attributes
|
||||
if product and product.mapped('attribute_line_ids').filtered(lambda r: not r.attribute_id.create_variant) and not line_id:
|
||||
if product and product.mapped('attribute_line_ids').filtered(lambda r: not r.attribute_id.create_variant == 'always') and not line_id:
|
||||
return self.env['sale.order.line']
|
||||
|
||||
domain = [('order_id', '=', self.id), ('product_id', '=', product_id)]
|
||||
if line_id:
|
||||
domain += [('id', '=', line_id)]
|
||||
return self.env['sale.order.line'].sudo().search(domain)
|
||||
else:
|
||||
domain += [('product_custom_variant_values', '=', False)]
|
||||
|
||||
lines = self.env['sale.order.line'].sudo().search(domain)
|
||||
|
||||
if line_id:
|
||||
return lines
|
||||
linked_line_id = kwargs.get('linked_line_id', False)
|
||||
optional_product_ids = set(kwargs.get('optional_product_ids', []))
|
||||
|
||||
lines = lines.filtered(lambda line: line.linked_line_id.id == linked_line_id)
|
||||
if optional_product_ids:
|
||||
# only match the lines with the same chosen optional products on the existing lines
|
||||
lines = lines.filtered(lambda line: optional_product_ids == set(line.mapped('option_line_ids.product_id.id')))
|
||||
else:
|
||||
lines = lines.filtered(lambda line: not line.option_line_ids)
|
||||
|
||||
return lines
|
||||
|
||||
@api.multi
|
||||
def _website_product_id_change(self, order_id, product_id, qty=0):
|
||||
@@ -101,10 +118,7 @@ class SaleOrder(models.Model):
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _get_line_description(self, order_id, product_id, attributes=None):
|
||||
if not attributes:
|
||||
attributes = {}
|
||||
|
||||
def _get_line_description(self, order_id, product_id, no_variant_attribute_values=None, custom_values=None):
|
||||
order = self.sudo().browse(order_id)
|
||||
product_context = dict(self.env.context)
|
||||
product_context.setdefault('lang', order.partner_id.lang)
|
||||
@@ -112,23 +126,20 @@ class SaleOrder(models.Model):
|
||||
|
||||
name = product.display_name
|
||||
|
||||
# add untracked attributes in the name
|
||||
untracked_attributes = []
|
||||
for k, v in attributes.items():
|
||||
# attribute should be like 'attribute-48-1' where 48 is the product_id, 1 is the attribute_id and v is the attribute value
|
||||
attribute_value = self.env['product.attribute.value'].sudo().browse(int(v))
|
||||
if attribute_value and not attribute_value.attribute_id.create_variant:
|
||||
untracked_attributes.append(attribute_value.name)
|
||||
if untracked_attributes:
|
||||
name += '\n%s' % (', '.join(untracked_attributes))
|
||||
|
||||
if product.description_sale:
|
||||
name += '\n%s' % (product.description_sale)
|
||||
|
||||
if no_variant_attribute_values:
|
||||
name += ''.join(['\n%s: %s' % (attribute_value['attribute_name'], attribute_value['attribute_value_name'])
|
||||
for attribute_value in no_variant_attribute_values])
|
||||
|
||||
if custom_values:
|
||||
name += ''.join(['\n%s: %s' % (custom_value['attribute_value_name'], custom_value['custom_value']) for custom_value in custom_values])
|
||||
|
||||
return name
|
||||
|
||||
@api.multi
|
||||
def _cart_update(self, product_id=None, line_id=None, add_qty=0, set_qty=0, attributes=None, **kwargs):
|
||||
def _cart_update(self, product_id=None, line_id=None, add_qty=0, set_qty=0, **kwargs):
|
||||
""" Add or set product quantity, add_qty can be negative """
|
||||
self.ensure_one()
|
||||
SaleOrderLineSudo = self.env['sale.order.line'].sudo()
|
||||
@@ -155,7 +166,21 @@ class SaleOrder(models.Model):
|
||||
# Create line if no line with product_id can be located
|
||||
if not order_line:
|
||||
values = self._website_product_id_change(self.id, product_id, qty=1)
|
||||
values['name'] = self._get_line_description(self.id, product_id, attributes=attributes)
|
||||
|
||||
custom_values = kwargs.get('product_custom_variant_values')
|
||||
if custom_values:
|
||||
values['product_custom_variant_values'] = [(0, 0, {
|
||||
'attribute_value_id': custom_value['attribute_value_id'],
|
||||
'custom_value': custom_value['custom_value']
|
||||
}) for custom_value in custom_values]
|
||||
|
||||
no_variant_attribute_values = kwargs.get('no_variant_attribute_values')
|
||||
if no_variant_attribute_values:
|
||||
values['product_no_variant_attribute_values'] = [
|
||||
(6, 0, [int(attribute['value']) for attribute in no_variant_attribute_values])
|
||||
]
|
||||
|
||||
values['name'] = self._get_line_description(self.id, product_id, no_variant_attribute_values=no_variant_attribute_values, custom_values=custom_values)
|
||||
order_line = SaleOrderLineSudo.create(values)
|
||||
|
||||
try:
|
||||
@@ -198,13 +223,34 @@ class SaleOrder(models.Model):
|
||||
|
||||
order_line.write(values)
|
||||
|
||||
return {'line_id': order_line.id, 'quantity': quantity}
|
||||
# link a product to the sales order
|
||||
if kwargs.get('linked_line_id'):
|
||||
linked_line = SaleOrderLineSudo.browse(kwargs['linked_line_id'])
|
||||
order_line.write({
|
||||
'linked_line_id': linked_line.id,
|
||||
'name': order_line.name + "\n" + _("Option for:") + ' ' + linked_line.product_id.display_name,
|
||||
})
|
||||
linked_line.write({"name": linked_line.name + "\n" + _("Option:") + ' ' + order_line.product_id.display_name})
|
||||
|
||||
option_lines = self.order_line.filtered(lambda l: l.linked_line_id.id == order_line.id)
|
||||
for option_line_id in option_lines:
|
||||
self._cart_update(option_line_id.product_id.id, option_line_id.id, add_qty, set_qty, **kwargs)
|
||||
|
||||
return {'line_id': order_line.id, 'quantity': quantity, 'option_ids': list(set(option_lines.ids))}
|
||||
|
||||
def _cart_accessories(self):
|
||||
""" Suggest accessories based on 'Accessory Products' of products in cart """
|
||||
for order in self:
|
||||
accessory_products = order.website_order_line.mapped('product_id.accessory_product_ids').filtered(lambda product: product.website_published)
|
||||
accessory_products -= order.website_order_line.mapped('product_id')
|
||||
products = order.website_order_line.mapped('product_id')
|
||||
accessory_products -= products
|
||||
|
||||
for product in products:
|
||||
for product_template in accessory_products.mapped('product_tmpl_id'):
|
||||
template_accessory_products = accessory_products.filtered(lambda accessory_product: accessory_product.product_tmpl_id == product_template)
|
||||
# remove from the accessories the ones that are not available for the selected product
|
||||
accessory_products -= template_accessory_products - product_template.get_filtered_variants(product)
|
||||
|
||||
return random.sample(accessory_products, len(accessory_products))
|
||||
|
||||
@api.multi
|
||||
@@ -240,6 +286,9 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
name_short = fields.Char(compute="_compute_name_short")
|
||||
|
||||
linked_line_id = fields.Many2one('sale.order.line', string='Linked Order Line', domain="[('order_id', '!=', order_id)]", ondelete='cascade')
|
||||
option_line_ids = fields.One2many('sale.order.line', 'linked_line_id', string='Options Linked')
|
||||
|
||||
@api.multi
|
||||
@api.depends('product_id.display_name')
|
||||
def _compute_name_short(self):
|
||||
|
||||
@@ -10,7 +10,9 @@ access_product_style,product.style.public,website_sale.model_product_style,,1,0,
|
||||
access_product_supplierinfo,product.supplierinfo.public,product.model_product_supplierinfo,,1,0,0,0
|
||||
access_product_attribute_public,product.attribute public,product.model_product_attribute,,1,0,0,0
|
||||
access_product_attribute_value_public,product.attribute value public,product.model_product_attribute_value,,1,0,0,0
|
||||
access_product_attribute_price_public,product.attribute price public,product.model_product_attribute_price,,1,0,0,0
|
||||
access_product_product_attribute,product.product.attribute value public,product.model_product_product_attribute_value,,1,0,0,0
|
||||
access_product_product_attribute_custom_value,product.attribute.custom value,sale.model_product_attribute_custom_value,,1,0,0,0
|
||||
access_product_attribute_filter_line,product.attribute filter line public,product.model_product_attribute_filter_line,,1,0,0,0
|
||||
access_product_attribute_line_public,product.attribute line public,product.model_product_attribute_line,,1,0,0,0
|
||||
access_fiscal_position_public,fiscal position public,account.model_account_fiscal_position,base.group_portal,1,0,0,0
|
||||
access_payment_term,payment term public,account.model_account_payment_term,base.group_portal,1,0,0,0
|
||||
|
||||
|
@@ -124,6 +124,7 @@ odoo.define('website_sale.website_sale', function (require) {
|
||||
'use strict';
|
||||
|
||||
var utils = require('web.utils');
|
||||
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
|
||||
var core = require('web.core');
|
||||
var config = require('web.config');
|
||||
var sAnimations = require('website.content.snippets.animation');
|
||||
@@ -131,10 +132,10 @@ require("website.content.zoomodoo");
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
|
||||
sAnimations.registry.WebsiteSale = sAnimations.Class.extend(ProductConfiguratorMixin, {
|
||||
selector: '.oe_website_sale',
|
||||
read_events: {
|
||||
'change input[name="add_qty"]': '_onChangeAddQuantity',
|
||||
'change form .js_product:first input[name="add_qty"]': '_onChangeAddQuantity',
|
||||
'mouseup .js_publish': '_onMouseupPublish',
|
||||
'touchend .js_publish': '_onMouseupPublish',
|
||||
'change .oe_cart input.js_quantity[data-product-id]': '_onChangeCartQuantity',
|
||||
@@ -145,7 +146,6 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
|
||||
'mouseup form.js_add_cart_json label': '_onMouseupAddCartLabel',
|
||||
'touchend form.js_add_cart_json label': '_onMouseupAddCartLabel',
|
||||
'change .css_attribute_color input': '_onChangeColorAttribute',
|
||||
'change input.js_variant_change, select.js_variant_change, input.js_product_change, [data-attribute_value_ids]': '_onChangeVariant',
|
||||
'click .show_coupon': '_onClickShowCoupon',
|
||||
'submit .o_website_sale_search': '_onSubmitSaleSearch',
|
||||
'change select[name="country_id"]': '_onChangeCountry',
|
||||
@@ -161,6 +161,8 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
|
||||
|
||||
this._changeCartQuantity = _.debounce(this._changeCartQuantity.bind(this), 500);
|
||||
this._changeCountry = _.debounce(this._changeCountry.bind(this), 500);
|
||||
|
||||
this.isWebsite = true;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -175,9 +177,7 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
|
||||
$('input.js_product_change', product).first().trigger('change');
|
||||
});
|
||||
|
||||
_.each(this.$('.js_add_cart_variants'), function (add_variant) {
|
||||
$('input.js_variant_change, select.js_variant_change', add_variant).first().trigger('change');
|
||||
});
|
||||
this.triggerVariantChange(this.$el);
|
||||
|
||||
this.$('select[name="country_id"]').change();
|
||||
|
||||
@@ -328,83 +328,24 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_priceToStr: function (price) {
|
||||
var l10n = _t.database.parameters;
|
||||
var precision = 2;
|
||||
|
||||
if ($(".decimal_precision").length) {
|
||||
precision = parseInt($(".decimal_precision").last().data('precision'));
|
||||
}
|
||||
var formatted = _.str.sprintf('%.' + precision + 'f', price).split('.');
|
||||
formatted[0] = utils.insert_thousand_seps(formatted[0]);
|
||||
return formatted.join(l10n.decimal_point);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateProductImage: function ($productContainer, product_id) {
|
||||
var $img;
|
||||
if ($('#o-carousel-product').length) {
|
||||
$img = $productContainer.find('img.js_variant_img');
|
||||
$img.attr("src", "/web/image/product.product/" + product_id + "/image");
|
||||
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
|
||||
.data('oe-model', 'product.product').data('oe-id', product_id);
|
||||
|
||||
var $thumbnail = $productContainer.find('img.js_variant_img_small');
|
||||
if ($thumbnail.length !== 0) { // if only one, thumbnails are not displayed
|
||||
$thumbnail.attr("src", "/web/image/product.product/" + product_id + "/image/90x90");
|
||||
$('.carousel').carousel(0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$img = $productContainer.find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/web/image/product.product/" + product_id + "/image");
|
||||
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
|
||||
.data('oe-model', 'product.product').data('oe-id', product_id);
|
||||
}
|
||||
// reset zooming constructs
|
||||
$img.filter('[data-zoom-image]').attr('data-zoom-image', $img.attr('src'));
|
||||
if ($img.data('zoomOdoo') !== undefined) {
|
||||
$img.data('zoomOdoo').isReady = false;
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickAddCartJSON: function (ev){
|
||||
this.onClickAddCartJSON(ev);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onChangeAddQuantity: function (ev) {
|
||||
var productIDs = [];
|
||||
var $addVariant = $(ev.currentTarget).closest(".js_product").find(".js_add_cart_variants");
|
||||
var qty = $(ev.currentTarget).closest('form').find('input[name="add_qty"]').val();
|
||||
var attributeValueIDs = $addVariant.data("attribute_value_ids");
|
||||
_.each(attributeValueIDs, function (entry) {
|
||||
productIDs.push(entry[0]);
|
||||
});
|
||||
|
||||
if ($("#product_detail").length) {
|
||||
// display the reduction from the pricelist in function of the quantity
|
||||
this._rpc({
|
||||
route: '/shop/get_unit_price',
|
||||
params: {
|
||||
product_ids: productIDs,
|
||||
add_qty: parseInt(qty),
|
||||
},
|
||||
}).then(function (data) {
|
||||
var current = attributeValueIDs;
|
||||
for (var j = 0 ; j < current.length ; j++) {
|
||||
current[j][2] = data[current[j][0]];
|
||||
}
|
||||
$addVariant.trigger('change');
|
||||
});
|
||||
}
|
||||
this.onChangeAddQuantity(ev);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -444,27 +385,10 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onClickAddCartJSON: function (ev) { // hack to add and remove from cart with json
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
var $input = $link.closest('.input-group').find("input");
|
||||
var product_id = +$input.closest('*:has(input[name="product_id"])').find('input[name="product_id"]').val();
|
||||
var min = parseFloat($input.data("min") || 0);
|
||||
var max = parseFloat($input.data("max") || Infinity);
|
||||
var quantity = ($link.has(".fa-minus").length ? -1 : 1) + parseFloat($input.val() || 0, 10);
|
||||
var new_qty = quantity > min ? (quantity < max ? quantity : max) : min;
|
||||
// if they are more of one input for this product (eg: option modal)
|
||||
$('input[name="'+$input.attr("name")+'"]').add($input).filter(function () {
|
||||
var $prod = $(this).closest('*:has(input[name="product_id"])');
|
||||
return !$prod.length || +$prod.find('input[name="product_id"]').val() === product_id;
|
||||
}).val(new_qty).change();
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onClickSubmit: function (ev) {
|
||||
_onClickSubmit: function (ev, forceSubmit) {
|
||||
if ($(ev.currentTarget).is('#add_to_cart, #products_grid .a-submit') && !forceSubmit) {
|
||||
return;
|
||||
}
|
||||
var $aSubmit = $(ev.currentTarget);
|
||||
if (!ev.isDefaultPrevented() && !$aSubmit.is(".disabled")) {
|
||||
ev.preventDefault();
|
||||
@@ -517,76 +441,6 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
|
||||
.filter(':has(input:checked)')
|
||||
.addClass("active");
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onChangeVariant: function (ev) {
|
||||
var $parent = $(ev.target).closest('.js_product');
|
||||
var $ul = $parent.find('.js_add_cart_variants');
|
||||
var $product_id = $parent.find('.product_id').first();
|
||||
var $price = $parent.find(".oe_price:first .oe_currency_value");
|
||||
var $default_price = $parent.find(".oe_default_price:first .oe_currency_value");
|
||||
var $optional_price = $parent.find(".oe_optional:first .oe_currency_value");
|
||||
var variant_ids = $ul.data("attribute_value_ids");
|
||||
var values = [];
|
||||
var unchanged_values = $parent.find('div.oe_unchanged_value_ids').data('unchanged_value_ids') || [];
|
||||
|
||||
_.each($parent.find('input.js_variant_change:checked, select.js_variant_change'), function (el) {
|
||||
values.push(+$(el).val());
|
||||
});
|
||||
values = values.concat(unchanged_values);
|
||||
var list_variant_id = parseInt($parent.find('input.js_product_change:checked').val());
|
||||
|
||||
$parent.find("label").removeClass("text-muted css_not_available");
|
||||
|
||||
var product_id = false;
|
||||
for (var k in variant_ids) {
|
||||
if (_.isEmpty(_.difference(variant_ids[k][1], values)) || variant_ids[k][0] === list_variant_id) {
|
||||
$price.html(this._priceToStr(variant_ids[k][2]));
|
||||
$default_price.html(this._priceToStr(variant_ids[k][3]));
|
||||
if (variant_ids[k][3]-variant_ids[k][2]>0.01) {
|
||||
$default_price.closest('.oe_website_sale').addClass("discount");
|
||||
$optional_price.closest('.oe_optional').show().css('text-decoration', 'line-through');
|
||||
$default_price.parent().removeClass('d-none');
|
||||
} else {
|
||||
$optional_price.closest('.oe_optional').hide();
|
||||
$default_price.parent().addClass('d-none');
|
||||
}
|
||||
product_id = variant_ids[k][0];
|
||||
this._updateProductImage($(ev.currentTarget).closest('tr.js_product, .oe_website_sale'), product_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_.each($parent.find("input.js_variant_change:radio, select.js_variant_change"), function (elem) {
|
||||
var $input = $(elem);
|
||||
var id = +$input.val();
|
||||
var values = [id];
|
||||
|
||||
_.each($parent.find("ul:not(:has(input.js_variant_change[value='" + id + "'])) input.js_variant_change:checked, select.js_variant_change"), function (elem) {
|
||||
values.push(+$(elem).val());
|
||||
});
|
||||
|
||||
for (var k in variant_ids) {
|
||||
if (!_.difference(values, variant_ids[k][1]).length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$input.closest("label").addClass("css_not_available");
|
||||
$input.find("option[value='" + id + "']").addClass("css_not_available");
|
||||
});
|
||||
|
||||
if (product_id) {
|
||||
$parent.removeClass("css_not_available");
|
||||
$product_id.val(product_id);
|
||||
$parent.find("#add_to_cart").removeClass("disabled");
|
||||
} else {
|
||||
$parent.addClass("css_not_available");
|
||||
$product_id.val(0);
|
||||
$parent.find("#add_to_cart").addClass("disabled");
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
odoo.define('website_sale_options.website_sale', function (require) {
|
||||
'use strict';
|
||||
|
||||
var core = require('web.core');
|
||||
var _t = core._t;
|
||||
var OptionalProductsModal = require('sale.OptionalProductsModal');
|
||||
var weContext = require('web_editor.context');
|
||||
var sAnimations = require('website.content.snippets.animation');
|
||||
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
|
||||
require('website_sale.website_sale');
|
||||
|
||||
sAnimations.registry.WebsiteSaleOptions = sAnimations.Class.extend(ProductConfiguratorMixin, {
|
||||
selector: '.oe_website_sale',
|
||||
read_events: {
|
||||
'click #add_to_cart, #products_grid .product_price .a-submit': '_onClickAdd',
|
||||
},
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this._handleAdd = _.debounce(this._handleAdd.bind(this), 200, true);
|
||||
this.isWebsite = true;
|
||||
|
||||
delete this.events['change [data-attribute_exclusions]'];
|
||||
delete this.events['change input.js_quantity'];
|
||||
delete this.events['click button.js_add_cart_json'];
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Prevents the default submit to allow showing product options
|
||||
* in a modal (if any)
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickAdd: function (ev) {
|
||||
ev.preventDefault();
|
||||
this._handleAdd($(ev.currentTarget).closest('form'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the optional products modal
|
||||
* and add handlers to the modal events (confirm, back, ...)
|
||||
*
|
||||
* @private
|
||||
* @param {$.Element} $form the related webshop form
|
||||
*/
|
||||
_handleAdd: function ($form) {
|
||||
var self = this;
|
||||
this.$form = $form;
|
||||
this.isWebsite = true;
|
||||
|
||||
var productSelector = [
|
||||
'input[type="hidden"][name="product_id"]',
|
||||
'input[type="radio"][name="product_id"]:checked'
|
||||
];
|
||||
|
||||
var productId = parseInt($form.find(productSelector.join(', ')).first().val(), 10);
|
||||
|
||||
var productReady = $.Deferred();
|
||||
if (productId){
|
||||
productReady.resolve(productId);
|
||||
} else {
|
||||
productReady = self._rpc({
|
||||
model: 'product.template',
|
||||
method: 'create_product_variant',
|
||||
args: [
|
||||
$form.find('.product_template_id').val(),
|
||||
JSON.stringify(self.getSelectedVariantValues($form))
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
productReady.done(function (productId){
|
||||
$form.find(productSelector.join(', ')).val(productId);
|
||||
|
||||
self.rootProduct = {
|
||||
product_id: productId,
|
||||
quantity: parseFloat($form.find('input[name="add_qty"]').val() || 1),
|
||||
product_custom_variant_values: self.getCustomVariantValues($form.find('.js_product')),
|
||||
variant_values: self.getSelectedVariantValues($form.find('.js_product')),
|
||||
no_variant_attribute_values: self.getNoVariantAttributeValues($form.find('.js_product'))
|
||||
};
|
||||
|
||||
self.optionalProductsModal = new OptionalProductsModal($form, {
|
||||
rootProduct: self.rootProduct,
|
||||
isWebsite: true,
|
||||
okButtonText: _t('Proceed to Checkout'),
|
||||
cancelButtonText: _t('Continue Shopping'),
|
||||
title: _t('Add to cart')
|
||||
}).open();
|
||||
|
||||
self.optionalProductsModal.on('options_empty', null, self._onModalOptionsEmpty.bind(self));
|
||||
self.optionalProductsModal.on('update_quantity', null, self._onOptionsUpdateQuantity.bind(self));
|
||||
self.optionalProductsModal.on('confirm', null, self._onModalConfirm.bind(self));
|
||||
self.optionalProductsModal.on('back', null, self._onModalBack.bind(self));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* No optional products found for this product
|
||||
* Add custom variant values and attribute values that do not generate variants
|
||||
* in the form data and trigger submit
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onModalOptionsEmpty: function () {
|
||||
var $productCustomVariantValues = $('<input>', {
|
||||
name: 'product_custom_variant_values',
|
||||
type: "hidden",
|
||||
value: JSON.stringify(this.rootProduct.product_custom_variant_values)
|
||||
});
|
||||
this.$form.append($productCustomVariantValues);
|
||||
|
||||
var $productNoVariantAttributeValues = $('<input>', {
|
||||
name: 'no_variant_attribute_values',
|
||||
type: "hidden",
|
||||
value: JSON.stringify(this.rootProduct.no_variant_attribute_values)
|
||||
});
|
||||
this.$form.append($productNoVariantAttributeValues);
|
||||
|
||||
this.$form.trigger('submit', [true]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update web shop base form quantity
|
||||
* when quantity is updated in the optional products window
|
||||
*
|
||||
* @private
|
||||
* @param {integer} quantity
|
||||
*/
|
||||
_onOptionsUpdateQuantity: function (quantity) {
|
||||
this.$form
|
||||
.find('input[name="add_qty"]')
|
||||
.first()
|
||||
.val(quantity)
|
||||
.trigger('change');
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit form and stay on the same page
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onModalBack: function () {
|
||||
this._onModalSubmit(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit form and go to shop
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onModalConfirm: function () {
|
||||
this._onModalSubmit(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Submits the form with additional parameters
|
||||
* - lang
|
||||
* - product_custom_variant_values: The products custom variant values
|
||||
*
|
||||
* @private
|
||||
* @param {Boolean} goToShop Triggers a page refresh to the url "shop/cart"
|
||||
*/
|
||||
_onModalSubmit: function (goToShop){
|
||||
var customValues = JSON.stringify(
|
||||
this.optionalProductsModal.getSelectedProducts()
|
||||
);
|
||||
|
||||
this.$form.ajaxSubmit({
|
||||
url: '/shop/cart/update_option',
|
||||
data: {
|
||||
lang: weContext.get().lang,
|
||||
custom_values: customValues
|
||||
},
|
||||
success: function (quantity) {
|
||||
if (goToShop) {
|
||||
var path = window.location.pathname.replace(/shop([\/?].*)?$/, "shop/cart");
|
||||
window.location.pathname = path;
|
||||
}
|
||||
var $quantity = $(".my_cart_quantity");
|
||||
$quantity.parent().parent().removeClass("d-none", !quantity);
|
||||
$quantity.html(quantity).hide().fadeIn(600);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return sAnimations.registry.WebsiteSaleOptions;
|
||||
|
||||
});
|
||||
@@ -38,6 +38,10 @@ tour.register('shop_buy_product', {
|
||||
extra_trigger: 'label:contains(Steel) input:propChecked',
|
||||
trigger: '#product_detail form[action^="/shop/cart/update"] .btn-primary',
|
||||
},
|
||||
{
|
||||
content: "click in modal on 'Proceed to checkout' button",
|
||||
trigger: 'button:contains("Proceed to Checkout")',
|
||||
},
|
||||
{
|
||||
content: "add suggested",
|
||||
extra_trigger: '#wrap:not(:has(#cart_products:contains("[A8767] Storage Box")))',
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
odoo.define("website_sale.tour_shop", function (require) {
|
||||
"use strict";
|
||||
|
||||
var tour = require("web_tour.tour");
|
||||
var base = require("web_editor.base");
|
||||
|
||||
tour.register("shop_custom_attribute_value", {
|
||||
url: "/shop",
|
||||
test: true,
|
||||
wait_for: base.ready()
|
||||
}, [{
|
||||
trigger: 'img[src*="/product.template/9"]',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'li.js_attribute_value span:contains(Custom)',
|
||||
extra_trigger: 'li.js_attribute_value',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'input.variant_custom_value',
|
||||
run: 'text Wood',
|
||||
}, {
|
||||
trigger: 'a:contains(Add to Cart)',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'div:contains(Custom: Wood)',
|
||||
extra_trigger: '.js_product.in_cart.main_product',
|
||||
run: function (){} // checks that Yep, it's wood!
|
||||
}, {
|
||||
trigger: 'button.js_add_cart_json:has(i.fa-plus)',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'div.oe_striked_price span:contains(750)',
|
||||
run: function (){}, // check
|
||||
}, {
|
||||
trigger: 'span.oe_price span:contains(600)',
|
||||
run: function (){}, // check
|
||||
}, {
|
||||
trigger: 'li.js_attribute_value span:contains(Aluminium)',
|
||||
extra_trigger: '.oe_optional_products_modal',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.oe_price span:contains(22.9)',
|
||||
run: function (){}, // check
|
||||
}, {
|
||||
trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Conference Chair)) .js_add',
|
||||
extra_trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Conference Chair))',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Chair floor protection)) .js_add',
|
||||
extra_trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Chair floor protection))',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: 'span:contains(1,269.80)',
|
||||
run: function (){}, // check
|
||||
}, {
|
||||
trigger: 'button:has(span:contains(Proceed to Checkout))',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'span:contains(Custom: Wood)',
|
||||
extra_trigger: '#cart_products',
|
||||
run: function (){}, // check
|
||||
}]);
|
||||
});
|
||||
@@ -0,0 +1,216 @@
|
||||
odoo.define('website_sale.tour_shop_customize', function (require) {
|
||||
'use strict';
|
||||
|
||||
var tour = require("web_tour.tour");
|
||||
var base = require("web_editor.base");
|
||||
|
||||
tour.register('shop_customize', {
|
||||
test: true,
|
||||
url: '/shop',
|
||||
wait_for: base.ready()
|
||||
},
|
||||
[
|
||||
{
|
||||
content: "open customize menu",
|
||||
trigger: '#customize-menu > a',
|
||||
},
|
||||
{
|
||||
content: "click on 'Product Attribute's Filters'",
|
||||
trigger: "#customize-menu a:contains(Product Attribute's Filters)",
|
||||
},
|
||||
{
|
||||
content: "select product attribute memory Steel",
|
||||
extra_trigger: 'body:not(:has(#customize-menu:visible .dropdown-menu:visible))',
|
||||
trigger: 'form.js_attributes label:contains(Steel) input:not(:checked)',
|
||||
},
|
||||
{
|
||||
content: "check the selection",
|
||||
trigger: 'form.js_attributes label:contains(Steel) input:checked',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "select product",
|
||||
extra_trigger: 'body:not(:has(.oe_website_sale .oe_product_cart:eq(2)))',
|
||||
trigger: '.oe_product_cart a:contains("Customizable Desk")',
|
||||
},
|
||||
{
|
||||
content: "open customize menu",
|
||||
trigger: '#customize-menu',
|
||||
extra_trigger: "#product_detail",
|
||||
run: function() {
|
||||
// ENABLE VARIANT GROUP
|
||||
$('body').addClass('notReady');
|
||||
var ajax = odoo.__DEBUG__.services['web.ajax'];
|
||||
var get_group_payload = {
|
||||
model: 'ir.model.data',
|
||||
method: 'xmlid_to_res_id',
|
||||
args: ['product.group_product_variant', false],
|
||||
kwargs: {}
|
||||
};
|
||||
ajax.jsonpRpc('/web/dataset/call_kw', 'call', get_group_payload).then(function(group_id) {
|
||||
ajax.jsonpRpc('/web/dataset/call_kw', 'call', {
|
||||
model: 'res.groups',
|
||||
method: 'write',
|
||||
args: [group_id, {'users': [[4, 1]]}],
|
||||
kwargs: {}
|
||||
}).then(function() {
|
||||
location.reload(true);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "open customize menu",
|
||||
trigger: '#customize-menu > a',
|
||||
extra_trigger: 'body:not(.notReady)',
|
||||
},
|
||||
{
|
||||
content: "check page loaded after enable variant group",
|
||||
trigger: '#customize-menu a:contains(List View of Variants)',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "click on 'List View of Variants'",
|
||||
trigger: "#customize-menu a:contains(List View of Variants)",
|
||||
},
|
||||
{
|
||||
content: "check page loaded after list of variant customization enabled",
|
||||
trigger: '.js_product_change',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "check price is 750 and set quantity to 2",
|
||||
trigger: ".js_product:first input.quantity:propValue(1)",
|
||||
extra_trigger: ".product_price .oe_price .oe_currency_value:containsExact(750.00)",
|
||||
run: "text 2",
|
||||
},
|
||||
{
|
||||
content: "verify pricelist based on quantity has effect",
|
||||
trigger: ".product_price .oe_price .oe_currency_value:containsExact(600.00)",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "check pricelit has been applied and switch to Aluminium variant",
|
||||
trigger: ".js_product label:contains('Aluminium')",
|
||||
},
|
||||
{
|
||||
content: "verify that price has changed when changing variant",
|
||||
trigger: ".product_price .oe_price .oe_currency_value:not(:containsExact(600.00))",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "open customize menu",
|
||||
trigger: '#customize-menu > a',
|
||||
},
|
||||
{
|
||||
content: "remove 'List View of Variants'",
|
||||
trigger: "#customize-menu a:contains(List View of Variants):has(input:checked)",
|
||||
},
|
||||
{
|
||||
content: "check page loaded after list of variant customization disabled",
|
||||
trigger: ".js_product:not(:has(.js_product_change))",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "check price is 750 and set quantity to 2",
|
||||
trigger: ".js_product:first input.quantity:propValue(1)",
|
||||
extra_trigger: ".product_price .oe_price .oe_currency_value:containsExact(750.00)",
|
||||
run: "text 2",
|
||||
},
|
||||
{
|
||||
content: "verify pricelist based on quantity has effect",
|
||||
trigger: ".product_price .oe_price .oe_currency_value:containsExact(600.00)",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "switch to Aluminium variant",
|
||||
trigger: ".js_product label:contains('Aluminium')",
|
||||
},
|
||||
{
|
||||
content: "verify that price has changed when changing variant",
|
||||
trigger: ".product_price .oe_price .oe_currency_value:not(:containsExact(600.00))",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "switch back to Steel variant",
|
||||
trigger: ".js_product label:contains('Steel')",
|
||||
},
|
||||
{
|
||||
content: "click on 'Add to Cart' button",
|
||||
trigger: "a:contains(Add to Cart)",
|
||||
},
|
||||
{
|
||||
content: "price is lowered by pricelist and not multiplied by quantity",
|
||||
trigger: "#product_confirmation .oe_price .oe_currency_value:containsExact(600.00)",
|
||||
extra_trigger: "#product_confirmation input.quantity:propValue(2)",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "set quantity to 1",
|
||||
trigger: "#product_confirmation .js_add_cart_json .fa-minus",
|
||||
},
|
||||
{
|
||||
content: "check that product page has been updated",
|
||||
trigger: ".js_product:first input.quantity:propValue(1)",
|
||||
extra_trigger: ".product_price .oe_price .oe_currency_value:containsExact(750.00)",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "check that add to cart modal has been updated",
|
||||
trigger: "#product_confirmation .oe_price .oe_currency_value:containsExact(750.00)",
|
||||
extra_trigger: "#product_confirmation input.quantity:propValue(1)",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "add an optional Warranty",
|
||||
trigger: ".js_product:contains(Warranty) a:contains(Add to Cart)",
|
||||
},
|
||||
{
|
||||
content: "click in modal on 'Proceed to checkout' button",
|
||||
extra_trigger: 'body:has(.js_product:contains(Warranty) a:contains(Add to Cart):hidden)',
|
||||
trigger: '.modal-footer a:contains("Proceed to Checkout")',
|
||||
},
|
||||
{
|
||||
content: "check quantity",
|
||||
trigger: '.my_cart_quantity:containsExact(2),.o_extra_menu_items .fa-plus',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "check optional product",
|
||||
trigger: '.optional_product',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "remove large cabinet from cart",
|
||||
trigger: '#cart_products a.js_add_cart_json:first',
|
||||
},
|
||||
{
|
||||
content: "check optional product is removed",
|
||||
trigger: '#wrap:not(:has(.optional_product))',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "click on shop",
|
||||
trigger: "a:contains(Continue Shopping)",
|
||||
extra_trigger: 'body:not(:has(#products_grid_before .js_attributes))',
|
||||
},
|
||||
{
|
||||
content: "open customize menu bis",
|
||||
extra_trigger: '#products_grid_before .js_attributes',
|
||||
trigger: '#customize-menu > a',
|
||||
},
|
||||
{
|
||||
content: "remove 'Product Attribute's Filters'",
|
||||
trigger: "#customize-menu a:contains(Product Attribute's Filters):has(input:checked)",
|
||||
},
|
||||
{
|
||||
content: "finish",
|
||||
extra_trigger: 'body:not(:has(#products_grid_before .js_attributes))',
|
||||
trigger: '#wrap:not(:has(li:has(.my_cart_quantity):visible))',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
@@ -29,66 +29,10 @@
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.td-product_name,
|
||||
h1[itemprop="name"],
|
||||
.oe_product_cart form h5 {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.td-product_name {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.td-img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.td-qty {
|
||||
width: 130px;
|
||||
a.input-group-addon {
|
||||
background-color: transparent;
|
||||
border: 0px;
|
||||
}
|
||||
}
|
||||
.td-action {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.td-price,
|
||||
.td-price-total {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.td-img,
|
||||
.td-price-total {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.td-qty {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.td-price {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 476px) {
|
||||
.td-qty {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
#modal_optional_products table thead,
|
||||
.oe_cart table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#modal_optional_products table td.td-img,
|
||||
.oe_cart table td.td-img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ---- Default Styles ---- */
|
||||
|
||||
@@ -483,67 +427,6 @@ div#payment_method {
|
||||
}
|
||||
/* product detail */
|
||||
|
||||
.css_attribute_color {
|
||||
display: inline-block;
|
||||
border: 1px solid #999999;
|
||||
text-align: center;
|
||||
|
||||
input {
|
||||
margin: 8px;
|
||||
height: 13px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border: 3px ridge #66ee66;
|
||||
}
|
||||
|
||||
&.active input {
|
||||
margin: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.css_not_available_msg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css_not_available.js_product {
|
||||
> *:nth-child(4) > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css_quantity,
|
||||
.product_price {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css_not_available_msg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.css_quantity {
|
||||
max-width: 125px;
|
||||
|
||||
input[name="add_qty"] {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
option.css_not_available {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
label.css_not_available {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
label.css_attribute_color.css_not_available {
|
||||
opacity: 1;
|
||||
background-image: url("/website_sale/static/src/img/redcross.png");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.product_detail_img {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
@@ -551,14 +434,6 @@ label.css_attribute_color.css_not_available {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.oe_default_price {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.discount .oe_default_price {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.oe_website_sale input.js_quantity {
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
|
||||
+1
-6
@@ -16,9 +16,4 @@ div#modal_optional_products table tr td {
|
||||
div#modal_optional_products .td-qty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.oe_striked_price {
|
||||
text-decoration: line-through;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
-4
@@ -6,8 +6,4 @@ import odoo.tests
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
|
||||
def test_01_admin_shop_customize_tour(self):
|
||||
self.env.ref('product.group_product_variant').write({
|
||||
'users': [(4, self.env.ref('base.user_admin').id)]
|
||||
})
|
||||
|
||||
self.phantom_js("/", "odoo.__DEBUG__.services['web_tour.tour'].run('shop_customize')", "odoo.__DEBUG__.services['web_tour.tour'].tours.shop_customize.ready", login="admin")
|
||||
@@ -28,35 +28,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_line_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.line.form.inherit</field>
|
||||
<field name="model">product.attribute.line</field>
|
||||
<field name="inherit_id" ref="product.product_attribute_line_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='value_ids']/tree/field[@name='name']" position="after">
|
||||
<field name="html_color"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_view_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.form.inherit</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="inherit_id" ref="product.product_attribute_view_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='main_fields']" position="after">
|
||||
<group>
|
||||
<field name="type"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='value_ids']/tree/field[@name='name']" position="after">
|
||||
<field name="html_color"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_pricelist_view">
|
||||
<field name="name">product.pricelist.website.form</field>
|
||||
<field name="model">product.pricelist</field>
|
||||
@@ -154,39 +125,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Product attribute -->
|
||||
|
||||
<record id="product_attribute_value_view_tree_inherit_website_sale" model="ir.ui.view">
|
||||
<field name="name">product.attribute.value.view.tree.inherit.website.sale</field>
|
||||
<field name="model">product.attribute.value</field>
|
||||
<field name="inherit_id" ref="product.product_attribute_value_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="html_color"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record id="variants_tree_view" model="ir.ui.view">
|
||||
<field name="name">variants.template.tree</field>
|
||||
<field name="model">product.attribute.value</field>
|
||||
<field name="inherit_id" ref="product.variants_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="html_color"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record id="attribute_tree_view" model="ir.ui.view">
|
||||
<field name="name">product.attribute.tree.type</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="inherit_id" ref="product.attribute_tree_view"></field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="type"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Product Public Categories -->
|
||||
<record id="product_public_category_form_view" model="ir.ui.view">
|
||||
<field name="name">product.public.category.form</field>
|
||||
|
||||
@@ -59,17 +59,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="sale_option_setting" title="Select optional products from the configuration form of your main products (in Sales tab).">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_sale_options"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_website_sale_options"/>
|
||||
<div class="text-muted">
|
||||
Suggest optional products when adding to cart
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="product_availability_setting">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_sale_stock"/>
|
||||
|
||||
@@ -14,14 +14,22 @@
|
||||
<link rel="stylesheet" type="text/scss" href="/website_sale/static/src/scss/website_sale.scss" />
|
||||
<link rel="stylesheet" type="text/scss" href="/website_sale/static/src/scss/website_mail.scss" />
|
||||
<link rel="stylesheet" type="text/scss" href="/website_sale/static/src/scss/website_sale_frontend.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/sale/static/src/scss/sale_portal.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/sale/static/src/scss/product_configurator.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_sale/static/src/scss/website_sale_options.scss"/>
|
||||
</xpath>
|
||||
<xpath expr="script[last()]" position="after">
|
||||
<script type="text/javascript" src="/sale/static/src/js/product_configurator_mixin.js"></script>
|
||||
<script type="text/javascript" src="/sale/static/src/js/product_configurator_modal.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_utils.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_payment.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_validate.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_tour_buy.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_tracking.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_tour_shop_customize.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_tour_shop_custom_attribute_value.js"></script>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_options.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -149,6 +157,12 @@
|
||||
</a>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('product_price')]/t[1]" position="attributes">
|
||||
<attribute name="t-if">len(product.product_variant_ids) == 1 and not len(product.optional_product_ids) >= 1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('product_price')]/t[2]" position="attributes">
|
||||
<attribute name="t-if">len(product.product_variant_ids) > 1 or len(product.optional_product_ids) >= 1</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="pricelist_list" name="Pricelists Dropdown">
|
||||
@@ -470,10 +484,11 @@
|
||||
<span itemprop="url" style="display:none;" t-esc="'%sshop/product/%s' % (request.httprequest.url_root, slug(product))"/>
|
||||
<form action="/shop/cart/update" method="POST">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
|
||||
<div class="js_product" t-if="product.product_variant_ids">
|
||||
<div class="js_product js_main_product" t-if="product.product_variant_ids">
|
||||
<t t-placeholder="select">
|
||||
<input type="hidden" class="product_id" name="product_id" t-att-value="product.product_variant_id.id if len(product.product_variant_ids) == 1 else '0'" />
|
||||
<t t-call="website_sale.variants">
|
||||
<input type="hidden" class="product_template_id" name="product_template_id" t-att-value="product.id" />
|
||||
<t t-call="sale.variants">
|
||||
<t t-set="ul_class" t-value="'flex-column'" />
|
||||
</t>
|
||||
</t>
|
||||
@@ -602,14 +617,14 @@
|
||||
"display_currency": product.currency_id,
|
||||
}'/>
|
||||
</h4>
|
||||
<h4 class="d-none oe_not_available bg-warning">Product not available</h4>
|
||||
<h4 class="d-none oe_not_available bg-warning">Option not available</h4>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="product_variants" inherit_id="website_sale.product" active="False" customize_show="True" name="List View of Variants">
|
||||
<xpath expr="//t[@t-placeholder='select']" position="replace">
|
||||
<t t-set="attribute_value_ids" t-value="get_attribute_value_ids(product)"/>
|
||||
<ul class="d-none js_add_cart_variants" t-att-data-attribute_value_ids="json.dumps(attribute_value_ids)"/>
|
||||
<t t-set="attribute_exclusions" t-value="get_attribute_exclusions(product, reference_product)"/>
|
||||
<ul class="d-none js_add_cart_variants" t-att-data-attribute_exclusions="json.dumps(attribute_exclusions)"/>
|
||||
<input type="hidden" t-if="len(product.product_variant_ids) == 1" class="product_id" name="product_id" t-att-value="product.product_variant_id.id"/>
|
||||
<t t-if="len(product.product_variant_ids) > 1">
|
||||
<div t-foreach="product.product_variant_ids" t-as="variant_id" class="custom-control custom-radio">
|
||||
@@ -626,75 +641,6 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="variants">
|
||||
<t t-set="attribute_value_ids" t-value="get_attribute_value_ids(product)"/>
|
||||
<ul t-attf-class="list-unstyled js_add_cart_variants #{ul_class}" t-att-data-attribute_value_ids="json.dumps(attribute_value_ids)">
|
||||
<t t-foreach="product.attribute_line_ids.sorted(key=lambda x: x.attribute_id.sequence)" t-as="variant_id">
|
||||
<li t-if="len(variant_id.value_ids) > 1">
|
||||
|
||||
<strong t-field="variant_id.attribute_id.name"/>
|
||||
|
||||
<t t-if="variant_id.attribute_id.type == 'select'">
|
||||
<select t-attf-class="form-control #{'js_variant_change' if variant_id.attribute_id.create_variant else ''}" t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)">
|
||||
<t t-foreach="variant_id.value_ids" t-as="value_id">
|
||||
<option t-att-value="value_id.id">
|
||||
<span t-field="value_id.name"/>
|
||||
<span t-if="value_id.price_extra">
|
||||
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" style="white-space: nowrap;" t-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": product.currency_id,
|
||||
"display_currency": website.currency_id
|
||||
}'/>
|
||||
</span>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
|
||||
<t t-if="variant_id.attribute_id.type == 'radio'">
|
||||
<ul class="list-unstyled">
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<t t-foreach="variant_id.value_ids" t-as="value_id">
|
||||
<li class="form-group js_attribute_value" style="margin: 0;">
|
||||
<label class="col-form-label" style="margin: 0 20px;">
|
||||
<input type="radio" t-att-class="'js_variant_change' if variant_id.attribute_id.create_variant else None" t-att-checked="'checked' if not inc else None" t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)" t-att-value="value_id.id" style="vertical-align: top; margin-right: 10px;"/>
|
||||
<span t-field="value_id.name"/>
|
||||
<span class="badge badge-pill" t-if="value_id.price_extra">
|
||||
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" style="white-space: nowrap;" t-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": product.currency_id,
|
||||
"display_currency": website.currency_id
|
||||
}'/>
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
|
||||
<t t-if="variant_id.attribute_id.type == 'color'">
|
||||
<ul class="list-inline">
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<li t-foreach="variant_id.value_ids" t-as="value_id" class="list-inline-item">
|
||||
<label t-attf-style="background-color:#{value_id.html_color or value_id.name}"
|
||||
t-attf-class="css_attribute_color #{'active' if not inc else ''}">
|
||||
<input type="radio" t-att-class="'js_variant_change' if variant_id.attribute_id.create_variant else None"
|
||||
t-att-checked="'checked' if not inc else None"
|
||||
t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)"
|
||||
t-att-value="value_id.id"
|
||||
t-att-title="value_id.name"/>
|
||||
</label>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template id="wizard_checkout" name="Wizard Checkout">
|
||||
<t t-set="website_sale_order" t-value="website.sale_get_order()"/>
|
||||
|
||||
@@ -840,7 +786,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="website_sale_order.website_order_line" t-as="line">
|
||||
<tr>
|
||||
<tr t-att-class="'optional_product info' if line.linked_line_id else None">
|
||||
<td colspan="2" t-if="not line.product_id.product_tmpl_id" class='td-img'></td>
|
||||
<td align="center" t-if="line.product_id.product_tmpl_id" class='td-img'>
|
||||
<span t-field="line.product_id.image_small" t-options="{'widget': 'image', 'class': 'rounded'}" />
|
||||
@@ -856,7 +802,7 @@
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-center td-qty">
|
||||
<div class="css_quantity input-group oe_website_spinner">
|
||||
<div t-if="not line.linked_line_id" class="css_quantity input-group oe_website_spinner">
|
||||
<div class="input-group-prepend">
|
||||
<a t-attf-href="#" class="btn btn-link js_add_cart_json d-none d-md-inline-block" aria-label="Remove one" title="Remove one">
|
||||
<i class="fa fa-minus"></i>
|
||||
@@ -869,6 +815,9 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="line.linked_line_id">
|
||||
<span class="js_quantity text-muted" t-att-data-line-id="line.id" t-att-data-product-id="line.product_id.id" t-esc="int(line.product_uom_qty)"/>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-center td-price" name="price">
|
||||
<t t-if="(compute_currency(line.product_id.lst_price) - line.price_reduce ) > 0.01 and website.get_current_pricelist().discount_policy=='without_discount'">
|
||||
|
||||
@@ -64,4 +64,8 @@
|
||||
<field name="attribute_id" ref="product_attribute_8"/>
|
||||
<field name="value_ids" eval="[(6,0,[ref('product_attribute_value_8')])]"/>
|
||||
</record>
|
||||
|
||||
<function model="product.template" name="create_variant_ids">
|
||||
<value model="product.template" eval="[ref('product.product_product_6_product_template')]" />
|
||||
</function>
|
||||
</odoo>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<record id="product_attribute_view_form" model="ir.ui.view">
|
||||
<field name="name">product.attribute.form.inherit</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="inherit_id" ref="website_sale.product_attribute_view_form"/>
|
||||
<field name="inherit_id" ref="product.product_attribute_view_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name='name' position='after'>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -1,23 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Optional Products',
|
||||
'category': 'Website',
|
||||
'summary': 'Suggest optional products when adding to cart',
|
||||
'version': '1.0',
|
||||
'description': """
|
||||
Suggest optional products when shoppers add products to their cart (e.g. for computers: warranty, OS software, extra components).
|
||||
Optional products are defined in the product form.
|
||||
""",
|
||||
'depends': ['website_sale'],
|
||||
'data': [
|
||||
'views/product_views.xml',
|
||||
'views/website_sale_options_templates.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/product_demo.xml',
|
||||
],
|
||||
'qweb': ['static/src/xml/*.xml'],
|
||||
'installable': True,
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
@@ -1,85 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, http
|
||||
from odoo.http import request
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
|
||||
|
||||
class WebsiteSaleOptions(WebsiteSale):
|
||||
|
||||
@http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True)
|
||||
def product(self, product, category='', search='', **kwargs):
|
||||
r = super(WebsiteSaleOptions, self).product(product, category, search, **kwargs)
|
||||
r.qcontext['optional_product_ids'] = [p.with_context({'active_id': p.id}) for p in product.optional_product_ids]
|
||||
return r
|
||||
|
||||
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True, multilang=False)
|
||||
def cart_options_update_json(self, product_id, add_qty=1, set_qty=0, goto_shop=None, lang=None, **kw):
|
||||
if lang:
|
||||
request.website = request.website.with_context(lang=lang)
|
||||
|
||||
order = request.website.sale_get_order(force_create=1)
|
||||
product = request.env['product.product'].browse(int(product_id))
|
||||
|
||||
option_ids = product.optional_product_ids.mapped('product_variant_ids').ids
|
||||
optional_product_ids = []
|
||||
for k, v in kw.items():
|
||||
if "optional-product-" in k and int(kw.get(k.replace("product", "add"))) and int(v) in option_ids:
|
||||
optional_product_ids.append(int(v))
|
||||
|
||||
attributes = self._filter_attributes(**kw)
|
||||
|
||||
value = {}
|
||||
if add_qty or set_qty:
|
||||
value = order._cart_update(
|
||||
product_id=int(product_id),
|
||||
add_qty=add_qty,
|
||||
set_qty=set_qty,
|
||||
attributes=attributes,
|
||||
optional_product_ids=optional_product_ids
|
||||
)
|
||||
|
||||
# options have all time the same quantity
|
||||
for option_id in optional_product_ids:
|
||||
order._cart_update(
|
||||
product_id=option_id,
|
||||
set_qty=value.get('quantity'),
|
||||
attributes=attributes,
|
||||
linked_line_id=value.get('line_id')
|
||||
)
|
||||
|
||||
return str(order.cart_quantity)
|
||||
|
||||
@http.route(['/shop/modal'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def modal(self, product_id, **kw):
|
||||
pricelist = request.website.get_current_pricelist()
|
||||
product_context = dict(request.context)
|
||||
quantity = kw['kwargs']['context']['quantity']
|
||||
if not product_context.get('pricelist'):
|
||||
product_context['pricelist'] = pricelist.id
|
||||
# fetch quantity from custom context
|
||||
product_context.update(kw.get('kwargs', {}).get('context', {}))
|
||||
|
||||
from_currency = request.env.user.company_id.currency_id
|
||||
to_currency = pricelist.currency_id
|
||||
company = request.env['res.company'].browse(request.env.context.get('company_id')) or request.env['res.users']._get_company()
|
||||
date = request.env.context.get('date') or fields.Date.today()
|
||||
compute_currency = lambda price: from_currency._convert(
|
||||
price, to_currency, company, date)
|
||||
product = request.env['product.product'].with_context(product_context).browse(int(product_id))
|
||||
|
||||
main_product_attr_ids = self.get_attribute_value_ids(product)
|
||||
for variant in main_product_attr_ids:
|
||||
if variant[0] == product.id:
|
||||
# We indeed need a list of lists (even with only 1 element)
|
||||
main_product_attr_ids = [variant]
|
||||
break
|
||||
|
||||
return request.env['ir.ui.view'].render_template("website_sale_options.modal", {
|
||||
'product': product,
|
||||
'quantity': quantity,
|
||||
'compute_currency': compute_currency,
|
||||
'get_attribute_value_ids': self.get_attribute_value_ids,
|
||||
'main_product_attr_ids': main_product_attr_ids,
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="product_product_1" model="product.product">
|
||||
<field name="name">Warranty</field>
|
||||
<field name="default_code">SERV_125889</field>
|
||||
<field name="list_price">20.0</field>
|
||||
<field name="website_sequence">100</field>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="type">service</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Warranty, issued to the purchaser of an article by its manufacturer, promising to repair or replace it if necessary within a specified period of time.</field>
|
||||
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_5')])]"/>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
</record>
|
||||
<record id="product_attribute_line_1" model="product.attribute.line">
|
||||
<field name="product_tmpl_id" ref="product_product_1_product_template"/>
|
||||
<field name="attribute_id" ref="product.product_attribute_3"/>
|
||||
<field name="value_ids" eval="[(6,0,[ref('product.product_attribute_value_5'), ref('product.product_attribute_value_6')])]"/>
|
||||
</record>
|
||||
<record id="product_product_1_product_template" model="product.template">
|
||||
<field name="attribute_line_ids" eval="[(6,0,[ref('product_attribute_line_1')])]"/>
|
||||
<field name="public_categ_ids" eval="[(6,0,[ref('website_sale.services')])]"/>
|
||||
</record>
|
||||
<record id="product.product_product_4_product_template" model="product.template">
|
||||
<field name="optional_product_ids" eval="[(6,0,[ref('product_product_1_product_template')])]"/>
|
||||
</record>
|
||||
<record id="product_attribute_price_1" model="product.attribute.price">
|
||||
<field name="product_tmpl_id" ref="product_product_1_product_template"/>
|
||||
<field name="value_id" ref="product.product_attribute_value_6"/>
|
||||
<field name="price_extra">18.00</field>
|
||||
</record>
|
||||
|
||||
<!-- -->
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,165 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-08-25 10:29+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: Afrikaans (http://www.transifex.com/odoo/odoo-9/language/"
|
||||
"af/)\n"
|
||||
"Language: af\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duur"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Prys"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Produk"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Produk Profielvorm"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Hoeveelheid"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,164 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-09-08 10:56+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: Amharic (http://www.transifex.com/odoo/odoo-9/language/am/)\n"
|
||||
"Language: am\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "የጊዜ ገደብ"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "ዋጋ"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "እቃ"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "የእቃው ማሳያ"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "ብዛት"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr "ዎስትና"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,159 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Mustafa Rawi <mustafa@cubexco.com>, 2018
|
||||
# hoxhe Aits <hoxhe0@gmail.com>, 2018
|
||||
# Osoul <baruni@osoul.ly>, 2018
|
||||
# Mohammed Albasha <m.albasha.ma@gmail.com>, 2018
|
||||
# Ghaith Gammar <g.gammar@saharaifs.net>, 2018
|
||||
# Martin Trigaux, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~11.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-21 13:18+0000\n"
|
||||
"PO-Revision-Date: 2018-09-21 13:18+0000\n"
|
||||
"Last-Translator: Martin Trigaux, 2018\n"
|
||||
"Language-Team: Arabic (https://www.transifex.com/odoo/teams/41243/ar/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: ar\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Continue Shopping</span><span class=\"d-inline d-md-"
|
||||
"none\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Proceed to Checkout</span><span class=\"d-inline d-md-"
|
||||
"none\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"d-none d-md-inline\"> from Cart</span></small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items d-none\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"d-none d-md-inline\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr "أضف إلى السلة"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Close"
|
||||
msgstr "إقفال"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Continue shopping"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "بند الأمر المقترن"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "منتجات اختيارية"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "الخيارات المقترنة"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "سعر"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Proceed to checkout"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "المنتج"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "قالب المنتج"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "المنتج غير متوفر"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "الكمية"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Remove one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sale Order"
|
||||
msgstr "أمر البيع"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "سطر أمر المبيعات"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "حدد خياراتك:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Suggest optional products when adding the product to a cart."
|
||||
msgstr ""
|
||||
@@ -1,187 +0,0 @@
|
||||
# #-#-#-#-# bg.po (Odoo 9.0) #-#-#-#-#
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Kaloyan Naumov <kaloyan@lumnus.net>, 2016
|
||||
# #-#-#-#-# bg.po (Odoo 9.0) #-#-#-#-#
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-09-08 10:56+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: Bulgarian (http://www.transifex.com/odoo/odoo-9/language/"
|
||||
"bg/)\n"
|
||||
"Language: bg\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"#-#-#-#-# bg.po (Odoo 9.0) #-#-#-#-#\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"#-#-#-#-# bg.po (Odoo 9.0) #-#-#-#-#\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
"%s\n"
|
||||
"Опция за: %s"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr "1 година"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr "2 години"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Продължи "
|
||||
"Пазаруването</span><span class=\"visible-xs-inline\">Продължи</span>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Завърши "
|
||||
"Поръчката</span><span class=\"visible-xs-inline\"> Поръчай</span>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr "Премахни от Количката"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
"<span class=\"js_item\">1 продукт</span><span class=\"js_items hidden\">5 "
|
||||
"продукта</span><br/>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr "<strong>Добави в количката</strong>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Продължителност"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Свързан ред от поръчка"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Опционни Продукти"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Свързани Опции"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Цена"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Продукт"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Шаблон за продукт"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Продуктът не е наличен"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr "Продукти, за да добавяне в Количката"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr "Продукти, за прелаганае при добавяне в количката."
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Количество"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Нареждане за продажба"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Ред от нареждане за продажба"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Изберете Вашите Опции:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr "Гаранция"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
"Гаранция, издаване към купувача от производителя/дистрибутор, обещаваща "
|
||||
"поправка или подмяна в рамките на определен период."
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr "натисни бутон 'Поръчка' в попъп прозореца"
|
||||
@@ -1,155 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Martin Trigaux, 2018
|
||||
# Boško Stojaković <bluesoft83@gmail.com>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~11.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-21 13:18+0000\n"
|
||||
"PO-Revision-Date: 2018-09-21 13:18+0000\n"
|
||||
"Last-Translator: Boško Stojaković <bluesoft83@gmail.com>, 2018\n"
|
||||
"Language-Team: Bosnian (https://www.transifex.com/odoo/teams/41243/bs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: bs\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Continue Shopping</span><span class=\"d-inline d-md-"
|
||||
"none\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Proceed to Checkout</span><span class=\"d-inline d-md-"
|
||||
"none\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"d-none d-md-inline\"> from Cart</span></small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items d-none\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"d-none d-md-inline\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr "Dodaj u korpu"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Close"
|
||||
msgstr "Zatvori"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Continue shopping"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Opcionalni proizvodi"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Cijena"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Proceed to checkout"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Proizvod"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Predlog proizvoda"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Proizvod nije dostupan"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Količina"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Remove one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sale Order"
|
||||
msgstr "Prodajni nalog"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Stavka prodajne narudžbe"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Suggest optional products when adding the product to a cart."
|
||||
msgstr ""
|
||||
@@ -1,163 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Quim - eccit <quim@eccit.com>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~18\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-10-02 11:26+0000\n"
|
||||
"PO-Revision-Date: 2017-10-02 11:26+0000\n"
|
||||
"Last-Translator: Quim - eccit <quim@eccit.com>, 2017\n"
|
||||
"Language-Team: Catalan (https://www.transifex.com/odoo/teams/41243/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: ca\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to Checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"hidden-xs\"> from Cart</span></small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items hidden\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"hidden-xs\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product_optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product_optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid ""
|
||||
"Optional Products are suggested whenever the customer hits *Add to Cart* "
|
||||
"(cross-sell strategy, e.g. for computers: warranty, software, etc.)."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Quotation"
|
||||
msgstr "Pressupost"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línia comanda de venda"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
@@ -1,156 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Martin Trigaux, 2018
|
||||
# Chris <krystof.reklamy13@gmail.com>, 2018
|
||||
# trendspotter, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~11.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-21 13:18+0000\n"
|
||||
"PO-Revision-Date: 2018-09-21 13:18+0000\n"
|
||||
"Last-Translator: trendspotter, 2018\n"
|
||||
"Language-Team: Czech (https://www.transifex.com/odoo/teams/41243/cs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: cs\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Continue Shopping</span><span class=\"d-inline d-md-"
|
||||
"none\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Proceed to Checkout</span><span class=\"d-inline d-md-"
|
||||
"none\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"d-none d-md-inline\"> from Cart</span></small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items d-none\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"d-none d-md-inline\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr "Přidat do košíku"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Close"
|
||||
msgstr "Zavřít"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Continue shopping"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Volitelné produkty"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Cena"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Proceed to checkout"
|
||||
msgstr "Pokračujte k pokladně"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Výrobek"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Šablona výrobku"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Množství"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Remove one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sale Order"
|
||||
msgstr "Prodejní objednávka"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Řádek potvrzení objednávky"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Suggest optional products when adding the product to a cart."
|
||||
msgstr ""
|
||||
@@ -1,173 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Sanne Kristensen <sanne@vkdata.dk>, 2017
|
||||
# Martin Trigaux <mat@odoo.com>, 2017
|
||||
# jonas jensen <j.jensen@tcomp.dk>, 2017
|
||||
# Laerke Mathiasen <lhm@flexerp.dk>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~18\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-10-02 11:26+0000\n"
|
||||
"PO-Revision-Date: 2017-10-02 11:26+0000\n"
|
||||
"Last-Translator: Laerke Mathiasen <lhm@flexerp.dk>, 2017\n"
|
||||
"Language-Team: Danish (https://www.transifex.com/odoo/teams/41243/da/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: da\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr "1 år"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr "2 år"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Fortsæt med at "
|
||||
"handle</span><span class=\"visible-xs-inline\">Fortsæt</span>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to Checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"hidden-xs\"> from Cart</span></small>"
|
||||
msgstr "<small>Fjern <span class=\"hidden-xs\"> fra Indkøbskurv</span></small>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items hidden\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"hidden-xs\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
"<strong>Tilføj<span class=\"hidden-xs\"> til Indkøbskurven</span></strong>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr "Tilføj til kurv"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Varighed"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Tilknyttede ordrelinjer"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr "Valgmulighed for:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr "Valgmulighed:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product_optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Valgfrie produkter"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product_optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid ""
|
||||
"Optional Products are suggested whenever the customer hits *Add to Cart* "
|
||||
"(cross-sell strategy, e.g. for computers: warranty, software, etc.)."
|
||||
msgstr ""
|
||||
"Valgfrie produkter foreslås, når kunden trykker på *Tilføj til kurv* "
|
||||
"(krydssalgs-strategi, f.eks. til computere: garanti, software osv.)."
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Valgmuligheder tilknyttet"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Pris"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Produktskabelon"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Produkt er ikke til rådighed"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Antal"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Quotation"
|
||||
msgstr "Tilbud"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Salgsordrelinje"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Vælg dine muligheder:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr "Garanti"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
"Garanti udstedt til køber af et produkt fra en fabrikant, med garanti på at "
|
||||
"reparere eller udskifte det nødvendige inden for en bestemt tidsperiode."
|
||||
@@ -1,156 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# e2f <projects@e2f.com>, 2018
|
||||
# Martin Trigaux, 2018
|
||||
# Ermin Trevisan <trevi@twanda.com>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~11.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-21 13:18+0000\n"
|
||||
"PO-Revision-Date: 2018-09-21 13:18+0000\n"
|
||||
"Last-Translator: Ermin Trevisan <trevi@twanda.com>, 2018\n"
|
||||
"Language-Team: German (https://www.transifex.com/odoo/teams/41243/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: de\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Continue Shopping</span><span class=\"d-inline d-md-"
|
||||
"none\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Proceed to Checkout</span><span class=\"d-inline d-md-"
|
||||
"none\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"d-none d-md-inline\"> from Cart</span></small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items d-none\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"d-none d-md-inline\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr "In den Warenkorb"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Continue shopping"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Verknüpfte Auftragsposition"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr "Option für:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr "Option:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Optionale Produkte"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Verlinkte Alternativen"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Preis"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Proceed to checkout"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Produktvorlage"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Das Produkt ist nicht verfügbar."
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Menge"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Remove one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sale Order"
|
||||
msgstr "Verkaufsauftrag"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Auftragsposition"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Selektieren Sie Ihre Alternativen"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Suggest optional products when adding the product to a cart."
|
||||
msgstr ""
|
||||
@@ -1,156 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Martin Trigaux, 2018
|
||||
# Kostas Goutoudis <goutoudis@gmail.com>, 2018
|
||||
# George Tarasidis <george_tarasidis@yahoo.com>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~11.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-21 13:18+0000\n"
|
||||
"PO-Revision-Date: 2018-09-21 13:18+0000\n"
|
||||
"Last-Translator: George Tarasidis <george_tarasidis@yahoo.com>, 2018\n"
|
||||
"Language-Team: Greek (https://www.transifex.com/odoo/teams/41243/el/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: el\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Continue Shopping</span><span class=\"d-inline d-md-"
|
||||
"none\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Proceed to Checkout</span><span class=\"d-inline d-md-"
|
||||
"none\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"d-none d-md-inline\"> from Cart</span></small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items d-none\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"d-none d-md-inline\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr "Προσθήκη στο Καλάθι"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Close"
|
||||
msgstr "Κλείσιμο"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Continue shopping"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Συνδεδεμένη Γραμμή Εντολής"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr "Επιλογή για:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr "Επιλογή:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Προαιρετικά Είδη"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Συνδεδεμένες Επιλογές"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "ΤΙΜΗ"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Proceed to checkout"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Είδος"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Πρότυπο Είδους "
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Μη Διαθέσιμο Είδος"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Ποσότητα"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Remove one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sale Order"
|
||||
msgstr "Παραγγελία"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Γραμμή Παραγγελίας"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Επιλέξτε τις επιλογές σας:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Suggest optional products when adding the product to a cart."
|
||||
msgstr ""
|
||||
@@ -1,165 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-09-08 10:56+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: English (Australia) (http://www.transifex.com/odoo/odoo-9/"
|
||||
"language/en_AU/)\n"
|
||||
"Language: en_AU\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duration"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Sales Order"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Sales Order Line"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,165 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-09-08 10:56+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: English (United Kingdom) (http://www.transifex.com/odoo/"
|
||||
"odoo-9/language/en_GB/)\n"
|
||||
"Language: en_GB\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duration"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Price"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Product"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Product Template"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Quantity"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Sales Order"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Sales Order Line"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,156 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Martin Trigaux, 2018
|
||||
# Gelo Joga Landoo <gj@landoo.es>, 2018
|
||||
# Nicolás Broggi <rnbroggi@gmail.com>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~11.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-21 13:18+0000\n"
|
||||
"PO-Revision-Date: 2018-09-21 13:18+0000\n"
|
||||
"Last-Translator: Nicolás Broggi <rnbroggi@gmail.com>, 2018\n"
|
||||
"Language-Team: Spanish (https://www.transifex.com/odoo/teams/41243/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Continue Shopping</span><span class=\"d-inline d-md-"
|
||||
"none\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"d-none d-md-"
|
||||
"inline\">Proceed to Checkout</span><span class=\"d-inline d-md-"
|
||||
"none\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove <span class=\"d-none d-md-inline\"> from Cart</span></small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<span class=\"js_item\">1 </span><span class=\"js_items d-none\">5 </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add<span class=\"d-none d-md-inline\"> to Cart</span></strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Add to Cart"
|
||||
msgstr "Añadir al Carrito"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Close"
|
||||
msgstr "Cerrar"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Continue shopping"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Línea de pedido de venta relacionada"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:46
|
||||
#, python-format
|
||||
msgid "Option for:"
|
||||
msgstr "Opción para:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:48
|
||||
#, python-format
|
||||
msgid "Option:"
|
||||
msgstr "Oprción:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Productos opcionales"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line__option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Opciones relacionadas"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Proceed to checkout"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla de producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Producto no disponible"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Remove one"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sale Order"
|
||||
msgstr "Pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línea de pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model_terms:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Seleccione sus opciones:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_product__optional_product_ids
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template__optional_product_ids
|
||||
msgid "Suggest optional products when adding the product to a cart."
|
||||
msgstr ""
|
||||
@@ -1,170 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-09-12 00:42+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: Spanish (Argentina) (http://www.transifex.com/odoo/odoo-9/"
|
||||
"language/es_AR/)\n"
|
||||
"Language: es_AR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
"%s\n"
|
||||
"Opción para: %s"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duración"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Línea de pedido de venta relacionada"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Productos opcionales"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Opciones relacionadas"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla de Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Producto no disponible"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr "Producto a añadir en su carro"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Orden de Ventas"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línea de Pedido de Ventas"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Seleccione sus opciones:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr "Garantía"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
"Garantía, emitida al comprador de un artículo por su fabricante, con la "
|
||||
"promesa de repararlo, o sustituirlo si es necesario, en un plazo determinado "
|
||||
"de tiempo."
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,165 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-08-25 10:29+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: Spanish (Bolivia) (http://www.transifex.com/odoo/odoo-9/"
|
||||
"language/es_BO/)\n"
|
||||
"Language: es_BO\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duración"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla de producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Pedido de Venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,165 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-09-08 10:57+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: Spanish (Chile) (http://www.transifex.com/odoo/odoo-9/"
|
||||
"language/es_CL/)\n"
|
||||
"Language: es_CL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línea de pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,177 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Mateo Tibaquirá <nestormateo@gmail.com>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-11-28 00:07+0000\n"
|
||||
"Last-Translator: Mateo Tibaquirá <nestormateo@gmail.com>\n"
|
||||
"Language-Team: Spanish (Colombia) (http://www.transifex.com/odoo/odoo-9/"
|
||||
"language/es_CO/)\n"
|
||||
"Language: es_CO\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
"%s\n"
|
||||
"Opción para: %s"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr "1 año"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr "2 años"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continuar "
|
||||
"Comprando</span><span class=\"visible-xs-inline\">Continuar</span>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceder "
|
||||
"con el pago</span><span class=\"visible-xs-inline\">Pagar</span>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr "<small>Remover del carrito</small>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
"<span class=\"js_item\">1 elemento</span><span class=\"js_items hidden\">5 "
|
||||
"elementos</span><br/>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr "<strong>Añadir al Carrito</strong>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duración"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Línea de Orden Enlazada"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Productos Opcionales"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Opciones Enlazadas"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla de Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Producto no disponible"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr "Producto a añadir en su carrito de compra"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr "Productos a proponer cuando añada al carrito."
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Pedido de Venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línea Orden de Venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Seleccione Sus Opciones:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr "Garantía"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
"Garantía, emitida al comprador de un artículo por su fabricante, con la "
|
||||
"promesa de reparar o sustituir si es necesario en un plazo determinado de "
|
||||
"tiempo."
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr "haga clic en el botón 'Proceder con el pago'"
|
||||
@@ -1,165 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-09-08 10:57+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: Spanish (Costa Rica) (http://www.transifex.com/odoo/odoo-9/"
|
||||
"language/es_CR/)\n"
|
||||
"Language: es_CR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duración"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla de producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línea pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,165 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2015-12-22 17:35+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: Spanish (Dominican Republic) (http://www.transifex.com/odoo/"
|
||||
"odoo-9/language/es_DO/)\n"
|
||||
"Language: es_DO\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duración"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línea pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr "Garantía"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr ""
|
||||
@@ -1,178 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * website_sale_options
|
||||
#
|
||||
# Translators:
|
||||
# Antonio Trueba, 2016
|
||||
# Rick Hunter <rick_hunter_ec@yahoo.com>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-08-18 14:08+0000\n"
|
||||
"PO-Revision-Date: 2016-01-23 05:08+0000\n"
|
||||
"Last-Translator: Rick Hunter <rick_hunter_ec@yahoo.com>\n"
|
||||
"Language-Team: Spanish (Ecuador) (http://www.transifex.com/odoo/odoo-9/"
|
||||
"language/es_EC/)\n"
|
||||
"Language: es_EC\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: code:addons/website_sale_options/models/sale_order.py:44
|
||||
#: code:addons/website_sale_options/models/sale_order.py:56
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s\n"
|
||||
"Option for: %s"
|
||||
msgstr ""
|
||||
"%s\n"
|
||||
"Opción para: %s"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_1
|
||||
msgid "1 year"
|
||||
msgstr "1 año"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute.value,name:website_sale_options.product_attribute_value_2
|
||||
msgid "2 year"
|
||||
msgstr "2 años"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continue "
|
||||
"Shopping</span><span class=\"visible-xs-inline\">Continue</span>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-chevron-left\"/> <span class=\"hidden-xs\">Continuar "
|
||||
"comprando</span><span class=\"visible-xs-inline\">Continuar</span>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceed "
|
||||
"to checkout</span><span class=\"visible-xs-inline\">Checkout</span>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-shopping-cart fa-fw\"/> <span class=\"hidden-xs\">Proceder "
|
||||
"al pago</span><span class=\"visible-xs-inline\">Revisar</span>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<small>Remove from cart</small>"
|
||||
msgstr "<small>Eliminar del carro</small>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid ""
|
||||
"<span class=\"js_item\">1 Item</span><span class=\"js_items hidden\">5 "
|
||||
"Items</span><br/>"
|
||||
msgstr ""
|
||||
"<span class=\"js_item\">1 elemento</span><span class=\"js_items hidden\">5 "
|
||||
"elementos</span><br/>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "<strong>Add to Cart</strong>"
|
||||
msgstr "<strong>Añadir al Carrito</strong>"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.attribute,name:website_sale_options.product_attribute_1
|
||||
msgid "Duration"
|
||||
msgstr "Duración"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_linked_line_id
|
||||
msgid "Linked Order Line"
|
||||
msgstr "Línea de pedido de venta relacionada"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Optional Products"
|
||||
msgstr "Productos opcionales"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,field_description:website_sale_options.field_sale_order_line_option_line_ids
|
||||
msgid "Options Linked"
|
||||
msgstr "Opciones relacionadas"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla producto"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product not available"
|
||||
msgstr "Producto no disponible"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Product to add in your shopping cart"
|
||||
msgstr "Producto a añadir en su carro"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model.fields,help:website_sale_options.field_product_template_optional_product_ids
|
||||
msgid "Products to propose when add to cart."
|
||||
msgstr "Productos a añadir en su carro"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Aviso para pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.model,name:website_sale_options.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Línea pedido de venta"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:ir.ui.view,arch_db:website_sale_options.modal
|
||||
msgid "Select Your Options:"
|
||||
msgstr "Seleccione sus opciones:"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,name:website_sale_options.product_product_1
|
||||
#: model:product.product,name:website_sale_options.service_cost_01
|
||||
#: model:product.template,name:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,name:website_sale_options.service_cost_01_product_template
|
||||
msgid "Warranty"
|
||||
msgstr "Garantía"
|
||||
|
||||
#. module: website_sale_options
|
||||
#: model:product.product,description_sale:website_sale_options.product_product_1
|
||||
#: model:product.product,description_sale:website_sale_options.service_cost_01
|
||||
#: model:product.template,description_sale:website_sale_options.product_product_1_product_template
|
||||
#: model:product.template,description_sale:website_sale_options.service_cost_01_product_template
|
||||
msgid ""
|
||||
"Warranty, issued to the purchaser of an article by its manufacturer, "
|
||||
"promising to repair or replace it if necessary within a specified period of "
|
||||
"time."
|
||||
msgstr ""
|
||||
"Garantía, emitida al comprador de un artículo por su fabricante, con la "
|
||||
"promesa de repararlo, o sustituirlo si es necesario, en un plazo determinado "
|
||||
"de tiempo."
|
||||
|
||||
#. module: website_sale_options
|
||||
#. openerp-web
|
||||
#: code:addons/website_sale_options/static/src/js/website_sale.test.js:13
|
||||
#, python-format
|
||||
msgid "click in modal on 'Proceed to checkout' button"
|
||||
msgstr "haga clic en modal en el botón 'Proceder al Pago'"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user