Browse Source

ansible deployment

kermit 2 years ago
parent
commit
91442ea621
100 changed files with 7335 additions and 0 deletions
  1. 58 0
      group_vars/aws.yml
  2. 58 0
      group_vars/digitalocean.yml
  3. 58 0
      group_vars/local.yml
  4. BIN
      roles/.DS_Store
  5. 20 0
      roles/certbot/tasks/certbot.yml
  6. 3 0
      roles/certbot/tasks/main.yml
  7. 68 0
      roles/common/handlers/main.yml
  8. 2 0
      roles/common/tasks/hostname.yml
  9. 9 0
      roles/common/tasks/locale.yml
  10. 12 0
      roles/common/tasks/main.yml
  11. 27 0
      roles/common/tasks/swap.yml
  12. 21 0
      roles/common/tasks/tools.yml
  13. 1 0
      roles/common/templates/etc/timezone
  14. 2 0
      roles/deploy-user/handlers/main.yml
  15. 29 0
      roles/deploy-user/tasks/main.yml
  16. 3 0
      roles/monit/handlers/main.yml
  17. 18 0
      roles/monit/tasks/main.yml
  18. 6 0
      roles/monit/templates/monit_sidekiq_config
  19. 34 0
      roles/odoo/.travis.yml
  20. 193 0
      roles/odoo/README.md
  21. 109 0
      roles/odoo/defaults/main.yml
  22. 34 0
      roles/odoo/files/addons/odoo-product-configurator/.gitignore
  23. 661 0
      roles/odoo/files/addons/odoo-product-configurator/LICENSE
  24. 41 0
      roles/odoo/files/addons/odoo-product-configurator/README.md
  25. 177 0
      roles/odoo/files/addons/odoo-product-configurator/doc/Makefile
  26. 5 0
      roles/odoo/files/addons/odoo-product-configurator/doc/_static/style.css
  27. 5 0
      roles/odoo/files/addons/odoo-product-configurator/doc/_templates/layout.html
  28. 335 0
      roles/odoo/files/addons/odoo-product-configurator/doc/conf.py
  29. 15 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional.rst
  30. 55 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/add_product.rst
  31. 35 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/attributes.rst
  32. 28 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/config_images.rst
  33. 30 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/config_restrictions.rst
  34. 27 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/config_steps.rst
  35. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_image1.png
  36. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product1.png
  37. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product2.png
  38. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product3.png
  39. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product4.png
  40. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product5.png
  41. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_restriction1.png
  42. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_restriction2.png
  43. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_steps1.png
  44. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_steps2.png
  45. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/config_button_off.png
  46. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/config_button_on.png
  47. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/configurator.png
  48. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/logo.png
  49. 53 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/installing.rst
  50. 28 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/introduction.rst
  51. 48 0
      roles/odoo/files/addons/odoo-product-configurator/doc/functional/pricing.rst
  52. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/images/logo.png
  53. 11 0
      roles/odoo/files/addons/odoo-product-configurator/doc/index.rst
  54. 9 0
      roles/odoo/files/addons/odoo-product-configurator/doc/technical.rst
  55. 131 0
      roles/odoo/files/addons/odoo-product-configurator/doc/technical/concept.rst
  56. BIN
      roles/odoo/files/addons/odoo-product-configurator/doc/technical/images/configuration-process-diagram.png
  57. 33 0
      roles/odoo/files/addons/odoo-product-configurator/doc/technical/pricing.rst
  58. 22 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/README.md
  59. 4 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/__init__.py
  60. 36 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/__manifest__.py
  61. 89 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/data/menu_configurable_product.xml
  62. 17 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/data/product_attribute.xml
  63. 91 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/config_image_ids.xml
  64. 342 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_attribute.xml
  65. 70 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_config_domain.xml
  66. 47 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_config_lines.xml
  67. 66 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_config_step.xml
  68. 172 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_template.xml
  69. 694 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/i18n/de.po
  70. 696 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/i18n/it.po
  71. 1019 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/i18n/product_configurator.pot
  72. 8 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/__init__.py
  73. 9 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/account.py
  74. 587 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/product.py
  75. 278 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/product_attribute.py
  76. 517 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/product_config.py
  77. 23 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/sale.py
  78. 9 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/stock.py
  79. 17 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/security/configurator_security.xml
  80. 30 0
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/security/ir.model.access.csv
  81. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/description/cover.png
  82. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/description/icon.png
  83. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-black-star-spoke-384.jpg
  84. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-black-star-spoke-387.jpg
  85. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-black.jpg
  86. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-red-star-spoke-384.jpg
  87. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-red-star-spoke-387.jpg
  88. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-silver-star-spoke-384.jpg
  89. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-silver-star-spoke-387.jpg
  90. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-silver.jpg
  91. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe.jpg
  92. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-advantage.jpg
  93. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-armrest.jpg
  94. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-engine.jpg
  95. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-luxury-line.jpg
  96. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-m-sport.jpg
  97. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-paint-silver.jpg
  98. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-smoker-package.jpg
  99. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-sport-line.jpg
  100. BIN
      roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-sunroof.jpg

+ 58 - 0
group_vars/aws.yml

@@ -0,0 +1,58 @@
+---
+# Server Timzone + Locale
+timezone: America/New_York
+locale: en_US.UTF-8
+
+# Swap
+# Uncommend here and in /roles/common/tasks/main.yml
+swap_enabled: true
+swap_file_path: /swapfile
+swap_file_size_kb: 1024
+
+# General settings
+deploy_dir: /var/www/app
+deploy_user: deploy
+deploy_password: deploy
+deploy_app_name: contactrocket
+deploy_server_hostname: your-server.net
+
+# Nginx
+nginx_https_enabled: true
+
+# Certbot
+certbot_url: https://dl.eff.org/certbot-auto
+certbot_dir: /opt/certbot
+certbot_email: webmaster@your-server.net
+
+# Authorized Hosts
+# This copies your local public key to the remote machine
+# for passwordless login. Modify this!
+ssh_public_key_files:
+  - /Users/machine/.ssh/id_rsa.pub
+
+
+# Ruby
+ruby_install_version: 0.6.1
+chruby_version: 0.3.9
+ruby_version: 2.2.6
+
+# Rails
+rails_secret_key_base: changemeaswelldudepleaseomgsosooncuzotherwisetheygetsupermadatyouomgomg
+
+# Elasticsearch
+elasticsearch_version: 2.3.0
+
+# Monit
+monit_sidekiq_enabled: true
+
+# Odoo
+odoo_version: 10.0
+odoo_user: odoo
+odoo_repo_type: git
+odoo_config_db_host: localhost
+odoo_config_db_user: postgres
+odoo_config_db_passwd: postgres
+odoo_repo_dest: "/home/odoo/odoo"
+odoo_config_admin_passwd: admin
+odoo_config_addons_path: "/home/odoo/odoo/addons"
+odoo_config_server_wide_modules: web,web_kanban,connector

+ 58 - 0
group_vars/digitalocean.yml

@@ -0,0 +1,58 @@
+---
+# Server Timzone + Locale
+timezone: America/New_York
+locale: en_US.UTF-8
+
+# Swap
+# Uncommend here and in /roles/common/tasks/main.yml
+swap_enabled: true
+swap_file_path: /swapfile
+swap_file_size_kb: 1024
+
+# General settings
+deploy_dir: /var/www/app
+deploy_user: deploy
+deploy_password: deploy
+deploy_app_name: contactrocket
+deploy_server_hostname: box.your-server.net
+
+# Nginx
+nginx_https_enabled: true
+
+# Certbot
+certbot_url: https://dl.eff.org/certbot-auto
+certbot_dir: /opt/certbot
+certbot_email: webmaster@your-server.net
+
+# Authorized Hosts
+# This copies your local public key to the remote machine
+# for passwordless login. Modify this!
+ssh_public_key_files:
+  - /Users/machine/.ssh/id_rsa.pub
+
+
+# Ruby
+ruby_install_version: 0.6.1
+chruby_version: 0.3.9
+ruby_version: 2.2.6
+
+# Rails
+rails_secret_key_base: changemeaswelldudepleaseomgsosooncuzotherwisetheygetsupermadatyouomgomg
+
+# Elasticsearch
+elasticsearch_version: 2.3.0
+
+# Monit
+monit_sidekiq_enabled: true
+
+# Odoo
+odoo_version: 10.0
+odoo_user: odoo
+odoo_repo_type: git
+odoo_config_db_host: localhost
+odoo_config_db_user: postgres
+odoo_config_db_passwd: postgres
+odoo_repo_dest: "/home/odoo/odoo"
+odoo_config_admin_passwd: admin
+odoo_config_addons_path: "/home/odoo/odoo/addons"
+odoo_config_server_wide_modules: web,web_kanban,connector

+ 58 - 0
group_vars/local.yml

@@ -0,0 +1,58 @@
+---
+# Server Timzone + Locale
+timezone: America/New_York
+locale: en_US.UTF-8
+
+# Swap
+# Uncommend here and in /roles/common/tasks/main.yml
+swap_enabled: true
+swap_file_path: /swapfile
+swap_file_size_kb: 1024
+
+# General settings
+deploy_dir: /var/www/app
+deploy_user: deploy
+deploy_password: deploy
+deploy_app_name: contactrocket
+deploy_server_hostname: your-server.local
+
+# Nginx
+nginx_https_enabled: true
+
+# Certbot
+certbot_url: https://dl.eff.org/certbot-auto
+certbot_dir: /opt/certbot
+certbot_email: webmaster@your-server.net
+
+# Authorized Hosts
+# This copies your local public key to the remote machine
+# for passwordless login. Modify this!
+ssh_public_key_files:
+  - /Users/machine/.ssh/id_rsa.pub
+
+
+# Ruby
+ruby_install_version: 0.6.1
+chruby_version: 0.3.9
+ruby_version: 2.2.6
+
+# Rails
+rails_secret_key_base: changemeaswelldudepleaseomgsosooncuzotherwisetheygetsupermadatyouomgomg
+
+# Elasticsearch
+elasticsearch_version: 2.3.0
+
+# Monit
+monit_sidekiq_enabled: true
+
+# Odoo
+odoo_version: 10.0
+odoo_user: odoo
+odoo_repo_type: git
+odoo_config_db_host: localhost
+odoo_config_db_user: postgres
+odoo_config_db_passwd: postgres
+odoo_repo_dest: "/home/odoo/odoo"
+odoo_config_admin_passwd: admin
+odoo_config_addons_path: "/home/odoo/odoo/addons"
+odoo_config_server_wide_modules: web,web_kanban,connector

BIN
roles/.DS_Store


+ 20 - 0
roles/certbot/tasks/certbot.yml

@@ -0,0 +1,20 @@
+---
+- name: Create certbot directory
+  file: path=/opt/certbot state=directory mode=0755 owner=root group=root
+
+- name: Install certbot standalone
+  get_url:
+    url: "{{ certbot_url }}"
+    dest: "{{ certbot_dir }}/certbot-auto"
+
+- name: Ensure certbot-auto is executable.
+  file:
+    path: "{{ certbot_dir }}/certbot-auto"
+    mode: 0755
+
+- name: Get initial certificate
+  command: "{{ certbot_dir }}/certbot-auto certonly --non-interactive --quiet --agree-tos --email {{ certbot_email }} --standalone --pre-hook 'service nginx stop' --post-hook 'service nginx start' -d {{ deploy_server_hostname }}"
+  notify: restart nginx
+
+- name: Add cerbot renewel cronjob
+  cron: name="renew letsencrypt certificates" hour="0" minute="0" job="/bin/bash {{ certbot_dir }}/certbot-auto renew --non-interactive --quiet --standalone --pre-hook 'service nginx stop' --post-hook 'service nginx start'"

+ 3 - 0
roles/certbot/tasks/main.yml

@@ -0,0 +1,3 @@
+---
+- include: certbot.yml
+  when: nginx_https_enabled is defined and nginx_https_enabled == true

+ 68 - 0
roles/common/handlers/main.yml

@@ -0,0 +1,68 @@
+- name: update tzdata
+  command: /usr/sbin/dpkg-reconfigure --frontend noninteractive tzdata
+- name: update apt package cache
+  apt: update_cache=yes
+  when: ansible_os_family == "Debian"
+
+- name: install common packages debian/ubuntu
+  apt: pkg={{ item }} state=latest
+  with_items:
+    - python
+    - python-dev
+    - python-pip
+    - build-essential
+    - git
+    - libffi-dev
+    - ntp
+    - make
+    - cmake
+    - g++
+    - debhelper
+    - devscripts
+    - netcat
+    - openssl
+    - libpcre3
+    - dnsmasq
+    - procps
+    - libssl-dev
+    - libyaml-dev
+    - libreadline-dev
+    - libxml2-dev
+    - libxslt1-dev
+    - libicu-dev
+    - libkrb5-dev
+    - libcurl4-gnutls-dev
+    - libqt4-dev
+    - zlib1g-dev
+    - dh-systemd
+    - autotools-dev
+    - apt-transport-https
+    - software-properties-common
+
+  when: ansible_os_family == "Debian"
+
+- name: install common packages redhat/centos
+  yum: pkg={{ item }} state=latest
+  with_items:
+    - git
+    - ntp
+    - make
+    - cmake
+    - pcre-devel
+    - automake
+    - gcc
+    - gcc-c++
+    - kernel-devel
+    - dnsmasq
+    - procps
+    - libxml2-devel
+    - libxslt-devel
+    - python-devel
+    - libicu-devel
+    - libffi-devel
+    - krb5-devel
+    - qt4-devel
+    - zlib-devel
+  when: ansible_os_family == "RedHat"
+
+

+ 2 - 0
roles/common/tasks/hostname.yml

@@ -0,0 +1,2 @@
+- name: set hostname
+  hostname: name={{ deploy_server_hostname }}

+ 9 - 0
roles/common/tasks/locale.yml

@@ -0,0 +1,9 @@
+- name: set locale to {{locale}}
+  command: /usr/sbin/update-locale LANG={{locale}} LC_ALL={{locale}}
+
+- name: set /etc/localtime to {{ timezone  }}
+  command: /bin/cp /usr/share/zoneinfo/{{ timezone  }} /etc/localtime
+
+- name: set /etc/timezone to {{ timezone  }}
+  template: src=etc/timezone dest=/etc/timezone
+  notify: update tzdata

+ 12 - 0
roles/common/tasks/main.yml

@@ -0,0 +1,12 @@
+---
+- name: gather facts
+  setup:
+
+- name: Perform Safe Upgrade
+  apt: upgrade=safe update_cache=yes
+
+- include: hostname.yml
+- include: swap.yml
+  when: swap_enabled is defined and swap_enabled == true
+- include: tools.yml
+- include: locale.yml

+ 27 - 0
roles/common/tasks/swap.yml

@@ -0,0 +1,27 @@
+---
+- name: Create swap file
+  command: dd if=/dev/zero of={{ swap_file_path }} bs=1024 count={{ swap_file_size_kb }}k creates={{ swap_file_path }}
+
+- name: Change swap file permissions
+  file: path={{ swap_file_path }} owner=root group=root mode=0600
+
+- name: Check swap file type
+  command: file {{ swap_file_path }}
+  register: swapfile
+
+- name: Make swap file
+  command: mkswap {{ swap_file_path }}
+  when: swapfile.stdout.find('swap file') == -1
+
+- name: Write swap entry in fstab
+  mount: name=none src={{ swap_file_path }} fstype=swap opts=sw passno=0 dump=0 state=present
+
+- name: Mount swap
+  command: swapon {{ swap_file_path }}
+  when: ansible_swaptotal_mb < 1
+
+- name: Set swapiness in /proc/sys/vm/swappiness
+  shell: echo 10 | tee /proc/sys/vm/swappiness
+
+- name: Set swapiness in /etc/sysctl.conf
+  shell: echo vm.swappiness = 10 | tee -a /etc/sysctl.conf

+ 21 - 0
roles/common/tasks/tools.yml

@@ -0,0 +1,21 @@
+- name: Install Tools
+  apt: name={{ item }} state=latest update_cache=true
+  with_items:
+    - build-essential
+    - tmux
+    - vim
+    - htop
+    - git-core
+    - wget
+    - zlib1g-dev
+    - libssl-dev
+    - libreadline-dev
+    - libyaml-dev
+    - libxml2-dev
+    - libxslt1-dev
+    - libffi-dev
+    - curl
+    - libcurl3
+    - libcurl3-gnutls
+    - libcurl4-openssl-dev
+    - nodejs

+ 1 - 0
roles/common/templates/etc/timezone

@@ -0,0 +1 @@
+{{ timezone  }}

+ 2 - 0
roles/deploy-user/handlers/main.yml

@@ -0,0 +1,2 @@
+- name: Restart sshd
+  service: name=ssh state=restarted

+ 29 - 0
roles/deploy-user/tasks/main.yml

@@ -0,0 +1,29 @@
+---
+- name: Add deployment user
+  action: 'user name={{ deploy_user }} password={{ deploy_password }} generate_ssh_key=yes shell=/bin/bash'
+
+#- name: Add odoo user
+#  action: 'user name=odoo password=odoo generate_ssh_key=yes shell=/bin/bash'
+
+- name: Add authorized deploy keys
+  action: "authorized_key user={{ deploy_user }} key=\"{{ lookup('file', item) }}\""
+  with_items: '{{ ssh_public_key_files }}'
+
+- name: Add deploy user to sudoers
+  action: 'lineinfile dest=/etc/sudoers regexp="{{ deploy_user }} ALL" line="{{ deploy_user }} ALL=(ALL) NOPASSWD: ALL" state=present'
+
+#- name: Add odoo user to sudoers
+#  action: 'lineinfile dest=/etc/sudoers regexp="odoo ALL" line="odoo ALL=(ALL) NOPASSWD: ALL" state=present'
+
+#- name: Remove sudo group rights
+#  action: lineinfile dest=/etc/sudoers regexp="^%sudo" state=absent
+#
+#- name: Disallow root SSH access
+#  action: lineinfile dest=/etc/ssh/sshd_config regexp="^PermitRootLogin" line="PermitRootLogin no" state=present
+#  notify: Restart sshd
+#
+#- name: Disallow password authentication
+#  action: lineinfile dest=/etc/ssh/sshd_config regexp="^PasswordAuthentication" line="PasswordAuthentication no" state=present
+#  notify: Restart sshd
+
+

+ 3 - 0
roles/monit/handlers/main.yml

@@ -0,0 +1,3 @@
+---
+- name: restart monit
+  service: name=monit state=restarted enabled=yes

+ 18 - 0
roles/monit/tasks/main.yml

@@ -0,0 +1,18 @@
+---
+- name: Install monit
+  apt: name=monit state=latest
+
+- name: Enable monit HTTP interface
+  blockinfile:
+    dest: /etc/monit/monitrc
+    marker: '# {mark} ANSIBLE MANAGED BLOCK'
+    block: |
+      set httpd port 2812 and
+        use address localhost  # only accept connection from localhost
+        allow localhost        # allow localhost to connect to the server and
+        allow admin:monit      # require user 'admin' with password 'monit'
+
+- name: Copy Sidekiq monit config
+  template: src=monit_sidekiq_config dest=/etc/monit/conf.d/sidekiq_{{ deploy_app_name }}.conf owner={{ deploy_user }} group={{ deploy_user }}
+  notify: restart monit
+  when: monit_sidekiq_enabled is defined and monit_sidekiq_enabled == true

+ 6 - 0
roles/monit/templates/monit_sidekiq_config

@@ -0,0 +1,6 @@
+# Monit configuration for Sidekiq
+check process sidekiq_{{ deploy_app_name }}_production0
+  with pidfile "{{ deploy_dir }}{{ deploy_app_name }}/shared/tmp/pids/sidekiq-0.pid"
+  start program = "/usr/bin/sudo -u {{ deploy_user }} /bin/bash -c 'cd {{ deploy_dir }}{{ deploy_app_name }}/current && /usr/local/bin/chruby-exec ruby-{{ ruby_version }} -- bundle exec sidekiq  --index 0 --pidfile {{ deploy_dir }}{{ deploy_app_name }}/shared/tmp/pids/sidekiq-0.pid --environment production  --logfile {{ deploy_dir }}{{ deploy_app_name }}/shared/log/sidekiq.log -d'"
+
+  stop program = "/usr/bin/sudo -u {{ deploy_user }} /bin/bash -c 'cd {{ deploy_dir }}{{ deploy_app_name }}/current && /usr/local/bin/chruby-exec ruby-{{ ruby_version }}-- bundle exec sidekiqctl stop {{ deploy_dir }}{{ deploy_app_name }}/shared/tmp/pids/sidekiq-0.pid'"

+ 34 - 0
roles/odoo/.travis.yml

@@ -0,0 +1,34 @@
+---
+sudo: required
+dist: trusty
+language: python
+cache: pip
+python: "2.7"
+
+env:
+  # Odoo 8.0
+  - ODOO_VERSION=8.0 ODOO_INSTALL_TYPE=standard ANSIBLE_VERSION="2.1,<2.2"
+  - ODOO_VERSION=8.0 ODOO_INSTALL_TYPE=buildout ANSIBLE_VERSION="2.1,<2.2"
+  - ODOO_VERSION=8.0 ODOO_INSTALL_TYPE=standard ANSIBLE_VERSION="2.2,<2.3"
+  - ODOO_VERSION=8.0 ODOO_INSTALL_TYPE=buildout ANSIBLE_VERSION="2.2,<2.3"
+  # Odoo 9.0
+  - ODOO_VERSION=9.0 ODOO_INSTALL_TYPE=standard ANSIBLE_VERSION="2.1,<2.2"
+  - ODOO_VERSION=9.0 ODOO_INSTALL_TYPE=buildout ANSIBLE_VERSION="2.1,<2.2"
+  - ODOO_VERSION=9.0 ODOO_INSTALL_TYPE=standard ANSIBLE_VERSION="2.2,<2.3"
+  - ODOO_VERSION=9.0 ODOO_INSTALL_TYPE=buildout ANSIBLE_VERSION="2.2,<2.3"
+  # Odoo 10.0
+  - ODOO_VERSION=10.0 ODOO_INSTALL_TYPE=standard ANSIBLE_VERSION="2.1,<2.2"
+  - ODOO_VERSION=10.0 ODOO_INSTALL_TYPE=buildout ANSIBLE_VERSION="2.1,<2.2"
+  - ODOO_VERSION=10.0 ODOO_INSTALL_TYPE=standard ANSIBLE_VERSION="2.2,<2.3"
+  - ODOO_VERSION=10.0 ODOO_INSTALL_TYPE=buildout ANSIBLE_VERSION="2.2,<2.3"
+
+before_install:
+  - sudo apt-get update -qq
+  - sudo apt-get install -qq python-apt
+
+install:
+  - pip install "ansible>=$ANSIBLE_VERSION"
+  - "{ echo '[defaults]'; echo 'roles_path = ../'; } >> ansible.cfg"
+
+script:
+  - ./tests/run.sh

+ 193 - 0
roles/odoo/README.md

@@ -0,0 +1,193 @@
+# Odoo [![Build Status](https://travis-ci.org/osiell/ansible-odoo.png)](https://travis-ci.org/osiell/ansible-odoo)
+
+Ansible role to install Odoo from a Git or Mercurial repository,
+and configure it.
+
+This role supports two types of installation:
+
+* **standard**: install the Odoo dependencies from APT repositories and the
+Odoo project from a Git/Hg repository. Odoo is configured with Ansible options
+(`odoo_config_*` ones).
+
+* **buildout**: build the Odoo project from a Git/Hg repository containing a
+Buildout configuration file based on the
+[anybox.recipe.odoo](https://pypi.python.org/pypi/anybox.recipe.odoo/) recipe.
+Odoo and its dependencies are then installed and executed inside a Python
+virtual environment. The configuration part is also managed by Buildout
+(`odoo_config_*` options are not used excepting the `odoo_config_db_*` ones
+for PostgreSQL related tasks).
+
+Minimum Ansible Version: 2.1
+
+## Supported versions and systems
+
+### Standard (odoo_install_type: standard)
+
+| System / Odoo | 8.0 | 9.0 | 10.0 |
+|---------------|-----|-----|------|
+| Debian 7      | yes |  -  |  -   |
+| Debian 8      | yes | yes | yes  |
+| Ubuntu 12.04  | yes |  -  |  -   |
+| Ubuntu 14.04  | yes | yes | yes  |
+| Ubuntu 16.04  | yes | yes | yes  |
+
+### Buildout (odoo_install_type: buildout)
+
+You only need a Debian-based system, all the stuff is then handled by Buildout
+to run Odoo >= 8.0.
+
+## Example (Playbook)
+
+### odoo_install_type: standard (default)
+
+Standard installation (assuming that PostgreSQL is installed and running on
+the same host):
+
+```yaml
+- name: Odoo
+  hosts: odoo_server
+  become: yes
+  roles:
+    - odoo
+  vars:
+    - odoo_version: 10.0
+    - odoo_config_admin_passwd: SuPerPassWorD
+```
+
+With the standard installation type you configure Odoo with the available
+`odoo_config_*` options.
+
+Standard installation but with PostgreSQL installed on a remote host (and
+available from your Ansible inventory):
+
+```yaml
+- name: Odoo
+  hosts: odoo_server
+  become: yes
+  roles:
+    - odoo
+  vars:
+    - odoo_version: 10.0
+    - odoo_config_admin_passwd: SuPerPassWorD
+    - odoo_config_db_host: pg_server
+    - odoo_config_db_user: odoo
+    - odoo_config_db_passwd: PaSsWoRd
+```
+
+Standard installation from a personnal Git repository such as your repository
+looks like this:
+
+```sh
+REPO/
+├── server              # could be a sub-repository of https://github.com/odoo/odoo
+├── addons_oca_web      # another sub-repository (https://github.com/OCA/web here)
+├── addons_oca_connector    # yet another sub-repository (https://github.com/OCA/connector)
+└── addons              # custom modules
+```
+
+Here we set some options required by the ``connector`` framework:
+
+```yaml
+- name: Odoo
+  hosts: odoo_server
+  become: yes
+  roles:
+    - odoo
+  vars:
+    - odoo_version: 10.0
+    - odoo_repo_type: git
+    - odoo_repo_url: https://SERVER/REPO
+    - odoo_repo_rev: master
+    - odoo_repo_dest: "/home/{{ odoo_user }}/odoo"
+    - odoo_init_env:
+        ODOO_CONNECTOR_CHANNELS: root:2
+    - odoo_config_admin_passwd: SuPerPassWorD
+    - odoo_config_addons_path:
+        - "/home/{{ odoo_user }}/odoo/server/openerp/addons"
+        - "/home/{{ odoo_user }}/odoo/server/addons"
+        - "/home/{{ odoo_user }}/odoo/addons_oca_web"
+        - "/home/{{ odoo_user }}/odoo/addons_oca_connector"
+        - "/home/{{ odoo_user }}/odoo/addons"
+    odoo_config_server_wide_modules: web,web_kanban,connector
+    odoo_config_workers: 8
+```
+
+### odoo_install_type: buildout
+
+With a Buildout installation type, Odoo is installed and configured directly
+by Buildout:
+
+```yaml
+- name: Odoo
+  hosts: odoo_server
+  become: yes
+  roles:
+    - odoo
+  vars:
+    - odoo_install_type: buildout
+    - odoo_version: 10.0
+    - odoo_repo_type: git
+    - odoo_repo_url: https://github.com/osiell/odoo-buildout-example.git
+    - odoo_repo_rev: "{{ odoo_version }}"
+    - odoo_repo_dest: "/home/{{ odoo_user }}/odoo"
+```
+
+The same but with PostgreSQL installed on a remote host (and available from
+your Ansible inventory):
+
+```yaml
+- name: Odoo
+  hosts: odoo_server
+  become: yes
+  roles:
+    - odoo
+  vars:
+    - odoo_install_type: buildout
+    - odoo_version: 10.0
+    - odoo_repo_type: git
+    - odoo_repo_url: https://github.com/osiell/odoo-buildout-example.git
+    - odoo_repo_rev: "{{ odoo_version }}"
+    - odoo_repo_dest: "/home/{{ odoo_user }}/odoo"
+    - odoo_config_db_host: pg_server
+    - odoo_config_db_user: odoo
+    - odoo_config_db_passwd: PaSsWoRd
+```
+
+By default Ansible is looking for a `bootstrap.py` script and a `buildout.cfg`
+file at the root of the cloned repository to call Buildout, but you can change
+that to point to your own files. Assuming your repository looks like this:
+
+```sh
+REPO/
+├── addons              # custom modules
+├── bin
+│   └── bootstrap.py
+├── builtout.cfg
+├── builtout.dev.cfg
+├── builtout.prod.cfg
+└── builtout.test.cfg
+```
+
+We just set the relevant options to tell Ansible the files to use with the
+`odoo_buildout_*` options:
+
+```yaml
+- name: Odoo
+  hosts: odoo_server
+  become: yes
+  roles:
+    - odoo
+  vars:
+    - odoo_install_type: buildout
+    - odoo_version: 10.0
+    - odoo_repo_type: git
+    - odoo_repo_url: https://SERVER/REPO
+    - odoo_repo_rev: master
+    - odoo_repo_dest: "/home/{{ odoo_user }}/odoo"
+    - odoo_buildout_bootstrap_path: "/home/{{ odoo_user }}/odoo/bin/bootstrap.py"
+    - odoo_buildout_config_path: "/home/{{ odoo_user }}/odoo/buildout.prod.cfg"
+```
+
+## Variables
+
+See the [defaults/main.yml](defaults/main.yml) file.

+ 109 - 0
roles/odoo/defaults/main.yml

@@ -0,0 +1,109 @@
+odoo_install_type: standard     # standard, buildout
+odoo_version: 9.0
+odoo_service: odoo
+odoo_user: deploy
+odoo_user_passwd: deploy
+odoo_user_system: False
+odoo_init: True
+odoo_init_env: {}
+    #VAR1: value1
+    #VAR2: value2
+odoo_logdir: "/var/log/{{ odoo_user }}"
+odoo_workdir: "/home/{{ odoo_user }}/odoo"
+odoo_rootdir: "{{ odoo_install_type == 'buildout' and '/home/'+odoo_user+'/odoo/parts/odoo' or '/home/'+odoo_user+'/odoo/server' }}"
+
+# Project repository to deploy
+odoo_repo_type: git     # git or hg
+odoo_repo_url: "{{ odoo_install_type == 'buildout' and 'https://github.com/osiell/odoo-buildout-example.git' or 'https://github.com/odoo/odoo.git' }}"
+odoo_repo_dest: "{{ odoo_install_type == 'buildout' and odoo_workdir or odoo_rootdir }}"
+odoo_repo_rev: "{{ odoo_version }}"
+odoo_repo_update: False  # Update the working copy or not. This option is
+                        # ignored on the first run (a checkout of the working
+                        # copy is always processed on the given revision)
+                        # WARNING: uncommited changes will be discarded!
+odoo_repo_depth: 1      # Set to 0 to clone the full history (slower)
+                        # (this option is not supported with hg)
+
+# Third party programs options
+odoo_wkhtmltox_version: 0.12.1      # Download URLs available in the
+                                    # 'odoo_wkhtmltox_urls' variable
+                                    # (see 'vars/main.yml')
+odoo_reportlab_font_url: http://www.reportlab.com/ftp/pfbfer.zip
+
+# Tasks related to PostgreSQL
+odoo_postgresql_set_user: True
+odoo_postgresql_user_role_attr: CREATEDB,NOSUPERADMIN
+odoo_postgresql_active_unaccent: True
+
+# Standard installation/configuration options (odoo_install_type == 'standard')
+odoo_config_file: "/home/{{ odoo_user }}/{{ odoo_service }}.conf"
+odoo_force_config: True
+odoo_config_addons_path:
+    - "/home/{{ odoo_user }}/odoo/server/openerp/addons"
+    - "/home/{{ odoo_user }}/odoo/server/addons"
+odoo_config_admin_passwd: admin
+odoo_config_auto_reload: False
+odoo_config_csv_internal_sep: ','
+odoo_config_data_dir: "/home/{{ odoo_user }}/.local/share/Odoo"
+odoo_config_db_host: localhost
+odoo_config_db_host_user: postgres
+odoo_config_db_maxconn: 64
+odoo_config_db_name: odoo
+odoo_config_db_passwd: postgres
+odoo_config_db_port: 5432
+odoo_config_db_template: template0
+odoo_config_db_user: postgres
+odoo_config_dbfilter: '.*'
+odoo_config_debug_mode: False           # <= 9.0
+odoo_config_pidfile: None
+odoo_config_proxy_mode: False
+odoo_config_email_from: False
+odoo_config_geoip_database: /usr/share/GeoIP/GeoLiteCity.dat
+odoo_config_limit_memory_hard: 805306368
+odoo_config_limit_memory_soft: 671088640
+odoo_config_limit_time_cpu: 60
+odoo_config_limit_time_real: 120
+odoo_config_limit_time_real_cron: -1    # >= 10.0
+odoo_config_list_db: True
+odoo_config_log_db: False
+odoo_config_log_level: info
+odoo_config_logfile: None
+odoo_config_logrotate: False
+odoo_config_longpolling_port: 8072
+odoo_config_osv_memory_age_limit: 1.0
+odoo_config_osv_memory_count_limit: False
+odoo_config_max_cron_threads: 2
+odoo_config_secure_cert_file: server.cert
+odoo_config_secure_pkey_file: server.pkey
+odoo_config_server_wide_modules: None
+odoo_config_smtp_password: False
+odoo_config_smtp_port: 25
+odoo_config_smtp_server: localhost
+odoo_config_smtp_ssl: False
+odoo_config_smtp_user: False
+odoo_config_syslog: True
+odoo_config_timezone: True
+odoo_config_translate_modules: "['all']"
+odoo_config_unaccent: False
+odoo_config_without_demo: False
+odoo_config_workers: 0
+odoo_config_xmlrpc: True
+odoo_config_xmlrpc_interface: ''
+odoo_config_xmlrpc_port: 8069
+odoo_config_xmlrpcs: True
+odoo_config_xmlrpcs_interface: ''
+odoo_config_xmlrpcs_port: 8071
+# Custom configuration options
+odoo_config_custom: {}
+    #your_option1: value1
+    #your_option2: value2
+
+# Buildout installation options (odoo_install_type == 'buildout')
+odoo_buildout_venv_path: "{{ odoo_workdir }}/sandbox"
+odoo_buildout_bootstrap_path: "{{ odoo_workdir }}/bootstrap.py"
+odoo_buildout_bin_path: "{{ odoo_workdir }}/bin/buildout"
+odoo_buildout_config_path: "{{ odoo_workdir }}/buildout.cfg"
+odoo_buildout_odoo_bin_path: "{{ odoo_workdir }}/bin/start_odoo"
+
+# Extra options
+odoo_user_sshkeys: ""       # ../../path/to/public_keys/*

+ 34 - 0
roles/odoo/files/addons/odoo-product-configurator/.gitignore

@@ -0,0 +1,34 @@
+# sphinx build directories
+_build/
+
+# dotfiles
+.*
+!.gitignore
+!.mailmap
+# compiled python files
+*.py[co]
+# setup.py egg_info
+*.egg-info
+# emacs backup files
+*~
+# hg stuff
+*.orig
+status
+# odoo filestore
+odoo/filestore
+# maintenance migration scripts
+odoo/addons/base/maintenance
+
+# generated for windows installer?
+install/win32/*.bat
+install/win32/meta.py
+
+# various virtualenv
+/bin/
+/build/
+/dist/
+/include/
+/lib/
+/man/
+/share/
+/src/

+ 661 - 0
roles/odoo/files/addons/odoo-product-configurator/LICENSE

@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.

+ 41 - 0
roles/odoo/files/addons/odoo-product-configurator/README.md

@@ -0,0 +1,41 @@
+[![Tech Doc](http://img.shields.io/badge/9.0-docs-8f8f8f.svg?style=flat)](https://www.pledra.com/odoo-product-configurator/documentation)
+
+# Odoo Product Configurator
+Odoo modules enabling dynamic product configuration
+
+The product configurator modules suite prevents automatic generation of variants in Odoo and provides on-demand generation through friendly user interfaces.
+
+[//]: # (addons)
+Available addons
+----------------
+addon | version | summary
+--- | --- | ---
+[product_configurator](product_configurator/) | 1.0 | Base for product_configurator_wizard and website_product_config
+[product_configurator_wizard](product_configurator_wizard/) | 1.0 | Dynamic configuration wizard for Odoo backend
+[product_configurator_mrp](product_configurator_mrp/) | 1.0  | MRP Support for product variants generation
+[website_product_configurator](website_product_configurator/) | 1.0 | Website configuration interface integrated with e-commerce
+[website_product_configurator_mrp](website_product_configurator_mrp) | 1.0 | MRP Support for website_product_configurator
+
+[//]: # (end addons)
+
+
+# Credits
+
+##Investors
+
+* initOS
+* Firma Casper Francke
+* WilldooIT
+* Camptocamp
+* Madsack Media Store
+* OpenIndustry.it
+* Asphalt Zipper
+* Access Windows and Doors Inc
+* BIG Consulting GmbH
+* Ursainfosystems
+* IT IS AG
+
+Maintainer
+----------
+
+[![Pledra Logo](https://www.pledra.com/logo.png)](https://www.pledra.com/)

+ 177 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/Makefile

@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PledraProductConfigurator.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PledraProductConfigurator.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/PledraProductConfigurator"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PledraProductConfigurator"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

+ 5 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/_static/style.css

@@ -0,0 +1,5 @@
+img.logo {
+    background-color: #fafafa !important;
+    border-radius: 20px !important;
+    padding: 8px !important;
+}

+ 5 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/_templates/layout.html

@@ -0,0 +1,5 @@
+{# layout.html #}
+{# Import the theme's layout. #}
+{% extends "!layout.html" %}
+
+{% set css_files = css_files + ['_static/style.css'] %}

+ 335 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/conf.py

@@ -0,0 +1,335 @@
+# -*- coding: utf-8 -*-
+#
+# Pledra Product Configurator documentation build configuration file, created by
+# sphinx-quickstart on Fri Apr 22 15:42:51 2016.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+
+import sphinx_rtd_theme
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('/opt/odoo/9.0/pledra-buildout/parts/odoo'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.coverage',
+    'sphinx.ext.ifconfig',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Pledra Product Configurator'
+copyright = u'2016, Paul Catinean'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'sphinx_rtd_theme'
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# (Optional) Logo. Should be small enough to fit the navbar (ideally 24x24).
+# Path should be relative to the ``_static`` files directory.
+html_logo = "images/logo.png"
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'PledraProductConfiguratordoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+  ('index', 'PledraProductConfigurator.tex', u'Pledra Product Configurator Documentation',
+   u'Paul Catinean', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'pledraproductconfigurator', u'Pledra Product Configurator Documentation',
+     [u'Paul Catinean'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'PledraProductConfigurator', u'Pledra Product Configurator Documentation',
+   u'Paul Catinean', 'PledraProductConfigurator', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+
+# -- Options for Epub output ----------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'Pledra Product Configurator'
+epub_author = u'Paul Catinean'
+epub_publisher = u'Paul Catinean'
+epub_copyright = u'2016, Paul Catinean'
+
+# The basename for the epub file. It defaults to the project name.
+#epub_basename = u'Pledra Product Configurator'
+
+# The HTML theme for the epub output. Since the default themes are not optimized
+# for small screen space, using the same theme for HTML and epub output is
+# usually not wise. This defaults to 'epub', a theme designed to save visual
+# space.
+#epub_theme = 'epub'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# A sequence of (type, uri, title) tuples for the guide element of content.opf.
+#epub_guide = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
+
+# Choose between 'default' and 'includehidden'.
+#epub_tocscope = 'default'
+
+# Fix unsupported image types using the PIL.
+#epub_fix_images = False
+
+# Scale large images.
+#epub_max_image_width = 0
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#epub_show_urls = 'inline'
+
+# If false, no index is generated.
+#epub_use_index = True

+ 15 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional.rst

@@ -0,0 +1,15 @@
+========================
+Functional Documentation
+========================
+
+.. toctree::
+    :titlesonly:
+
+    functional/introduction
+    functional/installing
+    functional/add_product
+    functional/attributes
+    functional/config_restrictions
+    functional/config_steps
+    functional/config_images
+    functional/pricing

+ 55 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/add_product.rst

@@ -0,0 +1,55 @@
+********************
+Adding a new product
+********************
+
+The Product Configurator makes it easy for you to add configurable products in just a few steps. In the rows below we will explain the entire process of adding a product that can be configured.
+
+Defining a configurable product
+===============================
+
+The first thing we need to do is navigate to **Sales** tab in the back-end and afterwards under the **Configurable Products** section we select **Configurable Templates** and click the **Create** button.
+
+.. image:: images/adding_product1.png
+    :align: center
+    :alt: alternate text
+
+.. note::
+    If you already have products in the database and wish to make them configurable just click on the form button with the wrench icon on the top right
+
+.. image:: images/config_button_on.png
+    :align: center
+    :alt: alternate text
+
+After we have clicked on the **Create** button in the first section we are now given Odoo's standard product form view. When we are done with naming our product and setting all the relevant information we can move on to the **Variants** tab inside the form.
+
+Adding attributes and values
+============================
+
+.. note::
+    If you are already running the system as an Administrator you will have access to the Product Variants and Product Configurator from the start. In order for other users to gain access you must add them to the Product Configurator group which will activate Product Variants as well.
+
+    To do this you must first activate Odoo's *Developer mode* as an Administrator. This is done by cliking in the right upper corner in the backend where the Administrator panel is located. Select **About** from the dropdown panel a pop-up window will appear. On the new opened pop-up window click the **Activate the developer mode**.
+
+    After the *Developer mode* is activated you must now navigate to the **Settings** tab and from the **Users** menu on the left click **Users**. Select your desired user and assign him the Product Configurator group.
+
+Every configurable product starts with the population of the *Attributes* table. The attributes inside the *Variants* tab are used to add all the possible options of the product. When a regular product has attributes set it will automatically generate all possible combinations on saving. However this is often a unwanted behavior and is inhibited by configurable products.
+
+We recomand adding data to the *Attributes* table by planing and analysing your product first. Include all possible options from the start, for example if colors, sizes and shapes the attribute table can be populated like this:
+
+===============  =============================
+ **Attributes**   **Attibute Values**
+---------------  -----------------------------
+  Color           Red, Blue, Green
+  Size            Small, Medium, Large
+  Shape           Square, Circle
+===============  =============================
+
+The **Requierd** field makes sure that the configuration process of the product cannot finish until it a value has been provided for the specific attribute. Also depending on the configuration interface it might also enforce client-side validation. This prevents users from proceeding to the next step until setting a value for the attribute in question.
+
+The **Multi** field marks the ability to store more than one value for the attribute. In our demo instance it is used for the car's Optionals (Sunroof, Tow Hook etc)
+
+The **Custom** field determines if the attribute allows values other than the ones provided. These are entered by the user in the configuration interface and can be also restricted to certain field types and even values. More on Custom values here (link)
+
+.. image:: images/conf_steps1.png
+    :align: center
+    :alt: alternate text

+ 35 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/attributes.rst

@@ -0,0 +1,35 @@
+**********
+Attributes
+**********
+
+==========
+Attributes
+==========
+
+Attributes in the Product Configurator have been enhanced a bit. Navigating to **Sales->Products->Attributes** we have the standard tree view. Creating a new attribute or clicking on an existing one now provides a form view.
+
+**Required** and **Multi** fields serve as default values when adding this attribute to a configurable template in the attribute lines.
+
+=============
+Custom values
+=============
+
+After adding the attribute to a configurable template and clicking the Custom boolean field both the frotend and backend configurator will check the custom field type. Depending on field type set you can have more options and different behavior in the configuration interfaces.
+
+**Integer** and **Float** values will permit a minimum and maximum value accepted for client-side and server-side validation
+
+**Color** field type provides a color picker widget
+
+**Searchable**: By default when a customer finishes configuration of a product, a search is made to see if there is such a product defined in the database already. This field determines if that search should include this custom value as well.
+
+================
+Attribute Values
+================
+
+Attribute values now have a link towards variants (product.product). Linking attribute values to products provides a large variety of benefits:
+
+1. When selecting attributes in the frontend, the price of the related product is pulled and can offer a price for the selection as well as the final cost by adding everything up.
+
+2. Images from the product variants can be used as thumbnails, it is a mandatory setup in order for the thumbnail view to work in the frontend.
+
+3. Bom's can be computed by using all the related products from the attributes.

+ 28 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/config_images.rst

@@ -0,0 +1,28 @@
+********************
+Configuration images
+********************
+
+Any product that is built from the ground up goes through changes in appearence. Having this in mind, the Product Configurator offers a easy way to help the user visualize his product from start to finish.
+
+Looking at the **Configuration Images** we notice 3 columns
+
+1. **Name** column defines the name of our image.
+
+2. **Image** column is where we upload our image.
+
+3. **Configuration** column holds the configuration code that will trigger the display of the related image. This means that in our case when the user selects Color: Red, he gets the first line, when he selects Color: Red, and Rims: Double-spoke 18" he gets the fourth image.
+
+.. note::
+    The algorithm applied tries to match the closest configuration. First it gets the line that matches all the values specified with the configuration of the user. If two or more are found it is sorted by sequence.
+
+|
+
+.. image:: images/adding_image1.png
+    :align: center
+    :alt: alternate text
+
+
+In our example this is necesary for creating diffrent appearances of the car when the user wants to select a diffrent pair of rims or another color. What the Product configurator does here is that it will switch betwen images in the interface. We use two separate images of the same car but in the second picture the rims are diffrent thus creating a shifting effect.
+
+.. note::
+    While this mechanism is fine for simple builds it is highly impractical for complex products with many images. An update is already in the roadmap to provide placement of static images on the canvas with different z-index instead of providing separate images. Also 3D models and animations are planned as well

+ 30 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/config_restrictions.rst

@@ -0,0 +1,30 @@
+**************************
+Configuration restrictions
+**************************
+
+Often times a product does not support all possible combinations and values must be restricted depending on the selection of others. The main purpose of the **Configuration Restrictions** table is to facilitate an error free final configuration. We have previosuly set all the possible options on the product in the attributes section, now we must add configuration restrictions to prevent incompatible builds.
+
+We will illustrate the concept and process by using our car demo product. Our objective is to create a rule for the *Engine* so when a user selects the fuel type as *Gasoline* or *Diesel* all incompatible engine models are eliminated from the options list.
+
+Switching to the Configurator tab under the *Configuration Restrictions* a new line is added. In this table you are given three colums: **Attribute line**, **Values** and **Restrictions**. First we pick our restricted attribute in this case Engine, and then the values we wish to restrict from that attribute. Our first line contains all the Gasoline engine types. Lastly in the Restrictions box we make a new entry using the *Create and Edit* option to determine under what conditions will the selected values be available.
+
+At this point in time the following setup can be translated into something such as: "Values '218i, 220i, 228i, M235i, M235i xDrive' of the attribute 'Engine' are only available if ".
+
+We then complete that statement by defining the rules of the restriction. First we add a name to give a readable reference of the rule the restrictions imply (Gasoline). Below the name field we are presented with 5 columns:
+
+1. **Attribute**: The attribute the rule depends on
+2. **Condition**: The condition that determines if the attribute should be "In" or "Not In" the values provided in the next column
+3. **Values**: Values of the attribute that should be checked
+4. **Operator**: "And / Or" operator for chaining with the next rule
+
+By adding "Fuel", "In", "Gasoline" we have made the full statement of "Values '218i, 220i, 228i, M235i, M235i xDrive' of the attribute 'Engine' are only available if Fuel has values in Gasoline".
+
+If the user selects the Engine type as Gasoline he will be prompted only the engines that work on that fuel type and not both. The same logic is applied in the next line to diesel engines. This enforces client-side validation as well as server-side meaning that a configuration that breaks these rules can not be saved into the database.
+
+Line three for example has the following restriction: If the engine type *218i* is selected you can not pick it in combination with the optional packages *Sport line* and *Luxury line*.
+
+.. image:: images/conf_restriction1.png
+    :align: center
+    :alt: alternate text
+
+This is a concept widely used everywhere in Odoo, including the search function called Domain Restrictions (https://www.odoo.com/documentation/9.0/reference/orm.html#reference-orm-domains)

+ 27 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/config_steps.rst

@@ -0,0 +1,27 @@
+*******************
+Configuration steps
+*******************
+
+If your product has many attributes there is a high chance you won't want a large form that mashes all the fields together. Grouping fields into separate steps enhances usability and simplifies navigation. Furthermore in the website configurator we can also set different views per each step and offer more visual flexibility.
+
+We will again use our demo product to ilustrate this feature. Having all our attributes previously defined and added to the configurable template from the previous chapter we will navigate to the Configurator tab and add a new line to the configuration steps table.
+
+The first parameter is the configuration step, you can just write any name (in our case Engine) and click create from the dropdown menu. Then on the right we select the attributes which will appear in this configuration steps (in our case Fuel type and Engine model).
+
+.. image:: images/conf_steps2.png
+    :align: center
+    :alt: alternate text
+
+At this point in time when loading the configuration interface the product will have the Engine configuration step and the two attributes and input
+fields to supply data.
+
+After adding as many steps as you please you can re-order them with drag and drop to change the sequence in which they appear through the configuration interface.
+
+For the website interface you can change the select box default view. First navigate to the configuration step in question either from the Configuration Steps menu on the left or by clicking the blue arrow on the configuration step table. Then select a different view in the View field and save.
+
+Now when you access the website configurator and reach your step by clickin on the tab or via Next button you will be presented with the new display.
+
+.. note::
+    So far the Product Configurator comes with two pre-defined views (Select-Box & Radio-Thumbail). In order for the thumbnail view to shows images of
+    attributes you must link the attributes to products. See more here (link)
+

BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_image1.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product1.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product2.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product3.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product4.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/adding_product5.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_restriction1.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_restriction2.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_steps1.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/conf_steps2.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/config_button_off.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/config_button_on.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/configurator.png


BIN
roles/odoo/files/addons/odoo-product-configurator/doc/functional/images/logo.png


+ 53 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/installing.rst

@@ -0,0 +1,53 @@
+***********************************
+Installing the Product Configurator
+***********************************
+
+In the rows below we will describe the process of installing the Product Configurator modules.
+
+For this installation example we will assume that our Odoo server path will be available at *"~/odoo/9.0/server/"*. Keep in mind that any other location of your preference could be used.
+
+1. Create the extra addons directory where the Product Configurator directory module will be placed:
+
+    We recommend you to do it like so *"~/odoo/9.0/extra-addons/"*
+
+
+2. Add the contents of the Product Configurator module in the newly created *"extra-addons"* directory:
+
+    Adding your files here can be done in any way. For example you can do this by using the Github repository
+    or if you recieved the files from Pledra, you can copy and paste them in the directory.
+
+    The new directory path should look like this *"~/odoo/9.0/extra-addons/product-configurator"*
+
+
+3. Let Odoo see that the Product Configurator module is added:
+
+    In your server *".conf"* file on the *"addons_path"* line add the path to the Product Configurator module (*"/odoo/9.0/extra-addons/product-configurator"*).
+
+    The addons path line should look like this *"addons_path = /odoo/9.0/server/openerp/addons,/odoo/9.0/server/addons,/odoo/9.0/extra-addons/product-configurator"*.
+
+
+4. Make the Product configurator module available in your Odoo instance:
+
+    Log in to Odoo using admin, enable the *Developer Mode* in the **About** box at the Admin panel which is located in the upper right corner, and in the **Apps** top menu select **Update Apps List**. Now Odoo should know about the Product configurator module.
+
+
+5. Select the **Apps** menu at the top and, in the search bar in the top right, delete the default Apps filter and search for Product configurator. Click on its Install button and the installation will be concluded.
+
+    If you want to install both the front-end and back-end Product configurator module you only have to install *"Website Product Configurator"*.
+
+
+User access
+------------
+
+In order for users to have access to the Product Configurator module we must first grant it (for example employees need access).
+
+1. Granting access to the Product Configurator is done like this:
+
+    The Odoo *Developer mode* should be activated first.
+
+    Then we navigate to **Settings** -> **Users** and click on the user we want do give access to.
+
+    A new page is opened and on the *Technical Settings* section we check **Manage Product Variants** and **Manage Product Configurator**.
+
+This will give users the ability to manage the Product Configurator module and use it's features.
+

+ 28 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/introduction.rst

@@ -0,0 +1,28 @@
+************
+Introduction
+************
+
+.. image:: images/configurator.png
+    :width: 200px
+    :align: left
+    :height: 200px
+    :alt: alternate text
+
+**What is the Product Configurator?**
+=====================================
+
+The Product Configurator is a versatile tool that is addresed to bussineses around the world that want to offer custom made products.
+
+Combining the simple use of Odoo with the Product Configurator we managed to achive a powerful tool that manages to adapt to every bussines type.
+
+.. Visual customization is one of the key points of our product. A modular approach using themes and building blocks ensures a visual display of the product you configure.
+
+Furthermore the Product Configurator comes with a series of standard views, themes and everything is easily extensible to give full freedom to the user.
+
+Setting up your product configuration requires no technical knowledge. With just a few clicks your customers have access to a simple user interface that automatically guides them through the full process of customization.
+
+.. This means you can quickly build your configuration process. No costly customization or a line of code is required.
+
+Saving time is also one of the key aspects of the Product Configurator. Every time you process a request from a customer via e-mail or phone, you lose precious time.
+
+We believe that any configuration process, no matter how complex, should not require manual intervention or costly and time-consuming customization. All you should have to do is process the final order and make better use of the time saved.

+ 48 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/functional/pricing.rst

@@ -0,0 +1,48 @@
+.. _pricing-functional:
+
+Pricing
+-------
+
+***********
+Computation
+***********
+
+Computing prices for configurable products is done differently from the legacy method.
+
+**Legacy method**
+
+In order to compute prices for standard variants Odoo does the following:
+
+1. Uses the product template price as a starting / base price.
+
+2. Loops through all attribute values of the product variant
+
+3. **Adds the template base price + sum of all price_extra fields on each attribute value.**
+
+
+**Product configurator method**
+
+Everything until step 2 remains the same. What is different is that we do not use the price_extra field
+from the product.attribute.value.
+
+Instead we use the price of the product linked to the product.attribute.value.
+
+**Base price (product.template) + sum of all products related to the attribute.values on the variant.**
+
+As such if you want to compute an extra price for a certain attribute, create a regular product, set a price and link the product to the attribute value in the form view.
+
+.. note::
+
+   In order for live pricing updates in the website configurator to work the same setup must be made
+
+For a technical explanation on how this is implemented please check the :ref:`Pricing Technical <pricing-technical>`.
+
+******
+Format
+******
+
+The format in which the price is displayed depends on the user's language.
+
+It follows the rules set in Settings->Languages-> * Language of the user *
+
+If you want to apply another format that is not available in the language model visit :ref:`Pricing Technical <pricing-technical>`.

BIN
roles/odoo/files/addons/odoo-product-configurator/doc/images/logo.png


+ 11 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/index.rst

@@ -0,0 +1,11 @@
+===================================
+Odoo Product Configurator by Pledra
+===================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   functional
+   technical

+ 9 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/technical.rst

@@ -0,0 +1,9 @@
+=======================
+Technical Documentation
+=======================
+
+.. toctree::
+    :titlesonly:
+
+   technical/concept
+   technical/pricing

+ 131 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/technical/concept.rst

@@ -0,0 +1,131 @@
+*******
+Concept
+*******
+
+.. image:: images/configuration-process-diagram.png
+    :align: center
+    :alt: alternate text
+
+Configurable Template (product.template)
+----------------------------------------
+
+Product Templates with the boolean config_ok field checked become Configurable Templates.
+
+When config_ok is True the template does not generate variants automatically anymore.
+
+The attributes set on the template are only mere instructions for a configurator to generate a user
+friendly interface.
+
+Configuration interfaces
+------------------------
+
+In order to give control to the user on the options he can pick we must generate a user friendly interface.
+
+We have provided at the time of this writing two configuration interfaces: The Backend Wizard and Website / Ecommerce Configurator.
+
+These interfaces read data from the configurable templates (Attributes, Values, Configuration Restrictions / Steps / Images etc).
+
+Using this information we are able to generate an interface that allows the user to select the available options on the product.
+
+We also have quite a few handy helper methods on the product.template to operate a configuration interface.
+
+The most important part is enforcing restrictions so users cannot make mistakes and generate unbuildable variants.
+
+
+Configuration Session (product.config.session)
+----------------------------------------------
+
+Whenever a user starts a configuration process, his selections must be saved in a session.
+
+This way the user does not loose his progress when moving through multiple steps and he can also save his configuration.
+
+Configuration sessions store the options selected by the user in either interface and validates them according to the restrictions applied on the product.template
+
+
+Configurable Products (product.product)
+---------------------------------------
+
+Same as the product.template the configurable products or variants have a config_ok boolean field.
+
+After a configuration session is valid and findal we can use the information form the session to generate a new product variant.
+
+
+*********
+Structure
+*********
+
+The logic is at the time of this writing divided between 3 modules:
+
+	1. Product Configurator Base
+		- This module holds the main methods required to build configuration interfaces, this includes:
+			a. Prevention of automatically generated variants.
+			b. Introduction of Required, Multi, Custom fields for attribute lines.
+			c. Configuration restrictions or configuration rules.
+			d. Configuration steps.
+			e. Configuration images.
+			f. Linking attribute values to product variants.
+			g. Managing active configuration sessions for external configurators.
+			h. Helper methods for creating, validating, searching configurable variants and more.
+	2. Product Configurator Wizard
+		- Based on the Product Configurator Base module it provides a native Odoo wizard with dynamically generated content.
+		- Integrated with backend models such as sale.order, mrp.production etc. can directly create and edit configurable variants directy to the related lines of the model.
+	3. Website Product Configurator
+		- Provides a web based form in the front-end for users to generate variants fully integrated with the e-shop module.
+
+
+**********************
+product.template model
+**********************
+
+The product.template object has a boolean field 'config_ok' that is used to determine if it is a regular template product or a configurable one. This is the marker that activates all the related functionality, without it behavior of the original model remains completely unchanged.
+
+Once this is checked the product.template:
+
+	1. No longer generated variants automatically
+	2. Has 3 extra fields on the attribute lines (Required, Multi, Custom) added by the base
+	3. Shows the 'Configurator' tab reveiling configuration information also added by the base.
+
+
+
+****************************
+Website Product Configurator
+****************************
+
+As with all the website_* modules, most of the logic lies in the controllers (commonly located in module/controllers/). In our controller located at the aformentioned location in website_product_config/controllers/main.py we have our main class WebsiteProductConfig.
+
+At the beginning of the class we define our two main routes used: cfg_tmpl_url, cfg_step_url. These can be changed by importing the class and overriding the properties with new values if one wishes to change the route.
+
+****
+Flow
+****
+
+action_configure()
+==================
+
+By accessing a configurable product using the routes above this is our first method that fires. It will run on every page load
+
+The first job of this method is to generate a dictionary of values that will be later used in the qweb templated also known as updating the qwebcontext
+
+	cfg_vars = self.config_vars(product_tmpl, active_step=config_step)
+
+Next we redefine the post argument given by the standard Odoo http layer as this does not support 'multi' data (input radio with same name). So we parse the werkzeug post with a separate method in which we organize multiple values in a list.
+
+	post = self.parse_config_post(product_tmpl)
+
+A differentiation must be made between accessing a configurable product (or a different configuration step) or posting configuration data via the form. This is why we look for the POST method on the werkzeug httprequest **if request.httprequest.method == 'POST':**
+
+Sanitization of data to prevent invalid / malicious input is done via config_parse. We will use only the output from this method to update configuration values
+
+	parsed_vals = self.config_parse(product_tmpl, post, config_step)
+
+If no errors were returned from the parsing method we can update the configuration for this user. This is used to retrieve the configuration values at a later time to pre-fill the values in the form. Also when the configuration is finished we can just create a new configurable variant using the validated and stored values.
+
+The related product.config.session model is updated with the validated values from the frontend and can be uniquely identified using the unique session id.
+
+
+
+
+
+
+
+

BIN
roles/odoo/files/addons/odoo-product-configurator/doc/technical/images/configuration-process-diagram.png


+ 33 - 0
roles/odoo/files/addons/odoo-product-configurator/doc/technical/pricing.rst

@@ -0,0 +1,33 @@
+.. _pricing-technical:
+
+=======
+Pricing
+=======
+
+Computation
+-----------
+
+As explained in the :ref:`Functional Section <pricing-functional>`, the final price of the variant
+is a sum of the product.template price + all related products of the selected attribute.values.
+
+We managed to do this by using the 'config_ok' boolean field to differentiate between regular products and configurable ones.
+
+By inheriting the `_compute_product_price_extra <https://github.com/odoo/odoo/blob/10.0/addons/product/models/product.py#L211>`_ method we delegate regular products to the original method and new ones to `ours <https://github.com/pledra/odoo-product-configurator/blob/10.0/product_configurator/models/product.py#L437>`_
+
+Format
+-----------
+
+Whenever the price of a configuration needs to be computed (regardless of the configuration interface used), the `get_cfg_price <https://github.com/pledra/odoo-product-configurator/blob/10.0/product_configurator/models/product.py#L122>`_ method is called. The keyword argument formatLang determines if the output is formatted by the settings on the user's language or regular float type for computation.
+
+If formatLang keyword argument is True then before returning the computed values the `formatPrices <https://github.com/pledra/odoo-product-configurator/blob/10.0/product_configurator/models/product.py#L112>`_ method is called.
+
+`formatPrices <https://github.com/pledra/odoo-product-configurator/blob/10.0/product_configurator/models/product.py#L112>`_ takes the output of `get_cfg_price <https://github.com/pledra/odoo-product-configurator/blob/10.0/product_configurator/models/product.py#L122>`_ method and applied Odoo's formatLang method on the result.
+
+If the language settings applied via formatLang do not satisfy your needs, you need to inherit the `formatPrices <https://github.com/pledra/odoo-product-configurator/blob/10.0/product_configurator/models/product.py#L112>`_ method and apply your own formatting rules.
+
+
+
+
+
+
+

+ 22 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/README.md

@@ -0,0 +1,22 @@
+#Odoo Product Configurator Base
+
+This module has all the mechanics to support product configuration. It serves as a base dependency for configuration interfaces.
+
+Features
+========
+
+- Inhibition of automatically created variants.
+- Extension of attribute lines to offer required, custom and multiple selection.
+- Configuration / Compatibility rules between attributes.
+- Separation of attributes in different steps.
+- Images for intermediate and final configurations.
+- Managing active configuration sessions for external configurators
+- Set of helper methods required for any Odoo configuration module.
+
+
+Usage
+=====
+
+This module is only the foundation for external configuration interfaces such as 'product_configurator_wizard' or 'website_product_configurator'.
+
+By itself this module does not configure custom products but offers the basis for generating, validating, updating configurable products using configuration interfaces.

+ 4 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/__init__.py

@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import tests

+ 36 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/__manifest__.py

@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+{
+    'name': 'Product Configurator Base',
+    'version': '10.0.1.0.0',
+    'category': 'Generic Modules/Base',
+    'summary': 'Base for product configuration interface modules',
+    'author': 'Pledra',
+    'license': 'AGPL-3',
+    'website': 'http://www.pledra.com/',
+    'depends': ['sale_stock'],
+    "data": [
+        'data/menu_configurable_product.xml',
+        'data/product_attribute.xml',
+        'security/configurator_security.xml',
+        'security/ir.model.access.csv',
+        'views/assets.xml',
+        'views/sale_view.xml',
+        'views/product_view.xml',
+        'views/product_attribute_view.xml',
+        'views/product_config_view.xml',
+    ],
+    'demo': [
+        'demo/product_template.xml',
+        'demo/product_attribute.xml',
+        'demo/product_config_domain.xml',
+        'demo/product_config_lines.xml',
+        'demo/product_config_step.xml',
+        'demo/config_image_ids.xml',
+    ],
+    'images': [
+        'static/description/cover.png'
+    ],
+    'test': [],
+    'installable': True,
+    'auto_install': False,
+}

+ 89 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/data/menu_configurable_product.xml

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <menuitem id="menu_product_configurable" name="Configurable Products" parent="sales_team.menu_base_partner" sequence="20" />
+
+        <record id="product_configurable_template_action" model="ir.actions.act_window">
+            <field name="name">Configurable Templates</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">product.template</field>
+            <field name="view_mode">kanban,tree,form</field>
+            <field name="view_type">form</field>
+            <field name="view_id" ref="product.product_template_kanban_view"/>
+            <field name="domain">[('config_ok','=',True)]</field>
+            <field name="context">{'default_config_ok': True, 'default_type': 'product'}</field>
+        </record>
+
+        <menuitem action="product_configurable_template_action"
+                  id="menu_product_configurable_template_action"
+                  parent="menu_product_configurable" sequence="20" />
+
+        <record id="product_configurable_variant_action" model="ir.actions.act_window">
+            <field name="name">Configured Variants</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">product.product</field>
+            <field name="view_mode">tree,form,kanban</field>
+            <field name="view_type">form</field>
+            <field name="search_view_id" ref="product.product_search_form_view"/>
+            <field name="view_id" eval="False"/> <!-- Force empty -->
+            <field name="domain">[('config_ok','=',True)]</field>
+            <field name="context">{'default_config_ok': True, 'default_type': 'product'}</field>
+        </record>
+
+        <menuitem id="menu_product_configurable_variants_action"
+                  action="product_configurable_variant_action"
+                  name="Configurable Variants"
+                  parent="menu_product_configurable" sequence="25"/>
+
+
+        <record id="product.product_template_action" model="ir.actions.act_window">
+            <field name="domain">[('config_ok','=',False)]</field>
+        </record>
+
+        <record id="product.product_normal_action_sell" model="ir.actions.act_window">
+            <field name="domain">[('config_ok','=',False)]</field>
+        </record>
+
+
+        <record id="product_config_steps_action" model="ir.actions.act_window">
+            <field name="name">Configuration Steps</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">product.config.step</field>
+            <field name="view_mode">tree,form</field>
+            <field name="view_type">form</field>
+        </record>
+
+        <menuitem id="menu_product_config_steps_action"
+                  action="product_config_steps_action"
+                  name="Configuration Steps"
+                  parent="menu_product_configurable" sequence="30"/>
+
+        <record id="product_config_domain_action" model="ir.actions.act_window">
+            <field name="name">Configuration Restrictions</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">product.config.domain</field>
+            <field name="view_mode">tree,form</field>
+            <field name="view_type">form</field>
+        </record>
+
+        <menuitem id="menu_product_config_domain_action"
+              action="product_config_domain_action"
+              name="Configuration Restrictions"
+              parent="menu_product_configurable" sequence="40"/>
+
+        <record id="product_config_session" model="ir.actions.act_window">
+            <field name="name">Configuration Sessions</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">product.config.session</field>
+            <field name="view_mode">tree,form</field>
+            <field name="view_type">form</field>
+        </record>
+
+        <menuitem id="menu_product_config_session"
+              action="product_config_session"
+              name="Configuration Sessions"
+              parent="menu_product_configurable" sequence="50"/>
+
+    </data>
+</openerp>

+ 17 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/data/product_attribute.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <record id="custom_attribute" model="product.attribute">
+            <field name="name">Custom</field>
+            <field name="active" eval="False"/>
+        </record>
+
+        <record id="custom_attribute_value" model="product.attribute.value">
+            <field name="name">Custom</field>
+            <field name="attribute_id" ref="custom_attribute"/>
+            <field name="active" eval="True"/>
+        </record>
+
+    </data>
+</openerp>

+ 91 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/config_image_ids.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+        <record id="config_image_1" model="product.config.image">
+            <field name="name">Coupé Red</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_red')
+            ])]"/>
+        </record>
+
+        <record id="config_image_2" model="product.config.image">
+            <field name="name">Coupé Silver</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-silver.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_silver')
+            ])]"/>
+        </record>
+
+        <record id="config_image_3" model="product.config.image">
+            <field name="name">Coupé Black</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-black.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_black')
+            ])]"/>
+        </record>
+
+        <record id="config_image_5" model="product.config.image">
+            <field name="name">Coupé Red Rims 384</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-red-star-spoke-384.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_red'),
+                ref('product_attribute_value_rims_384'),
+            ])]"/>
+        </record>
+
+        <record id="config_image_6" model="product.config.image">
+            <field name="name">Coupé Red Rims 387</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-red-star-spoke-387.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_red'),
+                ref('product_attribute_value_rims_387'),
+            ])]"/>
+        </record>
+
+        <record id="config_image_7" model="product.config.image">
+            <field name="name">Coupé Silver Rims 384</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-silver-star-spoke-384.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_silver'),
+                ref('product_attribute_value_rims_384'),
+            ])]"/>
+        </record>
+
+        <record id="config_image_8" model="product.config.image">
+            <field name="name">Coupé Silver Rims 387</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-silver-star-spoke-387.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_silver'),
+                ref('product_attribute_value_rims_387'),
+            ])]"/>
+        </record>
+
+        <record id="config_image_9" model="product.config.image">
+            <field name="name">Coupé Black Rims 384</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-black-star-spoke-384.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_black'),
+                ref('product_attribute_value_rims_384'),
+            ])]"/>
+        </record>
+
+        <record id="config_image_10" model="product.config.image">
+            <field name="name">Coupé Black Rims 387</field>
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe-black-star-spoke-387.jpg"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_attribute_value_black'),
+                ref('product_attribute_value_rims_387'),
+            ])]"/>
+        </record>
+
+</odoo>

+ 342 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_attribute.xml

@@ -0,0 +1,342 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+        <!-- == FUEL == -->
+
+        <record id="product_attribute_fuel" model="product.attribute">
+            <field name="name">Fuel</field>
+        </record>
+
+        <record id="product_attribute_value_gasoline" model="product.attribute.value">
+            <field name="name">Gasoline</field>
+            <field name="attribute_id" ref="product_attribute_fuel"/>
+        </record>
+
+        <record id="product_attribute_value_diesel" model="product.attribute.value">
+            <field name="name">Diesel</field>
+            <field name="attribute_id" ref="product_attribute_fuel"/>
+        </record>
+
+        <!-- == ENGINE == -->
+
+        <record id="product_attribute_engine" model="product.attribute">
+            <field name="name">Engine</field>
+        </record>
+
+        <!-- Gasoline Engines -->
+
+        <record id="product_attribute_value_218i" model="product.attribute.value">
+            <field name="name">218i</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_218i_coupe"/>
+        </record>
+
+        <record id="product_attribute_value_220i" model="product.attribute.value">
+            <field name="name">220i</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_220i_coupe"/>
+        </record>
+
+        <record id="product_attribute_value_228i" model="product.attribute.value">
+            <field name="name">228i</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_228i_coupe"/>
+        </record>
+
+        <record id="product_attribute_value_m235i" model="product.attribute.value">
+            <field name="name">M235i</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_m235i_coupe"/>
+        </record>
+
+        <record id="product_attribute_value_m235i_xdrive" model="product.attribute.value">
+            <field name="name">M235i xDrive</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_m2351_xdrive_coupe"/>
+        </record>
+
+        <!-- Diesel Engines -->
+
+        <record id="product_attribute_value_218d" model="product.attribute.value">
+            <field name="name">218d</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_218d_coupe"/>
+        </record>
+
+        <record id="product_attribute_value_220d" model="product.attribute.value">
+            <field name="name">220d</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_228i_coupe"/>
+        </record>
+
+        <record id="product_attribute_value_220d_xdrive" model="product.attribute.value">
+            <field name="name">220d xDrive</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_220d_xdrive_coupe"/>
+        </record>
+
+        <record id="product_attribute_value_225d" model="product.attribute.value">
+            <field name="name">225d</field>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="product_id" ref="product_engine_225d_coupe"/>
+        </record>
+
+        <!-- == LINES == -->
+
+        <record id="product_attribute_model_line" model="product.attribute">
+            <field name="name">Lines</field>
+        </record>
+
+        <record id="product_attribute_value_sport_line" model="product.attribute.value">
+            <field name="name">Sport Line</field>
+            <field name="attribute_id" ref="product_attribute_model_line"/>
+            <field name="product_id" ref="product_bmw_sport_line"/>
+        </record>
+
+        <record id="product_attribute_value_model_sport_line" model="product.attribute.value">
+            <field name="name">Model Sport Line</field>
+            <field name="attribute_id" ref="product_attribute_model_line"/>
+            <field name="product_id" ref="product_bmw_model_sport_line"/>
+        </record>
+
+        <record id="product_attribute_value_luxury_line" model="product.attribute.value">
+            <field name="name">Luxury Line</field>
+            <field name="attribute_id" ref="product_attribute_model_line"/>
+            <field name="product_id" ref="product_bmw_luxury_line"/>
+        </record>
+
+        <record id="product_attribute_value_model_luxury_line" model="product.attribute.value">
+            <field name="name">Model Luxury Line</field>
+            <field name="attribute_id" ref="product_attribute_model_line"/>
+            <field name="product_id" ref="product_bmw_model_luxury_line"/>
+        </record>
+
+        <record id="product_attribute_value_model_m_sport" model="product.attribute.value">
+            <field name="name">Model M Sport</field>
+            <field name="attribute_id" ref="product_attribute_model_line"/>
+            <field name="product_id" ref="product_bmw_model_m_sport"/>
+        </record>
+
+        <record id="product_attribute_value_model_advantage" model="product.attribute.value">
+            <field name="name">Model Advantage</field>
+            <field name="attribute_id" ref="product_attribute_model_line"/>
+            <field name="product_id" ref="product_bmw_model_advantage"/>
+        </record>
+
+        <!-- == COLOR == -->
+
+        <record id="product_attribute_color" model="product.attribute">
+            <field name="name">Color</field>
+        </record>
+
+        <record id="product_attribute_value_red" model="product.attribute.value">
+            <field name="name">Red</field>
+            <field name="attribute_id" ref="product_attribute_color"/>
+        </record>
+
+        <record id="product_attribute_value_silver" model="product.attribute.value">
+            <field name="name">Silver</field>
+            <field name="attribute_id" ref="product_attribute_color"/>
+            <field name="product_id" ref="product_paint_silver"/>
+        </record>
+
+        <record id="product_attribute_value_black" model="product.attribute.value">
+            <field name="name">Black</field>
+            <field name="attribute_id" ref="product_attribute_color"/>
+        </record>
+
+        <!-- == RIMS == -->
+
+        <record id="product_attribute_rims" model="product.attribute">
+            <field name="name">Rims</field>
+        </record>
+
+        <record id="product_attribute_value_rims_378" model="product.attribute.value">
+            <field name="name">V-spoke 16"</field>
+            <field name="attribute_id" ref="product_attribute_rims"/>
+        </record>
+
+        <record id="product_attribute_value_rims_387" model="product.attribute.value">
+            <field name="name">V-spoke 18"</field>
+            <field name="attribute_id" ref="product_attribute_rims"/>
+        </record>
+
+        <record id="product_attribute_value_rims_384" model="product.attribute.value">
+            <field name="name">Double-spoke 18"</field>
+            <field name="attribute_id" ref="product_attribute_rims"/>
+        </record>
+
+        <!-- == TAPISTRY == -->
+
+        <record id="product_attribute_tapistry" model="product.attribute">
+            <field name="name">Tapistry</field>
+        </record>
+
+        <record id="product_attribute_value_tapistry_black" model="product.attribute.value">
+            <field name="name">Black</field>
+            <field name="attribute_id" ref="product_attribute_tapistry"/>
+        </record>
+
+        <record id="product_attribute_value_tapistry_oyster_black" model="product.attribute.value">
+            <field name="name">Oyster/Black</field>
+            <field name="attribute_id" ref="product_attribute_tapistry"/>
+        </record>
+
+        <record id="product_attribute_value_tapistry_coral_red_black" model="product.attribute.value">
+            <field name="name">Coral Red/Black</field>
+            <field name="attribute_id" ref="product_attribute_tapistry"/>
+        </record>
+
+        <!-- == TRANSMISSION == -->
+
+        <record id="product_attribute_transmission" model="product.attribute">
+            <field name="name">Transmission</field>
+        </record>
+
+        <record id="product_attribute_value_steptronic" model="product.attribute.value">
+            <field name="name">Automatic (Steptronic)</field>
+            <field name="attribute_id" ref="product_attribute_transmission"/>
+            <field name="product_id" ref="product_2_series_transmission_steptronic"/>
+        </record>
+
+        <record id="product_attribute_value_steptronic_sport" model="product.attribute.value">
+            <field name="name">Automatic Sport (Steptronic)</field>
+            <field name="attribute_id" ref="product_attribute_transmission"/>
+            <field name="product_id" ref="product_2_series_transmission_steptronic_sport"/>
+        </record>
+
+        <!-- == Options == -->
+
+        <record id="product_attribute_options" model="product.attribute">
+            <field name="name">Options</field>
+        </record>
+
+        <record id="product_attribute_value_armrest" model="product.attribute.value">
+            <field name="name">Armrest</field>
+            <field name="attribute_id" ref="product_attribute_options"/>
+            <field name="product_id" ref="product_2_series_armrest"/>
+        </record>
+
+        <record id="product_attribute_value_smoker_package" model="product.attribute.value">
+            <field name="name">Smoker Package</field>
+            <field name="attribute_id" ref="product_attribute_options"/>
+            <field name="product_id" ref="product_2_series_smoker_package"/>
+        </record>
+
+        <record id="product_attribute_value_sunroof" model="product.attribute.value">
+            <field name="name">Sunroof</field>
+            <field name="attribute_id" ref="product_attribute_options"/>
+            <field name="product_id" ref="product_2_series_sunroof"/>
+        </record>
+
+        <record id="product_attribute_value_tow_hook" model="product.attribute.value">
+            <field name="name">Tow hook</field>
+            <field name="attribute_id" ref="product_attribute_options"/>
+            <field name="product_id" ref="product_2_series_towhook"/>
+        </record>
+
+
+        <!-- == Attribute Lines == -->
+
+        <record id="product_attribute_line_2_series_fuel" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_fuel"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_gasoline'),
+                ref('product_configurator.product_attribute_value_diesel')]
+            )]"/>
+            <field name="required" eval="True"/>
+        </record>
+
+        <record id="product_attribute_line_2_series_engine" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_218i'),
+                ref('product_configurator.product_attribute_value_220i'),
+                ref('product_configurator.product_attribute_value_228i'),
+                ref('product_configurator.product_attribute_value_m235i'),
+                ref('product_configurator.product_attribute_value_m235i_xdrive'),
+                ref('product_configurator.product_attribute_value_218d'),
+                ref('product_configurator.product_attribute_value_220d'),
+                ref('product_configurator.product_attribute_value_220d_xdrive'),
+                ref('product_configurator.product_attribute_value_225d'),
+                ]
+            )]"/>
+            <field name="required" eval="True"/>
+        </record>
+
+        <record id="product_attribute_line_2_series_model_line" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_model_line"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_sport_line'),
+                ref('product_configurator.product_attribute_value_model_sport_line'),
+                ref('product_configurator.product_attribute_value_luxury_line'),
+                ref('product_configurator.product_attribute_value_model_luxury_line'),
+                ref('product_configurator.product_attribute_value_model_m_sport'),
+                ref('product_configurator.product_attribute_value_model_advantage'),
+                ]
+            )]"/>
+            <field name="required" eval="False"/>
+        </record>
+
+        <record id="product_attribute_line_2_series_color" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_color"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_red'),
+                ref('product_configurator.product_attribute_value_black'),
+                ref('product_configurator.product_attribute_value_silver')]
+            )]"/>
+            <field name="required" eval="True"/>
+        </record>
+
+        <record id="product_attribute_line_2_series_rims" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_rims"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_rims_378'),
+                ref('product_configurator.product_attribute_value_rims_387'),
+                ref('product_configurator.product_attribute_value_rims_384')]
+            )]"/>
+            <field name="required" eval="True"/>
+        </record>
+
+        <record id="product_attribute_line_2_series_tapistry" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_tapistry"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_tapistry_black'),
+                ref('product_configurator.product_attribute_value_tapistry_oyster_black'),
+                ref('product_configurator.product_attribute_value_tapistry_coral_red_black')]
+            )]"/>
+            <field name="required" eval="True"/>
+        </record>
+
+        <record id="product_attribute_line_2_series_transmission" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_transmission"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_steptronic'),
+                ref('product_configurator.product_attribute_value_steptronic_sport'),
+                ]
+            )]"/>
+            <field name="required" eval="True"/>
+        </record>
+
+        <record id="product_attribute_line_2_series_options" model="product.attribute.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_id" ref="product_attribute_options"/>
+            <field name="value_ids" eval="[(6,0,[
+                ref('product_configurator.product_attribute_value_armrest'),
+                ref('product_configurator.product_attribute_value_smoker_package'),
+                ref('product_configurator.product_attribute_value_sunroof'),
+                ref('product_configurator.product_attribute_value_tow_hook'),
+                ]
+            )]"/>
+            <field name="required" eval="True"/>
+            <field name="multi" eval="True"/>
+        </record>
+
+</odoo>

+ 70 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_config_domain.xml

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+        <!-- Gasoline Engines -->
+
+        <record id="product_config_domain_gasoline" model="product.config.domain">
+            <field name="name">Gasoline</field>
+        </record>
+
+        <record id="product_config_domain_line_1" model="product.config.domain.line">
+            <field name="domain_id" ref="product_config_domain_gasoline"/>
+            <field name="attribute_id" ref="product_attribute_fuel"/>
+            <field name="condition">in</field>
+            <field name="operator">and</field>
+            <field name="value_ids" eval="[(6, 0, [
+                ref('product_attribute_value_gasoline')])]"/>
+        </record>
+
+        <!-- Diesel Engines -->
+
+        <record id="product_config_domain_diesel" model="product.config.domain">
+            <field name="name">Diesel</field>
+        </record>
+
+        <record id="product_config_domain_line_2" model="product.config.domain.line">
+            <field name="domain_id" ref="product_config_domain_diesel"/>
+            <field name="attribute_id" ref="product_attribute_fuel"/>
+            <field name="condition">in</field>
+            <field name="operator">and</field>
+            <field name="value_ids" eval="[(6, 0, [
+                ref('product_attribute_value_diesel')])]"/>
+        </record>
+
+
+        <!-- Model Lines -->
+
+        <record id="product_config_domain_218_engine" model="product.config.domain">
+            <field name="name">218i Engine</field>
+        </record>
+
+        <record id="product_config_domain_line_3" model="product.config.domain.line">
+            <field name="domain_id" ref="product_config_domain_218_engine"/>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="condition">in</field>
+            <field name="operator">and</field>
+            <field name="value_ids" eval="[(6, 0, [
+                ref('product_attribute_value_218i')])]"/>
+        </record>
+
+
+
+
+        <record id="product_config_domain_luxury_lines" model="product.config.domain">
+            <field name="name">Luxury Lines</field>
+        </record>
+
+        <record id="product_config_domain_line_4" model="product.config.domain.line">
+            <field name="domain_id" ref="product_config_domain_luxury_lines"/>
+            <field name="attribute_id" ref="product_attribute_engine"/>
+            <field name="condition">in</field>
+            <field name="operator">and</field>
+            <field name="value_ids" eval="[(6, 0, [
+                ref('product_attribute_value_220i'),
+                ref('product_attribute_value_228i'),
+                ref('product_attribute_value_218d'),
+                ref('product_attribute_value_220d'),
+                ref('product_attribute_value_220d_xdrive')])]"/>
+        </record>
+
+</odoo>

+ 47 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_config_lines.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+        <record id="product_config_line_gasoline_engines" model="product.config.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_line_id" ref="product_attribute_line_2_series_engine"/>
+            <field name="value_ids" eval="[(6, 0, [
+                ref('product_attribute_value_218i'),
+                ref('product_attribute_value_220i'),
+                ref('product_attribute_value_228i'),
+                ref('product_attribute_value_m235i'),
+                ref('product_attribute_value_m235i_xdrive')])]"/>
+            <field name="domain_id" ref="product_config_domain_gasoline"/>
+        </record>
+
+        <record id="product_config_line_diesel_engines" model="product.config.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_line_id" ref="product_attribute_line_2_series_engine"/>
+            <field name="value_ids" eval="[(6, 0, [
+              ref('product_attribute_value_218d'),
+              ref('product_attribute_value_220d'),
+              ref('product_attribute_value_220d_xdrive'),
+              ref('product_attribute_value_225d')])]"/>
+            <field name="domain_id" ref="product_config_domain_diesel"/>
+        </record>
+
+        <record id="product_config_line_218_lines" model="product.config.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_line_id" ref="product_attribute_line_2_series_model_line"/>
+            <field name="value_ids" eval="[(6, 0, [
+              ref('product_attribute_value_sport_line'),
+              ref('product_attribute_value_luxury_line')])]"/>
+            <field name="domain_id" ref="product_config_domain_218_engine"/>
+        </record>
+
+        <record id="product_config_line_luxury_lines" model="product.config.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="attribute_line_id" ref="product_attribute_line_2_series_model_line"/>
+            <field name="value_ids" eval="[(6, 0, [
+              ref('product_attribute_value_model_sport_line'),
+              ref('product_attribute_value_model_luxury_line'),
+              ref('product_attribute_value_model_m_sport'),
+              ref('product_attribute_value_model_advantage')])]"/>
+            <field name="domain_id" ref="product_config_domain_luxury_lines"/>
+        </record>
+
+</odoo>

+ 66 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_config_step.xml

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+        <!-- Configuration Steps -->
+
+        <record id="config_step_engine" model="product.config.step">
+            <field name="name">Engine</field>
+        </record>
+
+        <record id="config_step_body" model="product.config.step">
+            <field name="name">Body</field>
+        </record>
+
+        <record id="config_step_lines" model="product.config.step">
+            <field name="name">Lines</field>
+        </record>
+
+        <record id="config_step_interior" model="product.config.step">
+            <field name="name">Interior</field>
+        </record>
+
+        <record id="config_step_extras" model="product.config.step">
+            <field name="name">Extras</field>
+        </record>
+
+        <!-- Configuration Lines -->
+
+        <record id="2_series_config_step_body" model="product.config.step.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="config_step_id" ref="config_step_body"/>
+            <field name="attribute_line_ids" eval="[(6, 0, [
+                ref('product_attribute_line_2_series_color'),
+                ref('product_attribute_line_2_series_rims')])]"/>
+        </record>
+
+        <record id="2_series_config_step_lines" model="product.config.step.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="config_step_id" ref="config_step_lines"/>
+            <field name="attribute_line_ids" eval="[(6, 0, [
+                ref('product_attribute_line_2_series_model_line')])]"/>
+        </record>
+
+        <record id="2_series_config_step_interior" model="product.config.step.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="config_step_id" ref="config_step_interior"/>
+            <field name="attribute_line_ids" eval="[(6, 0, [
+                ref('product_attribute_line_2_series_tapistry')])]"/>
+        </record>
+
+       <record id="2_series_config_step_engine" model="product.config.step.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="config_step_id" ref="config_step_engine"/>
+            <field name="attribute_line_ids" eval="[(6, 0, [
+                ref('product_attribute_line_2_series_engine'),
+                ref('product_attribute_line_2_series_fuel')])]"/>
+        </record>
+
+       <record id="2_series_config_step_extras" model="product.config.step.line">
+            <field name="product_tmpl_id" ref="bmw_2_series"/>
+            <field name="config_step_id" ref="config_step_extras"/>
+            <field name="attribute_line_ids" eval="[(6, 0, [
+                ref('product_attribute_line_2_series_transmission'),
+                ref('product_attribute_line_2_series_options')])]"/>
+        </record>
+
+</odoo>

+ 172 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/demo/product_template.xml

@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+        <record id="product_category_bmw" model="product.category">
+            <field name="parent_id" ref="product.product_category_all"/>
+            <field name="name">BMW</field>
+        </record>
+
+        <record id="bmw_2_series" model="product.template">
+            <field name="name">2 Series</field>
+            <field name="config_ok" eval="True"/>
+            <field name="type">product</field>
+            <field name="categ_id" ref="product_category_bmw"/>
+            <field name="list_price" eval="25000"/>
+            <field name="image" type="base64" file="product_configurator/static/img/2-series-coupe.jpg"/>
+        </record>
+
+        <record id="product_bmw_sport_line" model="product.product">
+            <field name="name">Sport Line</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="0"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-sport-line.jpg"/>
+        </record>
+
+        <record id="product_bmw_luxury_line" model="product.product">
+            <field name="name">Luxury Line</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="0"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-luxury-line.jpg"/>
+        </record>
+
+        <record id="product_bmw_model_sport_line" model="product.product">
+            <field name="name">Model Sport Line</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="2666"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-sport-line.jpg"/>
+        </record>
+
+        <record id="product_bmw_model_luxury_line" model="product.product">
+            <field name="name">Model Luxury Line</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="3844"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-luxury-line.jpg"/>
+        </record>
+
+        <record id="product_bmw_model_m_sport" model="product.product">
+            <field name="name">Model M Sport</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="4526"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-m-sport.jpg"/>
+        </record>
+
+        <record id="product_bmw_model_advantage" model="product.product">
+            <field name="name">Model Advantage</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="992"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-advantage.jpg"/>
+        </record>
+
+        <record id="product_2_series_transmission_steptronic" model="product.product">
+            <field name="name">Automatic Transmission Steptronic</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="0"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-transmission-steptronic.jpg"/>
+        </record>
+
+        <record id="product_2_series_transmission_steptronic_sport" model="product.product">
+            <field name="name">Sport Automatic Transmission Steptronic</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="156"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-transmission-steptronic-sport.jpg"/>
+        </record>
+
+        <record id="product_2_series_sunroof" model="product.product">
+            <field name="name">Sunroof</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="842"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-sunroof.jpg"/>
+        </record>
+
+        <record id="product_2_series_armrest" model="product.product">
+            <field name="name">Armrest</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="0"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-armrest.jpg"/>
+        </record>
+
+        <record id="product_2_series_towhook" model="product.product">
+            <field name="name">Towhook</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="842"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-towhook.jpg"/>
+        </record>
+
+        <record id="product_2_series_smoker_package" model="product.product">
+            <field name="name">Smoker Package</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="32"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-smoker-package.jpg"/>
+        </record>
+
+        <record id="product_engine_218i_coupe" model="product.product">
+            <field name="name">218i Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="4078"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_220i_coupe" model="product.product">
+            <field name="name">220i Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="7240"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_228i_coupe" model="product.product">
+            <field name="name">228i Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="12634"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_m235i_coupe" model="product.product">
+            <field name="name">M235i Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="23236"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_m2351_xdrive_coupe" model="product.product">
+            <field name="name">M235i xDrive Coupe</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="23236"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_218d_coupe" model="product.product">
+            <field name="name">218d Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="7116"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_220d_coupe" model="product.product">
+            <field name="name">220d Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="9596"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_220d_xdrive_coupe" model="product.product">
+            <field name="name">220d xDrive Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="16181"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_engine_225d_coupe" model="product.product">
+            <field name="name">225d Coupé</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="16987"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-engine.jpg"/>
+        </record>
+
+        <record id="product_paint_silver" model="product.product">
+            <field name="name">Silver Paint</field>
+            <field name="type">product</field>
+            <field name="lst_price" eval="726"/>
+            <field name="image" type="base64" file="product_configurator/static/img/product-paint-silver.jpg"/>
+        </record>
+
+</odoo>

+ 694 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/i18n/de.po

@@ -0,0 +1,694 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# 	* product_configurator
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 10.0-20170309\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-03-09 16:46+0000\n"
+"PO-Revision-Date: 2017-03-10 09:16+0100\n"
+"Last-Translator: Ermin Trevisan <trevi@twanda.com>\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: de\n"
+"X-Generator: Poedit 1.8.7.1\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 "Aktiv"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_val_custom
+msgid "Allow custom value for this attribute?"
+msgstr "Benutzerdefinierte Werte für dieses Attribut erlauben?"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_custom
+msgid "Allow custom values for this attribute?"
+msgstr "Benutzerdefinierte Werte für dieses Attribut erlauben?"
+
+#. 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 "Auswahl von mehreren Werten für dieses Attribut erlauben?"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Attachment"
+msgstr "Dateianhang"
+
+#. 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 "Dateianhänge"
+
+#. 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
+msgid "Attribute"
+msgstr "Attribut"
+
+#. 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 "Attributabhängigkeiten"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attribute_line_id
+msgid "Attribute Line"
+msgstr "Attributzeile"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_attribute_line_ids
+msgid "Attribute Lines"
+msgstr "Attributzeilen"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view
+msgid "Attribute Value Dependencies"
+msgstr "Attributwert-Abhängigkeiten"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attr_line_val_ids
+msgid "Attribute Values"
+msgstr "Attributwerte"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:509
+#, python-format
+msgid "Attribute custom type is binary, attachments are the only accepted values with this custom field type"
+msgstr "Der benutzerdefinierte Attributstyp ist binär, nur Dateianhänge sind als Werte für diesen benutzerdefinierten Feldtyp erlaubt"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:514
+#, python-format
+msgid "Attribute custom type must be 'binary' for saving attachments to custom value"
+msgstr "Der benutzerdefinierte Attributstyp muss 'binär' sein um Dateianhänge zu dem benutzerdefinierten Wert zu speichern"
+
+#. 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 "Durch Deaktivieren dieses aktiven Feldes können Sie einen Attributwert sperren ohne ihn zu löschen"
+
+#. 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 "Durch Deaktivieren dieses aktiven Feldes können Sie ein Attribut sperren ohne es zu löschen"
+
+#. 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 "Kann konfiguriert werden"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:268
+#, python-format
+msgid "Cannot have a configuration step defined twice."
+msgstr "Ein Konfigurationsschritt kann nicht zweimal definiert werden."
+
+#. module: product_configurator
+#: sql_constraint:product.attribute.value.custom:0
+msgid "Cannot have two custom values for the same attribute"
+msgstr "Es darf keine zwei benutzerdefinierten Werte für das gleiche Attribut geben"
+
+#. 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 "Farbe"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_condition
+msgid "Condition"
+msgstr "Bedingung"
+
+#. module: product_configurator
+#: model:ir.ui.menu,name:product_configurator.menu_product_configurable
+msgid "Configurable Products"
+msgstr "Konfigurierbare Produkte"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_product_tmpl_id
+msgid "Configurable Template"
+msgstr "Konfigurierbare Vorlage"
+
+#. 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 "Konfigurierbare Vorlagen"
+
+#. module: product_configurator
+#: model:ir.ui.menu,name:product_configurator.menu_product_configurable_variants_action
+msgid "Configurable Variants"
+msgstr "Konfigurierbare Varianten"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_value_ids
+msgid "Configuration"
+msgstr "Konfiguration"
+
+#. 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 "Konfigurationsbilder"
+
+#. 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 "Konfigurationszeilen"
+
+#. 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 "Konfigurationsbeschränkungen"
+
+#. 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 "Konfigurationssitzungen"
+
+#. 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 "Konfigurationsschritt"
+
+#. 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 "Konfigurationsschritte"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_custom
+msgid "Configuration Values"
+msgstr "Konfigurationswerte"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:490
+#, python-format
+msgid "Configuration cannot have the same value inserted twice"
+msgstr "Der gleiche Wert kann nicht zweimal eingesetzt werden"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view
+msgid "Configurator"
+msgstr "Konfigurator"
+
+#. module: product_configurator
+#: model:ir.actions.act_window,name:product_configurator.product_configurable_variant_action
+msgid "Configured Variants"
+msgstr "Konfigurierte Varianten"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product.py:497
+#, python-format
+msgid "Could not convert custom value '%s' to '%s' on product variant: '%s'"
+msgstr "Der benutzerdefinierte Wert '%s' kann nicht zu '%s' konvertiert werden für Produktvariante: '%s'"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_create_uid
+#: 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_domain_line_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_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_session_custom_value_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_create_uid
+msgid "Created by"
+msgstr "Erstellt von"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_create_date
+#: 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_domain_line_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_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_session_custom_value_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_create_date
+msgid "Created on"
+msgstr "Angelegt am"
+
+#. 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 "Benutzerdefiniert"
+
+#. 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 "Benutzerdefinierter Wert"
+
+#. 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 "Benutzerdefinierte Werte"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_config_session_custom_value_value
+msgid "Custom value held as string"
+msgstr "Benutzerdefinierter Wert als Zeichenkette"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Date"
+msgstr "Datum"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "DateTime"
+msgstr "Datum&Uhrzeit"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_description
+msgid "Description"
+msgstr "Beschreibung"
+
+#. 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 "Bestimmt den erforderlichen Wert für dieses Attribut auch wenn er in der Vorlage geändert werden kann"
+
+#. 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 "Anzeigename"
+
+#. module: product_configurator
+#: selection:product.config.session,state:0
+msgid "Done"
+msgstr "Erledigt"
+
+#. module: product_configurator
+#: selection:product.config.session,state:0
+msgid "Draft"
+msgstr "Entwurf"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_custom_type
+msgid "Field Type"
+msgstr "Feldtyp"
+
+#. 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_attribute_value_custom_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_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
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_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 "Bild"
+
+#. 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 "Vererbt"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Integer"
+msgstr "Integer"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product.py:340
+#: code:addons/product_configurator/models/product_config.py:438
+#, python-format
+msgid "Invalid Configuration"
+msgstr "Ungültige Konfiguration"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_account_invoice_line
+msgid "Invoice Line"
+msgstr "Rechungsposition"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_required
+msgid "Is this attribute required?"
+msgstr "Ist dieses Attribute erforderlich?"
+
+#. 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 "Zuletzt geändert am"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_write_uid
+#: 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_image_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_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_line_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_write_uid
+msgid "Last Updated by"
+msgstr "Zuletzt aktualisiert durch"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_write_date
+#: 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_image_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_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_line_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_write_date
+msgid "Last Updated on"
+msgstr "Zuletzt aktualisiert am"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_max_val
+msgid "Max Value"
+msgstr "Max. Wert"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_min_val
+msgid "Min Value"
+msgstr "Min. Wert"
+
+#. 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 "Zuläßiger Mindestwert"
+
+#. 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 "Name"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_operator
+msgid "Operators"
+msgstr "Operatoren"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_price
+msgid "Price"
+msgstr "Preis"
+
+#. 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 "Produkt"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_attribute
+msgid "Product Attribute"
+msgstr "Produktattribut"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_form_view
+msgid "Product Attribute Values"
+msgstr "Produktattributwerte"
+
+#. 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 "Productkonfigurator"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_product_id
+msgid "Product ID"
+msgstr "Produkt-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 "Produktvorlage"
+
+#. module: product_configurator
+#: model:res.groups,name:product_configurator.group_product_configurator
+msgid "Products"
+msgstr "Produkte"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_product_id
+msgid "Related Product"
+msgstr "Ähnliches Produkt"
+
+#. 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 "Erforderlich"
+
+#. 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 "Beschränkungen"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_domain_id
+msgid "Rule"
+msgstr "Regel"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_config_domain_form_view
+msgid "Rules"
+msgstr "Regeln"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_sale_order_line
+msgid "Sales Order Line"
+msgstr "Auftragsposition"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_search_ok
+msgid "Searchable"
+msgstr "Durchsuchbar"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_attribute.py:100
+#, python-format
+msgid "Selected custom field type '%s' is not searchable"
+msgstr "Der gewählte benutzerdefinierte Feldtyp '%s' ist nicht durchsuchbar"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_attribute.py:115
+#, python-format
+msgid "Selected custom value '%s' must be between %s and %s"
+msgstr "Der gewählte benutzerdefinierte Wert '%s' muss zwischen %s und %s liegen"
+
+#. 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 "Reihenfolge"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_cfg_session_id
+msgid "Session"
+msgstr "Sitzung"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_state
+msgid "State"
+msgstr "Status"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_stock_move
+msgid "Stock Move"
+msgstr "Lagerbuchung"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Textarea"
+msgstr "Textbereich"
+
+#. 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 "Typ des im Frontend generierten benutzerdefinierten Felds"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_trans_implied_ids
+msgid "Transitively inherits"
+msgstr "Erbt übergangsweise"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_uom_id
+msgid "Unit of Measure"
+msgstr "Mengeneinheit"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_user_id
+msgid "User"
+msgstr "Benutzer"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view
+msgid "Validation"
+msgstr "Validierung"
+
+#. 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 "Wert"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_value_ids
+msgid "Value ids"
+msgstr "Wert-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 "Werte"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:213
+#, python-format
+msgid "Values entered for line '%s' generate a incompatible configuration"
+msgstr "Die für die Linie '%s' eingegebenen Werte erzeugen eine inkompatible Konfiguration"
+
+#. 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 "Schließen wir dieses Feld in der Suche ein, wenn wir Varianten mit der selben Konfiguration durchsuchen?"
+
+#. 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"

+ 696 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/i18n/it.po

@@ -0,0 +1,696 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# 	* product_configurator
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 10.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-03-28 10:31+0000\n"
+"PO-Revision-Date: 2017-03-28 10:49+0200\n"
+"Last-Translator: Andrea Piovesana <andrea.m.piovesana@gmail.com>\n"
+"Language-Team: Andrea PIovesana <andrea.m.piovesana@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: it\n"
+"X-Generator: Poedit 1.8.7.1\n"
+"X-Poedit-SourceCharset: UTF-8\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 "Attivo"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_val_custom
+msgid "Allow custom value for this attribute?"
+msgstr "Sono abilitati valori Custom per questo attributo?"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_custom
+msgid "Allow custom values for this attribute?"
+msgstr "Sono abilitati valori Custom per questo attributo?"
+
+#. 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 "E' abilitata la selezione multipla per questo attributo?"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Attachment"
+msgstr "Allegato"
+
+#. 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 "Allegati"
+
+#. 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
+msgid "Attribute"
+msgstr "Attributo"
+
+#. 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 "Dipendenze Attributo"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attribute_line_id
+msgid "Attribute Line"
+msgstr "Riga Attributo"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_attribute_line_ids
+msgid "Attribute Lines"
+msgstr "Righe Attributi"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view
+msgid "Attribute Value Dependencies"
+msgstr "Dipendenze Valori Attributo"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attr_line_val_ids
+msgid "Attribute Values"
+msgstr "Valori Attributo"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:509
+#, python-format
+msgid "Attribute custom type is binary, attachments are the only accepted values with this custom field type"
+msgstr "Il tipo di attributi custom è binario, comme allegati sono accettati valori con lo stesso tipo"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:514
+#, python-format
+msgid "Attribute custom type must be 'binary' for saving attachments to custom value"
+msgstr "Per associare un allegato il tipo di attributo custom deve essere binario"
+
+#. 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 "Deselezionando un campo attivo puoi disabilitare un valore di un attributo senza cancellarlo"
+
+#. 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 "Deselezionando un campo attivo puoi disabilitare i valori di un attributo senza cancellarli"
+
+#. 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 "Configurabile"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:268
+#, python-format
+msgid "Cannot have a configuration step defined twice."
+msgstr "Non possono essere definiti passi di configurazione doppi"
+
+#. module: product_configurator
+#: sql_constraint:product.attribute.value.custom:0
+msgid "Cannot have two custom values for the same attribute"
+msgstr "Non posso avere due valori custom per lo stesso attributo"
+
+#. 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 "Colore"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_condition
+msgid "Condition"
+msgstr "Condizione"
+
+#. module: product_configurator
+#: model:ir.ui.menu,name:product_configurator.menu_product_configurable
+msgid "Configurable Products"
+msgstr "Prodotti Configurabili"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_product_tmpl_id
+msgid "Configurable Template"
+msgstr "Template di Configurazione"
+
+#. 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 "Template di Configurazione"
+
+#. module: product_configurator
+#: model:ir.ui.menu,name:product_configurator.menu_product_configurable_variants_action
+msgid "Configurable Variants"
+msgstr "Varianti di Configurazione"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_value_ids
+msgid "Configuration"
+msgstr "Configurazione"
+
+#. 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 "Immagini di Configurazione"
+
+#. 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 "Righe di Configurazione"
+
+#. 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 "Vincoli di Configurazione"
+
+#. 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 "Sessioni di Configurazione"
+
+#. 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 "Passo di Configurazione"
+
+#. 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 "Passi di Configurazione"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_custom
+msgid "Configuration Values"
+msgstr "Valori di Configurazione"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:490
+#, python-format
+msgid "Configuration cannot have the same value inserted twice"
+msgstr "La configurazione non può avere lo stesso valore inserito doppio"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view
+msgid "Configurator"
+msgstr "Configuratore"
+
+#. module: product_configurator
+#: model:ir.actions.act_window,name:product_configurator.product_configurable_variant_action
+msgid "Configured Variants"
+msgstr "Varianti Configurate"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product.py:497
+#, python-format
+msgid "Could not convert custom value '%s' to '%s' on product variant: '%s'"
+msgstr "Non è convertibile Valore custom '%s' a '%s' per la variante prodotto: '%s'"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_create_uid
+#: 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_domain_line_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_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_session_custom_value_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_create_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_create_uid
+msgid "Created by"
+msgstr "Creato da"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_create_date
+#: 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_domain_line_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_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_session_custom_value_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_create_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_create_date
+msgid "Created on"
+msgstr "Creato"
+
+#. 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 "Custom"
+
+#. 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 "Valore Custom"
+
+#. 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 "Valori Custom"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_config_session_custom_value_value
+msgid "Custom value held as string"
+msgstr "Valore custom da stringa"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Date"
+msgstr "Data"
+
+#. 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 "Descrizione"
+
+#. 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 "Determina il valore richiesto di questo attributo anche se può cambiare a livello di template"
+
+#. 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 "Visualizza Nome"
+
+#. module: product_configurator
+#: selection:product.config.session,state:0
+msgid "Done"
+msgstr "Fatto"
+
+#. module: product_configurator
+#: selection:product.config.session,state:0
+msgid "Draft"
+msgstr "Bozza"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_custom_type
+msgid "Field Type"
+msgstr "Tipo Campo"
+
+#. 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_attribute_value_custom_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_id
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_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
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_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 "Immagine"
+
+#. 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 "Ereditato"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Integer"
+msgstr "Intero"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product.py:340
+#: code:addons/product_configurator/models/product_config.py:438
+#, python-format
+msgid "Invalid Configuration"
+msgstr "Configurazione errata"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_account_invoice_line
+msgid "Invoice Line"
+msgstr "Riga Fattura"
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_required
+msgid "Is this attribute required?"
+msgstr "Questo attributo è richiesto?"
+
+#. 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 "Ultima modifica il"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_write_uid
+#: 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_image_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_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_line_write_uid
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_write_uid
+msgid "Last Updated by"
+msgstr "Ultimo aggioramento da"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_write_date
+#: 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_image_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_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_line_write_date
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_write_date
+msgid "Last Updated on"
+msgstr "Ultimo aggioramento"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_max_val
+msgid "Max Value"
+msgstr "Valore Max."
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_min_val
+msgid "Min Value"
+msgstr "Valore Min."
+
+#. 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 "Minimo valore ammesso"
+
+#. 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 "Nome"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_operator
+msgid "Operators"
+msgstr "Operatore"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_price
+msgid "Price"
+msgstr "Prezzo"
+
+#. 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 "Prodotto"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_attribute
+msgid "Product Attribute"
+msgstr "Attributo Prodotto"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_form_view
+msgid "Product Attribute Values"
+msgstr "Valori Attributo Prodotto"
+
+#. 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 "Configuratore Prodotto"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_product_id
+msgid "Product ID"
+msgstr "ID Prodotto"
+
+#. 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 "Template Prodotto"
+
+#. module: product_configurator
+#: model:res.groups,name:product_configurator.group_product_configurator
+msgid "Products"
+msgstr "Prodotti"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_product_id
+msgid "Related Product"
+msgstr "Prodotti Correlati"
+
+#. 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 "Richiesto"
+
+#. 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 "Vincoli"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_domain_id
+msgid "Rule"
+msgstr "Regole"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_config_domain_form_view
+msgid "Rules"
+msgstr "Regole"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_sale_order_line
+msgid "Sales Order Line"
+msgstr "Righe Ordine di Vendita"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_search_ok
+msgid "Searchable"
+msgstr "Ricercabile"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_attribute.py:100
+#, python-format
+msgid "Selected custom field type '%s' is not searchable"
+msgstr "Tipi di valori custom selezionati '%s' non sono ricercabili"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_attribute.py:115
+#, python-format
+msgid "Selected custom value '%s' must be between %s and %s"
+msgstr "Il valore custom selezionato '%s' deve essere compreso tra %s e %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 "Sequenza"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_cfg_session_id
+msgid "Session"
+msgstr "Sessione"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_state
+msgid "State"
+msgstr "Stato"
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_stock_move
+msgid "Stock Move"
+msgstr "Movimento Magazzino"
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Textarea"
+msgstr "Testo"
+
+#. 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 "I tipi di campi custom generati nell'interfaccia"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_trans_implied_ids
+msgid "Transitively inherits"
+msgstr "Eredita transitivamente"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_uom_id
+msgid "Unit of Measure"
+msgstr "Unità di Misura"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_user_id
+msgid "User"
+msgstr "Utente"
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view
+msgid "Validation"
+msgstr "Validazione"
+
+#. 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 "Valore"
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_value_ids
+msgid "Value ids"
+msgstr "Id Valori "
+
+#. 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 "Valori"
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:213
+#, python-format
+msgid "Values entered for line '%s' generate a incompatible configuration"
+msgstr "Il valore inserito nella riga '%s' genera una configurazione non corretta"
+
+#. 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 "Nel controllare le varianti della stessa configurazione, si deve includere thesto campo nella ricerca?"
+
+#. 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"
+

+ 1019 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/i18n/product_configurator.pot

@@ -0,0 +1,1019 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+#	* product_configurator
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 10.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-11-26 10:24+0000\n"
+"PO-Revision-Date: 2016-11-26 10:24+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: product_configurator
+#: model:product.template,name:product_configurator.bmw_2_series
+msgid "2 Series"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_218d
+msgid "218d"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_218d_coupe
+#: model:product.template,name:product_configurator.product_engine_218d_coupe_product_template
+msgid "218d Coupé"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_218i
+msgid "218i"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_218i_coupe
+#: model:product.template,name:product_configurator.product_engine_218i_coupe_product_template
+msgid "218i Coupé"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_220d
+msgid "220d"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_220d_coupe
+#: model:product.template,name:product_configurator.product_engine_220d_coupe_product_template
+msgid "220d Coupé"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_220d_xdrive
+msgid "220d xDrive"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_220d_xdrive_coupe
+#: model:product.template,name:product_configurator.product_engine_220d_xdrive_coupe_product_template
+msgid "220d xDrive Coupé"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_220i
+msgid "220i"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_220i_coupe
+#: model:product.template,name:product_configurator.product_engine_220i_coupe_product_template
+msgid "220i Coupé"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_225d
+msgid "225d"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_225d_coupe
+#: model:product.template,name:product_configurator.product_engine_225d_coupe_product_template
+msgid "225d Coupé"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_228i
+msgid "228i"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_228i_coupe
+#: model:product.template,name:product_configurator.product_engine_228i_coupe_product_template
+msgid "228i Coupé"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_val_custom
+msgid "Allow custom value for this attribute?"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_custom
+msgid "Allow custom values for this attribute?"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_armrest
+#: model:product.product,name:product_configurator.product_2_series_armrest
+#: model:product.template,name:product_configurator.product_2_series_armrest_product_template
+msgid "Armrest"
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Attachment"
+msgstr ""
+
+#. 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 ""
+
+#. 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
+msgid "Attribute"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attribute_line_id
+msgid "Attribute Line"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_step_line_attribute_line_ids
+msgid "Attribute Lines"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view
+msgid "Attribute Name"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view
+msgid "Attribute Value Dependencies"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_line_attr_line_val_ids
+msgid "Attribute Values"
+msgstr ""
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:505
+#, python-format
+msgid "Attribute custom type is binary, attachments are the only accepted values with this custom field type"
+msgstr ""
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:510
+#, python-format
+msgid "Attribute custom type must be 'binary' for saving attachments to custom value"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_steptronic
+msgid "Automatic (Steptronic)"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_steptronic_sport
+msgid "Automatic Sport (Steptronic)"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_2_series_transmission_steptronic
+#: model:product.template,name:product_configurator.product_2_series_transmission_steptronic_product_template
+msgid "Automatic Transmission Steptronic"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.category,name:product_configurator.product_category_bmw
+msgid "BMW"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_black
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_tapistry_black
+msgid "Black"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.step,name:product_configurator.config_step_body
+#: model:product.config.step.line,name:product_configurator.2_series_config_step_body
+msgid "Body"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:268
+#, python-format
+msgid "Cannot have a configuration step defined twice."
+msgstr ""
+
+#. module: product_configurator
+#: sql_constraint:product.attribute.value.custom:0
+msgid "Cannot have two custom values for the same attribute"
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Char"
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+#: model:product.attribute,name:product_configurator.product_attribute_color
+msgid "Color"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_condition
+msgid "Condition"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.menu,name:product_configurator.menu_product_configurable
+msgid "Configurable Products"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_product_tmpl_id
+msgid "Configurable Template"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.ui.menu,name:product_configurator.menu_product_configurable_variants_action
+msgid "Configurable Variants"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_image_value_ids
+msgid "Configuration"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_custom
+msgid "Configuration Values"
+msgstr ""
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:486
+#, python-format
+msgid "Configuration cannot have the same value inserted twice"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_template_form_view
+msgid "Configurator"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.actions.act_window,name:product_configurator.product_configurable_variant_action
+msgid "Configured Variants"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_tapistry_coral_red_black
+msgid "Coral Red/Black"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_3
+msgid "Coupé Black"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_9
+msgid "Coupé Black Rims 384"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_10
+msgid "Coupé Black Rims 387"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_1
+msgid "Coupé Red"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_5
+msgid "Coupé Red Rims 384"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_6
+msgid "Coupé Red Rims 387"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_2
+msgid "Coupé Silver"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_7
+msgid "Coupé Silver Rims 384"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.image,name:product_configurator.config_image_8
+msgid "Coupé Silver Rims 387"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_config_session_custom_value_value
+msgid "Custom value held as string"
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Date"
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "DateTime"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_description
+msgid "Description"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_diesel
+msgid "Diesel"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: selection:product.config.session,state:0
+msgid "Done"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_rims_384
+msgid "Double-spoke 18\""
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.config.session,state:0
+msgid "Draft"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute,name:product_configurator.product_attribute_engine
+#: model:product.config.step,name:product_configurator.config_step_engine
+#: model:product.config.step.line,name:product_configurator.2_series_config_step_engine
+msgid "Engine"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.step,name:product_configurator.config_step_extras
+#: model:product.config.step.line,name:product_configurator.2_series_config_step_extras
+msgid "Extras"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_custom_type
+msgid "Field Type"
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Float"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute,name:product_configurator.product_attribute_fuel
+msgid "Fuel"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_gasoline
+msgid "Gasoline"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Integer"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.config.step,name:product_configurator.config_step_interior
+#: model:product.config.step.line,name:product_configurator.2_series_config_step_interior
+msgid "Interior"
+msgstr ""
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product.py:288
+#: code:addons/product_configurator/models/product_config.py:434
+#, python-format
+msgid "Invalid Configuration"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_account_invoice_line
+msgid "Invoice Line"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,help:product_configurator.field_product_attribute_line_required
+msgid "Is this attribute required?"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:product.attribute,name:product_configurator.product_attribute_model_line
+#: model:product.config.step,name:product_configurator.config_step_lines
+#: model:product.config.step.line,name:product_configurator.2_series_config_step_lines
+msgid "Lines"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_luxury_line
+#: model:product.product,name:product_configurator.product_bmw_luxury_line
+#: model:product.template,name:product_configurator.product_bmw_luxury_line_product_template
+msgid "Luxury Line"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_m235i
+msgid "M235i"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_m235i_coupe
+#: model:product.template,name:product_configurator.product_engine_m235i_coupe_product_template
+msgid "M235i Coupé"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_m235i_xdrive
+msgid "M235i xDrive"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_engine_m2351_xdrive_coupe
+#: model:product.template,name:product_configurator.product_engine_m2351_xdrive_coupe_product_template
+msgid "M235i xDrive Coupe"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_max_val
+msgid "Max Value"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_min_val
+msgid "Min Value"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_model_advantage
+#: model:product.product,name:product_configurator.product_bmw_model_advantage
+#: model:product.template,name:product_configurator.product_bmw_model_advantage_product_template
+msgid "Model Advantage"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_model_luxury_line
+#: model:product.product,name:product_configurator.product_bmw_model_luxury_line
+#: model:product.template,name:product_configurator.product_bmw_model_luxury_line_product_template
+msgid "Model Luxury Line"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_model_m_sport
+#: model:product.product,name:product_configurator.product_bmw_model_m_sport
+#: model:product.template,name:product_configurator.product_bmw_model_m_sport_product_template
+msgid "Model M Sport"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_model_sport_line
+#: model:product.product,name:product_configurator.product_bmw_model_sport_line
+#: model:product.template,name:product_configurator.product_bmw_model_sport_line_product_template
+msgid "Model Sport Line"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_operator
+msgid "Operators"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute,name:product_configurator.product_attribute_options
+msgid "Options"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_tapistry_oyster_black
+msgid "Oyster/Black"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_price
+msgid "Price"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_attribute
+msgid "Product Attribute"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_value_form_view
+msgid "Product Attribute Values"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view
+msgid "Product Attributes"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.module.category,name:product_configurator.product_config_category
+msgid "Product Configurator"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_custom_product_id
+msgid "Product ID"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:res.groups,name:product_configurator.group_product_configurator
+msgid "Products"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_red
+msgid "Red"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_value_product_id
+msgid "Related Product"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:product.attribute,name:product_configurator.product_attribute_rims
+msgid "Rims"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_line_domain_id
+msgid "Rule"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_config_domain_form_view
+msgid "Rules"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_sale_order_line
+msgid "Sales Order Line"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_search_ok
+msgid "Searchable"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_custom_value_cfg_session_id
+msgid "Session"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_silver
+msgid "Silver"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_paint_silver
+#: model:product.template,name:product_configurator.product_paint_silver_product_template
+msgid "Silver Paint"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_smoker_package
+#: model:product.product,name:product_configurator.product_2_series_smoker_package
+#: model:product.template,name:product_configurator.product_2_series_smoker_package_product_template
+msgid "Smoker Package"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_2_series_transmission_steptronic_sport
+#: model:product.template,name:product_configurator.product_2_series_transmission_steptronic_sport_product_template
+msgid "Sport Automatic Transmission Steptronic"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_sport_line
+#: model:product.product,name:product_configurator.product_bmw_sport_line
+#: model:product.template,name:product_configurator.product_bmw_sport_line_product_template
+msgid "Sport Line"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_state
+msgid "State"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_stock_move
+msgid "Stock Move"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_sunroof
+#: model:product.product,name:product_configurator.product_2_series_sunroof
+#: model:product.template,name:product_configurator.product_2_series_sunroof_product_template
+msgid "Sunroof"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute,name:product_configurator.product_attribute_tapistry
+msgid "Tapistry"
+msgstr ""
+
+#. module: product_configurator
+#: selection:product.attribute,custom_type:0
+msgid "Textarea"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_tow_hook
+msgid "Tow hook"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.product,name:product_configurator.product_2_series_towhook
+#: model:product.template,name:product_configurator.product_2_series_towhook_product_template
+msgid "Towhook"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_domain_trans_implied_ids
+msgid "Transitively inherits"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute,name:product_configurator.product_attribute_transmission
+msgid "Transmission"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_attribute_uom_id
+msgid "Unit of Measure"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_user_id
+msgid "User"
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_rims_378
+msgid "V-spoke 16\""
+msgstr ""
+
+#. module: product_configurator
+#: model:product.attribute.value,name:product_configurator.product_attribute_value_rims_387
+msgid "V-spoke 18\""
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.ui.view,arch_db:product_configurator.product_attribute_form_view
+msgid "Validation"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model.fields,field_description:product_configurator.field_product_config_session_value_ids
+msgid "Value ids"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: code:addons/product_configurator/models/product_config.py:213
+#, python-format
+msgid "Values entered for line '%s' generate a incompatible configuration"
+msgstr ""
+
+#. 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 ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_attribute_line
+msgid "product.attribute.line"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_attribute_value
+msgid "product.attribute.value"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_attribute_value_custom
+msgid "product.attribute.value.custom"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_domain
+msgid "product.config.domain"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_domain_line
+msgid "product.config.domain.line"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_image
+msgid "product.config.image"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_line
+msgid "product.config.line"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_session
+msgid "product.config.session"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_session_custom_value
+msgid "product.config.session.custom.value"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_step
+msgid "product.config.step"
+msgstr ""
+
+#. module: product_configurator
+#: model:ir.model,name:product_configurator.model_product_config_step_line
+msgid "product.config.step.line"
+msgstr ""
+

+ 8 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/__init__.py

@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from . import product_config
+from . import product_attribute
+from . import product
+from . import sale
+from . import stock
+from . import account

+ 9 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/account.py

@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields
+
+
+class AccountInvoiceLine(models.Model):
+    _inherit = 'account.invoice.line'
+
+    product_id = fields.Many2one(domain=[('config_ok', '=', False)])

+ 587 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/product.py

@@ -0,0 +1,587 @@
+# -*- coding: utf-8 -*-
+
+from odoo.tools.misc import formatLang
+from odoo.exceptions import ValidationError
+from odoo import models, fields, api, tools, _
+from lxml import etree
+
+
+class ProductTemplate(models.Model):
+    _inherit = 'product.template'
+
+    config_ok = fields.Boolean(string='Can be Configured')
+
+    config_line_ids = fields.One2many(
+        comodel_name='product.config.line',
+        inverse_name='product_tmpl_id',
+        string="Attribute Dependencies"
+    )
+
+    config_image_ids = fields.One2many(
+        comodel_name='product.config.image',
+        inverse_name='product_tmpl_id',
+        string='Configuration Images'
+    )
+
+    config_step_line_ids = fields.One2many(
+        comodel_name='product.config.step.line',
+        inverse_name='product_tmpl_id',
+        string='Configuration Lines'
+    )
+
+    def flatten_val_ids(self, value_ids):
+        """ Return a list of value_ids from a list with a mix of ids
+        and list of ids (multiselection)
+
+        :param value_ids: list of value ids or mix of ids and list of ids
+                           (e.g: [1, 2, 3, [4, 5, 6]])
+        :returns: flattened list of ids ([1, 2, 3, 4, 5, 6]) """
+        flat_val_ids = set()
+        for val in value_ids:
+            if not val:
+                continue
+            if isinstance(val, list):
+                flat_val_ids |= set(val)
+            elif isinstance(val, int):
+                flat_val_ids.add(val)
+        return list(flat_val_ids)
+
+    def get_open_step_lines(self, value_ids):
+        """
+        Returns a recordset of configuration step lines open for access given
+        the configuration passed through value_ids
+
+        e.g: Field A and B from configuration step 2 depend on Field C
+        from configuration step 1. Since fields A and B require action from
+        the previous step, configuration step 2 is deemed closed and redirect
+        is made for configuration step 1.
+
+        :param value_ids: list of value.ids representing the
+                          current configuration
+        :returns: recordset of accesible configuration steps
+        """
+
+        open_step_lines = self.env['product.config.step.line']
+
+        for cfg_line in self.config_step_line_ids:
+            for attr_line in cfg_line.attribute_line_ids:
+                available_vals = any(
+                    val for val in attr_line.value_ids if
+                    self.value_available(val.id, value_ids)
+                )
+                # TODO: Refactor when adding restriction to custom values
+                if available_vals or attr_line.custom:
+                    open_step_lines |= cfg_line
+                    break
+
+        return open_step_lines.sorted()
+
+    def get_adjacent_steps(self, value_ids, active_step_line_id=None):
+        """Returns the previous and next steps given the configuration passed
+        via value_ids and the active step line passed via cfg_step_line_id.
+
+        If there is no open step return empty dictionary"""
+
+        config_step_lines = self.config_step_line_ids
+
+        if not config_step_lines:
+            return {}
+
+        active_cfg_step_line = config_step_lines.filtered(
+            lambda l: l.id == active_step_line_id)
+
+        open_step_lines = self.get_open_step_lines(value_ids)
+
+        if not active_cfg_step_line:
+            return {'next_step': open_step_lines[0]}
+
+        nr_steps = len(open_step_lines)
+
+        adjacent_steps = {}
+
+        for i, cfg_step in enumerate(open_step_lines):
+            if cfg_step == active_cfg_step_line:
+                adjacent_steps.update({
+                    'next_step':
+                        None if i + 1 == nr_steps else open_step_lines[i + 1],
+                    'previous_step': None if i == 0 else open_step_lines[i - 1]
+                })
+        return adjacent_steps
+
+    def formatPrices(self, prices=None, dp='Product Price'):
+        if prices is None:
+            prices = {}
+        dp = None
+        prices['taxes'] = formatLang(
+            self.env, prices['taxes'], monetary=True, dp=dp)
+        prices['total'] = formatLang(
+            self.env, prices['total'], monetary=True, dp=dp)
+        prices['vals'] = [
+            (v[0], v[1], formatLang(self.env, v[2], monetary=True, dp=dp))
+            for v in prices['vals']
+        ]
+        return prices
+
+    @api.multi
+    def _get_option_values(self, value_ids, pricelist):
+        """Return only attribute values that have products attached with a
+        price set to them"""
+        value_obj = self.env['product.attribute.value'].with_context({
+            'pricelist': pricelist.id})
+        values = value_obj.sudo().browse(value_ids).filtered(
+            lambda x: x.product_id.price)
+        return values
+
+    @api.multi
+    def get_components_prices(self, prices, value_ids,
+                              custom_values, pricelist):
+        """Return prices of the components which make up the final
+        configured variant"""
+        vals = self._get_option_values(value_ids, pricelist)
+        for val in vals:
+            prices['vals'].append(
+                (val.attribute_id.name,
+                 val.product_id.name,
+                 val.product_id.price)
+            )
+            product = val.product_id.with_context({'pricelist': pricelist.id})
+            product_prices = product.taxes_id.sudo().compute_all(
+                price_unit=product.price,
+                currency=pricelist.currency_id,
+                quantity=1,
+                product=self,
+                partner=self.env.user.partner_id
+            )
+
+            total_included = product_prices['total_included']
+            taxes = total_included - product_prices['total_excluded']
+            prices['taxes'] += taxes
+            prices['total'] += total_included
+        return prices
+
+    @api.multi
+    def get_cfg_price(self, value_ids, custom_values=None,
+                      pricelist_id=None, formatLang=False):
+        """ Computes the price of the configured product based on the configuration
+            passed in via value_ids and custom_values
+
+        :param value_ids: list of attribute value_ids
+        :param custom_values: dictionary of custom attribute values
+        :param pricelist_id: id of pricelist to use for price computation
+        :param formatLang: boolean for formatting price dictionary
+        :returns: dictionary of prices per attribute and total price"""
+        self.ensure_one()
+        if custom_values is None:
+            custom_values = {}
+        if not pricelist_id:
+            pricelist = self.env.user.partner_id.property_product_pricelist
+            pricelist_id = pricelist.id
+        else:
+            pricelist = self.env['product.pricelist'].browse(pricelist_id)
+
+        currency = pricelist.currency_id
+
+        product = self.with_context({'pricelist': pricelist.id})
+
+        base_prices = product.taxes_id.sudo().compute_all(
+            price_unit=product.price,
+            currency=pricelist.currency_id,
+            quantity=1,
+            product=product,
+            partner=self.env.user.partner_id
+        )
+
+        total_included = base_prices['total_included']
+        total_excluded = base_prices['total_excluded']
+
+        prices = {
+            'vals': [
+                ('Base', self.name, total_excluded)
+            ],
+            'total': total_included,
+            'taxes': total_included - total_excluded,
+            'currency': currency.name
+        }
+
+        component_prices = self.get_components_prices(
+            prices, value_ids, custom_values, pricelist)
+        prices.update(component_prices)
+
+        if formatLang:
+            return self.formatPrices(prices)
+        return prices
+
+    @api.multi
+    def search_variant(self, value_ids, custom_values=None):
+        """ Searches product.variants with given value_ids and custom values
+            given in the custom_values dict
+
+            :param value_ids: list of product.attribute.values ids
+            :param custom_values: dict {product.attribute.id: custom_value}
+
+            :returns: product.product recordset of products matching domain
+        """
+        if custom_values is None:
+            custom_values = {}
+        attr_obj = self.env['product.attribute']
+        for product_tmpl in self:
+            domain = [('product_tmpl_id', '=', product_tmpl.id)]
+
+            for value_id in value_ids:
+                domain.append(('attribute_value_ids', '=', value_id))
+
+            attr_search = attr_obj.search([
+                ('search_ok', '=', True),
+                ('custom_type', 'not in', attr_obj._get_nosearch_fields())
+            ])
+
+            for attr_id, value in custom_values.iteritems():
+                if attr_id not in attr_search.ids:
+                    domain.append(
+                        ('value_custom_ids.attribute_id', '!=', int(attr_id)))
+                else:
+                    domain.append(
+                        ('value_custom_ids.attribute_id', '=', int(attr_id)))
+                    domain.append(('value_custom_ids.value', '=', value))
+
+            products = self.env['product.product'].search(domain)
+            return products
+
+    def get_config_image_obj(self, value_ids, size=None):
+        """
+        Retreive the image object that most closely resembles the configuration
+        code sent via value_ids list
+
+        The default image object is the template (self)
+        :param value_ids: a list representing the ids of attribute values
+                         (usually stored in the user's session)
+        :returns: path to the selected image
+        """
+        # TODO: Also consider custom values for image change
+        img_obj = self
+        max_matches = 0
+        value_ids = self.flatten_val_ids(value_ids)
+        for line in self.config_image_ids:
+            matches = len(set(line.value_ids.ids) & set(value_ids))
+            if matches > max_matches:
+                img_obj = line
+                max_matches = matches
+        return img_obj
+
+    @api.multi
+    def encode_custom_values(self, custom_values):
+        """ Hook to alter the values of the custom values before creating or writing
+
+            :param custom_values: dict {product.attribute.id: custom_value}
+
+            :returns: list of custom values compatible with write and create
+        """
+        attr_obj = self.env['product.attribute']
+        binary_attribute_ids = attr_obj.search([
+            ('custom_type', '=', 'binary')]).ids
+
+        custom_lines = []
+
+        for key, val in custom_values.iteritems():
+            custom_vals = {'attribute_id': key}
+            # TODO: Is this extra check neccesairy as we already make
+            # the check in validate_configuration?
+            attr_obj.browse(key).validate_custom_val(val)
+            if key in binary_attribute_ids:
+                custom_vals.update({
+                    'attachment_ids': [(6, 0, val.ids)]
+                })
+            else:
+                custom_vals.update({'value': val})
+            custom_lines.append((0, 0, custom_vals))
+        return custom_lines
+
+    @api.multi
+    def get_variant_vals(self, value_ids, custom_values=None, **kwargs):
+        """ Hook to alter the values of the product variant before creation
+
+            :param value_ids: list of product.attribute.values ids
+            :param custom_values: dict {product.attribute.id: custom_value}
+
+            :returns: dictionary of values to pass to product.create() method
+         """
+        self.ensure_one()
+
+        image = self.get_config_image_obj(value_ids).image
+        all_images = tools.image_get_resized_images(
+            image, avoid_resize_medium=True)
+        vals = {
+            'product_tmpl_id': self.id,
+            'attribute_value_ids': [(6, 0, value_ids)],
+            'taxes_id': [(6, 0, self.taxes_id.ids)],
+            'image': image,
+            'image_variant': image,
+            'image_medium': all_images['image_medium'],
+            'image_small': all_images['image_medium'],
+        }
+
+        if custom_values:
+            vals.update({
+                'value_custom_ids': self.encode_custom_values(custom_values)
+            })
+
+        return vals
+
+    @api.multi
+    def create_variant(self, value_ids, custom_values=None):
+        """ Creates a product.variant with the attributes passed via value_ids
+        and custom_values
+
+            :param value_ids: list of product.attribute.values ids
+            :param custom_values: dict {product.attribute.id: custom_value}
+
+            :returns: product.product recordset of products matching domain
+
+        """
+        if custom_values is None:
+            custom_values = {}
+        valid = self.validate_configuration(value_ids, custom_values)
+        if not valid:
+            raise ValidationError(_('Invalid Configuration'))
+        # TODO: Add all custom values to order line instead of product
+        vals = self.get_variant_vals(value_ids, custom_values)
+        variant = self.env['product.product'].create(vals)
+
+        return variant
+
+    # TODO: Refactor so multiple values can be checked at once
+    # also a better method for building the domain using the logical
+    # operators is required
+    @api.multi
+    def value_available(self, attr_val_id, value_ids):
+        """Determines whether the attr_value from the product_template
+            is available for selection given the configuration ids and the
+            dependencies set on the product template
+
+            :param attr_val_id: int of product.attribute.value object
+            :param value_ids: list of attribute value ids
+
+            :returns: True or False representing availability
+
+        """
+        self.ensure_one()
+        config_lines = self.config_line_ids.filtered(
+            lambda l: attr_val_id in l.value_ids.ids
+        )
+
+        domains = config_lines.mapped('domain_id').compute_domain()
+
+        for domain in domains:
+            if domain[1] == 'in':
+                if not set(domain[2]) & set(value_ids):
+                    return False
+            else:
+                if set(domain[2]) & set(value_ids):
+                    return False
+        return True
+
+    @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
+        are valid
+
+        :param value_ids: list of attribute value ids
+        :param custom_vals: custom values dict {attr_id: custom_val}
+        :param final: boolean marker to check required attributes.
+                      pass false to check non-final configurations
+
+        :returns: Error dict with reason of validation failure
+                  or True
+        """
+        # TODO: Raise ConfigurationError with reason
+        # Check if required values are missing for final configuration
+        if custom_vals is None:
+            custom_vals = {}
+
+        for line in self.attribute_line_ids:
+            # Validate custom values
+            attr = line.attribute_id
+            if attr.id in custom_vals:
+                attr.validate_custom_val(custom_vals[attr.id])
+            if final:
+                common_vals = set(value_ids) & set(line.value_ids.ids)
+                custom_val = custom_vals.get(attr.id)
+                if line.required and not common_vals and not custom_val:
+                    # TODO: Verify custom value type to be correct
+                    return False
+
+        # Check if all all the values passed are not restricted
+        for val in value_ids:
+            available = self.value_available(
+                val, [v for v in value_ids if v != val])
+            if not available:
+                return False
+
+        # Check if custom values are allowed
+        custom_attr_ids = self.attribute_line_ids.filtered(
+            'custom').mapped('attribute_id').ids
+
+        if not set(custom_vals.keys()) <= set(custom_attr_ids):
+            return False
+
+        # Check if there are multiple values passed for non-multi attributes
+        mono_attr_lines = self.attribute_line_ids.filtered(
+            lambda l: not l.multi)
+
+        for line in mono_attr_lines:
+            if len(set(line.value_ids.ids) & set(value_ids)) > 1:
+                return False
+        return True
+
+    @api.multi
+    def toggle_config(self):
+        for record in self:
+            record.config_ok = not record.config_ok
+
+    # Override name_search delegation to variants introduced by Odony
+    # TODO: Verify this is still a problem in v9
+    @api.model
+    def name_search(self, name='', args=None, operator='ilike', limit=100):
+        return super(models.Model, self).name_search(name=name,
+                                                     args=args,
+                                                     operator=operator,
+                                                     limit=limit)
+
+    @api.multi
+    def create_variant_ids(self):
+        """ Prevent configurable products from creating variants as these serve
+            only as a template for the product configurator"""
+        for product in self:
+            if self.config_ok:
+                return None
+            return super(ProductTemplate, self).create_variant_ids()
+
+    @api.multi
+    def unlink(self):
+        """ Prevent the removal of configurable product templates
+            from variants"""
+        for template in self:
+            variant_unlink = self.env.context.get('unlink_from_variant', False)
+            if template.config_ok and variant_unlink:
+                self -= template
+        res = super(ProductTemplate, self).unlink()
+        return res
+
+
+class ProductProduct(models.Model):
+    _inherit = 'product.product'
+    _rec_name = 'config_name'
+
+    def _get_conversions_dict(self):
+        conversions = {
+            'float': float,
+            'int': int
+        }
+        return conversions
+
+    @api.multi
+    def _compute_product_price_extra(self):
+        """Compute price of configurable products as sum
+        of products related to attribute values picked"""
+        products = self.filtered(lambda x: not x.config_ok)
+        configurable_products = self - products
+        if products:
+            prices = super(ProductProduct, self)._compute_product_price_extra()
+
+        conversions = self._get_conversions_dict()
+        for product in configurable_products:
+            lst_price = product.product_tmpl_id.lst_price
+            value_ids = product.attribute_value_ids.ids
+            # TODO: Merge custom values from products with cfg session
+            # and use same method to retrieve parsed custom val dict
+            custom_vals = {}
+            for val in product.value_custom_ids:
+                custom_type = val.attribute_id.custom_type
+                if custom_type in conversions:
+                    try:
+                        custom_vals[val.attribute_id.id] = conversions[
+                            custom_type](val.value)
+                    except:
+                        raise ValidationError(
+                            _("Could not convert custom value '%s' to '%s' on "
+                              "product variant: '%s'" % (val.value,
+                                                         custom_type,
+                                                         product.display_name))
+                        )
+                else:
+                    custom_vals[val.attribute_id.id] = val.value
+            prices = product.product_tmpl_id.get_cfg_price(
+                value_ids, custom_vals)
+            product.price_extra = prices['total'] - prices['taxes'] - lst_price
+
+    config_name = fields.Char(
+        string="Name",
+        size=256,
+        compute='_compute_name',
+    )
+    value_custom_ids = fields.One2many(
+        comodel_name='product.attribute.value.custom',
+        inverse_name='product_id',
+        string='Custom Values',
+        readonly=True
+    )
+
+    @api.multi
+    def _check_attribute_value_ids(self):
+        """ Removing multi contraint attribute to enable multi selection. """
+        return True
+
+    _constraints = [
+        (_check_attribute_value_ids, None, ['attribute_value_ids'])
+    ]
+
+    @api.model
+    def fields_view_get(self, view_id=None, view_type='form',
+                        toolbar=False, submenu=False):
+        """ For configurable products switch the name field with the config_name
+            so as to keep the view intact in whatever form it is at the moment
+            of execution and not duplicate the original just for the sole
+            purpose of displaying the proper name"""
+        res = super(ProductProduct, self).fields_view_get(
+            view_id=view_id, view_type=view_type,
+            toolbar=toolbar, submenu=submenu
+        )
+        if self.env.context.get('default_config_ok'):
+            xml_view = etree.fromstring(res['arch'])
+            xml_name = xml_view.xpath("//field[@name='name']")
+            xml_label = xml_view.xpath("//label[@for='name']")
+            if xml_name:
+                xml_name[0].attrib['name'] = 'config_name'
+                if xml_label:
+                    xml_label[0].attrib['for'] = 'config_name'
+                view_obj = self.env['ir.ui.view']
+                xarch, xfields = view_obj.postprocess_and_fields(self._name,
+                                                                 xml_view,
+                                                                 view_id)
+                res['arch'] = xarch
+                res['fields'] = xfields
+        return res
+
+    # TODO: Implement naming method for configured products
+    # TODO: Provide a field with custom name in it that defaults to a name
+    # pattern
+    def get_config_name(self):
+        return self.name
+
+    @api.multi
+    def unlink(self):
+        """ Signal unlink from product variant through context so
+            removal can be stopped for configurable templates """
+        ctx = dict(self.env.context, unlink_from_variant=True)
+        self.env.context = ctx
+        return super(ProductProduct, self).unlink()
+
+    @api.multi
+    def _compute_name(self):
+        """ Compute the name of the configurable products and use template
+            name for others"""
+        for product in self:
+            if product.config_ok:
+                product.config_name = product.get_config_name()
+            else:
+                product.config_name = product.name

+ 278 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/product_attribute.py

@@ -0,0 +1,278 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api, _
+from odoo.exceptions import ValidationError
+from ast import literal_eval
+
+# TODO: Implement a default attribute value field/method to load up on wizard
+
+
+class ProductAttribute(models.Model):
+    _inherit = 'product.attribute'
+
+    @api.multi
+    def copy(self, default=None):
+        for attr in self:
+            default.update({'name': attr.name + " (copy)"})
+            attr = super(ProductAttribute, attr).copy(default)
+            return attr
+
+    @api.model
+    def _get_nosearch_fields(self):
+        """Return a list of custom field types that do not support searching"""
+        return ['binary']
+
+    @api.onchange('custom_type')
+    def onchange_custom_type(self):
+        if self.custom_type in self._get_nosearch_fields():
+            self.search_ok = False
+
+    CUSTOM_TYPES = [
+        ('char', 'Char'),
+        ('int', 'Integer'),
+        ('float', 'Float'),
+        ('text', 'Textarea'),
+        ('color', 'Color'),
+        ('binary', 'Attachment'),
+        ('date', 'Date'),
+        ('datetime', 'DateTime'),
+    ]
+
+    active = fields.Boolean(
+        string='Active',
+        default=True,
+        help='By unchecking the active field you can '
+        'disable a attribute without deleting it'
+    )
+
+    min_val = fields.Integer(string="Min Value", help="Minimum value allowed")
+    max_val = fields.Integer(string="Max Value", help="Minimum value allowed")
+
+    # TODO: Exclude self from result-set of dependency
+    val_custom = fields.Boolean(
+        string='Custom Value',
+        help='Allow custom value for this attribute?'
+    )
+    custom_type = fields.Selection(
+        selection=CUSTOM_TYPES,
+        string='Field Type',
+        size=64,
+        help='The type of the custom field generated in the frontend'
+    )
+
+    description = fields.Text(string='Description', translate=True)
+
+    search_ok = fields.Boolean(
+        string='Searchable',
+        help='When checking for variants with '
+        'the same configuration, do we '
+        'include this field in the search?'
+    )
+
+    required = fields.Boolean(
+        string='Required',
+        default=True,
+        help='Determines the required value of this '
+        'attribute though it can be change on '
+        'the template level'
+    )
+
+    multi = fields.Boolean(
+        string="Multi",
+        help='Allow selection of multiple values for '
+        'this attribute?'
+    )
+
+    uom_id = fields.Many2one(
+        comodel_name='product.uom',
+        string='Unit of Measure'
+    )
+
+    image = fields.Binary(string='Image')
+
+    # TODO prevent the same attribute from being defined twice on the
+    # attribute lines
+
+    _order = 'sequence'
+
+    @api.constrains('custom_type', 'search_ok')
+    def check_searchable_field(self):
+        nosearch_fields = self._get_nosearch_fields()
+        if self.custom_type in nosearch_fields and self.search_ok:
+            raise ValidationError(
+                _("Selected custom field type '%s' is not searchable" %
+                  self.custom_type)
+            )
+
+    def validate_custom_val(self, val):
+        """ Pass in a desired custom value and ensure it is valid.
+        Probaly should check type, etc, but let's assume fine for the moment.
+        """
+        self.ensure_one()
+        if self.custom_type in ('int', 'float'):
+            minv = self.min_val
+            maxv = self.max_val
+            val = literal_eval(val)
+            if minv and maxv and (val < minv or val > maxv):
+                raise ValidationError(
+                    _("Selected custom value '%s' must be between %s and %s"
+                        % (self.name, self.min_val, self.max_val))
+                )
+            elif minv and val < minv:
+                raise ValidationError(
+                    _("Selected custom value '%s' must be at least %s" %
+                        (self.name, self.min_val))
+                )
+            elif maxv and val > maxv:
+                raise ValidationError(
+                    _("Selected custom value '%s' must be lower than %s" %
+                        (self.name, self.max_val + 1))
+                )
+
+
+class ProductAttributeLine(models.Model):
+    _inherit = 'product.attribute.line'
+
+    @api.onchange('attribute_id')
+    def onchange_attribute(self):
+        self.value_ids = False
+        self.required = self.attribute_id.required
+        # TODO: Remove all dependencies pointed towards the attribute being
+        # changed
+
+    custom = fields.Boolean(
+        string='Custom',
+        help="Allow custom values for this attribute?"
+    )
+    required = fields.Boolean(
+        string='Required',
+        help="Is this attribute required?"
+    )
+
+    multi = fields.Boolean(
+        string='Multi',
+        help='Allow selection of multiple values for this attribute?'
+    )
+
+    sequence = fields.Integer(string='Sequence', default=10)
+
+    # TODO: Order by dependencies first and then sequence so dependent fields
+    # do not come before master field
+    _order = 'product_tmpl_id, sequence, id'
+
+    # TODO: Constraint not allowing introducing dependencies that do not exist
+    # on the product.template
+
+
+class ProductAttributeValue(models.Model):
+    _inherit = 'product.attribute.value'
+
+    @api.multi
+    def copy(self, default=None):
+        default.update({'name': self.name + " (copy)"})
+        product = super(ProductAttributeValue, self).copy(default)
+        return product
+
+    active = fields.Boolean(
+        string='Active',
+        default=True,
+        help='By unchecking the active field you can '
+        'disable a attribute value without deleting it'
+    )
+    product_id = fields.Many2one(
+        comodel_name='product.product',
+        string='Related Product'
+    )
+
+    @api.model
+    def name_search(self, name='', args=None, operator='ilike', limit=100):
+        """Use name_search as a domain restriction for the frontend to show
+        only values set on the product template taking all the configuration
+        restrictions into account.
+
+        TODO: This only works when activating the selection not when typing
+        """
+        product_tmpl_id = self.env.context.get('_cfg_product_tmpl_id')
+        if product_tmpl_id:
+            # TODO: Avoiding browse here could be a good performance enhancer
+            product_tmpl = self.env['product.template'].browse(product_tmpl_id)
+            tmpl_vals = product_tmpl.attribute_line_ids.mapped('value_ids')
+            attr_restrict_ids = []
+            preset_val_ids = []
+            new_args = []
+            for arg in args:
+                # Restrict values only to value_ids set on product_template
+                if arg[0] == 'id' and arg[1] == 'not in':
+                    preset_val_ids = arg[2]
+                    # TODO: Check if all values are available for configuration
+                else:
+                    new_args.append(arg)
+            val_ids = set(tmpl_vals.ids)
+            if preset_val_ids:
+                val_ids -= set(arg[2])
+            val_ids = [v for v in val_ids if product_tmpl.value_available(
+                v, preset_val_ids)]
+            new_args.append(('id', 'in', val_ids))
+            mono_tmpl_lines = product_tmpl.attribute_line_ids.filtered(
+                lambda l: not l.multi)
+            for line in mono_tmpl_lines:
+                line_val_ids = set(line.mapped('value_ids').ids)
+                if line_val_ids & set(preset_val_ids):
+                    attr_restrict_ids.append(line.attribute_id.id)
+            if attr_restrict_ids:
+                new_args.append(('attribute_id', 'not in', attr_restrict_ids))
+            args = new_args
+        res = super(ProductAttributeValue, self).name_search(
+            name=name, args=args, operator=operator, limit=limit)
+        return res
+    # TODO: Prevent unlinking custom options by overriding unlink
+
+    # _sql_constraints = [
+    #    ('unique_custom', 'unique(id,allow_custom_value)',
+    #    'Only one custom value per dimension type is allowed')
+    # ]
+
+
+class ProductAttributeValueCustom(models.Model):
+
+    @api.multi
+    @api.depends('attribute_id', 'attribute_id.uom_id')
+    def _compute_val_name(self):
+        for attr_val_custom in self:
+            uom = attr_val_custom.attribute_id.uom_id.name
+            attr_val_custom.name = '%s%s' % (attr_val_custom.value, uom or '')
+
+    _name = 'product.attribute.value.custom'
+
+    name = fields.Char(
+        string='Name',
+        readonly=True,
+        compute="_compute_val_name",
+        store=True,
+    )
+    product_id = fields.Many2one(
+        comodel_name='product.product',
+        string='Product ID',
+        required=True,
+        ondelete='cascade'
+    )
+    attribute_id = fields.Many2one(
+        comodel_name='product.attribute',
+        string='Attribute',
+        required=True
+    )
+    attachment_ids = fields.Many2many(
+        comodel_name='ir.attachment',
+        relation='product_attr_val_custom_value_attachment_rel',
+        column1='attr_val_custom_id',
+        column2='attachment_id',
+        string='Attachments'
+    )
+    value = fields.Char(
+        string='Custom Value',
+    )
+
+    _sql_constraints = [
+        ('attr_uniq', 'unique(product_id, attribute_id)',
+         'Cannot have two custom values for the same attribute')
+    ]

+ 517 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/product_config.py

@@ -0,0 +1,517 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api, _
+from odoo.exceptions import Warning, ValidationError
+from ast import literal_eval
+
+
+class ProductConfigDomain(models.Model):
+    _name = 'product.config.domain'
+
+    @api.multi
+    @api.depends('implied_ids')
+    def _get_trans_implied(self):
+        "Computes the transitive closure of relation implied_ids"
+
+        def linearize(domains):
+            trans_domains = domains
+            for domain in domains:
+                implied_domains = domain.implied_ids - domain
+                if implied_domains:
+                    trans_domains |= linearize(implied_domains)
+            return trans_domains
+
+        for domain in self:
+            domain.trans_implied_ids = linearize(domain)
+
+    @api.multi
+    def compute_domain(self):
+        """ Returns a list of domains defined on a product.config.domain_line_ids
+            and all implied_ids"""
+        # TODO: Enable the usage of OR operators between domain lines and
+        # implied_ids
+        # TODO: Prevent circular dependencies
+        computed_domain = []
+        for domain in self:
+            for line in domain.trans_implied_ids.mapped('domain_line_ids'):
+                computed_domain.append(
+                    (line.attribute_id.id, line.condition, line.value_ids.ids)
+                )
+        return computed_domain
+
+    name = fields.Char(
+        string='Name',
+        required=True,
+        size=256
+    )
+    domain_line_ids = fields.One2many(
+        comodel_name='product.config.domain.line',
+        inverse_name='domain_id',
+        string='Restrictions',
+        required=True
+    )
+    implied_ids = fields.Many2many(
+        comodel_name='product.config.domain',
+        relation='product_config_domain_implied_rel',
+        string='Inherited',
+        column1='domain_id',
+        column2='parent_id'
+    )
+    trans_implied_ids = fields.Many2many(
+        comodel_name='product.config.domain',
+        compute=_get_trans_implied,
+        column1='domain_id',
+        column2='parent_id',
+        string='Transitively inherits'
+    )
+
+
+class ProductConfigDomainLine(models.Model):
+    _name = 'product.config.domain.line'
+
+    def _get_domain_conditions(self):
+        operators = [
+            ('in', 'In'),
+            ('not in', 'Not In')
+        ]
+
+        return operators
+
+    def _get_domain_operators(self):
+        andor = [
+            ('and', 'And'),
+            # ('or', 'Or')
+            # TODO: Not implemented in domain computation yet
+        ]
+
+        return andor
+
+    attribute_id = fields.Many2one(
+        comodel_name='product.attribute',
+        string='Attribute',
+        required=True)
+
+    domain_id = fields.Many2one(
+        comodel_name='product.config.domain',
+        required=True,
+        string='Rule')
+
+    condition = fields.Selection(
+        selection=_get_domain_conditions,
+        string="Condition",
+        required=True)
+
+    value_ids = fields.Many2many(
+        comodel_name='product.attribute.value',
+        relation='product_config_domain_line_attr_rel',
+        column1='line_id',
+        column2='attribute_id',
+        string='Values',
+        required=True
+    )
+
+    operator = fields.Selection(
+        selection=_get_domain_operators,
+        string='Operators',
+        default='and',
+        required=True
+    )
+
+
+class ProductConfigLine(models.Model):
+    _name = 'product.config.line'
+
+    # TODO: Prevent config lines having dependencies that are not set in other
+    # config lines
+    # TODO: Prevent circular depdencies: Length -> Color, Color -> Length
+
+    @api.onchange('attribute_line_id')
+    def onchange_attribute(self):
+        self.value_ids = False
+        self.domain_id = False
+
+    product_tmpl_id = fields.Many2one(
+        comodel_name='product.template',
+        string='Product Template',
+        ondelete='cascade',
+        required=True
+    )
+
+    attribute_line_id = fields.Many2one(
+        comodel_name='product.attribute.line',
+        string='Attribute Line',
+        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',
+        related='attribute_line_id.value_ids'
+    )
+
+    value_ids = fields.Many2many(
+        comodel_name='product.attribute.value',
+        id1="cfg_line_id",
+        id2="attr_val_id",
+        string="Values"
+    )
+
+    domain_id = fields.Many2one(
+        comodel_name='product.config.domain',
+        required=True,
+        string='Restrictions'
+    )
+
+    sequence = fields.Integer(string='Sequence', default=10)
+
+    _order = 'product_tmpl_id, sequence, id'
+
+    @api.multi
+    @api.constrains('value_ids')
+    def check_value_attributes(self):
+        for line in self:
+            value_attributes = line.value_ids.mapped('attribute_id')
+            if value_attributes != line.attribute_line_id.attribute_id:
+                raise ValidationError(
+                    _("Values must belong to the attribute of the "
+                      "corresponding attribute_line set on the configuration "
+                      "line")
+                )
+
+
+class ProductConfigImage(models.Model):
+    _name = 'product.config.image'
+
+    name = fields.Char('Name', size=128, required=True, translate=True)
+
+    product_tmpl_id = fields.Many2one(
+        comodel_name='product.template',
+        string='Product',
+        ondelete='cascade',
+        required=True
+    )
+
+    image = fields.Binary('Image', required=True)
+
+    sequence = fields.Integer(string='Sequence', default=10)
+
+    value_ids = fields.Many2many(
+        comodel_name='product.attribute.value',
+        string='Configuration'
+    )
+
+    _order = 'sequence'
+
+    @api.multi
+    @api.constrains('value_ids')
+    def _check_value_ids(self):
+        for cfg_img in self:
+            valid = cfg_img.product_tmpl_id.validate_configuration(
+                cfg_img.value_ids.ids, final=False)
+            if not valid:
+                raise ValidationError(
+                    _("Values entered for line '%s' generate "
+                      "a incompatible configuration" % cfg_img.name)
+                )
+
+
+class ProductConfigStep(models.Model):
+    _name = 'product.config.step'
+
+    # TODO: Prevent values which have dependencies to be set in a
+    #       step with higher sequence than the dependency
+
+    name = fields.Char(
+        string='Name',
+        size=128,
+        required=True,
+        translate=True
+    )
+
+
+class ProductConfigStepLine(models.Model):
+    _name = 'product.config.step.line'
+
+    name = fields.Char(related='config_step_id.name')
+
+    config_step_id = fields.Many2one(
+        comodel_name='product.config.step',
+        string='Configuration Step',
+        required=True
+    )
+    attribute_line_ids = fields.Many2many(
+        comodel_name='product.attribute.line',
+        relation='config_step_line_attr_id_rel',
+        column1='cfg_line_id',
+        column2='attr_id',
+        string='Attribute Lines'
+    )
+    product_tmpl_id = fields.Many2one(
+        comodel_name='product.template',
+        string='Product Template',
+        ondelete='cascade',
+        required=True
+    )
+    sequence = fields.Integer(
+        string='Sequence',
+        default=10
+    )
+
+    _order = 'sequence, config_step_id, id'
+
+    @api.constrains('config_step_id')
+    def _check_config_step(self):
+        cfg_step_lines = self.product_tmpl_id.config_step_line_ids
+        cfg_steps = cfg_step_lines.filtered(
+            lambda l: l != self).mapped('config_step_id')
+        if self.config_step_id in cfg_steps:
+            raise Warning(_('Cannot have a configuration step defined twice.'))
+
+
+class ProductConfigSession(models.Model):
+    _name = 'product.config.session'
+
+    @api.multi
+    @api.depends('value_ids')
+    def _compute_cfg_price(self):
+        for session in self:
+            custom_vals = session._get_custom_vals_dict()
+            price = session.product_tmpl_id.get_cfg_price(
+                session.value_ids.ids, custom_vals)
+            session.price = price['total']
+
+    @api.multi
+    def _get_custom_vals_dict(self):
+        """Retrieve session custom values as a dictionary of the form
+           {attribute_id: parsed_custom_value}"""
+        self.ensure_one()
+        custom_vals = {}
+        for val in self.custom_value_ids:
+            if val.attribute_id.custom_type in ['float', 'int']:
+                custom_vals[val.attribute_id.id] = literal_eval(val.value)
+            else:
+                custom_vals[val.attribute_id.id] = val.value
+        return custom_vals
+
+    product_tmpl_id = fields.Many2one(
+        comodel_name='product.template',
+        domain=[('config_ok', '=', True)],
+        string='Configurable Template',
+        required=True
+    )
+    value_ids = fields.Many2many(
+        comodel_name='product.attribute.value',
+        relation='product_config_session_attr_values_rel',
+        column1='cfg_session_id',
+        column2='attr_val_id',
+    )
+    user_id = fields.Many2one(
+        comodel_name='res.users',
+        required=True,
+        string='User'
+    )
+    custom_value_ids = fields.One2many(
+        comodel_name='product.config.session.custom.value',
+        inverse_name='cfg_session_id',
+        string='Custom Values'
+    )
+    price = fields.Float(
+        compute='_compute_cfg_price',
+        string='Price',
+        store=True,
+    )
+    state = fields.Selection(
+        string='State',
+        required=True,
+        selection=[
+            ('draft', 'Draft'),
+            ('done', 'Done')
+        ],
+        default='draft'
+    )
+
+    @api.multi
+    def action_confirm(self):
+        # TODO: Implement method to generate dict from custom vals
+        custom_val_dict = {
+            x.attribute_id.id: x.value or x.attachment_ids
+            for x in self.custom_value_ids
+        }
+        valid = self.product_tmpl_id.validate_configuration(
+            self.value_ids.ids, custom_val_dict)
+        if valid:
+            self.state = 'done'
+        return valid
+
+    @api.multi
+    def update_config(self, attr_val_dict=None, custom_val_dict=None):
+        """Update the session object with the given value_ids and custom values.
+
+        Use this method instead of write in order to prevent incompatible
+        configurations as this removed duplicate values for the same attribute.
+
+        :param attr_val_dict: Dictionary of the form {
+            int (attribute_id): attribute_value_id OR [attribute_value_ids]
+        }
+
+        :custom_val_dict: Dictionary of the form {
+            int (attribute_id): {
+                'value': 'custom val',
+                OR
+                'attachment_ids': {
+                    [{
+                        'name': 'attachment name',
+                        'datas': base64_encoded_string
+                    }]
+                }
+            }
+        }
+
+        """
+        if attr_val_dict is None:
+            attr_val_dict = {}
+        if custom_val_dict is None:
+            custom_val_dict = {}
+        update_vals = {}
+
+        value_ids = self.value_ids.ids
+        for attr_id, vals in attr_val_dict.iteritems():
+            attr_val_ids = self.value_ids.filtered(
+                lambda x: x.attribute_id.id == int(attr_id)).ids
+            # Remove all values for this attribute and add vals from dict
+            value_ids = list(set(value_ids) - set(attr_val_ids))
+            if not vals:
+                continue
+            if isinstance(vals, list):
+                value_ids += vals
+            elif isinstance(vals, int):
+                value_ids.append(vals)
+
+        if value_ids != self.value_ids.ids:
+            update_vals.update({
+                'value_ids': [(6, 0, value_ids)]
+            })
+
+        # Remove all custom values included in the custom_vals dict
+        self.custom_value_ids.filtered(
+            lambda x: x.attribute_id.id in custom_val_dict.keys()).unlink()
+
+        if custom_val_dict:
+            binary_field_ids = self.env['product.attribute'].search([
+                ('id', 'in', custom_val_dict.keys()),
+                ('custom_type', '=', 'binary')
+            ]).ids
+        for attr_id, vals in custom_val_dict.iteritems():
+            if not vals:
+                continue
+
+            if 'custom_value_ids' not in update_vals:
+                update_vals['custom_value_ids'] = []
+
+            custom_vals = {'attribute_id': attr_id}
+
+            if attr_id in binary_field_ids:
+                attachments = [(0, 0, {
+                    'name': val.get('name'),
+                    'datas': val.get('datas')
+                }) for val in vals]
+                custom_vals.update({'attachment_ids': attachments})
+            else:
+                custom_vals.update({'value': vals})
+
+            update_vals['custom_value_ids'].append((0, 0, custom_vals))
+
+        self.write(update_vals)
+
+    @api.multi
+    def write(self, vals):
+        """Validate configuration when writing new values to session"""
+        # TODO: Issue warning when writing to value_ids or custom_val_ids
+        res = super(ProductConfigSession, self).write(vals)
+        custom_val_dict = {
+            x.attribute_id.id: x.value or x.attachment_ids
+            for x in self.custom_value_ids
+        }
+        valid = self.product_tmpl_id.validate_configuration(
+            self.value_ids.ids, custom_val_dict, final=False)
+        if not valid:
+            raise ValidationError(_('Invalid Configuration'))
+        return res
+
+    # TODO: Disallow duplicates
+
+
+class ProductConfigSessionCustomValue(models.Model):
+    _name = 'product.config.session.custom.value'
+    _rec_name = 'attribute_id'
+
+    attribute_id = fields.Many2one(
+        comodel_name='product.attribute',
+        string='Attribute',
+        required=True
+    )
+    cfg_session_id = fields.Many2one(
+        comodel_name='product.config.session',
+        required=True,
+        ondelete='cascade',
+        string='Session'
+    )
+    value = fields.Char(
+        string='Value',
+        help='Custom value held as string',
+    )
+    attachment_ids = fields.Many2many(
+        comodel_name='ir.attachment',
+        relation='product_config_session_custom_value_attachment_rel',
+        column1='cfg_sesion_custom_val_id',
+        column2='attachment_id',
+        string='Attachments'
+    )
+
+    def eval(self):
+        """Return custom value evaluated using the related custom field type"""
+        field_type = self.attribute_id.custom_type
+        if field_type == 'binary':
+            vals = self.attachment_ids.mapped('datas')
+            if len(vals) == 1:
+                return vals[0]
+            return vals
+        elif field_type == 'int':
+            return int(self.value)
+        elif field_type == 'float':
+            return float(self.value)
+        return self.value
+
+    @api.constrains('cfg_session_id', 'attribute_id')
+    def unique_attribute(self):
+        if len(self.cfg_session_id.custom_value_ids.filtered(
+                lambda x: x.attribute_id == self.attribute_id)) > 1:
+            raise ValidationError(
+                _("Configuration cannot have the same value inserted twice")
+            )
+
+    # @api.constrains('cfg_session_id.value_ids')
+    # def custom_only(self):
+    #     """Verify that the attribute_id is not present in vals as well"""
+    #     import ipdb;ipdb.set_trace()
+    #     if self.cfg_session_id.value_ids.filtered(
+    #             lambda x: x.attribute_id == self.attribute_id):
+    #         raise ValidationError(
+    #             _("Configuration cannot have a selected option and a custom "
+    #               "value with the same attribute")
+    #         )
+
+    @api.constrains('attachment_ids', 'value')
+    def check_custom_type(self):
+        custom_type = self.attribute_id.custom_type
+        if self.value and custom_type == 'binary':
+            raise ValidationError(
+                _("Attribute custom type is binary, attachments are the only "
+                  "accepted values with this custom field type")
+            )
+        if self.attachment_ids and custom_type != 'binary':
+            raise ValidationError(
+                _("Attribute custom type must be 'binary' for saving "
+                  "attachments to custom value")
+            )

+ 23 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/sale.py

@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields
+
+
+# class sale_order_line_attribute(models.Model):
+#     _name = 'sale.order.line.attribute'
+
+#     custom_value = fields.Char('Custom Value', size=64)
+#     sale_line_id = fields.Many2one('sale.order.line', 'Sale Order Line')
+
+
+class SaleOrderLine(models.Model):
+    _inherit = 'sale.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=[('config_ok', '=', False)])

+ 9 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/models/stock.py

@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields
+
+
+class StockMove(models.Model):
+    _inherit = 'stock.move'
+
+    product_id = fields.Many2one(domain=[('config_ok', '=', False)])

+ 17 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/security/configurator_security.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+<data>
+
+    <record id="product_config_category" model="ir.module.category">
+        <field name="name">Product Configurator</field>
+    </record>
+
+    <record id="group_product_configurator" model="res.groups">
+        <field name="name">Products</field>
+        <field name="category_id" ref="product_config_category"/>
+        <field name="implied_ids" eval="[(4, ref('product.group_product_variant'))]"/>
+        <field name="users" eval="[(4, ref('base.user_root'))]"/>
+    </record>
+
+</data>
+</openerp>

+ 30 - 0
roles/odoo/files/addons/odoo-product-configurator/product_configurator/security/ir.model.access.csv

@@ -0,0 +1,30 @@
+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_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
+product_configurator_config_domain,Config Domain,model_product_config_domain,group_product_configurator,1,1,1,1
+product_configurator_config_domain_line,Config Domain Line,model_product_config_domain_line,group_product_configurator,1,1,1,1
+product_configurator_custom_value,Custom Value,model_product_attribute_value_custom,group_product_configurator,1,1,1,1
+product_configurator_config_session,Config Session,model_product_config_session,group_product_configurator,1,1,1,1
+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_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
+user_config_domain_line,User Config Domain Line,model_product_config_domain_line,base.group_user,1,0,0,0
+user_config_domain,User Config Domain,model_product_config_domain,base.group_user,1,0,0,0
+user_custom_value,User Custom Value,model_product_attribute_value_custom,base.group_user,1,0,0,0
+user_config_session,User Config Session,model_product_config_session,base.group_user,1,0,0,0
+user_config_session_custom_value,User Config Session Custom Value,model_product_config_session_custom_value,base.group_user,1,0,0,0
+,,,,,,,
+portal_config_image,Portal Config Image,model_product_config_image,base.group_portal,1,0,0,0
+portal_config_custom_value,Portal Custom Value,model_product_attribute_value_custom,base.group_portal,1,0,0,0
+portal_config_step,Portal Config Step,model_product_config_step,base.group_portal,1,0,0,0
+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_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

BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/description/cover.png


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/description/icon.png


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-black-star-spoke-384.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-black-star-spoke-387.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-black.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-red-star-spoke-384.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-red-star-spoke-387.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-silver-star-spoke-384.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-silver-star-spoke-387.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe-silver.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/2-series-coupe.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-advantage.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-armrest.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-engine.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-luxury-line.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-m-sport.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-paint-silver.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-smoker-package.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-sport-line.jpg


BIN
roles/odoo/files/addons/odoo-product-configurator/product_configurator/static/img/product-sunroof.jpg


Some files were not shown because too many files changed in this diff