diff --git a/product_configurator/__manifest__.py b/product_configurator/__manifest__.py index 4f75285a..dd241883 100755 --- a/product_configurator/__manifest__.py +++ b/product_configurator/__manifest__.py @@ -24,6 +24,7 @@ 'demo/product_attribute.xml', 'demo/product_config_domain.xml', 'demo/product_config_lines.xml', + 'demo/product_config_defaults.xml', 'demo/product_config_step.xml', 'demo/config_image_ids.xml', ], diff --git a/product_configurator/demo/product_config_defaults.xml b/product_configurator/demo/product_config_defaults.xml new file mode 100644 index 00000000..a305ccc7 --- /dev/null +++ b/product_configurator/demo/product_config_defaults.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/product_configurator/i18n/fr_CA.po b/product_configurator/i18n/fr_CA.po new file mode 100644 index 00000000..f263aa09 --- /dev/null +++ b/product_configurator/i18n/fr_CA.po @@ -0,0 +1,704 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_configurator +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-05-30 20:00+0000\n" +"PO-Revision-Date: 2017-05-30 16:13-0400\n" +"Last-Translator: Stéphane Le Cornec \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: fr_CA\n" +"X-Generator: Poedit 1.8.7\n" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_active +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_active +msgid "Active" +msgstr "Actif" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_val_custom +msgid "Allow custom value for this attribute?" +msgstr "Autoriser une valeur personnalisée pour cette caractéristique?" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_custom +msgid "Allow custom values for this attribute?" +msgstr "Permettre des valeurs personnalisées pour cette caractéristique?" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_multi +#: model:ir.model.fields,help:product_configurator.field_product_attribute_multi +msgid "Allow selection of multiple values for this attribute?" +msgstr "Permettre la sélection de plusieurs valeurs pour cette caractéristique?" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "Attachment" +msgstr "Pièce Jointe" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_attachment_ids +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_attachment_ids +msgid "Attachments" +msgstr "Pièces jointes" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_attribute_id +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_attribute_id +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_attribute_id +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_custom_tree +msgid "Attribute" +msgstr "Caractéristique" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_product_config_line_ids +#: model:ir.model.fields,field_description:product_configurator.field_product_template_config_line_ids +msgid "Attribute Dependencies" +msgstr "Dépendances de la caractéristique" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attribute_line_id +msgid "Attribute Line" +msgstr "Ligne de caractéristique" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_attribute_line_ids +msgid "Attribute Lines" +msgstr "Lignes de caractéristique" + +#. module: product_configurator +#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view +msgid "Attribute Value Dependencies" +msgstr "Dépendances de valeur de caractéristique" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attr_line_val_ids +msgid "Attribute Values" +msgstr "Valeurs de caractéristique" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_config.py:510 +#, python-format +msgid "Attribute custom type is binary, attachments are the only accepted values with this custom field type" +msgstr "Caractéristique binaire, les pièces jointes sont les seules valeurs acceptées avec ce type de champ personnalisé" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_config.py:515 +#, python-format +msgid "Attribute custom type must be 'binary' for saving attachments to custom value" +msgstr "La caractéristique doit être binaire pour l’enregistrement des pièces jointes au champ personnalisé" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_value_active +msgid "By unchecking the active field you can disable a attribute value without deleting it" +msgstr "En décochant le champ actif, vous pouvez désactiver une valeur de caractéristique sans le supprimer" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_active +msgid "By unchecking the active field you can disable a attribute without deleting it" +msgstr "En décochant le champ actif, vous pouvez désactiver une caractéristique sans le supprimer" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_product_config_ok +#: model:ir.model.fields,field_description:product_configurator.field_product_template_config_ok +msgid "Can be Configured" +msgstr "Paramétrables." + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_create_on_fly +msgid "Can create value on the fly" +msgstr "Peut créer de la valeur à la volée" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_config.py:269 +#, python-format +msgid "Cannot have a configuration step defined twice." +msgstr "Ne peut pas avoir une étape de configuration défini deux fois." + +#. module: product_configurator +#: sql_constraint:product.attribute.value.custom:0 +msgid "Cannot have two custom values for the same attribute" +msgstr "Ne peut pas avoir deux valeurs personnalisées pour la même caractéristique" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "Char" +msgstr "Char" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "Color" +msgstr "Couleur" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_condition +msgid "Condition" +msgstr "Condition" + +#. module: product_configurator +#: model:ir.ui.menu,name:product_configurator.menu_product_configurable +msgid "Configurable Products" +msgstr "Articles configurables" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_product_tmpl_id +msgid "Configurable Template" +msgstr "Modèle configurable" + +#. module: product_configurator +#: model:ir.actions.act_window,name:product_configurator.product_configurable_template_action +#: model:ir.ui.menu,name:product_configurator.menu_product_configurable_template_action +msgid "Configurable Templates" +msgstr "Modèles configurables" + +#. module: product_configurator +#: model:ir.ui.menu,name:product_configurator.menu_product_configurable_variants_action +msgid "Configurable Variants" +msgstr "Variantes configurables" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_value_ids +msgid "Configuration" +msgstr "Configuration" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_product_config_image_ids +#: model:ir.model.fields,field_description:product_configurator.field_product_template_config_image_ids +#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view +msgid "Configuration Images" +msgstr "Configuration des Images" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_product_config_step_line_ids +#: model:ir.model.fields,field_description:product_configurator.field_product_template_config_step_line_ids +msgid "Configuration Lines" +msgstr "Lignes de configuration" + +#. module: product_configurator +#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view +msgid "Configuration Options" +msgstr "Options de configuration" + +#. module: product_configurator +#: model:ir.actions.act_window,name:product_configurator.product_config_domain_action +#: model:ir.ui.menu,name:product_configurator.menu_product_config_domain_action +#: model:ir.ui.view,arch_db:product_configurator.product_config_domain_form_view +#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view +msgid "Configuration Restrictions" +msgstr "Restrictions de configuration" + +#. module: product_configurator +#: model:ir.actions.act_window,name:product_configurator.product_config_session +#: model:ir.ui.menu,name:product_configurator.menu_product_config_session +#: model:ir.ui.view,arch_db:product_configurator.product_config_session_form_view +#: model:ir.ui.view,arch_db:product_configurator.product_config_session_tree_view +msgid "Configuration Sessions" +msgstr "Sessions de configuration" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_config_step_id +#: model:ir.ui.view,arch_db:product_configurator.config_step_form_view +#: model:ir.ui.view,arch_db:product_configurator.config_step_tree_view +msgid "Configuration Step" +msgstr "Étape de configuration" + +#. module: product_configurator +#: model:ir.actions.act_window,name:product_configurator.product_config_steps_action +#: model:ir.ui.menu,name:product_configurator.menu_product_config_steps_action +#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view +msgid "Configuration Steps" +msgstr "Étapes de configuration" + +#. module: product_configurator +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_custom_form +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_custom_tree +msgid "Configuration Values" +msgstr "Valeurs de configuration" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_config.py:491 +#, python-format +msgid "Configuration cannot have the same value inserted twice" +msgstr "Configuration ne peut pas avoir la même valeur insérée deux fois" + +#. module: product_configurator +#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view +msgid "Configurator" +msgstr "Configurateur" + +#. module: product_configurator +#: model:ir.actions.act_window,name:product_configurator.product_configurable_variant_action +msgid "Configured Variants" +msgstr "Variantes configurées" + +#. module: product_configurator +#: code:addons/product_configurator/models/product.py:509 +#, python-format +msgid "Could not convert custom value '%s' to '%s' on product variant: '%s'" +msgstr "Pas pu convertir la valeur personnalisée « %s » à « %s » sur la variante de l'article: « %s »" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_create_uid +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_create_uid +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_create_date +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_create_date +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_create_date +msgid "Created on" +msgstr "Créé le" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_line_custom +#: model:product.attribute,name:product_configurator.custom_attribute +#: model:product.attribute.value,name:product_configurator.custom_attribute_value +msgid "Custom" +msgstr "Personnalisé" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_val_custom +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_value +msgid "Custom Value" +msgstr "Valeur personnalisé" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_ids +#: model:ir.model.fields,field_description:product_configurator.field_product_product_value_custom_ids +#: model:ir.model.fields,field_description:product_configurator.field_sale_order_line_custom_value_ids +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view +#: model:ir.ui.view,arch_db:product_configurator.product_config_session_form_view +#: model:ir.ui.view,arch_db:product_configurator.product_form_view_custom_vals_inherit +msgid "Custom Values" +msgstr "Valeurs personnalisées" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_config_session_custom_value_value +msgid "Custom value held as string" +msgstr "Valeur personnalisé en texte" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "Date" +msgstr "Date" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "DateTime" +msgstr "DateTime" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_description +msgid "Description" +msgstr "Description" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_required +msgid "Determines the required value of this attribute though it can be change on the template level" +msgstr "Détermine la valeur requise de cette caractéristique, mais il peut être le changement au niveau du modèle" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_display_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_display_name +msgid "Display Name" +msgstr "Afficher le nom" + +#. module: product_configurator +#: selection:product.config.session,state:0 +msgid "Done" +msgstr "Terminé" + +#. module: product_configurator +#: selection:product.config.session,state:0 +msgid "Draft" +msgstr "Brouillon" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_custom_type +msgid "Field Type" +msgstr "Type du Champ" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "Float" +msgstr "Float" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_id +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_id +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_id +msgid "ID" +msgstr "ID" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_image +#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_image +msgid "Image" +msgstr "Image" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_implied_ids +#: model:ir.ui.view,arch_db:product_configurator.product_config_domain_form_view +msgid "Inherited" +msgstr "Hérité" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "Integer" +msgstr "Integer" + +#. module: product_configurator +#: code:addons/product_configurator/models/product.py:347 +#: code:addons/product_configurator/models/product_config.py:439 +#, python-format +msgid "Invalid Configuration" +msgstr "Configuration non valide" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_account_invoice_line +msgid "Invoice Line" +msgstr "ligne de facturation" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_required +msgid "Is this attribute required?" +msgstr "Caractéristique requise?" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_image___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step___last_update +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line___last_update +msgid "Last Modified on" +msgstr "Dernière Modification le" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_write_uid +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_write_uid +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_write_date +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_write_date +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_max_val +msgid "Max Value" +msgstr "Valeur maximale" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_min_val +msgid "Min Value" +msgstr "Valeur minimale" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_max_val +#: model:ir.model.fields,help:product_configurator.field_product_attribute_min_val +msgid "Minimum value allowed" +msgstr "Valeur minimale permise" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_line_multi +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_multi +msgid "Multi" +msgstr "Multi" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_name +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_name +#: model:ir.model.fields,field_description:product_configurator.field_product_product_config_name +msgid "Name" +msgstr "Nom" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_operator +msgid "Operators" +msgstr "Opérateurs" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_price +msgid "Price" +msgstr "Prix" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_product +#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_product_tmpl_id +msgid "Product" +msgstr "Article" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_attribute +msgid "Product Attribute" +msgstr "Caractéristique de l'article" + +#. module: product_configurator +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_form_view +msgid "Product Attribute Values" +msgstr "Valeurs de caractéristique de l'article" + +#. module: product_configurator +#: model:ir.module.category,name:product_configurator.product_config_category +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view +msgid "Product Configurator" +msgstr "Configurateur de produits" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_product_id +msgid "Product ID" +msgstr "Article ID" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_template +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_product_tmpl_id +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_product_tmpl_id +msgid "Product Template" +msgstr "Modèle de produit" + +#. module: product_configurator +#: model:res.groups,name:product_configurator.group_product_configurator +msgid "Products" +msgstr "Articles" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_product_id +msgid "Related Product" +msgstr "Articles connexes" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_line_required +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_required +msgid "Required" +msgstr "Requis" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_domain_line_ids +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_domain_id +msgid "Restrictions" +msgstr "Restrictions" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_product_reuse_variant +#: model:ir.model.fields,field_description:product_configurator.field_product_template_reuse_variant +msgid "Reuse variants if exists" +msgstr "Réutiliser les variantes qui existent" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_domain_id +msgid "Rule" +msgstr "Règle" + +#. module: product_configurator +#: model:ir.ui.view,arch_db:product_configurator.product_config_domain_form_view +msgid "Rules" +msgstr "Règles" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_sale_order_line +msgid "Sales Order Line" +msgstr "Ligne de bons de commande" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_search_ok +msgid "Searchable" +msgstr "Consultable" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_attribute.py:108 +#, python-format +msgid "Selected custom field type '%s' is not searchable" +msgstr "Type de champ personnalisé sélectionné « %s » n’est pas consultable" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_attribute.py:129 +#, python-format +msgid "Selected custom value '%s' must be at least %s" +msgstr "Valeur personnalisée sélectionnée « %s » doit être au moins %s" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_attribute.py:124 +#, python-format +msgid "Selected custom value '%s' must be between %s and %s" +msgstr "Valeur personnalisée sélectionnée « %s » doit être comprise entre %s et %s" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_attribute.py:134 +#, python-format +msgid "Selected custom value '%s' must be lower than %s" +msgstr "Valeur personnalisée sélectionnée « %s » doit être inférieure à %s" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_line_sequence +#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_sequence +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_sequence +#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_sequence +msgid "Sequence" +msgstr "Séquence" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_cfg_session_id +msgid "Session" +msgstr "Session" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_state +msgid "State" +msgstr "État" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_stock_move +msgid "Stock Move" +msgstr "Mouvement de stock" + +#. module: product_configurator +#: selection:product.attribute,custom_type:0 +msgid "Textarea" +msgstr "Textarea" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_custom_type +msgid "The type of the custom field generated in the frontend" +msgstr "Le type de champ personnalisé généré dans le frontend" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_trans_implied_ids +msgid "Transitively inherits" +msgstr "Hérite transitivement" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_uom_id +msgid "Unit of Measure" +msgstr "Unité de mesure" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_user_id +msgid "User" +msgstr "Utilisateur" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_create_on_fly +msgid "User can create new value with product configurator" +msgstr "Utilisateur peut créer de nouvelle valeur avec le configurateur de produit" + +#. module: product_configurator +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view +msgid "Validation" +msgstr "Validation" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_value +#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_form_view +msgid "Value" +msgstr "Valeur" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_value_ids +msgid "Value ids" +msgstr "Value ids" + +#. module: product_configurator +#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_value_ids +#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_value_ids +msgid "Values" +msgstr "Valeurs" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_config.py:214 +#, python-format +msgid "Values entered for line '%s' generate a incompatible configuration" +msgstr "Les valeurs entrées pour la ligne « %s » génèrent une configuration incompatible" + +#. module: product_configurator +#: code:addons/product_configurator/models/product_config.py:177 +#, python-format +msgid "Values must belong to the attribute of the corresponding attribute_line set on the configuration line" +msgstr "Les valeurs doivent appartenir à la caractéristique correspondante sur la ligne de configuration" + +#. module: product_configurator +#: model:ir.model.fields,help:product_configurator.field_product_attribute_search_ok +msgid "When checking for variants with the same configuration, do we include this field in the search?" +msgstr "Lors de la vérification des variantes avec la même configuration, devons-nous inclure ce champ dans la recherche ?" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_attribute_line +msgid "product.attribute.line" +msgstr "product.attribute.line" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_attribute_value +msgid "product.attribute.value" +msgstr "product.attribute.value" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_attribute_value_custom +msgid "product.attribute.value.custom" +msgstr "product.attribute.value.custom" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_domain +msgid "product.config.domain" +msgstr "product.config.domain" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_domain_line +msgid "product.config.domain.line" +msgstr "product.config.domain.line" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_image +msgid "product.config.image" +msgstr "product.config.image" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_line +msgid "product.config.line" +msgstr "product.config.line" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_session +msgid "product.config.session" +msgstr "product.config.session" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_session_custom_value +msgid "product.config.session.custom.value" +msgstr "product.config.session.custom.value" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_step +msgid "product.config.step" +msgstr "product.config.step" + +#. module: product_configurator +#: model:ir.model,name:product_configurator.model_product_config_step_line +msgid "product.config.step.line" +msgstr "product.config.step.line" diff --git a/product_configurator/models/account.py b/product_configurator/models/account.py index 7d8d2a60..d8ec99b6 100644 --- a/product_configurator/models/account.py +++ b/product_configurator/models/account.py @@ -6,4 +6,4 @@ class AccountInvoiceLine(models.Model): _inherit = 'account.invoice.line' - product_id = fields.Many2one(domain=[('config_ok', '=', False)]) + product_id = fields.Many2one(domain=['|', ('reuse_variant', '=', True), ('config_ok', '=', False)]) diff --git a/product_configurator/models/product.py b/product_configurator/models/product.py index 9a6bfe56..7f48d674 100644 --- a/product_configurator/models/product.py +++ b/product_configurator/models/product.py @@ -11,12 +11,20 @@ class ProductTemplate(models.Model): config_ok = fields.Boolean(string='Can be Configured') + reuse_variant = fields.Boolean(string='Reuse variants if exists') + config_line_ids = fields.One2many( comodel_name='product.config.line', inverse_name='product_tmpl_id', string="Attribute Dependencies" ) + config_default_ids = fields.One2many( + comodel_name='product.config.default', + inverse_name='product_tmpl_id', + string="Attribute Defaults" + ) + config_image_ids = fields.One2many( comodel_name='product.config.image', inverse_name='product_tmpl_id', @@ -439,6 +447,44 @@ def values_available(self, attr_val_ids, sel_val_ids): return avail_val_ids + @api.multi + def find_default_value(self, selectable_value_ids, value_ids): + """Based on the current values, which of the available template value ids + is the best default value to use. + + :param selectable_value_ids: list of product.attribute.value + object already trimmed down as selectable, for one + attribute line. + :param value_ids: list of attribute value ids already chosen + + :returns: The first matched default id + + """ + self.ensure_one() + + if not selectable_value_ids: + return False + # assume all values are from the same attribute line - they should be! + default_lines = self.config_default_ids.filtered( + lambda l: set(l.value_ids.ids) & set(selectable_value_ids) + ) + + for default_line in default_lines: + if not default_line.domain_id: + # No domain - always considered true. Use this. + break + domains = default_line.mapped('domain_id').compute_domain() + if self.validate_domains_against_sels(domains, value_ids): + # Domain OK, use this + break + else: + # parsed all lines without a match + return False + # pick one at random... + return ( + set(default_line.value_ids.ids) & set(selectable_value_ids) + ).pop() + @api.multi def validate_configuration(self, value_ids, custom_vals=None, final=True): """ Verifies if the configuration values passed via value_ids and custom_vals diff --git a/product_configurator/models/product_attribute.py b/product_configurator/models/product_attribute.py index 9ec1dba5..b9684bd4 100644 --- a/product_configurator/models/product_attribute.py +++ b/product_configurator/models/product_attribute.py @@ -90,6 +90,11 @@ def onchange_custom_type(self): image = fields.Binary(string='Image') + create_on_fly = fields.Boolean( + string='Can create value on the fly', + help='User can create new value with product configurator' + ) + # TODO prevent the same attribute from being defined twice on the # attribute lines @@ -167,6 +172,27 @@ def onchange_attribute(self): class ProductAttributeValue(models.Model): _inherit = 'product.attribute.value' + @api.model + def create(self, vals): + if self.env.context.get('product_tmpl_id'): + # intercept already-created value + product_tmpl_id = self.env.context.get('product_tmpl_id') + attribute_id = vals.get('attribute_id') + line = self.env['product.attribute.line'].search([ + ('product_tmpl_id', '=', product_tmpl_id), + ('attribute_id', '=', attribute_id)]) + match = line.attribute_id.value_ids.filtered(lambda rec: rec.name == vals['name']) + if match: + record = match[0] + else: + # default behavior + record = super(ProductAttributeValue, self).create(vals) + # create related line + line.value_ids += record + else: + record = super(ProductAttributeValue, self).create(vals) + return record + @api.multi def copy(self, default=None): default.update({'name': self.name + " (copy)"}) diff --git a/product_configurator/models/product_config.py b/product_configurator/models/product_config.py index 64ae1a31..bf723bed 100644 --- a/product_configurator/models/product_config.py +++ b/product_configurator/models/product_config.py @@ -197,6 +197,59 @@ def check_value_attributes(self): ) +class ProductConfigDefault(models.Model): + _name = 'product.config.default' + + product_tmpl_id = fields.Many2one( + comodel_name='product.template', + string='Product Template', + ondelete='cascade', + required=True + ) + + # TODO: Find a more elegant way to restrict the value_ids + attr_line_val_ids = fields.Many2many( + comodel_name='product.attribute.value', + compute='_compute_attr_vals' + ) + + value_ids = fields.Many2many( + comodel_name='product.attribute.value', + id1="cfg_dflt_id", + id2="attr_val_id", + string="Values" + ) + + domain_id = fields.Many2one( + comodel_name='product.config.domain', + string='Applied If', + help='Default will be attempted if this rule passes, or leave blank ' + 'for a generic default' + ) + + sequence = fields.Integer(string='Sequence', default=10) + + _order = 'product_tmpl_id, sequence, id' + + @api.multi + def _compute_attr_vals(self): + for config_default in self: + config_default.attr_line_val_ids = \ + config_default.product_tmpl_id.attribute_line_ids.mapped( + 'value_ids' + ) + + @api.model + def default_get(self, fields): + result = super(ProductConfigDefault, self).default_get(fields) + if self.env.context.get('for_template_id'): + result['attr_line_val_ids'] = \ + self.env['product.template'].browse( + self.env.context['for_template_id'] + ).attribute_line_ids.mapped('value_ids').ids + return result + + class ProductConfigImage(models.Model): _name = 'product.config.image' diff --git a/product_configurator/models/sale.py b/product_configurator/models/sale.py index 46c95cec..cd7bb993 100644 --- a/product_configurator/models/sale.py +++ b/product_configurator/models/sale.py @@ -20,4 +20,4 @@ class SaleOrderLine(models.Model): string="Custom Values" ) - product_id = fields.Many2one(domain=[('config_ok', '=', False)]) + product_id = fields.Many2one(domain=['|', ('reuse_variant', '=', True), ('config_ok', '=', False)]) diff --git a/product_configurator/models/stock.py b/product_configurator/models/stock.py index 0eb9ef68..ecb62ccf 100644 --- a/product_configurator/models/stock.py +++ b/product_configurator/models/stock.py @@ -6,4 +6,4 @@ class StockMove(models.Model): _inherit = 'stock.move' - product_id = fields.Many2one(domain=[('config_ok', '=', False)]) + product_id = fields.Many2one(domain=['|', ('reuse_variant', '=', True), ('config_ok', '=', False)]) diff --git a/product_configurator/security/ir.model.access.csv b/product_configurator/security/ir.model.access.csv index 5d1d44e2..e21b3421 100644 --- a/product_configurator/security/ir.model.access.csv +++ b/product_configurator/security/ir.model.access.csv @@ -1,5 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink product_configurator_config_line,Config Line,model_product_config_line,group_product_configurator,1,1,1,1 +product_configurator_config_default,Config Default,model_product_config_default,group_product_configurator,1,1,1,1 product_configurator_config_image,Config Image,model_product_config_image,group_product_configurator,1,1,1,1 product_configurator_config_step,Config Step,model_product_config_step,group_product_configurator,1,1,1,1 product_configurator_config_step_line,Config Step Line,model_product_config_step_line,group_product_configurator,1,1,1,1 @@ -10,6 +11,7 @@ product_configurator_config_session,Config Session,model_product_config_session, product_configurator_config_session_custom_value,Config Session Custom Value,model_product_config_session_custom_value,group_product_configurator,1,1,1,1 ,,,,,,, user_config_line,User Config Line,model_product_config_line,base.group_user,1,0,0,0 +user_config_default,User Config Default,model_product_config_default,base.group_user,1,0,0,0 user_config_image,User Config Image,model_product_config_image,base.group_user,1,0,0,0 user_config_step,User Config Step,model_product_config_step,base.group_user,1,0,0,0 user_config_step_line,User Config Step Line,model_product_config_step_line,base.group_user,1,0,0,0 @@ -25,6 +27,7 @@ portal_config_step,Portal Config Step,model_product_config_step,base.group_porta portal_config_session,Portal Config Session,model_product_config_session,base.group_portal,1,0,0,0 portal_config_session_custom_value,Portal Config Session Custom Value,model_product_config_session_custom_value,base.group_portal,1,0,0,0 portal_configurator_config_line,Portal Config Line,model_product_config_line,base.group_portal,1,0,0,0 +portal_configurator_config_default,Portal Config Default,model_product_config_default,base.group_portal,1,0,0,0 portal_configurator_config_step_line,Portal Config Step Line,model_product_config_step_line,base.group_portal,1,0,0,0 portal_configurator_config_domain,Portal Config Domain,model_product_config_domain,base.group_portal,1,0,0,0 portal_configurator_config_domain_line,Portal Config Domain Line,model_product_config_domain_line,base.group_portal,1,0,0,0 diff --git a/product_configurator/tests/test_configuration_rules.py b/product_configurator/tests/test_configuration_rules.py index 984a3010..feac814e 100644 --- a/product_configurator/tests/test_configuration_rules.py +++ b/product_configurator/tests/test_configuration_rules.py @@ -101,4 +101,41 @@ def test_invalid_custom_value_configuration(self): self.assertFalse(validation, "Custom value accepted for fixed " "attribute color") - # TODO: Test configuration with disallowed custom type value + def test_configuration_defaults(self): + conf = ['gasoline', 'tapistry_black'] + engine_selections = self.env.ref( + 'product_configurator.product_config_line_gasoline_engines' + ) + attr_val_ids = self.get_attr_val_ids(conf) + default_value_engine = self.cfg_tmpl.find_default_value( + engine_selections.value_ids.ids, + attr_val_ids, + ) + self.assertEqual( + [default_value_engine], self.get_attr_val_ids(['218i']), + "Gasoline Engine default not set correctly" + ) + + color_selection_ids = self.get_attr_val_ids(['red', 'silver', 'black']) + attr_val_ids = self.get_attr_val_ids(conf) + default_value_color = self.cfg_tmpl.find_default_value( + color_selection_ids, + attr_val_ids, + ) + self.assertEqual( + [default_value_color], self.get_attr_val_ids(['red']), + "Gasoline Color default not set correctly" + ) + + color_selection_ids = self.get_attr_val_ids(['silver', 'black']) + attr_val_ids = self.get_attr_val_ids(conf) + default_value_color = self.cfg_tmpl.find_default_value( + color_selection_ids, + attr_val_ids, + ) + self.assertFalse( + default_value_color, + "Gasoline Color should not have been returned unselectable value" + ) + + # Test configuration with disallowed custom type value diff --git a/product_configurator/views/product_attribute_view.xml b/product_configurator/views/product_attribute_view.xml index bf373967..27f7ad98 100644 --- a/product_configurator/views/product_attribute_view.xml +++ b/product_configurator/views/product_attribute_view.xml @@ -52,6 +52,7 @@ + diff --git a/product_configurator/views/product_view.xml b/product_configurator/views/product_view.xml index 1519dece..a7ca2dd2 100644 --- a/product_configurator/views/product_view.xml +++ b/product_configurator/views/product_view.xml @@ -43,6 +43,9 @@ + + + + + + + + + + + + + diff --git a/product_configurator_name/README.md b/product_configurator_name/README.md new file mode 100644 index 00000000..f3efd038 --- /dev/null +++ b/product_configurator_name/README.md @@ -0,0 +1,14 @@ +#Odoo Product Configurator Name + +This module is an extension to product configurator to hide unnecessary attributes from product name +and show the attribute name as label when ambiguities might arise. + +Features +======== + +- Add display mode to comfigurable templates attributes (under variants tab) + +Usage +===== + +Change the display mode of the attribute to hide or with label as needed. diff --git a/product_configurator_name/__init__.py b/product_configurator_name/__init__.py new file mode 100644 index 00000000..567548c7 --- /dev/null +++ b/product_configurator_name/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import models +from . import tests diff --git a/product_configurator_name/__manifest__.py b/product_configurator_name/__manifest__.py new file mode 100644 index 00000000..f95fe595 --- /dev/null +++ b/product_configurator_name/__manifest__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Product Configurator Name', + 'version': '10.0', + 'category': 'Generic Modules/Base', + 'summary': 'product configuration interface for name', + 'description': """ + Purchase Product Configurator + """, + 'author': 'Microcom', + 'license': 'AGPL-3', + 'website': 'http://www.microcom.ca/', + 'depends': [ + 'product_configurator', + ], + "data": [ + 'views/product_view.xml', + ], + 'demo': [ + ], + 'images': [ + ], + 'test': [], + 'installable': True, + 'auto_install': False, +} diff --git a/product_configurator_name/i18n/fr_CA.po b/product_configurator_name/i18n/fr_CA.po new file mode 100644 index 00000000..9bdc8f78 --- /dev/null +++ b/product_configurator_name/i18n/fr_CA.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_configurator_name +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-05-30 19:52+0000\n" +"PO-Revision-Date: 2017-05-30 15:57-0400\n" +"Last-Translator: Stéphane Le Cornec \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: fr_CA\n" +"X-Generator: Poedit 1.8.7\n" + +#. module: product_configurator_name +#: model:ir.model.fields,field_description:product_configurator_name.field_product_product_name_override +msgid "Custom Name" +msgstr "Nom personnalisé" + +#. module: product_configurator_name +#: model:ir.model.fields,field_description:product_configurator_name.field_product_attribute_line_display_mode +msgid "Display mode" +msgstr "Mode d’affichage" + +#. module: product_configurator_name +#: selection:product.attribute.line,display_mode:0 +msgid "Hide" +msgstr "Masquer" + +#. module: product_configurator_name +#: model:ir.model,name:product_configurator_name.model_product_product +msgid "Product" +msgstr "Produit" + +#. module: product_configurator_name +#: selection:product.attribute.line,display_mode:0 +msgid "Value Only" +msgstr "Valeur seule" + +#. module: product_configurator_name +#: selection:product.attribute.line,display_mode:0 +msgid "With Label" +msgstr "Avec étiquette" + +#. module: product_configurator_name +#: model:ir.model,name:product_configurator_name.model_product_attribute_line +msgid "product.attribute.line" +msgstr "product.attribute.line" diff --git a/product_configurator_name/models/__init__.py b/product_configurator_name/models/__init__.py new file mode 100644 index 00000000..d4a9713e --- /dev/null +++ b/product_configurator_name/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import product +from . import product_attribute diff --git a/product_configurator_name/models/product.py b/product_configurator_name/models/product.py new file mode 100644 index 00000000..bc296cf7 --- /dev/null +++ b/product_configurator_name/models/product.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + + +class ProductProduct(models.Model): + _inherit = "product.product" + + name_override = fields.Char('Custom Name') + hidden_attribute_value_ids = fields.Many2many('product.attribute.value', string='Hidden Attributes', compute='_compute_hidden') + + def _compute_hidden(self): + for product in self: + hidden_ids = self.env['product.attribute.value'] + for line in product.attribute_line_ids.sorted('sequence'): + if line.display_mode == 'hide': + key = line.attribute_id.name + for value in product.attribute_value_ids: + if value.attribute_id.name == key: + hidden_ids += value + product.hidden_attribute_value_ids = hidden_ids + + @api.multi + def name_get(self): + """ Override variant name + """ + + def _name_get(d): + name = d.get('name', '') + code = self._context.get('display_default_code', True) and d.get('default_code', False) or False + if code: + name = '[%s] %s' % (code,name) + return (d['id'], name) + + partner_id = self._context.get('partner_id') + if partner_id: + partner_ids = [partner_id, self.env['res.partner'].browse(partner_id).commercial_partner_id.id] + else: + partner_ids = [] + + # all user don't have access to seller and partner + # check access and use superuser + self.check_access_rights("read") + self.check_access_rule("read") + + result = [] + for product in self.sudo(): + # START CHANGES + if product.name_override: + name = product.name_override + else: + if product.config_ok: + # prefetch values + value_dict = {} + for value in product.attribute_value_ids: + old_value = value_dict.get(value.attribute_id.name) + if old_value: + value_dict[value.attribute_id.name] = ', '.join([old_value, value.name]) + else: + value_dict[value.attribute_id.name] = value.name + # assemble variant + name_elements = [] + for line in product.attribute_line_ids.sorted('sequence'): + if line.display_mode == 'hide': + continue + key = line.attribute_id.name + if key in value_dict: + value = value_dict[key] + else: + continue + if line.display_mode == 'value': + name_elements.append(u'{}'.format(value)) + elif line.display_mode == 'attribute': + name_elements.append(u'{}: {}'.format(key, value)) + variant = ', '.join(name_elements) + else: + # display only the attributes with multiple possible values on the template + variable_attributes = product.attribute_line_ids.filtered(lambda l: len(l.value_ids) > 1).mapped('attribute_id') + variant = product.attribute_value_ids._variant_name(variable_attributes) + + name = variant and "%s (%s)" % (product.name, variant) or product.name + # END CHANGES + sellers = [] + if partner_ids: + sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and (x.product_id == product)] + if not sellers: + sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and not x.product_id] + if sellers: + for s in sellers: + seller_variant = s.product_name and ( + variant and "%s (%s)" % (s.product_name, variant) or s.product_name + ) or False + mydict = { + 'id': product.id, + 'name': seller_variant or name, + 'default_code': s.product_code or product.default_code, + } + temp = _name_get(mydict) + if temp not in result: + result.append(temp) + else: + mydict = { + 'id': product.id, + 'name': name, + 'default_code': product.default_code, + } + result.append(_name_get(mydict)) + return result diff --git a/product_configurator_name/models/product_attribute.py b/product_configurator_name/models/product_attribute.py new file mode 100644 index 00000000..409e30ba --- /dev/null +++ b/product_configurator_name/models/product_attribute.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + +DISPLAY_SELECTION = [('hide', 'Hide'), ('value', 'Value Only'), ('attribute', 'With Label')] + + +class ProductAttributeLine(models.Model): + _inherit = 'product.attribute.line' + _order = 'product_tmpl_id, sequence, id' + + + sequence = fields.Integer(string='Sequence', default=10) + display_mode = fields.Selection(DISPLAY_SELECTION, default='value', required=True) + + +class ProductAttributeValue(models.Model): + _inherit = 'product.attribute.value' + _order = 'sequence, name' + # test failed because of random order diff --git a/product_configurator_name/static/description/index.html b/product_configurator_name/static/description/index.html new file mode 100644 index 00000000..e93d665a --- /dev/null +++ b/product_configurator_name/static/description/index.html @@ -0,0 +1,16 @@ + + + + Product Configurator Name + Generate products names on-demand, easy and error-free + + + + + + + Hide unnecessary attributes from your product descriptions + This module is an extention to product configurator + It allow to configure how product attributes will be displayed + + diff --git a/product_configurator_name/tests/__init__.py b/product_configurator_name/tests/__init__.py new file mode 100644 index 00000000..16a29e32 --- /dev/null +++ b/product_configurator_name/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_name diff --git a/product_configurator_name/tests/test_name.py b/product_configurator_name/tests/test_name.py new file mode 100644 index 00000000..c9b88530 --- /dev/null +++ b/product_configurator_name/tests/test_name.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +from odoo.tests.common import TransactionCase + + +class ConfigurationCreate(TransactionCase): + + def setUp(self): + super(ConfigurationCreate, self).setUp() + self.cfg_tmpl = self.env.ref('product_configurator.bmw_2_series') + + attribute_vals = self.cfg_tmpl.attribute_line_ids.mapped('value_ids') + + self.attr_val_ext_ids = { + v: k for k, v in attribute_vals.get_external_id().iteritems() + } + + conf = [ + 'gasoline', '228i', 'model_luxury_line', 'silver', 'rims_384', + 'tapistry_black', 'steptronic', 'smoker_package', 'tow_hook' + ] + + self.attr_val_ids = self.get_attr_val_ids(conf) + + def get_attr_val_ids(self, ext_ids): + """Return a list of database ids using the external_ids + passed via ext_ids argument""" + + value_ids = [] + + attr_val_prefix = 'product_configurator.product_attribute_value_%s' + + for ext_id in ext_ids: + if ext_id in self.attr_val_ext_ids: + value_ids.append(self.attr_val_ext_ids[ext_id]) + elif attr_val_prefix % ext_id in self.attr_val_ext_ids: + value_ids.append( + self.attr_val_ext_ids[attr_val_prefix % ext_id] + ) + + return value_ids + + def test_name_get_odoo(self): + """Test variant with name_override set""" + + variant = self.env.ref('product.product_product_11') + + res = variant.name_get() + expected = [(variant.id, '[E-COM12] iPod (16 GB)')] + self.assertEqual(res, expected, 'Product should display iPod name') + + def test_name_get_name_override(self): + """Test variant with name_override set""" + + variant = self.cfg_tmpl.create_get_variant(self.attr_val_ids) + variant.name_override = 'override' + + res = variant.name_get() + expected = [(variant.id, 'override')] + self.assertEqual(res, expected, 'Product should display name_override') + + def test_name_get_default(self): + """Test variant as is""" + + variant = self.cfg_tmpl.create_get_variant(self.attr_val_ids) + + res = variant.name_get() + expected = [(variant.id, + '2 Series (Gasoline, 228i, Model Luxury Line, Silver, Double-spoke 18", ' + 'Black, Automatic (Steptronic), Smoker Package, Tow hook)')] + self.assertEqual(res, expected, 'Product name should display all values by default') + + def test_name_get_hide(self): + """Test variant name with hidden lines""" + color_line = self.env.ref('product_configurator.product_attribute_line_2_series_color') + color_line.display_mode = 'hide' + + variant = self.cfg_tmpl.create_get_variant(self.attr_val_ids) + + res = variant.name_get() + expected = [(variant.id, + '2 Series (Gasoline, 228i, Model Luxury Line, Double-spoke 18", ' + 'Black, Automatic (Steptronic), Smoker Package, Tow hook)')] + self.assertEqual(res, expected, 'Product name should hide color attribute') + + def test_name_get_with_label(self): + """Test variant name with hidden lines""" + color_line = self.env.ref('product_configurator.product_attribute_line_2_series_color') + color_line.display_mode = 'attribute' + + variant = self.cfg_tmpl.create_get_variant(self.attr_val_ids) + + res = variant.name_get() + expected = [(variant.id, + '2 Series (Gasoline, 228i, Model Luxury Line, Color: Silver, Double-spoke 18", ' + 'Black, Automatic (Steptronic), Smoker Package, Tow hook)')] + self.assertEqual(res, expected, 'Product name should display color attribute label') diff --git a/product_configurator_name/views/product_view.xml b/product_configurator_name/views/product_view.xml new file mode 100644 index 00000000..ebb63fd9 --- /dev/null +++ b/product_configurator_name/views/product_view.xml @@ -0,0 +1,52 @@ + + + + + product.configurator.product.template.form + product.template + + + + + + + + + + + product.configurator.product.variant.easy.form + product.product + + + + + + + + + + product.configurator.product.variant.normal.form + product.product + + + + + + + + + + + product.product.tree + product.product + + + + + + + + + + + diff --git a/product_configurator_purchase/README.md b/product_configurator_purchase/README.md new file mode 100644 index 00000000..65ab0cab --- /dev/null +++ b/product_configurator_purchase/README.md @@ -0,0 +1,13 @@ +#Odoo Product Configurator Purchase + +This module is an extension to product configurator to add widjet to purchase and add possibility to choose a product variant instead of always create a new one. + +Features +======== + +- Add configure button to Purchase order. + +Usage +===== + +To complete diff --git a/product_configurator_purchase/__init__.py b/product_configurator_purchase/__init__.py new file mode 100644 index 00000000..cde864ba --- /dev/null +++ b/product_configurator_purchase/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/product_configurator_purchase/__manifest__.py b/product_configurator_purchase/__manifest__.py new file mode 100644 index 00000000..15dd3eb8 --- /dev/null +++ b/product_configurator_purchase/__manifest__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Product Configurator Purchase', + 'version': '10.0', + 'category': 'Generic Modules/Base', + 'summary': 'product configuration interface for purchase', + 'description': """ + Purchase Product Configurator + """, + 'author': 'Microcom', + 'license': 'AGPL-3', + 'website': 'http://www.microcom.ca/', + 'depends': [ + 'purchase', + 'product_configurator', + 'product_configurator_wizard', + ], + "data": [ + 'views/purchase_views.xml', + 'views/stock_picking_views.xml', + ], + 'demo': [ + ], + 'images': [ + ], + 'test': [], + 'installable': True, + 'auto_install': False, +} diff --git a/product_configurator_purchase/i18n/fr_CA.po b/product_configurator_purchase/i18n/fr_CA.po new file mode 100644 index 00000000..71a2b6d8 --- /dev/null +++ b/product_configurator_purchase/i18n/fr_CA.po @@ -0,0 +1,101 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_configurator_purchase +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-05-30 20:30+0000\n" +"PO-Revision-Date: 2017-05-30 16:32-0400\n" +"Last-Translator: Stéphane Le Cornec \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: fr_CA\n" +"X-Generator: Poedit 1.8.7\n" + +#. module: product_configurator_purchase +#: model:ir.model.fields,field_description:product_configurator_purchase.field_purchase_order_line_config_ok +#: model:ir.model.fields,field_description:product_configurator_purchase.field_purchase_order_line_version_config_ok +msgid "Configurable" +msgstr "Configurable" + +#. module: product_configurator_purchase +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_configurable_purchase +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_configurable_stock +msgid "Configurable Products" +msgstr "Articles configurables" + +#. module: product_configurator_purchase +#: model:ir.actions.act_window,name:product_configurator_purchase.product_configurable_template_purchase_action +#: model:ir.actions.act_window,name:product_configurator_purchase.product_configurable_template_stock_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_configurable_template_purchase_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_configurable_template_stock_action +msgid "Configurable Templates" +msgstr "Modèles configurables" + +#. module: product_configurator_purchase +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_configurable_variants_purchase_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_configurable_variants_stock_action +msgid "Configurable Variants" +msgstr "Variantes configurables" + +#. module: product_configurator_purchase +#: model:ir.actions.act_window,name:product_configurator_purchase.product_config_domain_purchase_action +#: model:ir.actions.act_window,name:product_configurator_purchase.product_config_domain_stock_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_config_domain_purchase_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_config_domain_stock_action +msgid "Configuration Restrictions" +msgstr "Restrictions de configuration" + +#. module: product_configurator_purchase +#: model:ir.actions.act_window,name:product_configurator_purchase.product_config_session_purchase_action +#: model:ir.actions.act_window,name:product_configurator_purchase.product_config_session_stock_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_config_session_purchase_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_config_session_stock_action +msgid "Configuration Sessions" +msgstr "Sessions de configuration" + +#. module: product_configurator_purchase +#: model:ir.actions.act_window,name:product_configurator_purchase.product_config_steps_purchase_action +#: model:ir.actions.act_window,name:product_configurator_purchase.product_config_steps_stock_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_config_steps_purchase_action +#: model:ir.ui.menu,name:product_configurator_purchase.menu_product_config_steps_stock_action +msgid "Configuration Steps" +msgstr "Étapes de configuration" + +#. module: product_configurator_purchase +#: model:ir.actions.act_window,name:product_configurator_purchase.action_wizard_product_purchase_configurator +#: model:ir.ui.view,arch_db:product_configurator_purchase.purchase_order_form_config +msgid "Configure Product" +msgstr "Configurer le Produit" + +#. module: product_configurator_purchase +#: model:ir.actions.act_window,name:product_configurator_purchase.product_configurable_variant_purchase_action +#: model:ir.actions.act_window,name:product_configurator_purchase.product_configurable_variant_stock_action +msgid "Configured Variants" +msgstr "Variantes configurées" + +#. module: product_configurator_purchase +#: model:ir.model.fields,field_description:product_configurator_purchase.field_purchase_order_line_custom_value_ids +#: model:ir.model.fields,field_description:product_configurator_purchase.field_purchase_order_line_version_custom_value_ids +msgid "Custom Values" +msgstr "Valeurs personnalisées" + +#. module: product_configurator_purchase +#: model:ir.model.fields,field_description:product_configurator_purchase.field_purchase_order_line_version_product_id +msgid "Product" +msgstr "Article" + +#. module: product_configurator_purchase +#: model:ir.model,name:product_configurator_purchase.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "Ligne de commande fournisseur" + +#. module: product_configurator_purchase +#: model:ir.ui.view,arch_db:product_configurator_purchase.purchase_order_form_config +msgid "Reconfigure" +msgstr "reconfigurer" diff --git a/product_configurator_purchase/models/__init__.py b/product_configurator_purchase/models/__init__.py new file mode 100644 index 00000000..4160fd8f --- /dev/null +++ b/product_configurator_purchase/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import purchase, product_configurator diff --git a/product_configurator_purchase/models/product_configurator.py b/product_configurator_purchase/models/product_configurator.py new file mode 100644 index 00000000..a654d928 --- /dev/null +++ b/product_configurator_purchase/models/product_configurator.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + + +class ProductConfigurator(models.TransientModel): + _inherit = 'product.configurator' + + purchase_order_line_id = fields.Many2one( + comodel_name='purchase.order.line', + readonly=True, + ) + + @api.multi + def action_config_done(self): + """This fuction are copied from product_configurator_wizard.ProductConfigurator + We only add one "if" required for purchase_order + Parse values and execute final code before closing the wizard""" + custom_vals = { + l.attribute_id.id: + l.value or l.attachment_ids for l in self.custom_value_ids + } + + if self.product_id: + remove_cv_links = map(lambda cv: (2, cv), self.product_id.value_custom_ids.ids) + new_cv_links = self.product_id.product_tmpl_id.encode_custom_values(custom_vals) + self.product_id.write({ + 'attribute_value_ids': [(6, 0, self.value_ids.ids)], + 'value_custom_ids': remove_cv_links + new_cv_links, + }) + if self.order_line_id: + self.order_line_id.write(self._extra_line_values(self.order_line_id.order_id, + self.product_id, + new=False)) + if self.purchase_order_line_id: + self.purchase_order_line_id.write(self._extra_line_values(self.purchase_order_line_id.order_id, + self.product_id, + new=False)) + self.unlink() + return + else: + return super(ProductConfigurator, self).action_config_done() diff --git a/product_configurator_purchase/models/purchase.py b/product_configurator_purchase/models/purchase.py new file mode 100644 index 00000000..5cbbb56a --- /dev/null +++ b/product_configurator_purchase/models/purchase.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + + custom_value_ids = fields.One2many( + comodel_name='product.attribute.value.custom', + inverse_name='product_id', + related="product_id.value_custom_ids", + string="Custom Values" + ) + + product_id = fields.Many2one(domain=['|', ('reuse_variant', '=', True), ('config_ok', '=', False)]) + + config_ok = fields.Boolean( + related='product_id.config_ok', + string="Configurable" + ) + + @api.multi + def reconfigure_product(self): + """ Creates and launches a product configurator wizard with a linked + template and variant in order to re-configure a existing product. It is + esetially a shortcut to pre-fill configuration data of a variant""" + + cfg_steps = self.product_id.product_tmpl_id.config_step_line_ids + active_step = str(cfg_steps[0].id) if cfg_steps else 'configure' + + wizard_obj = self.env['product.configurator'] + wizard = wizard_obj.create({ + 'product_id': self.product_id.id, + 'state': active_step, + 'purchase_order_line_id': self.id, + }) + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'product.configurator', + 'name': "Configure Product", + 'view_mode': 'form', + 'context': dict( + self.env.context, + wizard_id=wizard.id, + ), + 'target': 'new', + 'res_id': wizard.id, + } diff --git a/product_configurator_purchase/static/description/index.html b/product_configurator_purchase/static/description/index.html new file mode 100644 index 00000000..10021906 --- /dev/null +++ b/product_configurator_purchase/static/description/index.html @@ -0,0 +1,16 @@ + + + + Product Configurator Purchase + Generate products on-demand, easy and error-free + + + + + + + Configure products directly in your purchase orders + This module is an extention to product configurator and Product configurator Wizard + It allow to configure product from purchase orders in addition to Sale orders + + diff --git a/product_configurator_purchase/views/purchase_views.xml b/product_configurator_purchase/views/purchase_views.xml new file mode 100644 index 00000000..1cacc64f --- /dev/null +++ b/product_configurator_purchase/views/purchase_views.xml @@ -0,0 +1,139 @@ + + + + + + + Configure Product + product.configurator + form + + new + + + + purchase.order.form.config + purchase.order + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + Configurable Templates + ir.actions.act_window + product.template + kanban,tree,form + form + + [('config_ok','=',True)] + {'default_config_ok': True, 'default_type': 'product'} + + + + + + Configured Variants + ir.actions.act_window + product.product + tree,form,kanban + form + + + [('config_ok','=',True)] + {'default_config_ok': True, 'default_type': 'product'} + + + + + + + [('config_ok','=',False)] + + + + [('config_ok','=',False)] + + + + + Configuration Steps + ir.actions.act_window + product.config.step + tree,form + form + + + + + + Configuration Restrictions + ir.actions.act_window + product.config.domain + tree,form + form + + + + + + Configuration Sessions + ir.actions.act_window + product.config.session + tree,form + form + + + + + diff --git a/product_configurator_purchase/views/stock_picking_views.xml b/product_configurator_purchase/views/stock_picking_views.xml new file mode 100644 index 00000000..43d9a0d7 --- /dev/null +++ b/product_configurator_purchase/views/stock_picking_views.xml @@ -0,0 +1,87 @@ + + + + + + + Configurable Templates + ir.actions.act_window + product.template + kanban,tree,form + form + + [('config_ok','=',True)] + {'default_config_ok': True, 'default_type': 'product'} + + + + + + Configured Variants + ir.actions.act_window + product.product + tree,form,kanban + form + + + [('config_ok','=',True)] + {'default_config_ok': True, 'default_type': 'product'} + + + + + + + [('config_ok','=',False)] + + + + [('config_ok','=',False)] + + + + + Configuration Steps + ir.actions.act_window + product.config.step + tree,form + form + + + + + + Configuration Restrictions + ir.actions.act_window + product.config.domain + tree,form + form + + + + + + Configuration Sessions + ir.actions.act_window + product.config.session + tree,form + form + + + + + diff --git a/product_configurator_wizard/tests/test_wizard.py b/product_configurator_wizard/tests/test_wizard.py index 3acd8643..2e410242 100644 --- a/product_configurator_wizard/tests/test_wizard.py +++ b/product_configurator_wizard/tests/test_wizard.py @@ -121,3 +121,51 @@ def test_reconfiguration(self): self.assertTrue(len(config_variants) == 2, "Wizard reconfiguration did not create a new variant") + + def test_wizard_domains(self): + """Test product configurator wizard default values""" + + # Start a new configuration wizard + wizard = self.env['product.configurator'].create({ + 'product_tmpl_id': self.cfg_tmpl.id + }) + + dynamic_fields = {} + for attribute_line in self.cfg_tmpl.attribute_line_ids: + field_name = '%s%s' % ( + wizard.field_prefix, + attribute_line.attribute_id.id + ) + dynamic_fields[field_name] = [] if attribute_line.multi else False + + attr_gasoline_vals = self.get_attr_values(['gasoline']) + attr_gasoline_dict = self.get_wizard_write_dict(wizard, + attr_gasoline_vals) + attr_218i_vals = self.get_attr_values(['218i']) + attr_218i_dict = self.get_wizard_write_dict(wizard, attr_218i_vals) + gasoline_engine_vals = self.env.ref( + 'product_configurator.product_config_line_gasoline_engines' + ).value_ids + + oc_vals = dynamic_fields.copy() + oc_vals.update({'id': wizard.id, + }) + oc_vals.update(attr_gasoline_dict) + oc_result = wizard.onchange( + oc_vals, + attr_gasoline_dict.keys()[0], + {} + ) + k, v = attr_218i_dict.iteritems().next() + self.assertEqual( + oc_result.get('value', {}).get(k), + v, + "Engine default value not set correctly by onchange wizard" + ) + oc_domain = oc_result.get('domain', {}).get(k, []) + domain_ids = oc_domain and oc_domain[0][2] or [] + self.assertEqual( + set(domain_ids), + set(gasoline_engine_vals.ids), + "Engine domain value not set correctly by onchange wizard" + ) diff --git a/product_configurator_wizard/wizard/product_configurator.py b/product_configurator_wizard/wizard/product_configurator.py index 5e0055d0..dd36c271 100644 --- a/product_configurator_wizard/wizard/product_configurator.py +++ b/product_configurator_wizard/wizard/product_configurator.py @@ -2,9 +2,11 @@ from lxml import etree +from datetime import datetime from odoo.osv import orm from odoo import models, fields, api, _ from odoo.exceptions import Warning, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT class FreeSelection(fields.Selection): @@ -109,10 +111,12 @@ def get_onchange_domains(self, values, cfg_val_ids): continue return domains - def get_form_vals(self, dynamic_fields, domains): + def get_form_vals(self, dynamic_fields, domains, cfg_step): """Generate a dictionary to return new values via onchange method. Domains hold the values available, this method enforces these values if a selection exists in the view that is not available anymore. + Also, if there are values blanked out by this, then try and see if + there is an available default. :param dynamic_fields: Dictionary with the current {dynamic_field: val} :param domains: Odoo domains restricting attribute values @@ -121,22 +125,56 @@ def get_form_vals(self, dynamic_fields, domains): """ vals = {} - dynamic_fields = {k: v for k, v in dynamic_fields.iteritems() if v} + dynamic_fields = dynamic_fields.copy() + # validate and eliminate values, and set defaults if they are on the + # current step + step_val_ids = cfg_step and \ + cfg_step.attribute_line_ids.mapped('value_ids').ids or \ + self.product_tmpl_id.attribute_line_ids.mapped('value_ids').ids for k, v in dynamic_fields.iteritems(): + available_val_ids = domains[k][0][2] + # Get this fresh every time as the loop can change the values as + # it goes! + config_val_ids = [dfv for dfv in dynamic_fields.values() + if dfv and not isinstance(dfv, list)] + for list_dfv in [dfv for dfv in dynamic_fields.values() + if dfv and isinstance(dfv, list)]: + config_val_ids.extend(list_dfv[0][2]) if not v: + # if the value currently is blank and on the current step, see + # if one can be set + if set(available_val_ids) & set(step_val_ids): + def_value_id = self.product_tmpl_id.find_default_value( + available_val_ids, config_val_ids + ) + if def_value_id: + dynamic_fields.update({k: def_value_id}) + vals[k] = def_value_id continue - available_val_ids = domains[k][0][2] if isinstance(v, list): value_ids = list(set(v[0][2]) & set(available_val_ids)) - dynamic_fields.update({k: value_ids}) + dynamic_fields[k] = [[6, 0, value_ids]] vals[k] = [[6, 0, value_ids]] elif v not in available_val_ids: - dynamic_fields.update({k: None}) - vals[k] = None - + # if the value is to be blanked, and it is on the current + # step, see if a default can be set + if set(available_val_ids) & set(step_val_ids): + def_value_id = self.product_tmpl_id.find_default_value( + available_val_ids, config_val_ids + ) or None + else: + def_value_id = None + dynamic_fields.update({k: def_value_id}) + vals[k] = def_value_id + + config_val_ids = [dfv for dfv in dynamic_fields.values() + if dfv and not isinstance(dfv, list)] + for list_dfv in [dfv for dfv in dynamic_fields.values() + if dfv and isinstance(dfv, list)]: + config_val_ids.extend(list_dfv[0][2]) product_img = self.product_tmpl_id.get_config_image_obj( - dynamic_fields.values()) + config_val_ids) vals.update(product_img=product_img.image) @@ -196,7 +234,34 @@ def onchange(self, values, field_name, field_onchange): cfg_val_ids = cfg_vals.ids + list(view_val_ids) domains = self.get_onchange_domains(values, cfg_val_ids) - vals = self.get_form_vals(dynamic_fields, domains) + vals = self.get_form_vals(dynamic_fields, domains, cfg_step) + modified_dynamics = {k: v + for k, v in vals.iteritems() + if k in dynamic_fields} + + while modified_dynamics: + # modified values may change domains! + dynamic_fields.update(modified_dynamics) + for k, v in modified_dynamics.iteritems(): + attr_id = int(k.split(self.field_prefix)[1]) + view_val_ids -= set(self.env['product.attribute.value'].search( + [('attribute_id', '=', attr_id)]).ids) + if v: + if isinstance(v, list): + view_val_ids |= set(v[0][2]) + elif isinstance(v, int): + view_val_ids.add(v) + + cfg_val_ids = cfg_vals.ids + list(view_val_ids) + + domains = self.get_onchange_domains(values, cfg_val_ids) + nvals = self.get_form_vals(dynamic_fields, domains, cfg_step) + # Stop possible recursion by not including values which have + # previously looped + modified_dynamics = {k: v + for k, v in nvals.iteritems() + if k in dynamic_fields and k not in vals} + vals.update(nvals) return {'value': vals, 'domain': domains} attribute_line_ids = fields.One2many( @@ -273,6 +338,8 @@ def fields_get(self, allfields=None, attributes=None): # If no configuration steps exist then get all attribute lines attribute_lines = wiz.product_tmpl_id.attribute_line_ids + # TODO: If last block is attempting to be clever, this next + # line is ignoring it. Need to determine what is best. attribute_lines = wiz.product_tmpl_id.attribute_line_ids # Generate relational fields with domains restricting values to @@ -368,6 +435,26 @@ def fields_view_get(self, view_id=None, view_type='form', # Update result dict from super with modified view res.update({'arch': etree.tostring(mod_view)}) + # set any default values + wiz_vals = wiz.read(dynamic_fields.keys())[0] + dynamic_field_vals = { + k: wiz_vals.get( + k, [] if v['type'] == 'many2many' else False + ) + for k, v in fields.iteritems() + if k.startswith(self.field_prefix) + } + domains = {k: dynamic_fields[k]['domain'] + for k in dynamic_field_vals.keys()} + try: + cfg_step_id = int(wiz.state) + cfg_step = wiz.product_tmpl_id.config_step_line_ids.filtered( + lambda x: x.id == cfg_step_id) + except: + cfg_step = self.env['product.config.step.line'] + vals = wiz.get_form_vals(dynamic_field_vals, domains, cfg_step) + if vals: + wiz.write(vals) return res @api.model @@ -471,6 +558,7 @@ def add_dynamic_fields(self, res, dynamic_fields, wiz): attrs['required'].append( (dependee_field, 'in', list(val_ids))) + # Create the new field in the view node = etree.Element( "field", @@ -478,10 +566,14 @@ def add_dynamic_fields(self, res, dynamic_fields, wiz): on_change="onchange_attribute_value(%s, context)" % field_name, default_focus="1" if attr_line == attr_lines[0] else "0", attrs=str(attrs), - context="{'show_attribute': False}", + context=str({ + 'show_attribute': False, + 'product_tmpl_id': wiz.product_tmpl_id.id, + 'default_attribute_id': attribute_id + }), options=str({ - 'no_create': True, - 'no_create_edit': True, + 'no_create': not attr_line.attribute_id.create_on_fly, + 'no_create_edit': not attr_line.attribute_id.create_on_fly, 'no_open': True }) ) @@ -790,10 +882,8 @@ def _extra_line_values(self, so, product, new=True): created or edited lines.""" vals = {} if new: - vals.update({ - 'name': product.display_name, - 'product_uom': product.uom_id.id, - }) + vals.update({'name': product.display_name}) + vals.update({'product_uom': product.uom_id.id}) return vals @api.multi @@ -811,28 +901,51 @@ def action_config_done(self): # In the meantime, at least make sure that a validation # error legitimately raised in a nested routine # is passed through. - try: - variant = self.product_tmpl_id.create_get_variant( - self.value_ids.ids, custom_vals) - except ValidationError: - raise - except: - raise ValidationError( - _('Invalid configuration! Please check all ' - 'required steps and fields.') - ) + domain = [['product_tmpl_id', '=', self.product_tmpl_id.id]] + + template_products = self.env['product.product'].search(domain) + product_found = False - so = self.env['sale.order'].browse(self.env.context.get('active_id')) + if self.product_tmpl_id.reuse_variant: + for product in template_products: + if product.attribute_value_ids == self.value_ids: + product_found = product + break + + if product_found: + variant = product_found + price = product_found.standard_price + uom = product_found.uom_id.id + else: + try: + variant = self.product_tmpl_id.create_variant( + self.value_ids.ids, custom_vals) + price = self.product_tmpl_id.standard_price + uom = self.product_tmpl_id.uom_id.id + except ValidationError: + raise + except: + raise ValidationError( + _('Invalid configuration! Please check all ' + 'required steps and fields.') + ) + + if self.env.context.get('active_model') == 'purchase.order': + order = self.env['purchase.order'].browse(self.env.context.get('active_id')) + line_vals = {'product_id': variant.id, 'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'product_uom': uom, 'price_unit': price, 'product_qty': 1} + else: + order = self.env['sale.order'].browse(self.env.context.get('active_id')) + line_vals = {'product_id': variant.id} - line_vals = {'product_id': variant.id} line_vals.update(self._extra_line_values( - self.order_line_id.order_id or so, variant, new=True) + self.order_line_id.order_id or order, variant, new=True) ) if self.order_line_id: self.order_line_id.write(line_vals) else: - so.write({'order_line': [(0, 0, line_vals)]}) + order.write({'order_line': [(0, 0, line_vals)]}) self.unlink() return diff --git a/website_product_configurator/website.py b/website_product_configurator/website.py index 7594a369..88a17f9d 100644 --- a/website_product_configurator/website.py +++ b/website_product_configurator/website.py @@ -13,5 +13,5 @@ def sale_product_domain(self): domain = super(Website, self).sale_product_domain() ext_id = 'website_product_configurator.layout_config_products_show' if not self.env.ref(ext_id).active: - domain.append(('config_ok', '=', False)) + domain.extend(['|', ('reuse_variant', '=', True), ('config_ok', '=', False)]) return domain