diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..62276b0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Configuration for known file extensions +[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[.eslintrc,*.{json,yml,yaml,rst,md}] +indent_size = 2 + +# Do not configure editor for libs and autogenerated content +[*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..28a0808 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,291 @@ +{ + "globals": { + "$": false, + "_": false, + "fuzzy": false, + "jQuery": false, + "moment": false, + "odoo": false, + "openerp": false, + "self": false + }, + "env": { + "browser": true + }, + "rules": { + "no-alert": "warn", + "no-array-constructor": "warn", + "no-bitwise": "off", + "no-caller": "warn", + "no-case-declarations": "warn", + "no-catch-shadow": "warn", + "no-class-assign": "warn", + "no-cond-assign": "warn", + "no-confusing-arrow": "warn", + "no-console": "off", + "no-const-assign": "warn", + "no-constant-condition": "warn", + "no-continue": "off", + "no-control-regex": "warn", + "no-debugger": "warn", + "no-delete-var": "warn", + "no-div-regex": "warn", + "no-dupe-args": "warn", + "no-dupe-class-members": "warn", + "no-dupe-keys": "warn", + "no-duplicate-case": "warn", + "no-duplicate-imports": "warn", + "no-else-return": "warn", + "no-empty": "warn", + "no-empty-character-class": "warn", + "no-empty-function": "warn", + "no-empty-pattern": "warn", + "no-eq-null": "warn", + "no-eval": "warn", + "no-ex-assign": "warn", + "no-extend-native": "warn", + "no-extra-bind": "warn", + "no-extra-boolean-cast": "warn", + "no-extra-label": "warn", + "no-extra-parens": "warn", + "no-extra-semi": "warn", + "no-fallthrough": "warn", + "no-floating-decimal": "warn", + "no-func-assign": "warn", + "no-implicit-coercion": ["warn", { + "allow": ["~"] + }], + "no-implicit-globals": "warn", + "no-implied-eval": "warn", + "no-inline-comments": "warn", + "no-inner-declarations": "warn", + "no-invalid-regexp": "warn", + "no-invalid-this": "off", + "no-irregular-whitespace": "warn", + "no-iterator": "warn", + "no-label-var": "warn", + "no-labels": "warn", + "no-lone-blocks": "warn", + "no-lonely-if": "warn", + "no-loop-func": "off", + "no-magic-numbers": "off", + "no-mixed-operators": "warn", + "no-mixed-requires": "warn", + "no-mixed-spaces-and-tabs": "warn", + "no-multi-spaces": "warn", + "no-multi-str": "warn", + "no-multiple-empty-lines": "warn", + "no-native-reassign": "warn", + "no-negated-condition": "warn", + "no-negated-in-lhs": "warn", + "no-nested-ternary": "off", + "no-new": "warn", + "no-new-func": "warn", + "no-new-object": "warn", + "no-new-require": "warn", + "no-new-symbol": "warn", + "no-new-wrappers": "warn", + "no-obj-calls": "warn", + "no-octal": "warn", + "no-octal-escape": "warn", + "no-param-reassign": "warn", + "no-path-concat": "warn", + "no-plusplus": "off", + "no-process-env": "warn", + "no-process-exit": "warn", + "no-proto": "warn", + "no-prototype-builtins": "warn", + "no-redeclare": "warn", + "no-regex-spaces": "warn", + "no-restricted-globals": "warn", + "no-restricted-imports": "warn", + "no-restricted-modules": "warn", + "no-restricted-syntax": "warn", + "no-return-assign": "warn", + "no-script-url": "warn", + "no-self-assign": "warn", + "no-self-compare": "warn", + "no-sequences": "warn", + "no-shadow": "warn", + "no-shadow-restricted-names": "warn", + "no-whitespace-before-property": "warn", + "no-spaced-func": "warn", + "no-sparse-arrays": "warn", + "no-sync": "warn", + "no-tabs": "warn", + "no-ternary": "off", + "no-trailing-spaces": "warn", + "no-this-before-super": "warn", + "no-throw-literal": "warn", + "no-undef": "warn", + "no-undef-init": "warn", + "no-undefined": "off", + "no-unexpected-multiline": "warn", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "warn", + "no-unneeded-ternary": "warn", + "no-unreachable": "warn", + "no-unsafe-finally": "warn", + "no-unused-expressions": "warn", + "no-unused-labels": "warn", + "no-unused-vars": "warn", + "no-use-before-define": "warn", + "no-useless-call": "warn", + "no-useless-computed-key": "warn", + "no-useless-concat": "warn", + "no-useless-constructor": "warn", + "no-useless-escape": "warn", + "no-useless-rename": "warn", + "no-void": "warn", + "no-var": "off", + "no-warning-comments": "off", + "no-with": "warn", + "array-bracket-spacing": "off", + "array-callback-return": "warn", + "arrow-body-style": "warn", + "arrow-parens": "warn", + "arrow-spacing": "off", + "accessor-pairs": "warn", + "block-scoped-var": "off", + "block-spacing": ["warn", "always"], + "brace-style": "warn", + "callback-return": "warn", + "camelcase": "off", + "capitalized-comments": ["warn", "always", { + "ignoreConsecutiveComments": true, + "ignoreInlineComments": true + }], + "comma-dangle": ["warn", "always-multiline"], + "comma-spacing": ["warn", { + "before": false, + "after": true + }], + "comma-style": "warn", + "complexity": [ + "warn", + 15 + ], + "computed-property-spacing": "off", + "consistent-return": "off", + "consistent-this": "off", + "constructor-super": "warn", + "curly": "warn", + "default-case": "off", + "dot-location": ["warn", "property"], + "dot-notation": "warn", + "eol-last": "warn", + "eqeqeq": "warn", + "func-names": "off", + "func-style": "off", + "generator-star-spacing": "off", + "global-require": "warn", + "guard-for-in": "off", + "handle-callback-err": "warn", + "id-blacklist": "warn", + "id-length": "off", + "id-match": "warn", + "indent": "warn", + "init-declarations": "warn", + "jsx-quotes": "warn", + "key-spacing": "off", + "keyword-spacing": "warn", + "linebreak-style": [ + "warn", + "unix" + ], + "lines-around-comment": "warn", + "max-depth": "warn", + "max-len": ["warn", { + "code": 88, + "ignorePattern": "odoo\\.define\\(", + "tabWidth": 4 + }], + "max-lines": "off", + "max-nested-callbacks": "warn", + "max-params": "off", + "max-statements": "off", + "max-statements-per-line": "warn", + "multiline-ternary": "off", + "new-cap": "off", + "new-parens": "warn", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "off", + "object-curly-newline": ["warn", { "consistent": true }], + "object-curly-spacing": ["warn", "never"], + "object-property-newline": ["warn", { + "allowAllPropertiesOnSameLine": true + }], + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": "off", + "operator-assignment": "warn", + "operator-linebreak": "warn", + "padded-blocks": "off", + "prefer-arrow-callback": "off", + "prefer-const": "warn", + "prefer-reflect": "off", + "prefer-rest-params": "off", + "prefer-spread": "off", + "prefer-template": "off", + "quote-props": "off", + "quotes": "off", + "radix": "warn", + "require-yield": "warn", + "rest-spread-spacing": "off", + "semi": [ + "warn", + "always" + ], + "semi-spacing": "warn", + "sort-imports": "warn", + "sort-vars": "off", + "space-before-blocks": "warn", + "space-before-function-paren": "warn", + "space-in-parens": "off", + "space-infix-ops": "off", + "space-unary-ops": "off", + "spaced-comment": ["warn", "always"], + "strict": ["warn", "function"], + "template-curly-spacing": "off", + "unicode-bom": "warn", + "use-isnan": "warn", + "valid-jsdoc": ["warn", { + "prefer": { + "arg": "param", + "argument": "param", + "augments": "extends", + "constructor": "class", + "exception": "throws", + "func": "function", + "method": "function", + "prop": "property", + "return": "returns", + "virtual": "abstract", + "yield": "yields" + }, + "preferType": { + "array": "Array", + "bool": "Boolean", + "boolean": "Boolean", + "number": "Number", + "object": "Object", + "str": "String", + "string": "String" + }, + "requireParamDescription": false, + "requireReturn": false, + "requireReturnDescription": false, + "requireReturnType": false + }], + "valid-typeof": "warn", + "vars-on-top": "off", + "wrap-iife": "warn", + "wrap-regex": "warn", + "yield-star-spacing": "off", + "yoda": "warn" + }, + "parserOptions": { + "ecmaVersion": 2017 + } +} diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..8eb3d2a --- /dev/null +++ b/.flake8 @@ -0,0 +1,11 @@ +[flake8] +max-line-length = 88 +max-complexity = 14 +# B = bugbear +# B9 = bugbear opinionated (incl line length) +select = B,C,E,F,W,T4,B9 +# E203: whitespace before ':' (black behaviour) +# E501: flake8 line length (covered by bugbear B950) +# W503: line break before binary operator (black behaviour) +ignore = E203,E501,W503 +per-file-ignores = __init__.py:F401 diff --git a/.github/workflows/deploy-next.yml b/.github/workflows/deploy-next.yml new file mode 100644 index 0000000..b8da43a --- /dev/null +++ b/.github/workflows/deploy-next.yml @@ -0,0 +1,48 @@ +name: Deploy (next) +on: + push: + branches: + - master + +env: + DOCKER_BUILDKIT: 1 + IMAGE_NAME: template-odoo + TAG: latest + REGISTRY: ????.dkr.ecr.us-east-1.amazonaws.com + REGION: us-east-1 + CLUSTER_NAME: EKS-Template + EKS_ROLE_ARN: arn:aws:iam::????:role/eks-deploy-role + NAMESPACE: template-odoo-next-dev + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.ACCESS_TOKEN }} + + - name: 'Build image' + run: docker build -t $IMAGE_NAME:$TAG odoo + + - name: 'Push to registry' + uses: jwalton/gh-ecr-push@v1 + with: + access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + region: ${{ env.REGION }} + image: ${{ env.IMAGE_NAME }}:${{ env.TAG }} + + - name: 'Deploy image' + uses: ianbelcher/eks-kubectl-action@master + with: + aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws_region: ${{ env.REGION }} + cluster_name: ${{ env.CLUSTER_NAME }} + eks_role_arn: ${{ env.EKS_ROLE_ARN }} + args: rollout restart deploy odoo -n ${{ env.NAMESPACE }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..a0df6b3 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,48 @@ +name: Deploy +on: + push: + branches: + - stable + +env: + DOCKER_BUILDKIT: 1 + IMAGE_NAME: template-odoo + TAG: stable + REGISTRY: ????.dkr.ecr.us-east-1.amazonaws.com + REGION: us-east-1 + CLUSTER_NAME: EKS-Template + EKS_ROLE_ARN: arn:aws:iam::????:role/eks-deploy-role + NAMESPACE: template-odoo-dev + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.ACCESS_TOKEN }} + + - name: 'Build image' + run: docker build -t ${{ env.IMAGE_NAME }}:${{ env.TAG }} odoo + + - name: 'Push to registry' + uses: jwalton/gh-ecr-push@v1 + with: + access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + region: ${{ env.REGION }} + image: ${{ env.IMAGE_NAME }}:${{ env.TAG }} + + - name: 'Deploy image' + uses: ianbelcher/eks-kubectl-action@master + with: + aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws_region: ${{ env.REGION }} + cluster_name: ${{ env.CLUSTER_NAME }} + eks_role_arn: ${{ env.EKS_ROLE_ARN }} + args: rollout restart deploy odoo -n ${{ env.NAMESPACE }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..581bb9a --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,11 @@ +name: pre-commit +on: + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..eacb92b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test +on: + pull_request: + +env: + DOCKER_BUILDKIT: 1 + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.ACCESS_TOKEN }} + + - name: 'Build' + uses: sudo-bot/action-docker-compose@latest + with: + cli-args: "build" + + - name: "Run tests" + uses: sudo-bot/action-docker-compose@latest + with: + cli-args: "run app --test-enable --workers=0 --stop-after-init -d test -i customer" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b06aac --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Created by .ignore support plugin (hsz.mobi) +.idea/ +/env/ +/*.conf +__pycache__ + +# Locust +/tests/locust/env +/tests/locust/locust.log + +# Selenium +/tests/selenium/env + +# Helm +/helm/odoo/charts +/helm/odoo/requirements.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5ca2a7e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "osi-addons"] + path = odoo/src/osi-addons + url = https://github.com/ursais/osi-addons.git + branch = 16.0 diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..d8804fd --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,12 @@ +[settings] +; see https://github.com/psf/black +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +combine_as_imports=True +use_parentheses=True +line_length=88 +known_odoo=odoo +known_odoo_addons=odoo.addons +sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER +known_third_party=anthem,helper,locust,locustodoorpc,pkg_resources,pytest,selenium,setuptools,tasks diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..01fc75c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,68 @@ +exclude: "^setup/|/static/lib/|/static/src/lib/" +default_language_version: + python: python3 +repos: +- repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: trailing-whitespace + # exclude autogenerated files + exclude: /README\.rst$|\.pot?$ + - id: end-of-file-fixer + # exclude autogenerated files + exclude: /README\.rst$|\.pot?$ + - id: debug-statements + - id: flake8 + name: flake8 except __init__.py + exclude: /__init__\.py$ + additional_dependencies: ["flake8-bugbear==19.8.0"] + - id: flake8 + name: flake8 only __init__.py + args: ["--extend-ignore=F401"] # ignore unused imports in __init__.py + files: /__init__\.py$ + additional_dependencies: ["flake8-bugbear==19.8.0"] + - id: fix-encoding-pragma + args: ["--remove"] + - id: check-case-conflict + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-symlinks + - id: check-xml + - id: mixed-line-ending + args: ["--fix=lf"] +- repo: https://github.com/pre-commit/mirrors-pylint + rev: v2.3.1 + hooks: + - id: pylint + name: pylint with optional checks + args: ["--rcfile=.pylintrc", "--exit-zero"] + verbose: true + additional_dependencies: ["pylint-odoo"] + - id: pylint + name: pylint with mandatory checks + args: ["--rcfile=.pylintrc-mandatory"] + additional_dependencies: ["pylint-odoo"] +- repo: https://github.com/asottile/pyupgrade + rev: v1.24.0 + hooks: + - id: pyupgrade +- repo: https://github.com/asottile/seed-isort-config + rev: v1.9.3 + hooks: + - id: seed-isort-config +- repo: https://github.com/pre-commit/mirrors-isort + rev: v4.3.21 + hooks: + - id: isort + name: isort except __init__.py + exclude: /__init__\.py$ +- repo: https://github.com/pre-commit/mirrors-eslint + rev: v6.5.1 + hooks: + - id: eslint + verbose: true diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..a118953 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,87 @@ +[MASTER] +load-plugins=pylint_odoo +score=n + +[ODOOLINT] +readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" +manifest_required_authors=Open Source Integrators +manifest_required_keys=license,maintainers +manifest_deprecated_keys=description,active +license_allowed=AGPL-3,LGPL-3,Other proprietary +valid_odoo_versions=15.0 + +[MESSAGES CONTROL] +disable=all + +# This .pylintrc contains optional AND mandatory checks and is meant to be +# loaded in an IDE to have it check everything, in the hope this will make +# optional checks more visible to contributors who otherwise never look at a +# green travis to see optional checks that failed. +# .pylintrc-mandatory containing only mandatory checks is used the pre-commit +# config as a blocking check. + +enable=anomalous-backslash-in-string, + api-one-deprecated, + api-one-multi-together, + assignment-from-none, + attribute-deprecated, + class-camelcase, + dangerous-default-value, + dangerous-view-replace-wo-priority, + duplicate-id-csv, + duplicate-key, + duplicate-xml-fields, + duplicate-xml-record-id, + eval-referenced, + eval-used, + incoherent-interpreter-exec-perm, + license-allowed, + manifest-author-string, + manifest-deprecated-key, + manifest-required-author, + manifest-required-key, + manifest-version-format, + method-compute, + method-inverse, + method-required-super, + method-search, + missing-import-error, + missing-manifest-dependency, + openerp-exception-warning, + pointless-statement, + pointless-string-statement, + print-used, + redundant-keyword-arg, + redundant-modulename-xml, + reimported, + relative-import, + return-in-init, + rst-syntax-error, + sql-injection, + too-few-format-args, + translation-field, + translation-required, + unreachable, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + # messages that do not cause the lint step to fail + consider-merging-classes-inherited, + create-user-wo-reset-password, + dangerous-filter-wo-user, + deprecated-module, + file-not-used, + invalid-commit, + missing-newline-extrafiles, + missing-readme, + no-utf8-coding-comment, + odoo-addons-relative-import, + old-api7-method-defined, + redefined-builtin, + too-complex, + unnecessary-utf8-coding-comment + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +reports=no diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory new file mode 100644 index 0000000..1205777 --- /dev/null +++ b/.pylintrc-mandatory @@ -0,0 +1,65 @@ +[MASTER] +load-plugins=pylint_odoo +score=n + +[ODOOLINT] +readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" +manifest_required_authors=Open Source Integrators +manifest_required_keys=license,maintainers +manifest_deprecated_keys=description,active +license_allowed=AGPL-3,LGPL-3,Other proprietary +valid_odoo_versions=15.0 + +[MESSAGES CONTROL] +disable=all + +enable=anomalous-backslash-in-string, + api-one-deprecated, + api-one-multi-together, + assignment-from-none, + attribute-deprecated, + class-camelcase, + dangerous-default-value, + dangerous-view-replace-wo-priority, + duplicate-id-csv, + duplicate-key, + duplicate-xml-fields, + duplicate-xml-record-id, + eval-referenced, + eval-used, + incoherent-interpreter-exec-perm, + license-allowed, + manifest-author-string, + manifest-deprecated-key, + manifest-required-author, + manifest-required-key, + manifest-version-format, + method-compute, + method-inverse, + method-required-super, + method-search, + missing-import-error, + missing-manifest-dependency, + openerp-exception-warning, + pointless-statement, + pointless-string-statement, + print-used, + redundant-keyword-arg, + redundant-modulename-xml, + reimported, + relative-import, + return-in-init, + rst-syntax-error, + sql-injection, + too-few-format-args, + translation-field, + translation-required, + unreachable, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +reports=no diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..73c5133 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,12 @@ + +Most of the files are + + Copyright (c) 2012-2018 Ursa Information Systems + +Many files also contain contributions from third parties. In this case the +original copyright of the contributions can be traced through the history of the +source version control system. + +When that is not the case, the files contain a prominent notice stating the +original copyright and applicable license, or come with their own dedicated +COPYRIGHT and/or LICENSE file. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..3b39f2c --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,63 @@ +# Install Odoo dependencies + +* libffi-dev +* libgeoip-dev +* libjpeg-dev +* libldap2-dev +* libsasl2-dev +* libxml2-dev +* libxslt1-dev +* nginx +* node-less +* postfix +* postgresql +* postgresql-server-dev-9.5 +* python-dev +* python-pip +* python-psycopg2 +* python-virtualenv +* zlib1g-dev + +# Webkit + +* Download Webkit from https://github.com/wkhtmltopdf/wkhtmltopdf/releases/tag/0.12.1 +* Install Webkit and create a symlink: + +`# ln -s /usr/local/bin/wkhtmltopdf /usr/bin/wkhtmltopdf` + +# PostgreSQL + +* Create a PostgreSQL user for odoo + +# Odoo + +* Create an Odoo user +* Clone the repository +* Change the ownership of the repo to odoo +* Create the environment + +`$ virtualenv env && . env/bin/activate && pip install -r requirements.txt` + +* Create /etc/odoo +* Create Odoo config file +* Create Odoo init script +* Create /var/log/odoo +* Create /var/backups/odoo +* Enable and start Odoo + +# Nginx + +* Create Nginx config file +* Create the SSL directory /etc/nginx/ssl +* Generate the SSL certificate + +``` +# openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -subj "/C=US/ST=CA/L=Los Angeles/O=IT/CN={{ fqdn }}" \ + -keyout /etc/nginx/ssl/odoo.key \ + -out /etc/nginx/ssl/odoo.crt +``` + +* Change the permission of the certificate file +* Enable new odoo virtual host +* Enable and start Nginx diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a1af68 --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +# Odoo 16.0 Template Project + +## Table of Contents +* [Prerequisites](#Prerequisites) +* [Build your environment](#Build-your-environment) + * [For private modules](#For-private-modules) + * [For existing public modules](#For-existing-public-modules) + * [For new public modules](#For-new-public-modules) +* [Deploy](#Deploy) +* [Tests](#Tests) +* [Environment Variables](#Environment-Variables) +* [Issues](#Issues) + +## Prerequisites + +Look at the [INSTALL](./INSTALL.md) file. + +## Build your environment + +Go to the root directory of the cloned Github repository and run: +```shell script +docker-compose build +``` + +To start Odoo: +```shell script +docker-compose up +``` + +### For private modules + +* Create a new branch and add your module in odoo/src/private-addons +* Add your module as a dependency of the customer module +* Commit, push your branch and create a pull request against `master` + +### For existing public modules + +Modules must be available on [Pypi](https://pypi.org), otherwise look at [the next section](#For-new-public-modules). + +* Create a new branch +* Add the module in `odoo/requirements.txt` +* Add the module as a dependency of the customer module +* Commit, push your branch and create a pull request against `master` + +### For new public modules + +* In Github, fork the repo in the `ursais` organization +* Add the repo as a submodule: +```shell +git submodule add --name repo -b 15.0 https://github.com/ursais/repo.git odoo/src/repo +``` +* Create a new branch in odoo/src/ and add your module +* Commit your changes and push your module to Github +* In Github (https://github.com/ursais), create a pull request against the corresponding OCA repository +* Add a section (1 per repo) in `repos.yml` and include your pull request +* Run Git Aggregator: +```shell +gitaggregate -c repos.yml -p -j 10 +``` +* Add your module as a dependency of the customer module +* Add your module in `odoo/Dockerfile` +* Commit, push your branch and create a pull request against `master` + +## Deploy + +Look at the [helm/README.md](./helm/README.md) file. + +## Tests + +* For functional tests using Selenium, please go to [odoo/tests/selenium](./odoo/tests/selenium). +* For performance tests using Locust, please go to [odoo/tests/locust](./odoo/tests/locust). + +## Environment Variables + +Description: Environment variables +| Name | Description | Default Value | +| ----------------------------- | -------------------------------------------------- | ------------------------------------- | +| `RUNNING_ENV` | Set to replicate what type of migration will occur options are production(create, migrate), qa(upgrade_existing,duplicate), test(upgrade_existing,duplicate), dev(drop latest, create, migrate), anything else for not triggering migration | `dev` | +| `PLATFORM` | Used to identify the cloud provider: aws, azure, do or local | `do` | +| `APP_IMAGE_VERSION` | Used to set the version of the image | `latest` | +| `DEBUG` | Display debugging information if set to 1 | | + +Description: A list of variables that have default values when not set in docker-compose.yml. +These environment variables can be altered to directly impact configurations of the build when using docker-compose up + +| Name | Description | Default Value | +| ----------------------------- | -------------------------------------------------- | ------------------------------------- | +| `ODOO_ADDONS_PATH` | Value set in odoo.conf for: addons_path | `/odoo/addons` | +| `ODOO_ADMIN_PASSWD` | Value set in odoo.conf for: admin_passwd | `admin` | +| `ODOO_CSV_INTERNAL_SEP` | Value set in odoo.conf for: csv_internal_sep | `,` | +| `ODOO_DATA_DIR` | Value set in odoo.conf for: data_dir | `/odoo/data` | +| `ODOO_DBFILTER` | Value set in odoo.conf for: dbfilter | `^[^backup\|defaultdb].*$` | +| `PGHOST` | Value set in odoo.conf for: db_host | `db` | +| `ODOO_DB_MAXCONN` | Value set in odoo.conf for: db_maxconn | `64` | +| `PGDATABASE` | Value set in odoo.conf for: db_name | `False` | +| `PGPASSWORD` | Value set in odoo.conf for: db_password | `odoo` | +| `PGPORT` | Value set in odoo.conf for: db_port | `5432` | +| `PGSSLMODE` | Value set in odoo.conf for: db_sslmode | `prefer` | +| `ODOO_DB_TEMPLATE` | Value set in odoo.conf for: db_template | `template0` | +| `PGUSER` | Value set in odoo.conf for: db_user | `odoo` | +| `ODOO_DEMO` | Value set in odoo.conf for: demo | `{}` | +| `ODOO_EMAIL_FROM` | Value set in odoo.conf for: email_from | `False` | +| `ODOO_GEOIP_DATABASE` | Value set in odoo.conf for: geoip_database | `/usr/share/GeoIP/GeoLite2-City.mmdb` | +| `ODOO_HTTP_ENABLE` | Value set in odoo.conf for: http_enable | `True` | +| `ODOO_HTTP_INTERFACE` | Value set in odoo.conf for: http_interface | | +| `ODOO_IMPORT_PARTIAL` | Value set in odoo.conf for: import_partial | | +| `ODOO_LIMIT_MEMORY_HARD` | Value set in odoo.conf for: limit_memory_hard | `4294967296` | +| `ODOO_LIMIT_MEMORY_SOFT` | Value set in odoo.conf for: limit_memory_soft | `2147483648` | +| `ODOO_LIMIT_REQUEST` | Value set in odoo.conf for: limit_request | `8192` | +| `ODOO_LIMIT_TIME_CPU` | Value set in odoo.conf for: limit_time_cpu | `1800` | +| `ODOO_LIMIT_TIME_REAL_CRON` | Value set in odoo.conf for: limit_time_real_cron | `120` | +| `ODOO_LIMIT_TIME_REAL` | Value set in odoo.conf for: limit_time_real | `1800` | +| `ODOO_LIST_DB` | Value set in odoo.conf for: list_db | `False` | +| `ODOO_LOG_DB` | Value set in odoo.conf for: log_db | `False` | +| `ODOO_LOG_DB_LEVEL` | Value set in odoo.conf for: log_db_level | `warning` | +| `ODOO_LOGFILE` | Value set in odoo.conf for: logfile | `None` | +| `ODOO_LOG_HANDLER` | Value set in odoo.conf for: log_handler | `:INFO` | +| `ODOO_LOG_LEVEL` | Value set in odoo.conf for: log_level | `info` | +| `ODOO_LOGROTATE` | Value set in odoo.conf for: logrotate | `False` | +| `ODOO_MAX_CRON_THREADS` | Value set in odoo.conf for: max_cron_threads | `1` | +| `ODOO_OSV_MEMORY_COUNT_LIMIT` | Value set in odoo.conf for: osv_memory_count_limit | `False` | +| `ODOO_PG_PATH` | Value set in odoo.conf for: pg_path | | +| `ODOO_PIDFILE` | Value set in odoo.conf for: pidfile | | +| `ODOO_PROXY_MODE` | Value set in odoo.conf for: proxy_mode | `True` | +| `ODOO_REPORTGZ` | Value set in odoo.conf for: reportgz | `False` | +| `ODOO_SCREENCASTS` | Value set in odoo.conf for: screencasts | `False` | +| `ODOO_SCREENSHOTS` | Value set in odoo.conf for: screenshots | `/tmp/odoo_tests` | +| `ODOO_SERVER_WIDE_MODULES` | Value set in odoo.conf for: server_wide_modules | `web,monitoring_status` | +| `ODOO_SMTP_PASSWORD` | Value set in odoo.conf for: smtp_password | `False` | +| `ODOO_SMTP_PORT` | Value set in odoo.conf for: smtp_port | `25` | +| `ODOO_SMTP_SERVER` | Value set in odoo.conf for: smtp_server | `localhost` | +| `ODOO_SMTP_SSL` | Value set in odoo.conf for: smtp_ssl | `False` | +| `ODOO_SMTP_USER` | Value set in odoo.conf for: smtp_user | `False` | +| `ODOO_SYSLOG` | Value set in odoo.conf for: syslog | `False` | +| `ODOO_TEST_ENABLE` | Value set in odoo.conf for: test_enable | `False` | +| `ODOO_TEST_FILE` | Value set in odoo.conf for: test_file | | +| `ODOO_TEST_TAGS` | Value set in odoo.conf for: test_tags | `None` | +| `ODOO_TRANSLATE_MODULES` | Value set in odoo.conf for: translate_modules | `['all']` | +| `ODOO_TRANSIENT_AGE_LIMIT` | Value set in odoo.conf for: transient_age_limit | `1.0` | +| `ODOO_UNACCENT` | Value set in odoo.conf for: unaccent | `False` | +| `ODOO_UPGRADE_PATH` | Value set in odoo.conf for: upgrade_path | | +| `ODOO_WITHOUT_DEMO` | Value set in odoo.conf for: without_demo | `all` | +| `ODOO_WORKERS` | Value set in odoo.conf for: workers | `3` | +| `ODOO_XMLRPC_INTERFACE` | Value set in odoo.conf for: xmlrpc_interface | | + +Description: Environment variables related to the Odoo filestore and Rclone +| Name | Description | Default Value | +| ----------------------------- | -------------------------------------------------- | ------------------------------------- | +| `PLATFORM` | Used to identify the cloud provider: aws, azure, do or local | `do`| +| `AWS_HOST` | Value for Aws host URL | `false` | +| `AWS_REGION` | Set value if using AWS platform for cloud filestore | | +| `AWS_ACCESS_KEY_ID` | Access key set for connection to AWS cloud filestore bucket | | +| `AWS_SECRET_ACCESS_KEY` | Secret key set for connection to AWS cloud filtestore bucket | | +| `AZURE_STORAGE_CONNECTION_STRING` | Value for Azure connection string | `false` | +| `AZURE_STORAGE_ACCOUNT_URL` | Set value if using azure platform for cloud filestore | | +| `AZURE_STORAGE_ACCOUNT_KEY` | Value for Azure storage account key | `false` | +| `AWS_DUPLICATE` | Set value to true to duplicate filestore with database | | +| `AWS_EMPTY_ON_DBDROP` | Set value to true to remove contents from filestore bucket when dropping db | | + +Description: Environment variables related to PostgreSQL client +| Name | Description | Default Value | +| ----------------------------- | -------------------------------------------------- | ------------------------------------- | +| `PGHOST` | Value set in odoo.conf for: db_host | `db` | +| `PGPORT` | Value set in odoo.conf for: db_port | `5432` | +| `PGUSER` | Value set in odoo.conf for: db_user | `odoo` | +| `PGPASSWORD` | Value set in odoo.conf for: db_password | `odoo` | +| `PGDATABASE` | Value set in odoo.conf for: db_name | `False` | +| `PGSSLMODE` | Value set in odoo.conf for: db_sslmode | `prefer` | + +Description: Environment variables related to Marabunta (migration.yml) +| Name | Description | Default Value | +| ----------------------------- | -------------------------------------------------- | ------------------------------------- | +| `RUNNING_ENV` | Set to replicate what type of migration will occur options are production(create, migrate), qa(upgrade_existing,duplicate), test(upgrade_existing,duplicate), dev(drop latest, create, migrate), anything else for not triggering migration | `dev` | +| `APP_IMAGE_VERSION` | Used to set a custom database name on migration | `latest` | +| `MARABUNTA_MODE` | The mode controls what operations should occur based off its value of external(serverside) or base(General) | `base` | +| `MARABUNTA_ALLOW_SERIE` | Allows multiple versions to upgrade | `false` | +| `MARABUNTA_FORCE_VERSION` | Force a specific version to be re-ran | | + +Description: Environment variables related to Anthem (songs) +| Name | Description | Default Value | +| ----------------------------- | -------------------------------------------------- | ------------------------------------- | +| `ODOO_DATA_PATH` | Set the path to the csv files | `/odoo/songs/data` | + +## Issues + +Report any issue to this +[Github project](https://github.com/ursais/odoo-template/issues). diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..552d4aa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,57 @@ +version: '2' +services: + db: + image: ursa/postgresql:latest + restart: always + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=odoo + - POSTGRES_PASSWORD=odoo + - PGDATA=/var/lib/postgresql/data/pgdata + volumes: + - 'postgres-data:/var/lib/postgresql/data' + + odoo: + build: + context: odoo + ports: + - '8069:8069' + - '8072:8072' + volumes: + - 'odoo-data:/odoo/data' + depends_on: + - db + environment: + - PGHOST=db + - PGUSER=odoo + - PGPASSWORD=odoo + - MIGRATE=False + - MARABUNTA_MODE=base + - MARABUNTA_ALLOW_SERIE=True + #- MARABUNTA_FORCE_VERSION=setup + - PLATFORM=local + - RUNNING_ENV=dev + - ODOO_ADMIN_USER_TIMEZONE=America/Phoenix + - ODOO_ADMIN_USER_PASSWORD=admin + - ODOO_DBFILTER=False + - ODOO_LIST_DB=True + stdin_open: true + tty: true +# cap_add: +# - sys_ptrace + + mail: + image: mailhog/mailhog + ports: + - 8025:8025 + +# spy: +# image: ursa/pyspy:latest +# pid: "host" +# privileged: "true" +# volumes: +# - .:/profiles + +volumes: + postgres-data: + odoo-data: diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 0000000..a9bd6e5 --- /dev/null +++ b/helm/README.md @@ -0,0 +1,133 @@ +# Odoo Deployment + +## Table of Contents +* [Development environment](#Development-environment) +* [Test environment](#Test-environment) +* [QA environment](#QA-environment) +* [Production environment](#Production-environment) +* [Secrets](#Secrets) +* [Release](#Release) +* [Known issues](#Known-issues) + +## Development environment + +Create the project: +```shell script +kubectl create namespace odoo-dev +``` +Edit the variables in helm/odoo/values.yaml + +Run +```shell script +helm dependency update helm/odoo +helm upgrade --install odoo helm/odoo +``` + +## Test environment + +Create the project: +```shell script +kubectl create namespace odoo-test +``` +Edit the variables in helm/odoo/values.test.yaml + +Run +```shell script +helm upgrade --install odoo -f helm/odoo/values.test.yaml helm/odoo +``` + +## QA environment + +Create the project: +```shell script +kubectl create namespace odoo-qa +``` +Edit the variables in helm/odoo/values.qa.yaml + +Run +```shell script +helm upgrade --install odoo -f helm/odoo/values.qa.yaml helm/odoo +``` + +## Production environment + +Create the project: +```shell script +kubectl create namespace odoo +``` +Edit the variables in helm/odoo/values.production.yaml + +Run +```shell script +helm upgrade --install odoo -f helm/odoo/values.production.yaml helm/odoo +``` + +## Secrets + +The installation generates random values for confidential information like passwords, +tokens, keys, etc. You need to replace them with the value from the provider. + +### External database + +```shell +SECRET=`echo -n "PASSWORD" | base64` +kubectl edit secret odoo-externaldb + data: + db-password: $SECRET +``` + +### Storage + +```shell +SECRET1=`echo -n "Account URL" | base64` +SECRET2=`echo -n "Connection string" | base64` +kubectl edit secret odoo + data: + azure-storage-account-url: $SECRET1 + azure-storage-connection-string: $SECRET2 +``` + +## Release + +Create a new release in the repository: +https://github.com/ursais/odoo-template/releases + +Update the deployment: +```shell script +TAG=20210527 +NAMESPACE=odoo-test +kubectl set image deploy odoo \ + odoo=docker.pkg.github.com/ursais/odoo-template/odoo-template:$TAG \ + --namespace $NAMESPACE +``` + +## Backup + +Install Velero: +```shell +export BUCKET=velerobackup- +export REGION=us-east-2 +velero install \ + --provider aws \ + --use-restic \ + --plugins velero/velero-plugin-for-aws:v1.2.0 \ + --bucket $BUCKET \ + --backup-location-config region=$REGION \ + --snapshot-location-config region=$REGION \ + --secret-file ./helm/credentials-velero +``` + +Create schedules: +```shell +velero create schedule full-daily --schedule="@every 24h" +velero create schedule odoo-daily --schedule="@every 24h" --include-namespaces -odoo +velero create schedule odoo-qa-daily --schedule="@every 24h" --include-namespaces -odoo-qa +velero create schedule odoo-test-daily --schedule="@every 24h" --include-namespaces -odoo-test +velero create schedule odoo-dev-daily --schedule="@every 24h" --include-namespaces -odoo-dev +``` + +## Known issues + +* https://github.com/bitnami/charts/issues/6121 + Upgrading the PostgreSQL helm chart regenerates the password in the secret and causes + Odoo's connections to fail. diff --git a/helm/credentials-velero b/helm/credentials-velero new file mode 100644 index 0000000..ba55ee0 --- /dev/null +++ b/helm/credentials-velero @@ -0,0 +1,3 @@ +[default] +aws_access_key_id= +aws_secret_access_key= diff --git a/helm/odoo/.helmignore b/helm/odoo/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/helm/odoo/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/helm/odoo/Chart.yaml b/helm/odoo/Chart.yaml new file mode 100644 index 0000000..eff7d74 --- /dev/null +++ b/helm/odoo/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +name: odoo +version: 15.0 +appVersion: 15.0 +description: Odoo instance +home: https://www.opensourceintegrators.com +icon: https://github.com/ursais.png +keywords: +- odoo +- crm +- www +- http +- web +sources: +- https://github.com/ursais/odoo-template +maintainers: +- name: Open Source Integrators + email: support@opensourceintegrators.com +engine: gotpl diff --git a/helm/odoo/requirements.yaml b/helm/odoo/requirements.yaml new file mode 100644 index 0000000..2e46068 --- /dev/null +++ b/helm/odoo/requirements.yaml @@ -0,0 +1,17 @@ +dependencies: +- name: mailhog + condition: mailhog.enabled + version: 3.x.x + repository: https://codecentric.github.io/helm-charts +- name: postfix + condition: postfix.enabled + version: 0.x.x + repository: https://rasooll.github.io/helm-charts +- name: postgresql + condition: postgresql.enabled + version: 8.x.x + repository: https://charts.bitnami.com/bitnami +- name: redis + condition: redis.enabled + version: 12.x.x + repository: https://charts.bitnami.com/bitnami diff --git a/helm/odoo/templates/NOTES.txt b/helm/odoo/templates/NOTES.txt new file mode 100644 index 0000000..94e86e2 --- /dev/null +++ b/helm/odoo/templates/NOTES.txt @@ -0,0 +1,11 @@ + +** Please be patient while the chart is being deployed ** + +1. Go to the Odoo URL: + + https://{{ .Values.application_domain }}/ + +2. Login with the following credentials: + + Email: {{ .Values.odooUsername }} + echo Password: $(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "odoo.fullname" . }} -o jsonpath="{.data.odoo-admin-user-password}" | base64 --decode) diff --git a/helm/odoo/templates/_helpers.tpl b/helm/odoo/templates/_helpers.tpl new file mode 100644 index 0000000..bd5ac35 --- /dev/null +++ b/helm/odoo/templates/_helpers.tpl @@ -0,0 +1,156 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "odoo.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "odoo.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Define the namespace. +*/}} +{{- define ".Values.namespace" -}} +{{- if .Values.environment eq "production" }} + {{- printf "%s" .Values.project -}} +{{- else -}} + {{- printf "%s-%s" .Values.project .Values.environment -}} +{{- end -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "odoo.postgresql.fullname" -}} +{{- printf "%s-%s" .Release.Name "postgresql" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "odoo.redis.fullname" -}} +{{- printf "%s-%s" .Release.Name "redis" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "odoo.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return the proper Odoo image name +*/}} +{{- define "odoo.image" -}} +{{- $registryName := .Values.image.registry -}} +{{- $repositoryName := .Values.image.repository -}} +{{- $tag := .Values.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "odoo.imagePullSecrets" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +Also, we can not use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- else if .Values.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- else if .Values.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Storage Class +*/}} +{{- define "odoo.storageClass" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +*/}} +{{- if .Values.global -}} + {{- if .Values.global.storageClass -}} + {{- if (eq "-" .Values.global.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.global.storageClass -}} + {{- end -}} + {{- else -}} + {{- if .Values.persistence.storageClass -}} + {{- if (eq "-" .Values.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.persistence.storageClass -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- else -}} + {{- if .Values.persistence.storageClass -}} + {{- if (eq "-" .Values.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.persistence.storageClass -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "odoo.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} diff --git a/helm/odoo/templates/buildconfig.yaml b/helm/odoo/templates/buildconfig.yaml new file mode 100644 index 0000000..dfeb02d --- /dev/null +++ b/helm/odoo/templates/buildconfig.yaml @@ -0,0 +1,30 @@ +{{ if eq .Values.distribution "openshift" }} +{{- if .Values.build.enabled }} +kind: BuildConfig +apiVersion: build.openshift.io/v1 +metadata: + name: {{ .Values.name | quote }} + annotations: + description: Defines how to build the application +spec: + source: + type: Git + git: + uri: {{ .Values.build.source_repository_url | quote }} + ref: {{ .Values.build.source_repository_ref | quote }} + contextDir: {{ .Values.build.context_dir | quote }} + strategy: + type: Docker + dockerStrategy: {} + output: + to: + kind: ImageStreamTag + name: {{ print .Values.name ":latest" | quote }} + triggers: + - type: GitHub + github: + secretReference: + name: {{ print .Values.name "-github-webhook" | quote }} + - type: ConfigChange +{{- end }} +{{- end }} diff --git a/helm/odoo/templates/cronjob.yaml b/helm/odoo/templates/cronjob.yaml new file mode 100644 index 0000000..7a4b49d --- /dev/null +++ b/helm/odoo/templates/cronjob.yaml @@ -0,0 +1,259 @@ +{{- if eq .Values.environment "production" }} +kind: CronJob +apiVersion: batch/v1beta1 +metadata: + name: {{ print .Values.name "-backup" | quote }} + annotations: + description: Backup the production database and filestore everyday +spec: + schedule: '50 6 * * *' + concurrencyPolicy: Forbid + jobTemplate: + spec: + parallelism: 1 + template: + metadata: + labels: + parent: {{ print .Values.name "-backup" | quote }} + spec: + restartPolicy: Never + containers: + - name: odoo-backup + image: {{ .Values.backup.image }} + args: + - backup + - odoo + env: + - name: RUNNING_ENV + value: {{ .Values.environment }} + - name: PGHOST + value: {{ ternary "odoo-postgresql" .Values.externalDatabase.host .Values.postgresql.enabled | quote }} + - name: PGPORT + value: {{ ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled | quote }} + - name: PGDATABASE + value: master + - name: PGUSER + value: {{ ternary .Values.postgresql.postgresqlUsername .Values.externalDatabase.user .Values.postgresql.enabled | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ ternary (include "odoo.postgresql.fullname" .) (printf "%s-%s" .Release.Name "externaldb") .Values.postgresql.enabled | quote }} + key: {{ ternary "postgresql-password" "db-password" .Values.postgresql.enabled | quote }} + - name: PGDEFAULTDB + value: {{ ternary .Values.postgresql.defaultdb .Values.externalDatabase.defaultdb .Values.postgresql.enabled | quote }} + - name: PGSSLMODE + value: {{ ternary .Values.postgresql.sslmode .Values.externalDatabase.sslmode .Values.postgresql.enabled | quote }} + - name: FILESTORE_PLATFORM + value: {{ .Values.platform }} + {{- if .Values.s3.enabled }} + - name: FILESTORE_AWS_HOST + value: {{ .Values.s3.aws_host }} + - name: FILESTORE_AWS_REGION + value: {{ .Values.s3.aws_region }} + - name: FILESTORE_AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-access-key-id" "" .Values.s3.enabled | quote }} + - name: FILESTORE_AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-secret-access-key" "" .Values.s3.enabled | quote }} + - name: FILESTORE_AWS_BUCKETNAME + value: {{ .Values.environment }} + {{- end }} + {{- if .Values.azure.enabled }} + - name: FILESTORE_AZURE_STORAGE_ACCOUNT_URL + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.azure.enabled | quote }} + key: {{ ternary "azure-storage-account-url" "" .Values.azure.enabled | quote }} + {{- end }} + - name: BACKUP_PLATFORM + value: {{ .Values.backup.platform }} + {{- if or (eq .Values.backup.platform "aws") (eq .Values.backup.platform "do") }} + - name: BACKUP_AWS_HOST + value: {{ .Values.backup.aws_host }} + - name: BACKUP_AWS_REGION + value: {{ .Values.backup.aws_region }} + - name: BACKUP_AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: "backup-credentials" + key: "aws-access-key-id" + - name: BACKUP_AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: "backup-credentials" + key: "aws-secret-access-key" + - name: BACKUP_AWS_BUCKETNAME + value: {{ .Values.backup.aws_bucketname }} + {{- end }} + {{- if eq .Values.backup.platform "azure" }} + - name: BACKUP_AZURE_STORAGE_ACCOUNT_URL + valueFrom: + secretKeyRef: + name: "backup-credentials" + key: "azure-storage-account-url" + {{- end }} + - name: REMOTE_PLATFORM + value: {{ .Values.remote.platform }} + {{- if .Values.remote.enabled }} + {{- if or (eq .Values.remote.platform "aws") (eq .Values.remote.platform "do") }} + - name: REMOTE_AWS_HOST + value: {{ .Values.remote.aws_host }} + - name: REMOTE_AWS_REGION + value: {{ .Values.remote.aws_region }} + - name: REMOTE_AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: "remote-credentials" + key: "aws-access-key-id" + - name: REMOTE_AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: "remote-credentials" + key: "aws-secret-access-key" + - name: REMOTE_AWS_BUCKETNAME + value: {{ .Values.remote.aws_bucketname }} + {{- end }} + {{- if eq .Values.remote.platform "azure" }} + - name: REMOTE_AZURE_STORAGE_ACCOUNT_URL + valueFrom: + secretKeyRef: + name: "remote-credentials" + key: "azure-storage-account-url" + {{- end }} + {{- end }} +{{- end }} +--- +{{- if or (eq .Values.environment "qa") (eq .Values.environment "test") }} +kind: CronJob +apiVersion: batch/v1beta1 +metadata: + name: {{ print .Values.name "-restore" | quote }} + annotations: + description: Restore the production database and filestore every week in BACKUP +spec: + schedule: '0 12 * * *' + concurrencyPolicy: Forbid + jobTemplate: + spec: + parallelism: 1 + template: + metadata: + labels: + parent: {{ print .Values.name "-restore" | quote }} + spec: + restartPolicy: Never + containers: + - name: odoo-restore + image: {{ .Values.backup.image }} + args: + - restore + - odoo + env: + - name: RUNNING_ENV + value: {{ .Values.environment }} + - name: PGHOST + value: {{ ternary "odoo-postgresql" .Values.externalDatabase.host .Values.postgresql.enabled | quote }} + - name: PGPORT + value: {{ ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled | quote }} + - name: PGDATABASE + value: backup + - name: PGUSER + value: {{ ternary .Values.postgresql.postgresqlUsername .Values.externalDatabase.user .Values.postgresql.enabled | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ ternary (include "odoo.postgresql.fullname" .) (printf "%s-%s" .Release.Name "externaldb") .Values.postgresql.enabled | quote }} + key: {{ ternary "postgresql-password" "db-password" .Values.postgresql.enabled | quote }} + - name: PGDEFAULTDB + value: {{ ternary .Values.postgresql.defaultdb .Values.externalDatabase.defaultdb .Values.postgresql.enabled | quote }} + - name: PGSSLMODE + value: {{ ternary .Values.postgresql.sslmode .Values.externalDatabase.sslmode .Values.postgresql.enabled | quote }} + - name: FILESTORE_PLATFORM + value: {{ .Values.platform }} + {{- if .Values.s3.enabled }} + - name: FILESTORE_AWS_HOST + value: {{ .Values.s3.aws_host }} + - name: FILESTORE_AWS_REGION + value: {{ .Values.s3.aws_region }} + - name: FILESTORE_AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ ternary (.Values.name) "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-access-key-id" "" .Values.s3.enabled | quote }} + - name: FILESTORE_AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ ternary (.Values.name) "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-secret-access-key" "" .Values.s3.enabled | quote }} + - name: FILESTORE_AWS_BUCKETNAME + value: {{ .Values.environment }} + {{- end }} + {{- if .Values.azure.enabled }} + - name: FILESTORE_AZURE_STORAGE_ACCOUNT_URL + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.azure.enabled | quote }} + key: {{ ternary "azure-storage-account-url" "" .Values.azure.enabled | quote }} + {{- end }} + - name: BACKUP_PLATFORM + value: {{ .Values.backup.platform }} + {{- if or (eq .Values.backup.platform "aws") (eq .Values.backup.platform "do") }} + - name: BACKUP_AWS_HOST + value: {{ .Values.backup.aws_host }} + - name: BACKUP_AWS_REGION + value: {{ .Values.backup.aws_region }} + - name: BACKUP_AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: "backup-credentials" + key: "aws-access-key-id" + - name: BACKUP_AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: "backup-credentials" + key: "aws-secret-access-key" + - name: BACKUP_AWS_BUCKETNAME + value: {{ .Values.backup.aws_bucketname }} + {{- end }} + {{- if eq .Values.backup.platform "azure" }} + - name: BACKUP_AZURE_STORAGE_ACCOUNT_URL + valueFrom: + secretKeyRef: + name: "backup-credentials" + key: "azure-storage-account-url" + {{- end }} + - name: REMOTE_PLATFORM + value: {{ .Values.remote.platform }} + {{- if .Values.remote.enabled }} + {{- if or (eq .Values.remote.platform "aws") (eq .Values.remote.platform "do") }} + - name: REMOTE_AWS_HOST + value: {{ .Values.remote.aws_host }} + - name: REMOTE_AWS_REGION + value: {{ .Values.remote.aws_region }} + - name: REMOTE_AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: "remote-credentials" + key: "aws-access-key-id" + - name: REMOTE_AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: "remote-credentials" + key: "aws-secret-access-key" + - name: REMOTE_AWS_BUCKETNAME + value: {{ .Values.remote.aws_bucketname }} + {{- end }} + {{- if eq .Values.remote.platform "azure" }} + - name: REMOTE_AZURE_STORAGE_ACCOUNT_URL + valueFrom: + secretKeyRef: + name: "remote-credentials" + key: "azure-storage-account-url" + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/odoo/templates/deployment.yaml b/helm/odoo/templates/deployment.yaml new file mode 100644 index 0000000..cd05334 --- /dev/null +++ b/helm/odoo/templates/deployment.yaml @@ -0,0 +1,205 @@ +{{ if eq .Values.distribution "kubernetes" }} +kind: Deployment +apiVersion: apps/v1 +metadata: + name: {{ .Values.name | quote }} + annotations: + description: Defines how to deploy the application server +spec: + strategy: + type: Recreate + replicas: 1 + minReadySeconds: 120 + selector: + matchLabels: + name: {{ .Values.name | quote }} + app: {{ .Values.name | quote }} + release: {{ .Values.name | quote }} + template: + metadata: + name: {{ .Values.name | quote }} + labels: + name: {{ .Values.name | quote }} + app: {{ .Values.name | quote }} + release: {{ .Values.name | quote }} + spec: + imagePullSecrets: + - name: github-packages + volumes: + - name: odoo-data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "odoo.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} + containers: + - name: {{ .Values.name | quote }} + image: {{ .Values.image | quote }} + env: + - name: PGHOST + value: {{ ternary "odoo-postgresql" .Values.externalDatabase.host .Values.postgresql.enabled | quote }} + - name: PGPORT + value: {{ ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled | quote }} + - name: PGUSER + value: {{ ternary .Values.postgresql.postgresqlUsername .Values.externalDatabase.user .Values.postgresql.enabled | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ ternary (include "odoo.postgresql.fullname" .) (printf "%s-%s" .Release.Name "externaldb") .Values.postgresql.enabled | quote }} + key: {{ ternary "postgresql-password" "db-password" .Values.postgresql.enabled | quote }} + - name: PGSSLMODE + value: {{ ternary .Values.postgresql.sslmode .Values.externalDatabase.sslmode .Values.postgresql.enabled | quote }} + - name: DEFAULTDB + value: {{ ternary .Values.postgresql.defaultdb .Values.externalDatabase.defaultdb .Values.postgresql.enabled | quote }} + - name: MARABUNTA_MODE + value: {{ .Values.marabunta.mode | quote }} + - name: MARABUNTA_ALLOW_SERIE + value: {{ .Values.marabunta.allow_serie | quote }} + - name: ODOO_ADMIN_PASSWD + valueFrom: + secretKeyRef: + name: {{ .Values.name | quote }} + key: "odoo-admin-passwd" + - name: ODOO_ADMIN_USER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.name | quote }} + key: "odoo-admin-user-password" + - name: ODOO_ADMIN_USER_TIMEZONE + value: {{ .Values.timezone | quote }} + - name: RUNNING_ENV + value: {{ .Values.environment }} + - name: PLATFORM + value: {{ .Values.platform }} + - name: ODOO_SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.name | quote }} + key: "odoo-smtp-password" + {{- if .Values.postfix.enabled }} + - name: ODOO_SMTP_PORT + value: "25" + - name: ODOO_SMTP_SERVER + value: {{ printf "%s-%s" .Values.name "postfix" | quote }} + {{- else if .Values.mailhog.enabled }} + - name: ODOO_SMTP_PORT + value: "1025" + - name: ODOO_SMTP_SERVER + value: {{ printf "%s-%s" .Values.name "mailhog" | quote }} + {{- else }} + - name: ODOO_SMTP_PORT + value: {{ .Values.smtp.port | quote }} + - name: ODOO_SMTP_SERVER + value: {{ .Values.smtp.server | quote }} + {{- end }} + - name: ODOO_SMTP_SSL + value: {{ .Values.smtp.ssl | quote }} + - name: ODOO_SMTP_USER + value: {{ .Values.smtp.user | quote }} + {{- if .Values.s3.enabled }} + - name: AWS_HOST + value: {{ .Values.s3.aws_host }} + - name: AWS_REGION + value: {{ .Values.s3.aws_region }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-access-key-id" "" .Values.s3.enabled | quote }} + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-secret-access-key" "" .Values.s3.enabled | quote }} + - name: AWS_BUCKETNAME + value: {{ .Values.s3.aws_bucketname | quote }} + - name: AWS_DUPLICATE + value: "true" + - name: AWS_EMPTY_ON_DBDROP + value: "true" + {{- end }} + {{- if .Values.azure.enabled }} + - name: AZURE_STORAGE_ACCOUNT_URL + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.azure.enabled | quote }} + key: {{ ternary "azure-storage-account-url" "" .Values.azure.enabled | quote }} + - name: AZURE_STORAGE_CONNECTION_STRING + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.azure.enabled | quote }} + key: {{ ternary "azure-storage-connection-string" "" .Values.azure.enabled | quote }} + {{- end }} + {{- if .Values.redis.enabled }} + - name: ODOO_SESSION_REDIS + value: {{ .Values.redis.enabled | quote }} + - name: ODOO_SESSION_REDIS_HOST + value: {{ .Values.redis.host }} + - name: ODOO_SESSION_REDIS_PORT + value: {{ .Values.redis.port | quote }} + - name: ODOO_SESSION_REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ ternary (include "odoo.redis.fullname" .) (printf "%s-%s" .Release.Name "password") .Values.redis.enabled | quote }} + key: {{ ternary "redis-password" "redis-password" .Values.redis.enabled | quote }} + - name: ODOO_SESSION_REDIS_PREFIX + value: {{ .Values.redis.prefix }} + - name: ODOO_SESSION_REDIS_EXPIRATION + value: {{ .Values.redis.expiration | quote }} + - name: ODOO_SESSION_REDIS_EXPIRATION_ANONYMOUS + value: {{ .Values.redis.expiration_anonymous | quote }} + - name: ODOO_SESSION_REDIS_COPY_EXISTING_FS_SESSIONS + value: {{ .Values.redis.copy_existing_fs_sessions | quote }} + - name: ODOO_SESSION_REDIS_PURGE_EXISTING_FS_SESSIONS + value: {{ .Values.redis.purge_existing_fs_sessions | quote }} + {{- end }} + - name: ODOO_LIST_DB + value: {{ .Values.odooListDB | quote }} + - name: ODOO_DBFILTER + value: {{ .Values.odooDBFilter | quote }} + - name: ODOO_SERVER_WIDE_MODULES + value: {{ .Values.odooServerWideModules | quote }} + ports: + - name: tcp-odoo-http + containerPort: 8069 + - name: tcp-odoo-im + containerPort: 8072 + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /monitoring/status + port: tcp-odoo-http + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /monitoring/status + port: tcp-odoo-http + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + resources: +{{ toYaml .Values.resources | indent 10 }} + {{- if .Values.persistence.enabled }} + volumeMounts: + - name: odoo-data + mountPath: /odoo/data + volumes: + - name: odoo-data + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "odoo.fullname" .) }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} +{{- end }} diff --git a/helm/odoo/templates/deploymentconfig.yaml b/helm/odoo/templates/deploymentconfig.yaml new file mode 100644 index 0000000..6aa60ca --- /dev/null +++ b/helm/odoo/templates/deploymentconfig.yaml @@ -0,0 +1,192 @@ +{{- if eq .Values.distribution "openshift" }} +kind: DeploymentConfig +apiVersion: apps.openshift.io/v1 +metadata: + name: {{ .Values.name | quote }} + annotations: + description: Defines how to deploy the application server + template.alpha.openshift.io/wait-for-ready: 'true' +spec: + strategy: + type: Recreate + triggers: + - type: ImageChange + imageChangeParams: + automatic: true + containerNames: + - {{ .Values.name | quote }} + from: + kind: ImageStreamTag + namespace: {{ .Values.namespace | quote }} + name: {{ print .Values.name ":latest" | quote }} + - type: ConfigChange + replicas: 1 + minReadySeconds: 120 + selector: + name: {{ .Values.name | quote }} + app: {{ .Values.name | quote }} + release: {{ .Values.name | quote }} + template: + metadata: + name: {{ .Values.name | quote }} + labels: + name: {{ .Values.name | quote }} + app: {{ .Values.name | quote }} + release: {{ .Values.name | quote }} + spec: + volumes: + - name: odoo-data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "odoo.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} + containers: + - name: {{ .Values.name | quote }} + image: " " + env: + - name: PGHOST + value: {{ ternary "odoo-postgresql" .Values.externalDatabase.host .Values.postgresql.enabled | quote }} + - name: PGPORT + value: {{ ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled | quote }} + - name: PGUSER + value: {{ ternary .Values.postgresql.postgresqlUsername .Values.externalDatabase.user .Values.postgresql.enabled | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ ternary (include "odoo.postgresql.fullname" .) (printf "%s-%s" .Release.Name "externaldb") .Values.postgresql.enabled | quote }} + key: {{ ternary "postgresql-password" "db-password" .Values.postgresql.enabled | quote }} + - name: DEFAULTDB + value: {{ ternary .Values.postgresql.defaultdb .Values.externalDatabase.defaultdb .Values.postgresql.enabled | quote }} + - name: PGSSLMODE + value: {{ ternary .Values.postgresql.sslmode .Values.externalDatabase.sslmode .Values.postgresql.enabled | quote }} + - name: MARABUNTA_MODE + value: {{ .Values.marabunta.mode | quote }} + - name: MARABUNTA_ALLOW_SERIE + value: {{ .Values.marabunta.allow_serie | quote }} + - name: ODOO_ADMIN_PASSWD + valueFrom: + secretKeyRef: + name: {{ .Values.name | quote }} + key: "odoo-admin-passwd" + - name: ODOO_ADMIN_USER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.name | quote }} + key: "odoo-admin-user-password" + - name: ODOO_ADMIN_USER_TIMEZONE + value: {{ .Values.timezone | quote }} + - name: RUNNING_ENV + value: {{ .Values.environment }} + - name: PLATFORM + value: {{ .Values.platform }} + - name: ODOO_SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.name | quote }} + key: "odoo-smtp-password" + {{- if .Values.postfix.enabled }} + - name: ODOO_SMTP_PORT + value: "25" + - name: ODOO_SMTP_SERVER + value: {{ printf "%s-%s" .Values.name "postfix" | quote }} + {{- else if .Values.mailhog.enabled }} + - name: ODOO_SMTP_PORT + value: "1025" + - name: ODOO_SMTP_SERVER + value: {{ printf "%s-%s" .Values.name "mailhog" | quote }} + {{- else }} + - name: ODOO_SMTP_PORT + value: {{ .Values.smtp.port | quote }} + - name: ODOO_SMTP_SERVER + value: {{ .Values.smtp.server | quote }} + {{- end }} + - name: ODOO_SMTP_SSL + value: {{ .Values.smtp.ssl | quote }} + - name: ODOO_SMTP_USER + value: {{ .Values.smtp.user | quote }} + {{- if .Values.s3.enabled }} + - name: AWS_HOST + value: {{ .Values.s3.aws_host }} + - name: AWS_REGION + value: {{ .Values.s3.aws_region }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-access-key-id" "" .Values.s3.enabled | quote }} + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ ternary .Values.name "" .Values.s3.enabled | quote }} + key: {{ ternary "aws-secret-access-key" "" .Values.s3.enabled | quote }} + - name: AWS_BUCKETNAME + value: "{{ .Values.environment }}-{db}" + {{- end }} + {{- if .Values.redis.enabled }} + - name: ODOO_SESSION_REDIS + value: {{ .Values.redis.enabled | quote }} + - name: ODOO_SESSION_REDIS_HOST + value: {{ .Values.redis.host }} + - name: ODOO_SESSION_REDIS_PORT + value: {{ .Values.redis.port | quote }} + - name: ODOO_SESSION_REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ ternary (include "odoo.redis.fullname" .) (printf "%s-%s" .Release.Name "password") .Values.redis.enabled | quote }} + key: {{ ternary "redis-password" "redis-password" .Values.redis.enabled | quote }} + - name: ODOO_SESSION_REDIS_PREFIX + value: {{ .Values.redis.prefix }} + - name: ODOO_SESSION_REDIS_EXPIRATION + value: {{ .Values.redis.expiration | quote }} + - name: ODOO_SESSION_REDIS_EXPIRATION_ANONYMOUS + value: {{ .Values.redis.expiration_anonymous | quote }} + - name: ODOO_SESSION_REDIS_COPY_EXISTING_FS_SESSIONS + value: {{ .Values.redis.copy_existing_fs_sessions | quote }} + - name: ODOO_SESSION_REDIS_PURGE_EXISTING_FS_SESSIONS + value: {{ .Values.redis.purge_existing_fs_sessions | quote }} + {{- end }} + ports: + - name: tcp-odoo-http + containerPort: 8069 + - name: tcp-odoo-im + containerPort: 8072 + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /monitoring/status + port: tcp-odoo-http + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /monitoring/status + port: tcp-odoo-http + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + resources: +{{ toYaml .Values.resources | indent 10 }} + {{- if .Values.persistence.enabled }} + volumeMounts: + - name: odoo-data + mountPath: /odoo/data + volumes: + - name: odoo-data + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "odoo.fullname" .) }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} +{{- end }} diff --git a/helm/odoo/templates/hpa.yaml b/helm/odoo/templates/hpa.yaml new file mode 100644 index 0000000..f7d9c09 --- /dev/null +++ b/helm/odoo/templates/hpa.yaml @@ -0,0 +1,20 @@ +{{- if or (eq .Values.environment "qa") (eq .Values.environment "production") }} +kind: HorizontalPodAutoscaler +apiVersion: autoscaling/v2beta2 +metadata: + name: {{ .Values.name | quote }} +spec: + scaleTargetRef: + kind: Deployment + name: {{ .Values.name | quote }} + apiVersion: apps/v1 + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 50 +{{- end }} diff --git a/helm/odoo/templates/imagestream.yaml b/helm/odoo/templates/imagestream.yaml new file mode 100644 index 0000000..1348cb7 --- /dev/null +++ b/helm/odoo/templates/imagestream.yaml @@ -0,0 +1,10 @@ +{{- if eq .Values.distribution "openshift" }} +{{- if .Values.build.enabled }} +kind: ImageStream +apiVersion: image.openshift.io/v1 +metadata: + name: {{ .Values.name | quote }} + annotations: + description: Keeps track of changes in the application image +{{- end }} +{{- end }} diff --git a/helm/odoo/templates/ingress.yaml b/helm/odoo/templates/ingress.yaml new file mode 100644 index 0000000..128cdd8 --- /dev/null +++ b/helm/odoo/templates/ingress.yaml @@ -0,0 +1,49 @@ +{{ if eq .Values.distribution "kubernetes" }} +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: {{ .Values.name | quote }} + annotations: + kubernetes.io/ingress.class: {{ .Values.ingress.class }} + {{ if eq .Values.ingress.class "alb" }} + alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": + { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.cert }} + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]' + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/subnets: {{ .Values.ingress.subnets }} + alb.ingress.kubernetes.io/target-type: instance + external-dns.alpha.kubernetes.io/hostname: {{ .Values.application_domain }} + {{ end }} + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + tls: + - hosts: + - {{ .Values.application_domain | quote }} + secretName: aks-ingress-tls + rules: + - host: {{ .Values.application_domain | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: ssl-redirect + port: + name: use-annotation + - path: /longpolling + pathType: Prefix + backend: + service: + name: {{ .Values.name | quote }} + port: + number: 8072 + - path: / + pathType: Prefix + backend: + service: + name: {{ .Values.name | quote }} + port: + number: 8069 +{{- end }} diff --git a/helm/odoo/templates/pvc.yaml b/helm/odoo/templates/pvc.yaml new file mode 100644 index 0000000..389b4ef --- /dev/null +++ b/helm/odoo/templates/pvc.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "odoo.fullname" . }} + labels: + app: {{ template "odoo.name" . }} + chart: {{ template "odoo.chart" . }} + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{ include "odoo.storageClass" . }} +{{- end -}} diff --git a/helm/odoo/templates/route.yaml b/helm/odoo/templates/route.yaml new file mode 100644 index 0000000..ee2e92a --- /dev/null +++ b/helm/odoo/templates/route.yaml @@ -0,0 +1,53 @@ +{{- if eq .Values.distribution "openshift" }} +kind: Route +apiVersion: route.openshift.io/v1 +metadata: + name: odoo-http + annotations: + haproxy.router.openshift.io/timeout: 1800s +spec: + host: {{ .Values.application_domain | quote }} + path: "/" + to: + kind: Service + name: {{ .Values.name | quote }} + port: + targetPort: http + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect +--- +kind: Route +apiVersion: route.openshift.io/v1 +metadata: + name: odoo-im +spec: + host: {{ .Values.application_domain | quote }} + path: "/longpolling" + to: + kind: Service + name: {{ .Values.name | quote }} + port: + targetPort: im + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect +--- +{{- if .Values.mailhog.enabled }} +kind: Route +apiVersion: route.openshift.io/v1 +metadata: + name: mailhog +spec: + host: {{ printf "%s-%s" "mailhog" .Values.application_domain | quote }} + path: "/" + to: + kind: Service + name: {{ printf "%s-%s" .Values.name "mailhog" | quote }} + port: + targetPort: http + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect +{{- end }} +{{- end }} diff --git a/helm/odoo/templates/secrets.yaml b/helm/odoo/templates/secrets.yaml new file mode 100644 index 0000000..a078c8c --- /dev/null +++ b/helm/odoo/templates/secrets.yaml @@ -0,0 +1,117 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "odoo.fullname" . }} + labels: + app: {{ template "odoo.name" . }} + chart: {{ template "odoo.chart" . }} + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +type: Opaque +data: + {{ if .Release.IsInstall }} + {{ if .Values.s3.enabled }} + aws-access-key-id: {{ randAlphaNum 16 | b64enc | quote }} + aws-secret-access-key: {{ randAlphaNum 16 | b64enc | quote }} + {{ end }} + {{ if .Values.azure.enabled }} + azure-storage-account-url: {{ randAlphaNum 16 | b64enc | quote }} + azure-storage-connection-string: {{ randAlphaNum 16 | b64enc | quote }} + {{ end }} + odoo-admin-passwd: {{ randAlphaNum 16 | b64enc | quote }} + odoo-admin-user-password: {{ randAlphaNum 16 | b64enc | quote }} + odoo-smtp-password: {{ randAlphaNum 16 | b64enc | quote }} + {{ else }} + {{ if .Values.s3.enabled }} + aws-access-key-id: {{ index (lookup "v1" "Secret" .Release.Namespace (include "odoo.fullname" . )).data "aws-access-key-id" }} + aws-secret-access-key: {{ index (lookup "v1" "Secret" .Release.Namespace (include "odoo.fullname" . )).data "aws-secret-access-key" }} + {{ end }} + {{ if .Values.azure.enabled }} + azure-storage-account-url: {{ index (lookup "v1" "Secret" .Release.Namespace (include "odoo.fullname" . )).data "azure-storage-account-url" }} + azure-storage-connection-string: {{ index (lookup "v1" "Secret" .Release.Namespace (include "odoo.fullname" . )).data "azure-storage-connection-string" }} + {{ end }} + odoo-admin-passwd: {{ index (lookup "v1" "Secret" .Release.Namespace (include "odoo.fullname" . )).data "odoo-admin-passwd" }} + odoo-admin-user-password: {{ index (lookup "v1" "Secret" .Release.Namespace (include "odoo.fullname" . )).data "odoo-admin-user-password" }} + odoo-smtp-password: {{ index (lookup "v1" "Secret" .Release.Namespace (include "odoo.fullname" . )).data "odoo-smtp-password" }} + {{ end }} +--- +{{- if not .Values.postgresql.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-%s" .Release.Name "externaldb" }} + labels: + app: {{ printf "%s-%s" .Release.Name "externaldb" }} + chart: {{ template "odoo.chart" . }} + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +type: Opaque +data: + {{ if .Release.IsInstall }} + db-password: {{ randAlphaNum 16 | b64enc | quote }} + {{ else }} + db-password: {{ index (lookup "v1" "Secret" .Release.Namespace (printf "%s-%s" .Release.Name "externaldb" )).data "db-password" }} + {{ end }} +{{- end }} +--- +{{ if .Values.backup.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: backup-credentials + labels: + app: {{ template "odoo.name" . }} + chart: {{ template "odoo.chart" . }} + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +type: Opaque +data: +{{ if .Release.IsInstall }} + {{ if or (eq .Values.backup.platform "aws") (eq .Values.backup.platform "do") }} + aws-access-key-id: {{ randAlphaNum 16 | b64enc | quote }} + aws-secret-access-key: {{ randAlphaNum 16 | b64enc | quote }} + {{ end }} + {{ if eq .Values.backup.platform "azure" }} + azure-storage-account-url: {{ randAlphaNum 16 | b64enc | quote }} + {{ end }} +{{ else }} + {{ if or (eq .Values.backup.platform "aws") (eq .Values.backup.platform "do") }} + aws-access-key-id: {{ index (lookup "v1" "Secret" .Release.Namespace "backup-credentials").data "aws-access-key-id" }} + aws-secret-access-key: {{ index (lookup "v1" "Secret" .Release.Namespace "backup-credentials").data "aws-secret-access-key" }} + {{ end }} + {{ if eq .Values.backup.platform "azure" }} + azure-storage-account-url: {{ index (lookup "v1" "Secret" .Release.Namespace "backup-credentials").data "azure-storage-account-url" }} + {{ end }} +{{ end }} +{{ end }} +--- +{{ if .Values.remote.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: remote-credentials + labels: + app: {{ template "odoo.name" . }} + chart: {{ template "odoo.chart" . }} + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +type: Opaque +data: +{{ if .Release.IsInstall }} + {{ if or (eq .Values.remote.platform "aws") (eq .Values.remote.platform "do") }} + aws-access-key-id: {{ randAlphaNum 16 | b64enc | quote }} + aws-secret-access-key: {{ randAlphaNum 16 | b64enc | quote }} + {{ end }} + {{ if eq .Values.remote.platform "azure" }} + azure-storage-account-url: {{ randAlphaNum 16 | b64enc | quote }} + {{ end }} +{{ else }} + {{ if or (eq .Values.remote.platform "aws") (eq .Values.remote.platform "do") }} + aws-access-key-id: {{ index (lookup "v1" "Secret" .Release.Namespace "remote-credentials").data "aws-access-key-id" }} + aws-secret-access-key: {{ index (lookup "v1" "Secret" .Release.Namespace "remote-credentials").data "aws-secret-access-key" }} + {{ end }} + {{ if eq .Values.remote.platform "azure" }} + azure-storage-account-url: {{ index (lookup "v1" "Secret" .Release.Namespace "remote-credentials").data "azure-storage-account-url" }} + {{ end }} +{{ end }} +{{ end }} diff --git a/helm/odoo/templates/service.yaml b/helm/odoo/templates/service.yaml new file mode 100644 index 0000000..8aba84d --- /dev/null +++ b/helm/odoo/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "odoo.fullname" . }} + labels: + app: {{ template "odoo.name" . }} + chart: {{ template "odoo.chart" . }} + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +spec: + type: {{ .Values.service.type }} + {{- if (and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP))) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort")) }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + ports: + - name: http + protocol: TCP + port: 8069 + targetPort: tcp-odoo-http + - name: im + protocol: TCP + port: 8072 + targetPort: tcp-odoo-im + selector: + app: {{ template "odoo.name" . }} + release: {{ .Release.Name | quote }} diff --git a/helm/odoo/values.production.yaml b/helm/odoo/values.production.yaml new file mode 100644 index 0000000..9c803d8 --- /dev/null +++ b/helm/odoo/values.production.yaml @@ -0,0 +1,130 @@ +## Database external to the cluster +externalDatabase: + host: template-odoo.db.example.com + user: odoo + port: 5432 + defaultdb: postgres + sslmode: prefer + +## Database in the cluster +postgresql: + enabled: false +# postgresqlUsername: odoo +# defaultdb: postgres +# persistence: +# enabled: true +# accessMode: ReadWriteOnce +# size: 2Gi + +ingress: + class: alb + cert: arn:aws:acm:us-east-2:123456789:certificate/abc123 + subnets: subnet-abc123,subnet-abc123 + +service: + type: NodePort + externalTrafficPolicy: Cluster + +build: + enabled: false + +persistence: + enabled: false +# storageClass: nfs +# accessMode: ReadWriteMany +# size: 2Gi + +azure: + enabled: false + +s3: + enabled: true + aws_host: s3.us-east-2.amazonaws.com + aws_region: us-east-2 + aws_bucketname: production-{db}-template + +backup: + enabled: true + image: ursa/backup:latest + platform: aws + aws_host: s3.us-east-2.amazonaws.com + aws_region: us-east-2 + aws_bucketname: odoo-backup-template + +remote: + enabled: false +# platform: +# aws_host: s3.us-east-2.amazonaws.com +# aws_region: us-east-2 +# aws_bucketname: odoobackup-template + +redis: + enabled: true + host: odoo-redis-master + port: 6379 + prefix: template-odoo + expiration: 604800 + expiration_anonymous: 10800 + copy_existing_fs_sessions: 0 + purge_existing_fs_sessions: 1 + cluster: + enabled: false + master: + persistence: + storageClass: gp2 + size: 2Gi + +mailhog: + enabled: false + +postfix: + enabled: false + +smtp: + enabled: false + ssl: false + user: false + +marabunta: + allow_serie: false + mode: external + +resources: + limits: + cpu: "1" + memory: 2Gi + requests: + cpu: 300m + memory: 512Mi + +livenessProbe: + enabled: true + initialDelaySeconds: 600 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +readinessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +affinity: {} + +name: odoo +fullname: "Template Odoo Production" +project: template-odoo +environment: production +platform: aws +distribution: kubernetes +application_domain: odoo.apps.cluster.example.com +timezone: America/Phoenix +image: docker.pkg.github.com/ursais/odoo-template/odoo-template:latest +odooUsername: admin +odooEmail: support@example.com +odooServerWideModules: web,monitoring_status,session_redis +odooListDB: false diff --git a/helm/odoo/values.qa.yaml b/helm/odoo/values.qa.yaml new file mode 100644 index 0000000..dfa4270 --- /dev/null +++ b/helm/odoo/values.qa.yaml @@ -0,0 +1,131 @@ +## Database external to the cluster +externalDatabase: + host: template-odoo-qa.db.example.com + user: odoo + port: 5432 + defaultdb: postgres + sslmode: prefer + +## Database in the cluster +postgresql: + enabled: false +# postgresqlUsername: odoo +# defaultdb: postgres +# persistence: +# enabled: true +# accessMode: ReadWriteOnce +# size: 2Gi + +ingress: + class: alb + cert: arn:aws:acm:us-east-2:123456789:certificate/abc123 + subnets: subnet-abc123,subnet-abc123 + +service: + type: NodePort + externalTrafficPolicy: Cluster + +build: + enabled: false + +persistence: + enabled: false +# storageClass: nfs +# accessMode: ReadWriteMany +# size: 2Gi + +azure: + enabled: false + +s3: + enabled: true + aws_host: s3.us-east-2.amazonaws.com + aws_region: us-east-2 + aws_bucketname: qa-{db}-template + +backup: + enabled: true + image: ursa/backup:latest + platform: aws + aws_host: s3.us-east-2.amazonaws.com + aws_region: us-east-2 + aws_bucketname: odoo-backup-template + +remote: + enabled: false +# platform: +# aws_host: s3.us-east-2.amazonaws.com +# aws_region: us-east-2 +# aws_bucketname: odoobackup-template + +redis: + enabled: true + host: odoo-redis-master + port: 6379 + prefix: template-odoo-qa + expiration: 604800 + expiration_anonymous: 10800 + copy_existing_fs_sessions: 0 + purge_existing_fs_sessions: 1 + cluster: + enabled: false + master: + persistence: + storageClass: gp2 + size: 2Gi + +mailhog: + enabled: true + +postfix: + enabled: false + +smtp: + enabled: true + ssl: false + user: false + +marabunta: + allow_serie: true + mode: external + +resources: + limits: + cpu: "1" + memory: 2Gi + requests: + cpu: 300m + memory: 512Mi + +livenessProbe: + enabled: true + initialDelaySeconds: 600 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +readinessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +affinity: {} + +name: odoo +fullname: "Template Odoo QA" +project: template-odoo-qa +environment: qa +platform: aws +distribution: kubernetes +application_domain: qa-odoo.apps.cluster.example.com +timezone: America/Phoenix +image: docker.pkg.github.com/ursais/odoo-template/odoo-template:latest +odooUsername: admin +odooEmail: support@example.com +odooServerWideModules: web,monitoring_status,session_redis +odooListDB: true +odooDBFilter: "^.*[^backup].*$" diff --git a/helm/odoo/values.test.yaml b/helm/odoo/values.test.yaml new file mode 100644 index 0000000..e6c2ff7 --- /dev/null +++ b/helm/odoo/values.test.yaml @@ -0,0 +1,131 @@ +## Database external to the cluster +externalDatabase: + host: template-odoo-test.db.example.com + user: odoo + port: 5432 + defaultdb: postgres + sslmode: prefer + +## Database in the cluster +postgresql: + enabled: false +# postgresqlUsername: odoo +# defaultdb: postgres +# persistence: +# enabled: true +# accessMode: ReadWriteOnce +# size: 2Gi + +ingress: + class: alb + cert: arn:aws:acm:us-east-2:123456789:certificate/abc123 + subnets: subnet-abc123,subnet-abc123 + +service: + type: NodePort + externalTrafficPolicy: Cluster + +build: + enabled: false + +persistence: + enabled: false +# storageClass: nfs +# accessMode: ReadWriteMany +# size: 2Gi + +azure: + enabled: false + +s3: + enabled: true + aws_host: s3.us-east-2.amazonaws.com + aws_region: us-east-2 + aws_bucketname: test-{db}-template + +backup: + enabled: true + image: ursa/backup:latest + platform: aws + aws_host: s3.us-east-2.amazonaws.com + aws_region: us-east-2 + aws_bucketname: odoo-backup-template + +remote: + enabled: false +# platform: +# aws_host: s3.us-east-2.amazonaws.com +# aws_region: us-east-2 +# aws_bucketname: odoobackup-template + +redis: + enabled: true + host: odoo-redis-master + port: 6379 + prefix: template-odoo-test + expiration: 604800 + expiration_anonymous: 10800 + copy_existing_fs_sessions: 0 + purge_existing_fs_sessions: 1 + cluster: + enabled: false + master: + persistence: + storageClass: gp2 + size: 2Gi + +mailhog: + enabled: true + +postfix: + enabled: false + +smtp: + enabled: true + ssl: false + user: false + +marabunta: + allow_serie: true + mode: external + +resources: + limits: + cpu: "1" + memory: 2Gi + requests: + cpu: 300m + memory: 512Mi + +livenessProbe: + enabled: true + initialDelaySeconds: 600 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +readinessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +affinity: {} + +name: odoo +fullname: "Template Odoo Test" +project: template-odoo-test +environment: test +platform: aws +distribution: kubernetes +application_domain: test-odoo.apps.cluster.example.com +timezone: America/Phoenix +image: docker.pkg.github.com/ursais/odoo-template/odoo-template:latest +odooUsername: admin +odooEmail: support@example.com +odooServerWideModules: web,monitoring_status,session_redis +odooListDB: true +odooDBFilter: "^.*[^backup].*$" diff --git a/helm/odoo/values.yaml b/helm/odoo/values.yaml new file mode 100644 index 0000000..d5443d5 --- /dev/null +++ b/helm/odoo/values.yaml @@ -0,0 +1,134 @@ +## Database external to the cluster +externalDatabase: + host: false +# user: odoo +# port: 5432 +# defaultdb: postgres +# sslmode: prefer + +## Database in the cluster +postgresql: + enabled: true + postgresqlUsername: odoo + defaultdb: postgres + persistence: + enabled: true + accessMode: ReadWriteOnce + size: 2Gi + sslmode: prefer + +ingress: + class: alb + cert: arn:aws:acm:us-east-2:123456789:certificate/abc123 + subnets: subnet-abc123,subnet-abc123 + +service: + type: NodePort + externalTrafficPolicy: Cluster + +build: + enabled: false +# source_repository_url: https://github.com/ursais/odoo-template.git +# source_repository_ref: 14.0 +# context_dir: odoo + +persistence: + enabled: false +# storageClass: nfs +# accessMode: ReadWriteMany +# size: 4Gi + +azure: + enabled: false + +s3: + enabled: true + aws_host: s3.us-east-2.amazonaws.com + aws_region: us-east-2 + aws_bucketname: dev-{db}-template + +backup: + enabled: false +# image: ursa/backup:latest +# platform: aws +# aws_host: s3.us-east-2.amazonaws.com +# aws_region: us-east-2 +# aws_bucketname: odoo-backup-template + +remote: + enabled: false +# platform: +# aws_host: s3.us-east-2.amazonaws.com +# aws_region: us-east-2 +# aws_bucketname: odoobackup-template + +redis: + enabled: true + host: odoo-redis-master + port: 6379 + prefix: template-odoo-dev + expiration: 604800 + expiration_anonymous: 10800 + copy_existing_fs_sessions: 0 + purge_existing_fs_sessions: 1 + cluster: + enabled: false + master: + persistence: + storageClass: gp2 + size: 2Gi + +mailhog: + enabled: true + +postfix: + enabled: false + +smtp: + enabled: true + ssl: false + user: false + +marabunta: + allow_serie: true + mode: external + +resources: + limits: + cpu: "1" + memory: 2Gi + requests: + cpu: 300m + memory: 512Mi + +livenessProbe: + enabled: true + initialDelaySeconds: 600 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +readinessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +affinity: {} + +name: odoo +fullname: "Template Odoo Dev" +project: template-odoo-dev +environment: dev +platform: aws +distribution: kubernetes +application_domain: dev-odoo.apps.cluster.example.com +timezone: America/Phoenix +image: docker.pkg.github.com/ursais/odoo-template/odoo-template:latest +odooUsername: admin +odooEmail: support@example.com +odooServerWideModules: web,monitoring_status,session_redis +odooListDB: true diff --git a/odoo/Dockerfile b/odoo/Dockerfile new file mode 100644 index 0000000..15ac56c --- /dev/null +++ b/odoo/Dockerfile @@ -0,0 +1,15 @@ +FROM ursa/odoo-16.0:latest +MAINTAINER Open Source Integrators + +# Copy files +COPY --chown=odoo setup.py /odoo/ +COPY --chown=odoo migration.yml /odoo/ +COPY --chown=odoo requirements.txt /odoo/ +COPY --chown=odoo songs /odoo/songs + +# Copy Addons +# COPY src/paid-addons /odoo/addons/ +COPY --chown=odoo src/private-addons /odoo/addons/ +# COPY src/enterprise /odoo/addons/ + +RUN cd /odoo && pip3 install -r requirements.txt diff --git a/odoo/migration.yml b/odoo/migration.yml new file mode 100644 index 0000000..b5dbadd --- /dev/null +++ b/odoo/migration.yml @@ -0,0 +1,32 @@ +migration: + options: + install_command: odoo + install_args: --log-level=debug + backup: + command: ' + export DATE=$(date +%Y%m%dT%H%M%S) && + psql -c "CREATE DATABASE $DATE WITH TEMPLATE $PGDATABASE;"' + stop_on_failure: true + ignore_if: test "${RUNNING_ENV}" != "production" + + versions: + - version: setup + backup: false + operations: +# pre: +# - psql -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + post: + - anthem songs.setup.base::main + - /bin/sh -c "VERSION=15.0.1.0.0 anthem songs.environment::main" + addons: + upgrade: + - customer + - monitoring_status + - web_environment_ribbon +# modes: +# external: +# operations: +# addons: +# upgrade: +# - attachment_??? +# - session_redis diff --git a/odoo/requirements.txt b/odoo/requirements.txt new file mode 100644 index 0000000..8a22469 --- /dev/null +++ b/odoo/requirements.txt @@ -0,0 +1,5 @@ +./odoo +-e . # Install the songs + +# Add project specific packages below +num2words diff --git a/odoo/setup.py b/odoo/setup.py new file mode 100644 index 0000000..5d1095e --- /dev/null +++ b/odoo/setup.py @@ -0,0 +1,24 @@ +# Copyright (C) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from setuptools import find_packages, setup + +setup( + name="odoo-songs", + version="14.0.1.0.0", + description="Odoo ERP", + license="GNU Affero General Public License v3 or later (AGPLv3+)", + author="Open Source Integrators", + author_email="support@opensourceintegrators.com", + url="https://opensourceintegrators.com", + packages=["songs"] + ["songs.%s" % p for p in find_packages("./songs")], + include_package_data=True, + classifiers=[ + "Development Status :: 4 - Beta", + "License :: OSI Approved", + "License :: OSI Approved :: " + "GNU Affero General Public License v3 or later (AGPLv3+)", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: Implementation :: CPython", + ], +) diff --git a/odoo/songs/__init__.py b/odoo/songs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/odoo/songs/common.py b/odoo/songs/common.py new file mode 100644 index 0000000..304b2dd --- /dev/null +++ b/odoo/songs/common.py @@ -0,0 +1,115 @@ +# Copyright 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import os + +from anthem.lyrics.loaders import load_csv_stream +from anthem.lyrics.records import switch_company +from pkg_resources import Requirement, resource_stream + +req = Requirement.parse("odoo-songs") + + +def load_csv(ctx, path, delimiter=",", header=None, header_exclude=None): + content = resource_stream(req, path) + model = os.path.splitext(os.path.basename(path))[0] + load_csv_stream( + ctx, + model, + content, + delimiter=delimiter, + header=header, + header_exclude=header_exclude, + ) + + +def load_users_csv(ctx, path, delimiter=","): + # make sure we don't send any email + ctx.env["res.users"].with_context( + {"no_reset_password": True, "tracking_disable": True} + ) + load_csv(ctx, path, delimiter) + + +def load_warehouses(ctx, company, path): + # in multicompany moded we must force the company + # otherwise the sequences that stock module generates automatically + # will have the wrong company assigned. + with switch_company(ctx, company) as ctx: + load_csv(ctx, path) + # NOTE: dirty hack here. + # We are forced to load the CSV twice because + # if you are modifying the existing base warehouse (stock.warehouse0) + # and you've changed the `code` (short name) + # the changes are not reflected on existing sequences + # until you load warehouse data again. + # We usually don't have that many WHs so... it's fine :) + load_csv(ctx, path) + + +def get_files(default_file): + """Check if there is a DATA_DIR in environment else open default_file. + + DATA_DIR is passed by importer.sh when importing splitted file in parallel + + Returns a generator of file to import as DATA_DIR can contain a split of + csv file + """ + try: + dir_path = os.environ["DATA_DIR"] + except KeyError: + yield resource_stream(req, default_file) + else: + file_list = os.listdir(dir_path) + for file_name in file_list: + file_path = os.path.join(dir_path, file_name) + yield open(file_path) + + +def load_csv_parallel(ctx, path, defer_parent_computation=True, delimiter=","): + """Use me to load an heavy file ~2k of lines or more. + + Then calling this method as a parameter of importer.sh + + importer.sh will split the file in chunks per number of processor + and per 500. + This method will be called once per chunk in order to do the csv loading + on multiple processes. + + Usage:: + + @anthem.log + def setup_locations(ctx): + load_csv_parallel( + ctx, + 'data/install/stock.location.csv', + defer_parent_computation=True) + + Then in `migration.yml`:: + + - importer.sh songs.install.inventory::setup_locations /opt/odoo/data/install/stock.location.csv + # if defer_parent_computation=True + - anthem songs.install.inventory::location_compute_parents + + """ # noqa + load_ctx = ctx.env.context.copy() + model = os.path.splitext(os.path.basename(path))[0] + if defer_parent_computation: + load_ctx.update({"defer_parent_store_computation": "manually"}) + if isinstance(model, str): + model = ctx.env[model] + model = model.with_context(**load_ctx) + for content in get_files(path): + load_csv_stream(ctx, model, content, delimiter=delimiter) + + +def deferred_compute_parents(ctx, model): + """Use me for heavy files after calling `deferred_import`. + + Usage:: + + @anthem.log + def location_compute_parents(ctx): + deferred_compute_parents(ctx, 'stock.location') + + """ + ctx.env[model]._parent_store_compute() diff --git a/odoo/songs/data/images/logo.png b/odoo/songs/data/images/logo.png new file mode 100644 index 0000000..85d0176 Binary files /dev/null and b/odoo/songs/data/images/logo.png differ diff --git a/odoo/songs/environment.py b/odoo/songs/environment.py new file mode 100644 index 0000000..b1d05c9 --- /dev/null +++ b/odoo/songs/environment.py @@ -0,0 +1,81 @@ +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import os + +import anthem +from anthem.lyrics.records import create_or_update + + +@anthem.log +def reset_queue_jobs(ctx): + """Reset Queue Jobs""" + jobs = ctx.env["queue.job"].search([("state", "in", ["started", "enqueued"])]) + jobs.write({"state": "pending"}) + + +@anthem.log +def setup_admin_user(ctx): + """Setup Admin User""" + admin = ctx.env.ref("base.user_admin") + admin.write( + { + "new_password": os.environ.get("ODOO_ADMIN_USER_PASSWORD"), + "tz": os.environ.get("ODOO_ADMIN_USER_TIMEZONE"), + } + ) + admin._set_new_password() + + +@anthem.log +def set_mail_server(ctx): + """Set Mail Server""" + if os.getenv("RUNNING_ENV") != "production": + mailhog = create_or_update( + ctx, + "ir.mail_server", + "__setup__.ir_mail_server_mailhog", + { + "name": "MailHog", + "smtp_host": os.environ.get("ODOO_SMTP_SERVER", "mail"), + "smtp_port": os.environ.get("ODOO_SMTP_PORT", 25), + }, + ) + try: + mailhog.test_smtp_connection() + except Exception as exception: + ctx.log_line("Test SMTP connection to MailHog: %s" % str(exception)) + + +@anthem.log +def set_ribbon(ctx): + """Set Ribbon""" + if os.getenv("RUNNING_ENV") != "production": + background = ctx.env["ir.config_parameter"].search( + [("key", "=", "ribbon.background.color")] + ) + background.value = "rgba(0,128,0,.6)" + color = ctx.env["ir.config_parameter"].search([("key", "=", "ribbon.color")]) + color.value = "#f0f0f0" + name = ctx.env["ir.config_parameter"].search([("key", "=", "ribbon.name")]) + name.value = os.getenv("RUNNING_ENV").upper() + "
({db_name})" + + +@anthem.log +def set_version(ctx): + """Set in the database""" + create_or_update( + ctx, + "ir.config_parameter", + "__setup__.ir_database_version", + {"key": "database.version", "value": os.getenv("VERSION", "setup")}, + ) + + +@anthem.log +def main(ctx): + """Environment""" + setup_admin_user(ctx) + set_mail_server(ctx) + set_ribbon(ctx) + # reset_queue_jobs(ctx) + set_version(ctx) diff --git a/odoo/songs/setup/__init__.py b/odoo/songs/setup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/odoo/songs/setup/base.py b/odoo/songs/setup/base.py new file mode 100644 index 0000000..d93e56f --- /dev/null +++ b/odoo/songs/setup/base.py @@ -0,0 +1,52 @@ +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import os +from base64 import b64encode + +import anthem +from pkg_resources import resource_string + +from ..common import req + + +@anthem.log +def setup_admin_user(ctx): + """Setup admin user""" + admin = ctx.env.ref("base.user_admin") + admin.write( + { + "new_password": os.environ.get("ODOO_ADMIN_USER_PASSWORD"), + "tz": os.environ.get("ODOO_ADMIN_USER_TIMEZONE"), + } + ) + admin._set_new_password() + + +@anthem.log +def setup_company(ctx): + """Setup company""" + # load logo on company + logo_content = resource_string(req, "songs/data/images/logo.png") + b64_logo = b64encode(logo_content) + + values = { + "name": "Open Source Integrators", + "street": "PO Box 940", + "zip": "85236", + "state_id": ctx.env.ref("base.state_us_3").id, + "city": "Higley", + "country_id": ctx.env.ref("base.us").id, + "phone": "+1 (855) 811-2377", + "email": "contact@opensourceintegrators.com", + "website": "https://www.opensourceintegrators.com", + "vat": "", + "logo": b64_logo, + "currency_id": ctx.env.ref("base.USD").id, + } + ctx.env.ref("base.main_company").write(values) + + +@anthem.log +def main(ctx): + setup_company(ctx) + setup_admin_user(ctx) diff --git a/odoo/src/osi-addons b/odoo/src/osi-addons new file mode 160000 index 0000000..654a71f --- /dev/null +++ b/odoo/src/osi-addons @@ -0,0 +1 @@ +Subproject commit 654a71fddad6ad95f397c7025de681b8eaa62806 diff --git a/odoo/src/paid-addons/.gitkeep b/odoo/src/paid-addons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/customer/README.rst b/odoo/src/private-addons/customer/README.rst new file mode 100644 index 0000000..0ae07eb --- /dev/null +++ b/odoo/src/private-addons/customer/README.rst @@ -0,0 +1,70 @@ +======== +CUSTOMER +======== + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: AGPL-3 + +|badge1| |badge2| + +This module provides the scope (dependencies) and customizations of Odoo for Customer. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* Go to the `project blueprint `_. + +Usage +===== + +* Go to the `project documentation `_. + +Support +======= + +Please report any issue or bugs to support@opensourceintegrators.com. + +Credits +======= + +Contributors +------------ + +* Open Source Integrators + + * Project Manager + * Business Analyst + * Tech Lead + +Maintainers +----------- + +This module is maintained by Open Source Integrators. + +.. image:: https://github.com/ursais.png + :alt: Open Source Integrators + :target: https://www.opensourceintegrators.com + +Open Source Integratorsâ„¢ (OSI) provides customers a unique combination of +open source business process consulting and implementations. + +.. |maintainer-max3903| image:: https://github.com/max3903.png?size=40px + :target: https://github.com/max3903 + :alt: max3903 + +Current `maintainer `__: + +|maintainer-max3903| + +This module is part of the `Odoo Template `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo/src/private-addons/customer/__init__.py b/odoo/src/private-addons/customer/__init__.py new file mode 100644 index 0000000..49eeb1e --- /dev/null +++ b/odoo/src/private-addons/customer/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2017 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/odoo/src/private-addons/customer/__manifest__.py b/odoo/src/private-addons/customer/__manifest__.py new file mode 100644 index 0000000..563e94f --- /dev/null +++ b/odoo/src/private-addons/customer/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright (C) 2017 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Customer", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "summary": "Customer Configuration and Data", + "author": "Open Source Integrators", + "maintainer": "Open Source Integrators", + "website": "https://www.opensourceintegrators.com", + "depends": ["elearning_content"], + "data": [], + "application": True, + "maintainers": ["ursais"], +} diff --git a/odoo/src/private-addons/customer/static/description/icon.png b/odoo/src/private-addons/customer/static/description/icon.png new file mode 100644 index 0000000..85d0176 Binary files /dev/null and b/odoo/src/private-addons/customer/static/description/icon.png differ diff --git a/odoo/src/private-addons/elearning_content/README.rst b/odoo/src/private-addons/elearning_content/README.rst new file mode 100644 index 0000000..0304f3a --- /dev/null +++ b/odoo/src/private-addons/elearning_content/README.rst @@ -0,0 +1,65 @@ +======================== +Training / Documentation +======================== + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: AGPL-3 + +|badge1| |badge2| + +This module provides the base of the training content and documentation of Odoo. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +* Go to Elearning +* Select or create a training course +* Invite your employees to their corresponding training + +Support +======= + +Please report any issue or bugs to support@opensourceintegrators.com. + +Credits +======= + +Contributors +------------ + +* Open Source Integrators + + * Maxime Chambreuil + +Maintainers +----------- + +This module is maintained by Open Source Integrators. + +.. image:: https://github.com/ursais.png + :alt: Open Source Integrators + :target: https://www.opensourceintegrators.com + +Open Source Integratorsâ„¢ (OSI) provides customers a unique combination of +open source business process consulting and implementations. + +.. |maintainer-max3903| image:: https://github.com/max3903.png?size=40px + :target: https://github.com/max3903 + :alt: max3903 + +Current `maintainer `__: + +|maintainer-max3903| + +This module is part of the `Odoo Template `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo/src/private-addons/elearning_content/__init__.py b/odoo/src/private-addons/elearning_content/__init__.py new file mode 100644 index 0000000..81e367a --- /dev/null +++ b/odoo/src/private-addons/elearning_content/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/odoo/src/private-addons/elearning_content/__manifest__.py b/odoo/src/private-addons/elearning_content/__manifest__.py new file mode 100644 index 0000000..372bf0f --- /dev/null +++ b/odoo/src/private-addons/elearning_content/__manifest__.py @@ -0,0 +1,30 @@ +# Copyright (C) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Elearning Content", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "summary": "Training Content and Documentation", + "author": "Open Source Integrators", + "maintainer": "Open Source Integrators", + "website": "https://www.opensourceintegrators.com", + "depends": ["website_slides"], + "data": [ + "data/slide.channel.csv", + "data/slide.slide.csv", + # Odoo Interface + "data/00-interface/slide.slide.xml", + # Accounting + # Inventory + # Manufacturing + # Service + # Purchase + # Sales / CRM + # Website + # Human Resources + # Administration + # Development + ], + "application": True, + "maintainers": ["ursais"], +} diff --git a/odoo/src/private-addons/elearning_content/data/00-interface/slide.slide.xml b/odoo/src/private-addons/elearning_content/data/00-interface/slide.slide.xml new file mode 100644 index 0000000..7584d3b --- /dev/null +++ b/odoo/src/private-addons/elearning_content/data/00-interface/slide.slide.xml @@ -0,0 +1,227 @@ + + + + + Log in + 110 + webpage + + + 0 + +
+
+

Log In

+
+
+
+
+ + + + Browse the menu + 120 + webpage + + + 0 + +
+
+

Browse the menu

+
+
+
+
+ + + + Search, filter and group + 130 + webpage + + + 0 + +
+
+

Search, filter and group

+
+
+
+
+ + + + Favorites and Dashboard + 140 + webpage + + + 0 + +
+
+

Favorites and Dashboard

+
+
+
+
+ + + + Import/Export + 150 + webpage + + + 0 + +
+
+

Import/Export

+
+
+
+
+ + + + List + 210 + webpage + + + 0 + +
+
+

List

+
+
+
+
+ + + + Form + 220 + webpage + + + 0 + +
+
+

Form

+
+
+
+
+ + + + Kanban + 230 + webpage + + + 0 + +
+
+

Kanban

+
+
+
+
+ + + + Calendar + 240 + webpage + + + 0 + +
+
+

Calendar

+
+
+
+
+ + + + Map + 250 + webpage + + + 0 + +
+
+

Map

+
+
+
+
+ + + + Report an issue + 310 + webpage + + + 0 + +
+
+

Methods

+ +

Subject

+

Please enter a meaningful subject including the severity of the + issue (low, medium, high, critical) +

+

Description

+

Make sure to answer the following questions:

+
+Environment
+
+* What server and database was used for testing?
+* What company (in multi-company setup) and user (or role) was used?
+
+Steps to reproduce
+
+* What is the high level process used? Please provide steps, for example:
+
+  * Go to Accounting/Vendors/Bills
+  * Create new Vendor Bill or open vendor bill 123
+  * Problem when adding product line manually
+
+Result
+
+* What is the current result? If necessary, provide screenshot or complete error message
+
+Expected Result
+
+* What is the expected result?
+                    
+
+
+
+
+ +
diff --git a/odoo/src/private-addons/elearning_content/data/10-account/.gitkeep b/odoo/src/private-addons/elearning_content/data/10-account/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/20-stock/.gitkeep b/odoo/src/private-addons/elearning_content/data/20-stock/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/30-mrp/.gitkeep b/odoo/src/private-addons/elearning_content/data/30-mrp/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/30-service/.gitkeep b/odoo/src/private-addons/elearning_content/data/30-service/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/40-purchase/.gitkeep b/odoo/src/private-addons/elearning_content/data/40-purchase/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/50-sale/.gitkeep b/odoo/src/private-addons/elearning_content/data/50-sale/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/60-website/.gitkeep b/odoo/src/private-addons/elearning_content/data/60-website/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/70-hr/.gitkeep b/odoo/src/private-addons/elearning_content/data/70-hr/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/90-admin/.gitkeep b/odoo/src/private-addons/elearning_content/data/90-admin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/99-devel/.gitkeep b/odoo/src/private-addons/elearning_content/data/99-devel/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/src/private-addons/elearning_content/data/slide.channel.csv b/odoo/src/private-addons/elearning_content/data/slide.channel.csv new file mode 100644 index 0000000..856e5f9 --- /dev/null +++ b/odoo/src/private-addons/elearning_content/data/slide.channel.csv @@ -0,0 +1,12 @@ +"id","sequence","name","channel_type","enroll","visibility","description" +"slide_channel_odoo_interface","0","Odoo Interface","Training","On Invitation","Members Only","General course to learn how to use Odoo" +"slide_channel_accounting","10","Accounting","Training","On Invitation","Members Only","Accounting course: AR, AP, banking, etc." +"slide_channel_inventory","20","Inventory","Training","On Invitation","Members Only","Invetnory course: Reception, shipping, barcode scanning" +"slide_channel_manufacturing","30","Manufacturing","Training","On Invitation","Members Only","Manufacturing course: How to process a manufacturing/work order" +"slide_channel_service","30","Services","Training","On Invitation","Members Only","Service course: How to manage your tasks, tickets or FSM orders" +"slide_channel_purchase","40","Purchase","Training","On Invitation","Members Only","Purchase course: How to manage your purchase orders" +"slide_channel_sales","50","Sales / CRM","Training","On Invitation","Members Only","Sales/CRM course: How to manage your leads, quotations and sales orders" +"slide_channel_website","60","Website","Training","On Invitation","Members Only","Website course: How to manage menu items, pages and content" +"slide_channel_hr","70","Human Resources","Training","On Invitation","Members Only","HR course: How to report your expenses, request leaves" +"slide_channel_odoo_admin","90","Odoo Administration","Training","On Invitation","Members Only","Odoo administration course: How to manage users, groups, access rights and record rules" +"slide_channel_odoo_dev","99","Odoo Development","Training","On Invitation","Members Only","Odoo development course: How to customize Odoo" diff --git a/odoo/src/private-addons/elearning_content/data/slide.slide.csv b/odoo/src/private-addons/elearning_content/data/slide.slide.csv new file mode 100644 index 0000000..d0bb403 --- /dev/null +++ b/odoo/src/private-addons/elearning_content/data/slide.slide.csv @@ -0,0 +1,39 @@ +"id","sequence","name","slide_type","channel_id/id","is_category" +"slide_slide_odoo_interface_navigation","100","Navigation","Document","slide_channel_odoo_interface","1" +"slide_slide_odoo_interface_views","200","Views","Document","slide_channel_odoo_interface","1" +"slide_slide_odoo_interface_support","300","Support","Document","slide_channel_odoo_interface","1" +"slide_slide_account_receivables","100","Account Receivables","Document","slide_channel_accounting","1" +"slide_slide_account_payables","200","Account Payables","Document","slide_channel_accounting","1" +"slide_slide_account_banking","300","Banking","Document","slide_channel_accounting","1" +"slide_slide_account_reporting","400","Reporting","Document","slide_channel_accounting","1" +"slide_slide_account_assets","500","Assets","Document","slide_channel_accounting","1" +"slide_slide_account_budgets","600","Budgets","Document","slide_channel_accounting","1" +"slide_slide_stock_master","100","Master Data","Document","slide_channel_inventory","1" +"slide_slide_stock_receiving","200","Receiving","Document","slide_channel_inventory","1" +"slide_slide_stock_picking","300","Picking","Document","slide_channel_inventory","1" +"slide_slide_stock_packing","400","Packing","Document","slide_channel_inventory","1" +"slide_slide_stock_shipping","500","Shipping","Document","slide_channel_inventory","1" +"slide_slide_mrp_master","100","Master Data","Document","slide_channel_manufacturing","1" +"slide_slide_mrp_operations","200","Operations","Document","slide_channel_manufacturing","1" +"slide_slide_service_master","100","Master Data","Document","slide_channel_service","1" +"slide_slide_service_project","200","Project","Document","slide_channel_service","1" +"slide_slide_service_helpdesk","300","Helpdesk","Document","slide_channel_service","1" +"slide_slide_service_field","400","Field Service","Document","slide_channel_service","1" +"slide_slide_purchase_master","100","Master Data","Document","slide_channel_purchase","1" +"slide_slide_purchase_purchase","200","Purchase","Document","slide_channel_purchase","1" +"slide_slide_sale_master","100","Master Data","Document","slide_channel_sales","1" +"slide_slide_sale_crm","200","CRM","Document","slide_channel_sales","1" +"slide_slide_sale_sales","300","Sales","Document","slide_channel_sales","1" +"slide_slide_website_menu","100","Menu & Pages","Document","slide_channel_website","1" +"slide_slide_website_themes","200","Themes","Document","slide_channel_website","1" +"slide_slide_website_blog","300","Blog & Posts","Document","slide_channel_website","1" +"slide_slide_website_ecommerce","400","eCommerce","Document","slide_channel_website","1" +"slide_slide_hr_master","100","Master Data","Document","slide_channel_hr","1" +"slide_slide_hr_expense","200","Expenses","Document","slide_channel_hr","1" +"slide_slide_hr_leaves","300","Leaves","Document","slide_channel_hr","1" +"slide_slide_admin_security","100","Security","Document","slide_channel_odoo_admin","1" +"slide_slide_admin_audit","200","Audit","Document","slide_channel_odoo_admin","1" +"slide_slide_dev_module","100","Module","Document","slide_channel_odoo_dev","1" +"slide_slide_dev_models","200","Models","Document","slide_channel_odoo_dev","1" +"slide_slide_dev_views","300","Views","Document","slide_channel_odoo_dev","1" +"slide_slide_dev_reports","400","Reports","Document","slide_channel_odoo_dev","1" diff --git a/odoo/src/private-addons/elearning_content/static/description/icon.png b/odoo/src/private-addons/elearning_content/static/description/icon.png new file mode 100644 index 0000000..85d0176 Binary files /dev/null and b/odoo/src/private-addons/elearning_content/static/description/icon.png differ diff --git a/odoo/src/public-addons/.gitkeep b/odoo/src/public-addons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/odoo/tests/locust/README.md b/odoo/tests/locust/README.md new file mode 100644 index 0000000..9f7a84b --- /dev/null +++ b/odoo/tests/locust/README.md @@ -0,0 +1,31 @@ +# Performance Tests with Locust + +## Installation + +```shell script +python3 -m venv env +. env/bin/activate +pip install -r requirements.txt +``` + +## Running the tests + +* Edit `locust.conf` to your needs +* Run +```shell script +export ODOO_DB_NAME=Test +export ODOO_LOGIN=admin +export ODOO_PASSWORD=admin +locust --config=locust.conf +``` +* Go to http://localhost:8089 + +## Advanced topics + +It is possible to set the weight of the users (CRMUser, SaleUser, ProjectUser, +PurchaseUser, AccountUser, StockUser) by editing the values in `locustfile.py`. + +You can also run specific user, for example: +```shell script +locust --config=locust.conf SaleUser +``` diff --git a/odoo/tests/locust/helper.py b/odoo/tests/locust/helper.py new file mode 100644 index 0000000..1d00a7d --- /dev/null +++ b/odoo/tests/locust/helper.py @@ -0,0 +1,209 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import datetime +import random + +from locust import TaskSet + + +class BaseBackendTaskSet(TaskSet): + def on_start(self): + self.client.login(self.user.db_name, self.user.login, self.user.password) + + +def search_browse(client, model, domain, random_pick=False): + Model = client.env[model] + record_ids = Model.search_read(domain, ["id"]) + if len(record_ids) == 0: + return False + elif random_pick: + record_ids = random.choice(record_ids)["id"] + else: + record_ids = [rek["id"] for rek in record_ids] + browse_records = Model.browse(record_ids) + return browse_records + + +def find_random_delivery(client): + model = "stock.picking" + domain = [("origin", "!=", False), ("state", "=", "confirmed")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_invoice_draft(client): + model = "account.invoice" + domain = [("type", "=", "out_invoice"), ("state", "=", "draft")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_invoice_open(client): + model = "account.invoice" + domain = [("type", "=", "out_invoice"), ("state", "=", "open")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_new_lead(client): + model = "crm.lead" + domain = [("stage_id.name", "=", "New")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_picking_in(client): + model = "stock.picking" + domain = [("state", "=", "assigned")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_picking_wave(client): + model = "stock.picking.wave" + domain = [("state", "in", ("in_progress", "done"))] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_picking_wave_running(client): + model = "stock.picking.wave" + domain = [("state", "=", "in_progress")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_product_purchase(client): + model = "product.product" + domain = [("purchase_ok", "=", True)] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_product_sale(client): + model = "product.product" + domain = [("sale_ok", "=", True), ("type", "=", "product")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_purchase_order(client): + model = "purchase.order" + domain = [("invoice_status", "=", "to invoice")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_quotation(client): + model = "sale.order" + domain = [ + ("state", "=", "draft"), + ("order_line", "!=", False), + ("date_order", ">", "2020-07-20"), + ] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_rfq(client): + model = "purchase.order" + domain = [("state", "=", "draft")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_sale_order(client): + model = "sale.order" + domain = [("state", "=", "sale")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_supplier(client): + model = "res.partner" + domain = [("supplier", "=", True)] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_customer(client): + model = "res.partner" + domain = [("customer", "=", True)] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_pricelist(client): + model = "product.pricelist" + domain = [] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_template(client): + model = "sale.quote.template" + domain = [] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_task(client): + model = "project.task" + domain = [("user_id", "=", False)] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_task_done(client): + model = "project.task" + domain = [("user_id", "!=", False), ("stage_id.name", "ilike", "work done")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_task_ready(client): + model = "project.task" + domain = [("user_id", "!=", False), ("stage_id.name", "ilike", "confirmed")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_vendor_bill_draft(client): + model = "account.invoice" + domain = [("type", "=", "in_invoice"), ("state", "=", "draft")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_vendor_bill_open(client): + model = "account.invoice" + domain = [("type", "=", "in_invoice"), ("state", "=", "open")] + return search_browse(client, model, domain, random_pick=True) + + +def find_random_won_lead(client): + model = "crm.lead" + domain = [ + ("stage_id.name", "=", "Won"), + ("partner_id", "!=", False), + ("order_ids", "=", False), + ] + return search_browse(client, model, domain, random_pick=True) + + +def generate_street_address(client): + # generates "random" address str based on time + now = datetime.datetime.now() + usec = now.strftime("%f") + adr_num = int(usec[2:]) + if adr_num < 1000: + adr_num += 1000 + adr_num = str(adr_num) + sec = usec[0:2] + fsec = sec[0] + lsec = sec[1] + if fsec == "0": + if lsec == "0": + sec = "1" + lsec = "1" + else: + sec = lsec + if fsec == "1": + suff = "TH" + elif lsec == "1": + suff = "ST" + elif lsec == "2": + suff = "ND" + elif lsec == "3": + suff = "RD" + else: + suff = "TH" + if int(fsec) < 3: + adr_dir = "N" + elif int(fsec) < 6: + adr_dir = "W" + elif int(fsec) < 8: + adr_dir = "S" + else: + adr_dir = "E" + adr_ln = adr_num + " " + adr_dir + " " + sec + suff + " STREET" + return adr_ln diff --git a/odoo/tests/locust/locust.conf b/odoo/tests/locust/locust.conf new file mode 100644 index 0000000..8c42018 --- /dev/null +++ b/odoo/tests/locust/locust.conf @@ -0,0 +1,6 @@ +locustfile = locustfile.py +logfile = locust.log +headless = false +host = https://example.com +users = 10 +hatch-rate = 3 diff --git a/odoo/tests/locust/locustfile.py b/odoo/tests/locust/locustfile.py new file mode 100644 index 0000000..d52dd60 --- /dev/null +++ b/odoo/tests/locust/locustfile.py @@ -0,0 +1,41 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from locust import between +from locustodoorpc.client import OdooRPCLocust +from tasks import account, crm, project, purchase, sale, stock + + +class AccountUser(OdooRPCLocust): + wait_time = between(1, 15) + weight = 10 + tasks = [account.AccountTaskSet] + + +class CRMUser(OdooRPCLocust): + wait_time = between(1, 15) + weight = 10 + tasks = [crm.CRMTaskSet] + + +class ProjectUser(OdooRPCLocust): + wait_time = between(1, 15) + weight = 10 + tasks = [project.ProjectTaskSet] + + +class PurchaseUser(OdooRPCLocust): + wait_time = between(1, 15) + weight = 10 + tasks = [purchase.PurchaseTaskSet] + + +class SaleUser(OdooRPCLocust): + wait_time = between(1, 15) + weight = 10 + tasks = [sale.SaleTaskSet] + + +class StockUser(OdooRPCLocust): + wait_time = between(1, 15) + weight = 10 + tasks = [stock.StockTaskSet] diff --git a/odoo/tests/locust/requirements.txt b/odoo/tests/locust/requirements.txt new file mode 100644 index 0000000..06d5cff --- /dev/null +++ b/odoo/tests/locust/requirements.txt @@ -0,0 +1,28 @@ +certifi==2019.9.11 +chardet==3.0.4 +Click==7.0 +ConfigArgParse==1.2.3 +Flask==1.1.2 +Flask-BasicAuth==0.2.0 +gevent==20.6.2 +geventhttpclient==1.4.4 +geventhttpclient-wheels==1.3.1.dev2 +greenlet==0.4.16 +idna==2.8 +itsdangerous==1.1.0 +Jinja2==2.11.3 +locust==1.1.1 +-e git+ssh://git@github.com/ursais/locustodoorpc.git#egg=locustodoorpc +MarkupSafe==1.1.1 +msgpack==1.0.0 +msgpack-python==0.5.6 +OdooRPC==0.7.0 +psutil==5.7.2 +pyzmq==18.1.1 +requests==2.22.0 +six==1.13.0 +urllib3==1.26.5 +Werkzeug==0.16.0 +wheel==0.34.2 +zope.event==4.4 +zope.interface==5.1.0 diff --git a/odoo/tests/locust/tasks/__init__.py b/odoo/tests/locust/tasks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/odoo/tests/locust/tasks/account.py b/odoo/tests/locust/tasks/account.py new file mode 100644 index 0000000..3598d22 --- /dev/null +++ b/odoo/tests/locust/tasks/account.py @@ -0,0 +1,142 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import datetime +import logging + +import helper +from locust import task + + +class AccountTaskSet(helper.BaseBackendTaskSet): + def on_start(self, *args, **kwargs): + super(AccountTaskSet, self).on_start(*args, **kwargs) + + @task(20) + def generate_invoice_from_task(self): + task_id = helper.find_random_task_done(self.client) + if not task_id: + logging.INFO("Failed to generate Invoice -- no Task found") + return () + logging.INFO("Created Invoice for task: " + task_id.name) + return task_id.generate_invoices() + + @task(10) + def confirm_customer_invoice(self): + invoice_id = helper.find_random_invoice_draft(self.client) + if not invoice_id: + logging.INFO("Failed to confirm Invoice -- none found") + return () + return invoice_id.action_invoice_open() + + @task(10) + def register_invoice_payment(self): + invoice_id = helper.find_random_invoice_open(self.client) + if not invoice_id: + logging.INFO("Failed to register Payment -- no Invoice found") + return () + account_payment = self.client.env["account.payment"] + journal_id = self.client.env["account.journal"].search( + [("code", "ilike", "INV")], limit=1 + ) + if type(journal_id) != int: + journal_id = journal_id.id + journal_rec = self.client.env["account.journal"].browse(journal_id) + pay_method = journal_rec.inbound_payment_method_ids[0] + vals = { + "partner_id": invoice_id.partner_id.id, + "partner_type": "customer", + "payment_method_id": pay_method.id, + "payment_type": "inbound", + "journal_id": journal_rec.id, + "amount": invoice_id.residual, + "communication": invoice_id.number, + } + payment_id = account_payment.create(vals) + if type(payment_id) != int: + payment_id = payment_id.id + payment_rec = account_payment.browse(payment_id) + invoice_lines = self.client.env["payment.invoice.line"] + vals = { + "payment_id": payment_rec.id, + "invoice_id": invoice_id.id, + "date_invoice": invoice_id.date_invoice.strftime("%Y-%m-%d"), + "amount": invoice_id.residual, + "amount_total": invoice_id.residual, + "residual": invoice_id.residual, + } + invoice_lines.create(vals) + return payment_rec.post() + + @task(10) + def generate_vendor_bill_from_po(self): + order_id = helper.find_random_purchase_order(self.client) + if not order_id: + logging.INFO("Failed to generate Vendor Bill -- no PO found") + return () + vendor_bill = self.client.env["account.invoice"] + today = datetime.date.today().strftime("%Y-%m-%d") + invoice_num = "INV" + order_id.name[2:] + vals = { + "partner_id": order_id.partner_id.id, + "purchase_id": order_id.id, + "account_id": order_id.partner_id.property_account_payable_id.id, + "date_invoice": today, + "supplier_invoice_number": invoice_num, + "type": "in_invoice", + "origin": order_id.name, + } + bill_id = vendor_bill.create(vals) + order_id.write({"invoice_status": "invoiced"}) + if type(bill_id) != int: + bill_id = bill_id.id + bill_rec = vendor_bill.browse(bill_id) + logging.INFO("Created Vendor Bill from: " + order_id.name) + return bill_rec.purchase_order_change() + + @task(10) + def confirm_vendor_bill(self): + vendor_bill = helper.find_random_vendor_bill_draft(self.client) + if not vendor_bill: + logging.INFO("Failed to confirm Vendor Bill -- none found") + return () + return vendor_bill.action_invoice_open() + + @task(10) + def register_vendor_payment(self): + vendor_bill = helper.find_random_vendor_bill_open(self.client) + if not vendor_bill: + logging.INFO("Failed to register Payment -- no Vendor Bill found") + return () + account_payment = self.client.env["account.payment"] + journal_id = self.client.env["account.journal"].search( + [("code", "ilike", "BILL")], limit=1 + ) + if type(journal_id) != int: + journal_id = journal_id.id + journal_rec = self.client.env["account.journal"].browse(journal_id) + pay_method = journal_rec.outbound_payment_method_ids[0] + vals = { + "partner_id": vendor_bill.partner_id.id, + "partner_type": "supplier", + "payment_method_id": pay_method.id, + "payment_type": "outbound", + "journal_id": journal_rec.id, + "amount": vendor_bill.residual, + "communication": vendor_bill.number, + } + payment_id = account_payment.create(vals) + if type(payment_id) != int: + payment_id = payment_id.id + payment_rec = self.client.env["account.payment"].browse(payment_id) + invoice_lines = self.client.env["payment.invoice.line"] + vals = { + "payment_id": payment_rec.id, + "invoice_id": vendor_bill.id, + "date_invoice": vendor_bill.date_invoice.strftime("%Y-%m-%d"), + "amount": vendor_bill.residual, + "amount_total": vendor_bill.residual, + "residual": vendor_bill.residual, + } + invoice_lines.create(vals) + logging.INFO("Posted payment for: " + payment_rec.partner_id.name) + return payment_rec.post() diff --git a/odoo/tests/locust/tasks/crm.py b/odoo/tests/locust/tasks/crm.py new file mode 100644 index 0000000..cfc722a --- /dev/null +++ b/odoo/tests/locust/tasks/crm.py @@ -0,0 +1,100 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging +import random + +import helper +from locust import task + + +class CRMTaskSet(helper.BaseBackendTaskSet): + def on_start(self, *args, **kwargs): + super(CRMTaskSet, self).on_start(*args, **kwargs) + self.Lead = self.client.env["crm.lead"] + self.Sale = self.client.env["sale.order"] + self.Partner = self.client.env["res.partner"] + + @task(20) + def create_lead(self): + partner_rec = helper.find_random_customer(self.client) + team_rec = helper.search_browse( + self.client, "crm.team", [("name", "=", "Direct Sales")], random_pick=True + ) + stage_rec = helper.search_browse( + self.client, + "crm.stage", + [("team_id", "=", False), ("name", "=", "New")], + random_pick=True, + ) + vals = { + "probability": 10, + "team_id": team_rec.id, + "partner_id": partner_rec.id, + "planned_revenue": random.randrange( + 1000, 100001, 1000 + ), # random revenue from 1k to 10k by 1k increments + "priority": str(random.randrange(0, 4, 1)), # random priority from 0-3 + "type": "opportunity", + "name": partner_rec.name, + "stage_id": stage_rec.id, + } + lead_id = self.Lead.create(vals) + logging.INFO("Created Lead: " + partner_rec.name) + return lead_id + + @task(4) + def mark_lead_won(self): + lead_rec = helper.find_random_new_lead(self.client) + if not lead_rec: + logging.INFO("Failed to mark Lead won -- none found") + return () + return lead_rec.action_set_won() + + @task(4) + def create_quotation(self): + lead_rec = helper.find_random_won_lead(self.client) + if not lead_rec: + logging.INFO("Failed to create Quotation -- no Lead found") + return () + vals = { + "partner_id": lead_rec.partner_id.id, + "state": "draft", + "opportunity_id": lead_rec.id, + "partner_invoice_id": lead_rec.partner_id.id, + "partner_shipping_id": lead_rec.site_id.id, + "pricelist_id": lead_rec.partner_id.property_product_pricelist.id, + } + sale_id = self.Sale.create(vals) + if type(sale_id) != int: + sale_id = sale_id.id + sale_rec = self.Sale.browse(sale_id) + # separate order creation and lines, onchange will clear lines + product_ids = [] + for x in range(0, 3): + domain = [("id", "not in", product_ids)] if x > 0 else [] + prod = helper.find_associated_product( + self.client, sale_rec.partner_shipping_id, domain + ) + product_ids += [prod.id] + product_ids = self.client.env["product.product"].browse(product_ids) + sale_order_line = self.client.env["sale.order.line"] + for p in product_ids: + vals = { + "name": p.name, + "product_id": p.id, + "product_uom_qty": 1, + "product_uom": p.uom_id.id, + "price_unit": p.list_price, + "order_id": sale_rec.id, + } + sale_order_line.create(vals) + logging.INFO("Created SO: " + sale_rec.name) + return sale_rec + + @task(3) + def confirm_quotation(self): + quote_id = helper.find_random_quotation(self.client) + if not quote_id: + logging.INFO("Failed to confirm Quotation -- none found") + return () + return quote_id.action_confirm() diff --git a/odoo/tests/locust/tasks/project.py b/odoo/tests/locust/tasks/project.py new file mode 100644 index 0000000..d3930ba --- /dev/null +++ b/odoo/tests/locust/tasks/project.py @@ -0,0 +1,44 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +import helper +from locust import task + + +class ProjectTaskSet(helper.BaseBackendTaskSet): + def on_start(self, *args, **kwargs): + super(ProjectTaskSet, self).on_start(*args, **kwargs) + + @task(10) + def edit_task(self): + task_id = helper.find_random_task(self.client) + if not task_id: + logging.INFO("Failed to edit Task -- none found") + return () + user_id = helper.find_associated_user(self.client, task_id.site_id) + if not user_id: + logging.INFO("Failed to edit Task -- none found") + return () + stage_id = self.client.env["project.task.type"].search_read( + [("name", "=", "Reviewed")], ["id"] + )[0]["id"] + vals = { + "user_id": user_id.id, + "date_start": "2020-01-01 00:00:00", + "planned_hours": 40, + "stage_id": stage_id, + } + return task_id.write(vals) + + @task(10) + def confirm_task(self): + task_id = helper.find_random_task_ready(self.client) + if not task_id: + logging.INFO("Failed to confirm Task -- none found") + return () + stage_id = self.client.env["project.task.type"].search_read( + [("name", "=", "Work Done")], ["id"] + )[0]["id"] + task_id.write({"stage_id": stage_id}) + return task_id diff --git a/odoo/tests/locust/tasks/purchase.py b/odoo/tests/locust/tasks/purchase.py new file mode 100644 index 0000000..9852458 --- /dev/null +++ b/odoo/tests/locust/tasks/purchase.py @@ -0,0 +1,55 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +import helper +from locust import task + + +class PurchaseTaskSet(helper.BaseBackendTaskSet): + def on_start(self, *args, **kwargs): + super(PurchaseTaskSet, self).on_start(*args, **kwargs) + self.Purchase = self.client.env["purchase.order"] + + @task(20) + def create_new_rfq(self): + supplier_id = helper.find_random_supplier(self.client) + product_id = helper.find_random_product_purchase(self.client) + vals = { + "partner_id": supplier_id.id, + "order_line": [ + ( + 0, + 0, + { + "date_planned": "2020-01-01 00:00:00", + "name": product_id.name, + "product_id": product_id.id, + "product_qty": 1, + "product_uom": product_id.uom_id.id, + "price_unit": product_id.price and product_id.price or 100.0, + }, + ) + ], + } + logging.INFO("Created PO with supplier: " + supplier_id.name) + return self.Purchase.create(vals) + + @task(15) + def confirm_rfq(self): + order_id = helper.find_random_rfq(self.client) + if not order_id: + logging.INFO("Failed to confirm RFQ -- none found") + return () + return order_id.button_confirm() + + @task(10) + def receive_products(self): + picking_id = helper.find_random_picking_in(self.client) + if not picking_id: + logging.INFO("Failed to receive DO -- none found") + return () + for product in picking_id.pack_operation_product_ids: + qty_to_do = product.product_qty + product.write({"qty_done": qty_to_do}) + return picking_id.do_new_transfer() diff --git a/odoo/tests/locust/tasks/sale.py b/odoo/tests/locust/tasks/sale.py new file mode 100644 index 0000000..3adc150 --- /dev/null +++ b/odoo/tests/locust/tasks/sale.py @@ -0,0 +1,92 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import random +from datetime import datetime + +import helper +from locust import task + + +class SaleTaskSet(helper.BaseBackendTaskSet): + def on_start(self, *args, **kwargs): + super(SaleTaskSet, self).on_start(*args, **kwargs) + self.Sale = self.client.env["sale.order"] + self.now = datetime.now().strftime("%Y-%m-%d, %H:%M:%S") + + @task(10) + def create_sale_order(self): + """Create and confirm a sales order with one line""" + vals = { + "partner_id": 17591, + "picking_policy": "direct", + "date_order": self.now, + "user_id": 1, + "contract_type_id": 1, + "pricelist_id": 1, + "validity_date": "2020-08-31", + "warehouse_id": 1, + "partner_invoice_id": 17591, + "partner_shipping_id": 193636, + "template_id": 3, + "order_line": [ + ( + 0, + 0, + { + "product_id": 21052, + "price_unit": 1000, + "product_uom_qty": 1, + "price_subtotal": 1000, + "currency_id": 1, + "product_uom": 1, + "purchase_price": 200, + "price_total": 1000, + "name": "Main Product", + "salesman_id": 1, + }, + ) + ], + } + sale_id = self.Sale.create(vals) + self.Sale.browse(sale_id).action_confirm() + + @task(10) + def create_big_sale_order(self): + """Create and confirm a sales order with more than 10 lines""" + vals = { + "partner_id": 17591, + "picking_policy": "direct", + "date_order": self.now, + "user_id": 1, + "contract_type_id": 1, + "pricelist_id": 1, + "validity_date": "2020-08-31", + "warehouse_id": 1, + "partner_invoice_id": 17591, + "partner_shipping_id": 193636, + "template_id": 3, + "order_line": [], + } + lines = [] + random_number = random.randint(10, 50) + for _i in range(3, random_number): + product = helper.find_random_product_sale(self.client) + line = ( + 0, + 0, + { + "price_unit": product.lst_price, + "product_uom_qty": 1, + "price_subtotal": product.lst_price * 1, + "currency_id": 1, + "product_uom": product.uom_id.id, + "can_edit": True, + "price_total": product.lst_price * 1, + "name": product.display_name, + "product_id": product.id, + }, + ) + lines.append(line) + vals.update({"order_line": lines}) + sale_id = self.Sale.create(vals) + self.Sale.browse(sale_id).action_confirm() diff --git a/odoo/tests/locust/tasks/stock.py b/odoo/tests/locust/tasks/stock.py new file mode 100644 index 0000000..937cc33 --- /dev/null +++ b/odoo/tests/locust/tasks/stock.py @@ -0,0 +1,44 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +import helper +from locust import task + + +class StockTaskSet(helper.BaseBackendTaskSet): + def on_start(self, *args, **kwargs): + super(StockTaskSet, self).on_start(*args, **kwargs) + self.Task = self.client.env["project.task"] + + @task(10) + def edit_delivery_lines(self): + # confirms moves in DO to finish SO + delivery_id = helper.find_random_delivery(self.client) + if not delivery_id: + logging.INFO("Failed to finish DO -- none found") + return () + delivery_id.action_assign() + if all(move.state == "assigned" for move in delivery_id.move_lines): + for operation in delivery_id.pack_operation_product_ids: + qty_to_do = operation.product_qty + operation.write({"qty_done": qty_to_do}) + logging.INFO("Confirmed DO: " + delivery_id.name) + return delivery_id.do_new_transfer() + + @task(5) + def print_picking_wave(self): + self.Task.action_picking_wave() + picking_wave_id = helper.find_random_picking_wave(self.client) + if not picking_wave_id: + logging.INFO("Failed to print Picking Wave -- none found") + return () + return picking_wave_id.print_picking() + + @task(5) + def confirm_picking_wave(self): + picking_wave_id = helper.find_random_picking_wave_running(self.client) + if not picking_wave_id: + logging.INFO("Failed to confirm Picking Wave --none found") + return () + return picking_wave_id.done() diff --git a/odoo/tests/selenium/CreateLead.side b/odoo/tests/selenium/CreateLead.side new file mode 100644 index 0000000..54cf837 --- /dev/null +++ b/odoo/tests/selenium/CreateLead.side @@ -0,0 +1,2610 @@ +{ + "id": "af22b45f-9c76-4a03-85e9-5603711ad1ba", + "version": "2.0", + "name": "Template", + "url": "https://HOST/web?db=DATABASE", + "tests": [{ + "id": "8f15db82-4f3b-4944-8ba9-6c16f2fb8645", + "name": "02 Create Builder", + "commands": [{ + "id": "90f6a457-2820-4300-b2b9-c5e79a9444ab", + "comment": "Open Home Page", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "870065c4-d602-489e-ae82-3022381e0495", + "comment": "Small window to ensure dropdown is present", + "command": "setWindowSize", + "target": "1200x800", + "targets": [], + "value": "" + }, { + "id": "ee0d6e31-b240-43ce-9f5b-c77d15334cc3", + "comment": "Check if user is logged in", + "command": "storeXpathCount", + "target": "xpath=//nav/a", + "targets": [], + "value": "homemenucount" + }, { + "id": "1ed4549e-b14b-487d-869e-12d8261d9a45", + "comment": "If user is not logged in, log in", + "command": "if", + "target": "${homemenucount} < 1", + "targets": [], + "value": "" + }, { + "id": "40d5a9f6-06a9-4f49-b9a5-d9193fc552c0", + "comment": "", + "command": "click", + "target": "xpath=//label[contains(.,'Employee')]", + "targets": [], + "value": "" + }, { + "id": "c3807d1d-3154-43bf-91ca-1ed2413faa40", + "comment": "", + "command": "click", + "target": "xpath=//a[contains(@href,'/web/login')]", + "targets": [], + "value": "" + }, { + "id": "92c34a7c-68ec-4e96-aa08-25f65b6d20c1", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "160060d4-b029-4b0a-9276-34ca3d362390", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "9966aab9-a23d-4f0f-bf6a-f92b40f00cce", + "comment": "", + "command": "sendKeys", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "admin" + }, { + "id": "94a255f2-124f-468f-af20-6f6f4f5d9e3b", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "a7118ff7-c459-4212-84e4-c64d77c5a2c5", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "20c358a5-b183-464c-88bb-ccc5a6505a7b", + "comment": "Password goes here", + "command": "sendKeys", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "PASSWORD" + }, { + "id": "ac4a0072-f85a-4ae2-a579-063986ae7793", + "comment": "", + "command": "click", + "target": "xpath=//button[contains(@type,'submit')]", + "targets": [], + "value": "" + }, { + "id": "1e347f1a-4e25-4a66-95e1-ed3d8578524e", + "comment": "If user is logged in, go to App menu", + "command": "else", + "target": "", + "targets": [], + "value": "" + }, { + "id": "d77bd9c4-7f4d-4c92-8c1e-6a8fd582c265", + "comment": "", + "command": "click", + "target": "xpath=//nav/a", + "targets": [ + ["css=.fa-th", "css:finder"], + ["xpath=//nav[@id='oe_main_menu_navbar']/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '#')]", "xpath:href"], + ["xpath=//a", "xpath:position"] + ], + "value": "" + }, { + "id": "f6698ce0-0b12-4f91-b0d1-bfbbe1d8387e", + "comment": "", + "command": "end", + "target": "", + "targets": [], + "value": "" + }, { + "id": "868a4388-a083-4cf7-9a08-25b20133fa05", + "comment": "Open Contacts App", + "command": "click", + "target": "xpath=//a/div[contains(.,'Contacts')]", + "targets": [ + ["linkText=Contacts", "linkText"], + ["css=.o_app:nth-child(5)", "css:finder"], + ["xpath=//a[contains(@href, '#menu_id=612&action_id=677')]", "xpath:href"], + ["xpath=//a[5]", "xpath:position"] + ], + "value": "" + }, { + "id": "2a70ac1e-1615-472e-8970-0aa39b4be649", + "comment": "Open Kanban view", + "command": "click", + "target": "xpath=//button[contains(@data-view-type,'kanban')]", + "targets": [], + "value": "" + }, { + "id": "c8fe24dc-c46c-42ab-8c04-316c6603d4fc", + "comment": "Wait, otherwise wrong searchbar is selected", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@class,'o-kanban-button-new')][contains(@accesskey,'c')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "dcf76680-6bff-454c-ac77-258c4d88d00c", + "comment": "Search for existing record", + "command": "click", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "4975d96e-f11a-40f9-b903-873ab627791d", + "comment": "", + "command": "type", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "Presidentialtest" + }, { + "id": "b309ea8c-c347-4460-a1a5-e2e9b8369485", + "comment": "", + "command": "sendKeys", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "fbe47068-dc33-47e1-ae5d-fc270371b1ac", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "e4077b29-d765-4f77-98ec-8f820a6bda4b", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "31d293b1-3672-4203-8b8d-d894ae0e5549", + "comment": "No existing record", + "command": "assertElementNotPresent", + "target": "xpath=//div[contains(@class, 'o_kanban_image')]", + "targets": [], + "value": "" + }, { + "id": "14890965-d113-4ad5-84d0-25e08e70aab5", + "comment": "Create new record", + "command": "click", + "target": "css=.o-kanban-button-new", + "targets": [ + ["css=.o-kanban-button-new", "css:finder"], + ["xpath=(//button[@type='button'])[6]", "xpath:attributes"], + ["xpath=//div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "5ceabbe6-abef-4f2e-a7e8-baa02e272cec", + "comment": "Check Company", + "command": "click", + "target": "xpath=//div/input[contains(@value,'company')]", + "targets": [], + "value": "" + }, { + "id": "dc3bb228-052a-4445-9b42-750bc6231020", + "comment": "Check Builder", + "command": "click", + "target": "xpath=//input[contains(@name, 'is_builder')]", + "targets": [], + "value": "" + }, { + "id": "2febb916-2c42-4472-9ca8-34793a216438", + "comment": "Name", + "command": "click", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "" + }, { + "id": "016c5d73-0ee9-4d24-b083-539764d5e412", + "comment": "", + "command": "sendKeys", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "Presidentialtest AUS" + }, { + "id": "91f11983-2006-40f8-864e-2dc3e5a2d352", + "comment": "Address", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'Street...')]", + "targets": [], + "value": "" + }, { + "id": "aa1f5039-2477-41f3-ac61-819067a4a3af", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'Street...')]", + "targets": [], + "value": "111 Presidential Way" + }, { + "id": "96132bb3-0390-4246-bbcb-d5eec0158a5a", + "comment": "City", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'City')]", + "targets": [], + "value": "" + }, { + "id": "4664c762-0081-433a-82db-38f0d240ea1a", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'City')]", + "targets": [], + "value": "Austin" + }, { + "id": "6382d1fb-0600-4b00-94dd-e0308b57c838", + "comment": "State", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'State')]", + "targets": [], + "value": "" + }, { + "id": "d9dbde7c-94a8-45ad-9331-1699cd9bbf5c", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'State')]", + "targets": [], + "value": "Texas" + }, { + "id": "25b7eb2f-e820-4093-b341-2082285a17b8", + "comment": "", + "command": "click", + "target": "linkText=Texas", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "79022996-7228-4245-a451-fc987dcf9f14", + "comment": "County", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'County')]", + "targets": [], + "value": "" + }, { + "id": "8605f7f4-09d8-4fe9-bb95-1326aa470f9e", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'County')]", + "targets": [], + "value": "Austin" + }, { + "id": "7f353198-e95f-4553-9439-4aec957955c9", + "comment": "", + "command": "click", + "target": "linkText=Austin", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "e84bd76e-4b6a-4d14-b386-e6d68047a2f4", + "comment": "ZIP code", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'ZIP')]", + "targets": [ + ["css=.o_address_zip", "css:finder"], + ["xpath=(//input[@type='text'])[11]", "xpath:attributes"], + ["xpath=//div/input[4]", "xpath:position"] + ], + "value": "" + }, { + "id": "8c83825e-93d1-4d93-aac1-046243fbe6d0", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'ZIP')]", + "targets": [], + "value": "76060" + }, { + "id": "3b8cf066-2761-441c-a51d-907a3cee30a9", + "comment": "Country", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'Country')]", + "targets": [ + ["css=.o_address_country .o_form_input", "css:finder"], + ["xpath=(//input[@type='text'])[12]", "xpath:attributes"], + ["xpath=//div[4]/div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "733b88d5-15ff-4620-9fce-080ab073a398", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'Country')]", + "targets": [], + "value": "United States" + }, { + "id": "45b967a9-3882-40e8-a0bf-31262fa3ba96", + "comment": "", + "command": "click", + "target": "linkText=United States", + "targets": [ + ["linkText=United States", "linkText"], + ["css=#ui-id-95 > a", "css:finder"], + ["xpath=//a[contains(text(),'United States')]", "xpath:link"], + ["xpath=//li[@id='ui-id-95']/a", "xpath:idRelative"], + ["xpath=//ul[4]/li[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'United States')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "b28f2c98-64b3-4255-ac8b-877b30be2397", + "comment": "Phone", + "command": "click", + "target": "xpath=//tr/td/label[contains(.,'Phone')]/../../td/input", + "targets": [], + "value": "" + }, { + "id": "9f226193-248a-428c-97c6-cda6c727d808", + "comment": "", + "command": "sendKeys", + "target": "xpath=//tr/td/label[contains(.,'Phone')]/../../td/input", + "targets": [], + "value": "(817) 555-1111" + }, { + "id": "377cec49-2868-4664-b98b-6472be131c69", + "comment": "Email", + "command": "click", + "target": "xpath=//tr/td/label[contains(.,'Email')]/../../td/input", + "targets": [], + "value": "" + }, { + "id": "7d4aa1d0-1c90-4a98-8bb1-81fb4c116cf6", + "comment": "", + "command": "sendKeys", + "target": "xpath=//tr/td/label[contains(.,'Email')]/../../td/input", + "targets": [], + "value": "sales@presidentialtest.com" + }, { + "id": "dc7efac2-a906-434b-83d7-532d301d28be", + "comment": "Open Contacts Tab", + "command": "//click", + "target": "xpath=//li/a[contains(.,'Contacts & Addresses')]", + "targets": [], + "value": "" + }, { + "id": "1feb469d-f6b1-4d5f-bcf7-57540d9176a6", + "comment": "Create Contact", + "command": "//click", + "target": "css=.o-kanban-button-new", + "targets": [], + "value": "", + "opensWindow": true, + "windowHandleName": "contactcreationwindow", + "windowTimeout": 30000 + }, { + "id": "86cd4b75-210e-4593-b716-d793efbfb880", + "comment": "", + "command": "//selectWindow", + "target": "", + "targets": [], + "value": "contactcreationwindow" + }, { + "id": "f42bf511-49ab-4af0-b3d9-2f581b308148", + "comment": "", + "command": "//waitForElementPresent", + "target": "css=.modal-footer > .btn:nth-child(1) > span", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "313fbf32-e7fb-4634-ab6c-d0bcadc9f2e0", + "comment": "Name", + "command": "//selectWindow", + "target": "id=o_field_input_90", + "targets": [], + "value": "" + }, { + "id": "5855bbd9-8196-482d-b1b2-e1265893c47e", + "comment": "", + "command": "//sendKeys", + "target": "xpath=//div[contains(@class,'modal-header')]//label[contains(.,'Contact Name')]", + "targets": [], + "value": "Jamestest Garfieldtest" + }, { + "id": "e79569c7-5cae-49fa-bfd8-4888579131a4", + "comment": "Job Position", + "command": "//click", + "target": "xpath=//div[contains(@class,'modal-header')]//input[contains(@placeholder,'e.g. Sales Director')]", + "targets": [], + "value": "" + }, { + "id": "543f1855-42bb-4f50-ada3-d820bbfd0bd9", + "comment": "", + "command": "//sendKeys", + "target": "xpath=//div[contains(@class,'modal-header')]//input[contains(@placeholder,'e.g. Sales Director')]", + "targets": [], + "value": "Purchasing Manager" + }, { + "id": "be3cd5ee-3ec0-4977-a145-3dc94d9697e0", + "comment": "Email", + "command": "//click", + "target": "xpath=//div[contains(@class,'modal-header')]//label[contains(.,'Email')]/../../td/input", + "targets": [], + "value": "" + }, { + "id": "79838c59-c73e-4fe9-8d6c-da1f4c493217", + "comment": "", + "command": "//sendKeys", + "target": "xpath=//div[contains(@class,'modal-header')]//label[contains(.,'Email')]/../../td/input", + "targets": [], + "value": "jgarfield@presidentialtest.com" + }, { + "id": "fb6c534b-d0b2-457f-ab44-91778d4e3293", + "comment": "Phone", + "command": "//click", + "target": "xpath=//div[contains(@class,'modal-header')]//label[contains(.,'Phone')]/../../td/input", + "targets": [], + "value": "" + }, { + "id": "3e542f7d-9f7d-4f48-bf56-5d20d3b98807", + "comment": "", + "command": "//sendKeys", + "target": "xpath=//div[contains(@class,'modal-header')]//label[contains(.,'Phone')]/../../td/input", + "targets": [], + "value": "(817) 555-2222" + }, { + "id": "e5ecd158-15de-4cab-aba8-726153b9aa92", + "comment": "Save Contact", + "command": "//click", + "target": "xpath=//span[contains(.,'Save & Close')]", + "targets": [], + "value": "" + }, { + "id": "c8c70a47-8cdc-43fd-a9c6-a11d1e363770", + "comment": "Open Sales Tab", + "command": "click", + "target": "xpath=//li/a[contains(.,'Sales & Purchases')]", + "targets": [ + ["linkText=Contacts", "linkText"], + ["css=.o_app:nth-child(5)", "css:finder"], + ["xpath=//a[contains(@href, '#menu_id=612&action_id=677')]", "xpath:href"], + ["xpath=//a[5]", "xpath:position"] + ], + "value": "" + }, { + "id": "7b72a839-e92a-4615-909c-20a05656fc3f", + "comment": "Pricelist", + "command": "click", + "target": "xpath=//label[contains(.,'Sale Pricelist')]/../../td/div/div/input", + "targets": [ + ["css=.o_form_button_save", "css:finder"], + ["xpath=(//button[@type='button'])[8]", "xpath:attributes"], + ["xpath=//div[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "bf81b3ec-5b78-4eb7-b579-afe9d1fa044c", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(.,'Sale Pricelist')]/../../td/div/div/input", + "targets": [], + "value": "horton" + }, { + "id": "625814a7-211b-420d-a91b-731186111c12", + "comment": "", + "command": "click", + "target": "linkText=DR Horton AUS (USD)", + "targets": [ + ["linkText=DR Horton AUS (USD)", "linkText"], + ["css=#ui-id-55 > a", "css:finder"], + ["xpath=//a[contains(text(),'DR Horton AUS (USD)')]", "xpath:link"], + ["xpath=//li[@id='ui-id-55']/a", "xpath:idRelative"], + ["xpath=//ul[12]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'DR Horton AUS (USD)')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "87404f42-bd86-482a-9668-1bea4cd783fe", + "comment": "Save record", + "command": "click", + "target": "xpath=//button[contains(@accesskey,'s')]", + "targets": [ + ["css=.o_form_button_save", "css:finder"], + ["xpath=(//button[@type='button'])[8]", "xpath:attributes"], + ["xpath=//div[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "5a0cf3a5-46f6-4494-9fbd-93917d136496", + "comment": "Check for Edit button", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@accesskey,'a')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "eaaaf6e7-5ce9-46d0-ae07-700c0d0e946f", + "comment": "Check name", + "command": "waitForElementPresent", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "95a2e163-5767-465d-9219-5c85f460f4a3", + "comment": "Check name", + "command": "assertText", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "Presidentialtest AUS" + }] + }, { + "id": "4d2b4a98-471c-4430-b05b-fb2d0342bee9", + "name": "01 Create Owner", + "commands": [{ + "id": "d2d29960-b266-40ec-9405-a31da4c823fb", + "comment": "Open Home Page", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "fdb5873c-b342-45be-a068-d5c085ab5ea2", + "comment": "Small window to ensure dropdown is present", + "command": "setWindowSize", + "target": "1200x800", + "targets": [], + "value": "" + }, { + "id": "53b341d7-72b1-49e4-9b22-126064e53bde", + "comment": "Check if user is logged in", + "command": "storeXpathCount", + "target": "xpath=//nav/a", + "targets": [], + "value": "homemenucount" + }, { + "id": "870c4710-8b84-4bd8-be3d-3af278e9dd36", + "comment": "If user is not logged in, log in", + "command": "if", + "target": "${homemenucount} < 1", + "targets": [], + "value": "" + }, { + "id": "50d868a9-af64-4274-91c8-d17068681d54", + "comment": "", + "command": "click", + "target": "xpath=//label[contains(.,'Employee')]", + "targets": [], + "value": "" + }, { + "id": "fb760c98-0384-4280-9c23-727c904edf2c", + "comment": "", + "command": "click", + "target": "xpath=//a[contains(@href,'/web/login')]", + "targets": [], + "value": "" + }, { + "id": "1ee292e9-0f19-481d-8968-d38b247ed207", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "78052854-18ad-43e9-a20c-e84be361da03", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "8789382d-f4ed-4857-92cd-c485b55bc7ed", + "comment": "", + "command": "sendKeys", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "admin" + }, { + "id": "6ba0afd7-43de-4ddf-bf22-f0b3c3a3d61f", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "47a68eaa-74d3-432b-b182-b842e461c9c9", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "f313c832-3a76-45fc-81a3-53d842faf092", + "comment": "Password goes here", + "command": "sendKeys", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "PASSWORD" + }, { + "id": "cdeae5c9-33ce-49c4-bbff-fc16f8b18dfb", + "comment": "", + "command": "click", + "target": "xpath=//button[contains(@type,'submit')]", + "targets": [], + "value": "" + }, { + "id": "b0ce48e1-6f94-43c0-b979-2c6a08db3483", + "comment": "If user is logged in, go to App menu", + "command": "else", + "target": "", + "targets": [], + "value": "" + }, { + "id": "ba5f078a-02b1-4766-bd92-260b206529ef", + "comment": "", + "command": "click", + "target": "xpath=//nav/a", + "targets": [ + ["css=.fa-th", "css:finder"], + ["xpath=//nav[@id='oe_main_menu_navbar']/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '#')]", "xpath:href"], + ["xpath=//a", "xpath:position"] + ], + "value": "" + }, { + "id": "cd337956-b519-4b9d-a472-760aacb085d7", + "comment": "", + "command": "end", + "target": "", + "targets": [], + "value": "" + }, { + "id": "c794c91a-f283-48d2-a0a5-c70fa201cbe1", + "comment": "Open Contacts App", + "command": "click", + "target": "xpath=//a/div[contains(.,'Contacts')]", + "targets": [ + ["linkText=Contacts", "linkText"], + ["css=.o_app:nth-child(5)", "css:finder"], + ["xpath=//a[contains(@href, '#menu_id=612&action_id=677')]", "xpath:href"], + ["xpath=//a[5]", "xpath:position"] + ], + "value": "" + }, { + "id": "03df0294-dcc9-4c85-8efa-26f9295de221", + "comment": "Open Kanban view", + "command": "click", + "target": "xpath=//button[contains(@data-view-type,'kanban')]", + "targets": [], + "value": "" + }, { + "id": "ad8d1640-b98f-43e6-9dda-d0965f70a617", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "e9cb6d9b-6ed9-4657-8d05-4d8605426036", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "d410cfc8-82e2-4ce7-9bd7-5229e50701c2", + "comment": "Wait, otherwise wrong searchbar is selected", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@class,'o-kanban-button-new')][contains(@accesskey,'c')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "e27e626e-9717-4056-9d62-dbb10292f65a", + "comment": "", + "command": "waitForElementVisible", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "BASETIMEOUT" + }, { + "id": "58d023c3-c0be-404f-aede-0b7c61ac5982", + "comment": "Search for existing record", + "command": "click", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "4aac98b0-e497-4818-99a0-e87ee7ae1f9e", + "comment": "", + "command": "type", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "Johntest Adamstest" + }, { + "id": "cac10f3a-049d-4a75-9fce-ff9bba14ea3c", + "comment": "", + "command": "sendKeys", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "b45ddfe6-4ada-4e93-8f6a-f29ef7d0cd35", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "ee420b3a-6881-4e93-8ae1-5d433a90f98d", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "6e93ffe8-1e81-4f5e-b121-e5201c8c6758", + "comment": "No existing record", + "command": "assertElementNotPresent", + "target": "xpath=//div[contains(@class, 'o_kanban_image')]", + "targets": [], + "value": "" + }, { + "id": "4ed44906-8345-433e-95bc-fae6a8b34f40", + "comment": "Create new record", + "command": "click", + "target": "css=.o-kanban-button-new", + "targets": [ + ["css=.o-kanban-button-new", "css:finder"], + ["xpath=(//button[@type='button'])[6]", "xpath:attributes"], + ["xpath=//div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "e6038f77-e1fa-4736-b5c1-c7a7769ba880", + "comment": "Check Owner", + "command": "click", + "target": "xpath=//input[contains(@name, 'is_home_owner')]", + "targets": [], + "value": "" + }, { + "id": "5508f68f-6e39-4f55-bc20-4a1768c27ef0", + "comment": "Name", + "command": "click", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "" + }, { + "id": "81566c9c-bf32-4e8c-96d2-ee885a3652cd", + "comment": "", + "command": "sendKeys", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "Johntest Adamstest" + }, { + "id": "7031b61e-f2b3-45d7-8f32-6a681c532dbd", + "comment": "Address", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'Street...')]", + "targets": [], + "value": "" + }, { + "id": "21eb921f-62f2-4ec7-b4d3-e53859a64764", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'Street...')]", + "targets": [], + "value": "101 Capitol Hill" + }, { + "id": "46165577-b4dc-419f-942c-382aac8b5a08", + "comment": "City", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'City')]", + "targets": [], + "value": "" + }, { + "id": "04267221-6626-42de-aaf1-0f1383e12796", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'City')]", + "targets": [], + "value": "Austin" + }, { + "id": "e02906ed-b3a0-465d-a636-a21509c65878", + "comment": "State", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'State')]", + "targets": [], + "value": "" + }, { + "id": "1454eb59-941b-4589-ba8c-e6f30edb4b9d", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'State')]", + "targets": [], + "value": "Texas" + }, { + "id": "5cfa9ce7-1131-4273-939d-97fba5b0f07a", + "comment": "", + "command": "click", + "target": "linkText=Texas", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "d4dfd065-b71f-42a0-82c7-61593c4680d3", + "comment": "County", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'County')]", + "targets": [], + "value": "" + }, { + "id": "fa3f8ee0-f5ff-4cbd-a939-945f397b7b15", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'County')]", + "targets": [], + "value": "Austin" + }, { + "id": "7ee7d8af-cf01-481e-902d-8eb6b1c0e877", + "comment": "", + "command": "click", + "target": "linkText=Austin", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "98be117c-137d-4b82-865c-7edc1974d49c", + "comment": "ZIP code", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'ZIP')]", + "targets": [ + ["css=.o_address_zip", "css:finder"], + ["xpath=(//input[@type='text'])[11]", "xpath:attributes"], + ["xpath=//div/input[4]", "xpath:position"] + ], + "value": "" + }, { + "id": "e043ebae-3061-4e80-aa1e-378b85beb5f0", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'ZIP')]", + "targets": [], + "value": "12345" + }, { + "id": "e69d395b-499f-49ef-911d-f1260d63e632", + "comment": "Country", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'Country')]", + "targets": [ + ["css=.o_address_country .o_form_input", "css:finder"], + ["xpath=(//input[@type='text'])[12]", "xpath:attributes"], + ["xpath=//div[4]/div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "c6f5aade-e4d0-4831-aec0-51f265f0820b", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'Country')]", + "targets": [], + "value": "United States" + }, { + "id": "f0f14cb8-149e-4032-b5a8-0936e3779133", + "comment": "", + "command": "click", + "target": "linkText=United States", + "targets": [ + ["linkText=United States", "linkText"], + ["css=#ui-id-95 > a", "css:finder"], + ["xpath=//a[contains(text(),'United States')]", "xpath:link"], + ["xpath=//li[@id='ui-id-95']/a", "xpath:idRelative"], + ["xpath=//ul[4]/li[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'United States')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "5520519c-0eef-456c-b953-114983529715", + "comment": "Phone", + "command": "click", + "target": "xpath=//tr/td/label[contains(.,'Phone')]/../../td/input", + "targets": [], + "value": "" + }, { + "id": "37f222c3-3db8-4c6b-b670-9cac325404e9", + "comment": "", + "command": "sendKeys", + "target": "xpath=//tr/td/label[contains(.,'Phone')]/../../td/input", + "targets": [], + "value": "(222) 333-4444" + }, { + "id": "da0479b1-5651-4290-be4f-1658e9ccc801", + "comment": "Email", + "command": "click", + "target": "xpath=//tr/td/label[contains(.,'Email')]/../../td/input", + "targets": [], + "value": "" + }, { + "id": "cc1400a1-254e-46cc-bd41-8787a52066d9", + "comment": "", + "command": "sendKeys", + "target": "xpath=//tr/td/label[contains(.,'Email')]/../../td/input", + "targets": [], + "value": "john@2ndpresident.gov" + }, { + "id": "fba56226-071d-469e-9a07-a690982b091e", + "comment": "Save record", + "command": "click", + "target": "xpath=//button[contains(@accesskey,'s')]", + "targets": [ + ["css=.o_form_button_save", "css:finder"], + ["xpath=(//button[@type='button'])[8]", "xpath:attributes"], + ["xpath=//div[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "266cd303-50b6-4385-ad4e-d6532564c78c", + "comment": "Check for Edit button", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@accesskey,'a')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "5e0cba73-330b-4818-803a-eb28dbb110c5", + "comment": "Check name", + "command": "waitForElementPresent", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "7ab4b295-c694-4b7a-b058-0818a585a2f8", + "comment": "Check name", + "command": "assertText", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "Johntest Adamstest" + }] + }, { + "id": "78cec64d-9db8-4284-8cce-62179181740b", + "name": "03 Create Community", + "commands": [{ + "id": "5537f874-0a18-466f-8a7c-b0b49f32b00a", + "comment": "Open Home Page", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "ec5d0355-c4fb-4490-ae0b-23f4b7873c65", + "comment": "Small window to ensure dropdown is present", + "command": "setWindowSize", + "target": "1200x800", + "targets": [], + "value": "" + }, { + "id": "1e74bce1-69db-4c55-849c-aa4820586eca", + "comment": "Check if user is logged in", + "command": "storeXpathCount", + "target": "xpath=//nav/a", + "targets": [], + "value": "homemenucount" + }, { + "id": "01796238-4039-47ca-9751-60b32adad7d2", + "comment": "If user is not logged in, log in", + "command": "if", + "target": "${homemenucount} < 1", + "targets": [], + "value": "" + }, { + "id": "9c78b3f1-cc25-453c-9dfe-04aa7b07bd81", + "comment": "", + "command": "click", + "target": "xpath=//label[contains(.,'Employee')]", + "targets": [], + "value": "" + }, { + "id": "119ab0e9-ef17-454e-8f18-60df6a097445", + "comment": "", + "command": "click", + "target": "xpath=//a[contains(@href,'/web/login')]", + "targets": [], + "value": "" + }, { + "id": "af5e1f7f-b886-4084-a346-723d14815e72", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "80820792-5741-4d93-b7ae-9f18929c9408", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "ebb57c0a-0970-4899-8aee-e8a15ffd6d1c", + "comment": "", + "command": "sendKeys", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "admin" + }, { + "id": "58af11a3-7248-4421-ac55-2e29d945b8a8", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "a50ebb66-0e0d-4dd0-a99b-c9e872195c99", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "f4fcff09-7da1-4308-a843-fcf999ff6c33", + "comment": "Password goes here", + "command": "sendKeys", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "PASSWORD" + }, { + "id": "c2d534f4-b5a6-4a07-88cc-338dc11c2622", + "comment": "", + "command": "click", + "target": "xpath=//button[contains(@type,'submit')]", + "targets": [], + "value": "" + }, { + "id": "675a5294-3221-4040-a76d-16faa94c6668", + "comment": "If user is logged in, go to App menu", + "command": "else", + "target": "", + "targets": [], + "value": "" + }, { + "id": "981e9086-2a37-49f8-a8e8-895b9de63f55", + "comment": "", + "command": "click", + "target": "xpath=//nav/a", + "targets": [ + ["css=.fa-th", "css:finder"], + ["xpath=//nav[@id='oe_main_menu_navbar']/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '#')]", "xpath:href"], + ["xpath=//a", "xpath:position"] + ], + "value": "" + }, { + "id": "5e2d1c57-db53-4408-9f4f-479d649d4295", + "comment": "", + "command": "end", + "target": "", + "targets": [], + "value": "" + }, { + "id": "5ed9dd2d-8b4d-4026-9b17-9b6e7e2b58ba", + "comment": "Open Contacts App", + "command": "click", + "target": "xpath=//a/div[contains(.,'Contacts')]", + "targets": [ + ["linkText=Contacts", "linkText"], + ["css=.o_app:nth-child(5)", "css:finder"], + ["xpath=//a[contains(@href, '#menu_id=612&action_id=677')]", "xpath:href"], + ["xpath=//a[5]", "xpath:position"] + ], + "value": "" + }, { + "id": "2150282e-14e0-4009-bb8f-084ad23d10b5", + "comment": "Open Communities", + "command": "click", + "target": "xpath=//a[contains(@class, 'o_menu_entry_lvl_2')]/span[contains(.,'Communities')]", + "targets": [], + "value": "" + }, { + "id": "be193f75-19d5-4dd0-856b-3c9539207ca7", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "f9584c2f-3fea-4dd6-8005-6de7a0266900", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "f56c0e54-0d7b-4da1-bc84-3c5d6b4ac2b9", + "comment": "Wait, otherwise wrong searchbar is selected", + "command": "waitForElementVisible", + "target": "xpath=//ol[contains(@class,'breadcrumb')]/li[contains(.,'Communities')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "d1499795-df59-4c7f-af42-b7681fd504ca", + "comment": "Search for existing record", + "command": "click", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "b4e9da90-098f-4cc0-a557-95fca3996dfe", + "comment": "", + "command": "type", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "Clevelandtest" + }, { + "id": "e1fb9b9b-a407-45c7-88cc-05385a80b93e", + "comment": "", + "command": "sendKeys", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "2a77d1e9-4dbb-45a4-b0ae-11fe01474a83", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "5005373d-0642-403a-a925-1ca0aaaf48ab", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "888745e2-859b-4986-bf78-1286710e447d", + "comment": "No existing record", + "command": "assertElementNotPresent", + "target": "xpath=//td[contains(@data-field, 'name')]", + "targets": [], + "value": "" + }, { + "id": "9503a542-cbc4-4499-9ef3-3dbb428058bb", + "comment": "Create new record", + "command": "click", + "target": "css=.o_list_button_add", + "targets": [ + ["css=.o_list_button_add", "css:finder"], + ["xpath=(//button[@type='button'])[6]", "xpath:attributes"], + ["xpath=//div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "045efaf0-8891-492b-a17b-75729674c5ae", + "comment": "Name", + "command": "click", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "" + }, { + "id": "59a15f7d-b777-4fb6-808a-55316526d889", + "comment": "", + "command": "sendKeys", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "Clevelandtest (AUS)" + }, { + "id": "f8cfe7cc-e977-4b6f-bec8-a4dd61f16828", + "comment": "City", + "command": "click", + "target": "xpath=//label[contains(.,'City')]/../../td/input", + "targets": [ + ["id=o_field_input_3", "id"], + ["css=#o_field_input_3", "css:finder"], + ["xpath=//input[@id='o_field_input_3']", "xpath:attributes"], + ["xpath=//td[2]/input", "xpath:position"] + ], + "value": "" + }, { + "id": "d9f7ab62-b14e-4b91-b1cc-234a8919befc", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(.,'City')]/../../td/input", + "targets": [], + "value": "Austin, TX" + }, { + "id": "d1e7acd4-f749-48a8-8242-25f56b7a17f5", + "comment": "ZIP", + "command": "click", + "target": "xpath=//label[contains(.,'Zip Code')]/../../td/input", + "targets": [], + "value": "" + }, { + "id": "6f6f2fd9-e58e-454c-9007-b7fefa19627b", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(.,'Zip Code')]/../../td/input", + "targets": [], + "value": "76060" + }, { + "id": "705f2dd6-8df7-4891-9191-acc6b2ef0c71", + "comment": "Save record", + "command": "click", + "target": "xpath=//button[contains(@accesskey,'s')]", + "targets": [ + ["css=.o_form_button_save", "css:finder"], + ["xpath=(//button[@type='button'])[8]", "xpath:attributes"], + ["xpath=//div[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "b43d0032-b0d6-4342-82c0-6fb16327010e", + "comment": "Check for Edit button", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@accesskey,'a')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "e825142b-18af-4601-a29c-c6a141c8550f", + "comment": "Check name", + "command": "waitForElementPresent", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "3cea3e0f-6cce-474c-ac41-93a57a21301e", + "comment": "Check name", + "command": "assertText", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "Clevelandtest (AUS)" + }] + }, { + "id": "e96d4fb8-2c4f-46d3-8c5f-31aadc72a661", + "name": "04 Create Community Line", + "commands": [{ + "id": "23195cbe-0a25-461c-9e09-686e2a6cbf62", + "comment": "Open Home Page", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "f6ff7d9b-e1ef-4211-a444-2f37343ffd7f", + "comment": "Small window to ensure dropdown is present", + "command": "setWindowSize", + "target": "1200x800", + "targets": [], + "value": "" + }, { + "id": "59742bc3-8f6e-41d2-ab2d-5828f7d0fcd2", + "comment": "Check if user is logged in", + "command": "storeXpathCount", + "target": "xpath=//nav/a", + "targets": [], + "value": "homemenucount" + }, { + "id": "302b7605-8fd1-4e9d-b759-2a86f22ddbdb", + "comment": "If user is not logged in, log in", + "command": "if", + "target": "${homemenucount} < 1", + "targets": [], + "value": "" + }, { + "id": "f848c932-40cc-499e-af43-75e5b9e330d8", + "comment": "", + "command": "click", + "target": "xpath=//label[contains(.,'Employee')]", + "targets": [], + "value": "" + }, { + "id": "632dbc0e-883c-4019-8b23-ae009522f25e", + "comment": "", + "command": "click", + "target": "xpath=//a[contains(@href,'/web/login')]", + "targets": [], + "value": "" + }, { + "id": "f05ad0d2-2a8d-470d-ab8a-bd24074d3c16", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "ae614b97-629d-4e23-a20e-79be516552d9", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "92bd09d9-4d37-474e-9631-8995c49d84f7", + "comment": "", + "command": "sendKeys", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "admin" + }, { + "id": "d259f799-a36b-4aa4-b175-2ae502d9d87a", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "852a0a69-6490-4260-8ec6-dd3791623eb4", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "1f477f4a-7080-436b-b1fb-d8e5ddc7748d", + "comment": "Password goes here", + "command": "sendKeys", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "PASSWORD" + }, { + "id": "5900f2c2-b979-49f5-b834-f5173a69627b", + "comment": "", + "command": "click", + "target": "xpath=//button[contains(@type,'submit')]", + "targets": [], + "value": "" + }, { + "id": "203f5b08-cdbd-41ea-aae5-ae5ba4ecf3b9", + "comment": "If user is logged in, go to App menu", + "command": "else", + "target": "", + "targets": [], + "value": "" + }, { + "id": "f4d05463-353c-42d6-96d8-a536cd1f4378", + "comment": "", + "command": "click", + "target": "xpath=//nav/a", + "targets": [ + ["css=.fa-th", "css:finder"], + ["xpath=//nav[@id='oe_main_menu_navbar']/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '#')]", "xpath:href"], + ["xpath=//a", "xpath:position"] + ], + "value": "" + }, { + "id": "f048d368-6af6-45c1-86ab-de18bbce9935", + "comment": "", + "command": "end", + "target": "", + "targets": [], + "value": "" + }, { + "id": "bdd491a1-a0a6-4490-81d9-a1ba04b390bb", + "comment": "Open Contacts App", + "command": "click", + "target": "xpath=//a/div[contains(.,'Contacts')]", + "targets": [ + ["linkText=Contacts", "linkText"], + ["css=.o_app:nth-child(5)", "css:finder"], + ["xpath=//a[contains(@href, '#menu_id=612&action_id=677')]", "xpath:href"], + ["xpath=//a[5]", "xpath:position"] + ], + "value": "" + }, { + "id": "f95707e6-3381-44e1-9f29-04f6a175aeb1", + "comment": "Open Configuration dropdown", + "command": "click", + "target": "xpath=//a[contains(.,'Configuration')]", + "targets": [], + "value": "" + }, { + "id": "0e039afa-50c0-4c75-98b0-ab493d263fd5", + "comment": "Open Community Lines", + "command": "click", + "target": "xpath=//a[contains(@class, 'o_menu_entry_lvl_2')]/span[text()='Community Line']", + "targets": [], + "value": "" + }, { + "id": "c8f2e953-95f2-4402-a400-ae1414164da5", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "96e61b58-e7bb-4ba3-97d7-e4cdfb195592", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "ca663002-7a34-4f3e-a20e-c2e2108dc0f2", + "comment": "Wait, otherwise wrong searchbar is selected", + "command": "waitForElementVisible", + "target": "xpath=//ol[contains(@class,'breadcrumb')]/li[contains(.,'Community Line')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "b58cee0e-0c16-433f-bb13-76c509187a94", + "comment": "Search for existing record", + "command": "click", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "ecc93919-e40f-4cb5-bc89-d890de308ab6", + "comment": "", + "command": "type", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "Clevelandtest" + }, { + "id": "185dc99e-1027-42aa-a300-f8b7120b1707", + "comment": "", + "command": "sendKeys", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "78197d06-5d4f-4b25-965b-df2c87db48e6", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "29953a99-c2fa-42c6-9e73-4e4d328393bd", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "c16da2fc-1083-4626-83aa-df2bef43a2dc", + "comment": "No existing record", + "command": "assertElementNotPresent", + "target": "xpath=//td[contains(@data-field,'community_id')]", + "targets": [], + "value": "" + }, { + "id": "5e753a62-ccf4-4f38-99e2-d1f1292e2f7c", + "comment": "Create new record", + "command": "click", + "target": "css=.o_list_button_add", + "targets": [ + ["css=.o_list_button_add", "css:finder"], + ["xpath=(//button[@type='button'])[6]", "xpath:attributes"], + ["xpath=//div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "14629213-9131-40b7-a151-1161ef210e3d", + "comment": "Community", + "command": "click", + "target": "xpath=//label[contains(.,'Community')]/../../td/div/div/input", + "targets": [ + ["id=o_field_input_3", "id"], + ["css=#o_field_input_3", "css:finder"], + ["xpath=//input[@id='o_field_input_3']", "xpath:attributes"], + ["xpath=//td[2]/input", "xpath:position"] + ], + "value": "" + }, { + "id": "5eb98ad6-e2a7-414a-88f1-e6520d829b54", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(.,'Community')]/../../td/div/div/input", + "targets": [], + "value": "Clevelandtest" + }, { + "id": "c5b17e24-ddf1-4d9e-91fa-7cf4cbb0e48e", + "comment": "", + "command": "click", + "target": "linkText=Clevelandtest (AUS)", + "targets": [], + "value": "" + }, { + "id": "43be2a4c-e424-4fd1-b705-46995a8ff8f4", + "comment": "Builder", + "command": "click", + "target": "xpath=//label[contains(.,'Home Builder')]/../../td/div/div/input", + "targets": [], + "value": "" + }, { + "id": "ba60dd60-c37d-4401-9c6f-bed690864b7c", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(.,'Home Builder')]/../../td/div/div/input", + "targets": [], + "value": "Presidentialtest" + }, { + "id": "3f5b83ba-a024-40ed-9445-6facf5b32b84", + "comment": "", + "command": "click", + "target": "linkText=Presidentialtest AUS", + "targets": [], + "value": "" + }, { + "id": "128dff32-cb8b-4946-b478-acd0078644ae", + "comment": "Save record", + "command": "click", + "target": "xpath=//button[contains(@accesskey,'s')]", + "targets": [ + ["css=.o_form_button_save", "css:finder"], + ["xpath=(//button[@type='button'])[8]", "xpath:attributes"], + ["xpath=//div[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "c20c73e8-7634-401f-9f81-c4d68a73227e", + "comment": "Check for Edit button", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@accesskey,'a')]", + "targets": [], + "value": "BASETIMEOUT" + }] + }, { + "id": "3c43015d-a816-48ba-ad69-1cd7b2d57324", + "name": "05 Create Site", + "commands": [{ + "id": "3f2b43a6-809e-42fd-b729-f1670d5eb541", + "comment": "Open Home Page", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "319a7d7f-c7c3-403e-980e-11ed597843c1", + "comment": "Small window to ensure dropdown is present", + "command": "setWindowSize", + "target": "1200x800", + "targets": [], + "value": "" + }, { + "id": "9631a45e-0edf-444f-9d21-8a006c9946a9", + "comment": "Check if user is logged in", + "command": "storeXpathCount", + "target": "xpath=//nav/a", + "targets": [], + "value": "homemenucount" + }, { + "id": "11a5c2be-d2ea-4ff4-940c-c66a4377f865", + "comment": "If user is not logged in, log in", + "command": "if", + "target": "${homemenucount} < 1", + "targets": [], + "value": "" + }, { + "id": "445de2ac-df26-4fb9-b19d-63bf2c327f88", + "comment": "", + "command": "click", + "target": "xpath=//label[contains(.,'Employee')]", + "targets": [], + "value": "" + }, { + "id": "7f81e029-8bdc-4a3b-b9a0-757e2abd54c6", + "comment": "", + "command": "click", + "target": "xpath=//a[contains(@href,'/web/login')]", + "targets": [], + "value": "" + }, { + "id": "05eb2526-b9be-4580-a914-e130db7232b5", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "845daa06-bca6-4e29-8dac-6445fa96cf86", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "9925a3e6-7ea6-4dfc-88ba-a7b8ecb13369", + "comment": "", + "command": "sendKeys", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "admin" + }, { + "id": "fc73a84a-0d14-45ba-aaf0-4b4da53df793", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "c90c75a0-c128-4bf3-971b-008b9755cf35", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "b04277f5-49e2-426f-84a9-e3b699b206e2", + "comment": "Password goes here", + "command": "sendKeys", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "PASSWORD" + }, { + "id": "971affca-ee9f-4de7-987d-9201c63cbfa5", + "comment": "", + "command": "click", + "target": "xpath=//button[contains(@type,'submit')]", + "targets": [], + "value": "" + }, { + "id": "0ac570cf-31da-4bbb-a998-bd8634555bdf", + "comment": "If user is logged in, go to App menu", + "command": "else", + "target": "", + "targets": [], + "value": "" + }, { + "id": "937d14db-0c5a-4c0d-9c42-d1b76db39065", + "comment": "", + "command": "click", + "target": "xpath=//nav/a", + "targets": [ + ["css=.fa-th", "css:finder"], + ["xpath=//nav[@id='oe_main_menu_navbar']/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '#')]", "xpath:href"], + ["xpath=//a", "xpath:position"] + ], + "value": "" + }, { + "id": "b5ef1f0f-f633-4259-a682-b5c694f2bab9", + "comment": "", + "command": "end", + "target": "", + "targets": [], + "value": "" + }, { + "id": "9b6fc802-ce6f-49c4-8633-ddd6f5a86185", + "comment": "Open Contacts App", + "command": "click", + "target": "xpath=//a/div[contains(.,'Contacts')]", + "targets": [ + ["linkText=Contacts", "linkText"], + ["css=.o_app:nth-child(5)", "css:finder"], + ["xpath=//a[contains(@href, '#menu_id=612&action_id=677')]", "xpath:href"], + ["xpath=//a[5]", "xpath:position"] + ], + "value": "" + }, { + "id": "c2ac7346-0b4c-44e4-a651-15ddc0ca0823", + "comment": "Open Kanban view", + "command": "click", + "target": "xpath=//button[contains(@data-view-type,'kanban')]", + "targets": [], + "value": "" + }, { + "id": "4a351dee-52b7-47b4-bcc8-4b743139c4c1", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "0822b892-2178-4e2f-a8da-fcee64902039", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "03ba8141-9ba0-487e-8844-84fb1c85a11e", + "comment": "Wait, otherwise wrong searchbar is selected", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@class,'o-kanban-button-new')][contains(@accesskey,'c')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "fdd58849-f36e-41fc-a4f2-d236bbcabd11", + "comment": "Search for existing record", + "command": "click", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "45db32e5-d0ff-4b9a-83a2-74a70021bb72", + "comment": "", + "command": "type", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "Eisenhowertestt" + }, { + "id": "5675c9c9-4c27-4b8d-9ad6-3a42241fd88c", + "comment": "", + "command": "sendKeys", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "8cbbde31-ebca-462c-a823-ec4138552a9f", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "e80c50e8-1ee2-4843-b4d9-8c5670fe7dc8", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "89cc53b4-7ac9-439c-9d6e-0b3447cc1b5d", + "comment": "No existing record", + "command": "assertElementNotPresent", + "target": "xpath=//div[contains(@class, 'o_kanban_image')]", + "targets": [], + "value": "" + }, { + "id": "6bc81bd2-d68f-4539-9607-2ac58570444b", + "comment": "Create new record", + "command": "click", + "target": "css=.o-kanban-button-new", + "targets": [ + ["css=.o-kanban-button-new", "css:finder"], + ["xpath=(//button[@type='button'])[6]", "xpath:attributes"], + ["xpath=//div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "62c9c190-f1af-47a4-9dc5-b21e0ebcfd9c", + "comment": "Check Site", + "command": "click", + "target": "xpath=//input[contains(@name, 'is_site')]", + "targets": [], + "value": "" + }, { + "id": "3c8a8238-5611-4b7b-978c-8c3f3b9852cd", + "comment": "Name", + "command": "click", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "" + }, { + "id": "e7c75fa4-d9ff-4d41-b2a7-a56931aa1e1e", + "comment": "", + "command": "sendKeys", + "target": "xpath=//h1/input[contains(@placeholder, 'Name')]", + "targets": [], + "value": "100 Eisenhowertestt Ave" + }, { + "id": "2f27d032-e47f-43c2-b404-02474ba36f05", + "comment": "Address", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'Street...')]", + "targets": [], + "value": "" + }, { + "id": "ef2a1d04-618e-4864-8e36-5dc0e80830be", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'Street...')]", + "targets": [], + "value": "100 Eisenhowertestt Ave" + }, { + "id": "a8fcd049-a577-4ad8-9d6a-75e172ff8751", + "comment": "City", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'City')]", + "targets": [], + "value": "" + }, { + "id": "02d60144-fbd7-4779-9afc-6f73c80cc0d1", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'City')]", + "targets": [], + "value": "Austin" + }, { + "id": "a1472ac7-8004-404d-9ce7-baf4831c1d8e", + "comment": "State", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'State')]", + "targets": [], + "value": "" + }, { + "id": "bb9f70eb-9bb2-434f-ae26-445d290bb079", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'State')]", + "targets": [], + "value": "Texas" + }, { + "id": "905aa1e2-96c7-49ad-9f0a-52e4db7b30ed", + "comment": "", + "command": "click", + "target": "linkText=Texas", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "99f16d55-b3ac-4957-a993-29295004d46c", + "comment": "County", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'County')]", + "targets": [], + "value": "" + }, { + "id": "3d20ca95-ec72-43e4-9208-bf24e01ac427", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'County')]", + "targets": [], + "value": "Austin" + }, { + "id": "5db74ab9-7932-49cb-8919-777e88204527", + "comment": "", + "command": "click", + "target": "linkText=Austin", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "5ebc482c-b54f-4ec8-97aa-9c1696bccb4c", + "comment": "ZIP code", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'ZIP')]", + "targets": [ + ["css=.o_address_zip", "css:finder"], + ["xpath=(//input[@type='text'])[11]", "xpath:attributes"], + ["xpath=//div/input[4]", "xpath:position"] + ], + "value": "" + }, { + "id": "6bf9da3c-42c6-4f0f-9592-c8657955a884", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/input[contains(@placeholder, 'ZIP')]", + "targets": [], + "value": "12345" + }, { + "id": "68d7d31a-f1be-4ee0-8326-fc7814fcf9b4", + "comment": "Country", + "command": "click", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'Country')]", + "targets": [ + ["css=.o_address_country .o_form_input", "css:finder"], + ["xpath=(//input[@type='text'])[12]", "xpath:attributes"], + ["xpath=//div[4]/div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "e26fc2e7-ac39-4aa2-afb7-de1255ccaa23", + "comment": "", + "command": "sendKeys", + "target": "xpath=//div[contains(@class, 'o_address_format')]/div/div/input[contains(@placeholder, 'Country')]", + "targets": [], + "value": "United States" + }, { + "id": "0a492957-b165-4100-ab19-4dcfc5bb863c", + "comment": "", + "command": "click", + "target": "linkText=United States", + "targets": [ + ["linkText=United States", "linkText"], + ["css=#ui-id-95 > a", "css:finder"], + ["xpath=//a[contains(text(),'United States')]", "xpath:link"], + ["xpath=//li[@id='ui-id-95']/a", "xpath:idRelative"], + ["xpath=//ul[4]/li[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'United States')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "fe300385-2234-4b0b-b060-41fd2e03b852", + "comment": "Owner", + "command": "click", + "target": "xpath=//tr/td/label[contains(.,'Current Owner')]/../../td/div/div/input", + "targets": [], + "value": "" + }, { + "id": "2e6a1a59-65b8-4c59-86e1-4b6d704b5066", + "comment": "", + "command": "sendKeys", + "target": "xpath=//tr/td/label[contains(.,'Current Owner')]/../../td/div/div/input", + "targets": [], + "value": "Johntest Adamstest" + }, { + "id": "7c3dee18-ec8d-42d1-b918-1e58a593f2e1", + "comment": "", + "command": "click", + "target": "linkText=Johntest Adamstest", + "targets": [ + ["linkText=United States", "linkText"], + ["css=#ui-id-95 > a", "css:finder"], + ["xpath=//a[contains(text(),'United States')]", "xpath:link"], + ["xpath=//li[@id='ui-id-95']/a", "xpath:idRelative"], + ["xpath=//ul[4]/li[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'United States')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "b63bb288-06a8-4688-9acf-c28c6d0ea9d2", + "comment": "Open Home Details tab", + "command": "click", + "target": "xpath=//li/a[contains(.,'Home Details')]", + "targets": [], + "value": "" + }, { + "id": "715ddb95-2c27-4997-813e-b4738eeb2f3a", + "comment": "Builder", + "command": "click", + "target": "xpath=//tr/td/label[contains(.,'Home Builder')]/../../td/div/div/input", + "targets": [], + "value": "" + }, { + "id": "191828af-4b76-43db-be59-5bf8cd3bf3a7", + "comment": "", + "command": "sendKeys", + "target": "xpath=//tr/td/label[contains(.,'Home Builder')]/../../td/div/div/input", + "targets": [], + "value": "presidentialtest" + }, { + "id": "5afc8a1f-cc6d-4355-926c-ebf97f74def8", + "comment": "", + "command": "click", + "target": "linkText=Presidentialtest AUS", + "targets": [ + ["linkText=United States", "linkText"], + ["css=#ui-id-95 > a", "css:finder"], + ["xpath=//a[contains(text(),'United States')]", "xpath:link"], + ["xpath=//li[@id='ui-id-95']/a", "xpath:idRelative"], + ["xpath=//ul[4]/li[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'United States')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "ee9f3faa-cc8c-4516-ba8b-509b271a5a85", + "comment": "Community", + "command": "click", + "target": "xpath=//tr/td/label[normalize-space(text())='Community']/../../td/div/div/input", + "targets": [], + "value": "" + }, { + "id": "c20d465d-ac8e-4fc0-b92f-6af4d0eef228", + "comment": "", + "command": "sendKeys", + "target": "xpath=//tr/td/label[normalize-space(text())='Community']/../../td/div/div/input", + "targets": [], + "value": "clevelandtest" + }, { + "id": "ac299695-b5c9-4338-9035-7db7d2fcd018", + "comment": "", + "command": "click", + "target": "linkText=Clevelandtest (AUS)", + "targets": [ + ["linkText=United States", "linkText"], + ["css=#ui-id-95 > a", "css:finder"], + ["xpath=//a[contains(text(),'United States')]", "xpath:link"], + ["xpath=//li[@id='ui-id-95']/a", "xpath:idRelative"], + ["xpath=//ul[4]/li[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'United States')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "5fb7fd1b-c8e6-4cf0-bd05-eea388e7ef51", + "comment": "Save record", + "command": "click", + "target": "xpath=//button[contains(@accesskey,'s')]", + "targets": [ + ["css=.o_form_button_save", "css:finder"], + ["xpath=(//button[@type='button'])[8]", "xpath:attributes"], + ["xpath=//div[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "3b87ff51-e682-49ba-8b8b-7ed5956393d7", + "comment": "Check for Edit button", + "command": "waitForElementVisible", + "target": "xpath=//button[contains(@accesskey,'a')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "6deca0ed-b9bc-4b7e-aea8-7e3ff0dfaeb8", + "comment": "", + "command": "waitForElementPresent", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "593ac4a5-7815-42f0-bda1-518d59b6dcee", + "comment": "Check name", + "command": "assertText", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "100 Eisenhowertestt Ave" + }] + }, { + "id": "9326d358-c83d-489c-9b3c-f6b2784ea50a", + "name": "06 Create Lead", + "commands": [{ + "id": "f618d5e7-cc7f-409e-8c0c-ae4db0b9a538", + "comment": "Open Home Page", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "71120c12-b3da-4a1e-8db2-52cd72fad6d5", + "comment": "Small window to ensure dropdown is present", + "command": "setWindowSize", + "target": "1200x800", + "targets": [], + "value": "" + }, { + "id": "2aca38a9-cc0d-4e46-b98b-4c75c7a7a691", + "comment": "Check if user is logged in", + "command": "storeXpathCount", + "target": "xpath=//nav/a", + "targets": [], + "value": "homemenucount" + }, { + "id": "4728a878-5133-40ad-8e7d-961737cddc2e", + "comment": "If user is not logged in, log in", + "command": "if", + "target": "${homemenucount} < 1", + "targets": [], + "value": "" + }, { + "id": "7632fd6d-ec93-49d2-bfd6-bbb0811967f7", + "comment": "", + "command": "click", + "target": "xpath=//label[contains(.,'Employee')]", + "targets": [], + "value": "" + }, { + "id": "fa1cb539-0d95-4ba3-ad1b-bc5d83a0a467", + "comment": "", + "command": "click", + "target": "xpath=//a[contains(@href,'/web/login')]", + "targets": [], + "value": "" + }, { + "id": "c993bd51-5822-4f56-8d39-2e2338d37b82", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "f718b934-add8-4202-b592-5d7628fce36f", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "" + }, { + "id": "973e951a-becd-46db-8be9-22a9f7fe5cea", + "comment": "", + "command": "sendKeys", + "target": "xpath=//input[@id='login']", + "targets": [], + "value": "admin" + }, { + "id": "76151998-606c-4c93-9bc0-a4b9f5de028e", + "comment": "", + "command": "click", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "a7be662f-8709-4246-9822-17c988445a36", + "comment": "", + "command": "type", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "" + }, { + "id": "1dcdd704-9580-4a65-bf1f-6d7a0186d23a", + "comment": "Password goes here", + "command": "sendKeys", + "target": "xpath=//input[@id='password']", + "targets": [], + "value": "PASSWORD" + }, { + "id": "0eaee9a7-7744-4f9b-a665-35eb57feb300", + "comment": "", + "command": "click", + "target": "xpath=//button[contains(@type,'submit')]", + "targets": [], + "value": "" + }, { + "id": "08ac3308-ee0a-41d1-ae1b-4575a42d79c4", + "comment": "If user is logged in, go to App menu", + "command": "else", + "target": "", + "targets": [], + "value": "" + }, { + "id": "8465b231-918a-42f4-975a-9a6371fe0aa2", + "comment": "", + "command": "click", + "target": "xpath=//nav/a", + "targets": [ + ["css=.fa-th", "css:finder"], + ["xpath=//nav[@id='oe_main_menu_navbar']/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '#')]", "xpath:href"], + ["xpath=//a", "xpath:position"] + ], + "value": "" + }, { + "id": "6ffeaf15-cb6a-4b30-816f-efad873fc73b", + "comment": "", + "command": "end", + "target": "", + "targets": [], + "value": "" + }, { + "id": "908b9299-735d-484f-97c9-9b7fc935ff2d", + "comment": "Open Sales App", + "command": "click", + "target": "xpath=//a/div[contains(.,'Sales')]", + "targets": [ + ["linkText=Contacts", "linkText"], + ["css=.o_app:nth-child(5)", "css:finder"], + ["xpath=//a[contains(@href, '#menu_id=612&action_id=677')]", "xpath:href"], + ["xpath=//a[5]", "xpath:position"] + ], + "value": "" + }, { + "id": "7897b3fa-952c-4791-bed1-1d65d2eff75c", + "comment": "Open Pipeline", + "command": "click", + "target": "xpath=//div[text()='Lead Generation']/../../..//button[text()='Pipeline']", + "targets": [], + "value": "" + }, { + "id": "05e13f26-e415-4834-8506-4f21278b68ae", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "368c595e-c9ad-447f-b96b-dff8c4d6651e", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "d370abd2-0ec7-485e-87bd-ecc4ec1d4bd4", + "comment": "Wait for page to load", + "command": "waitForElementVisible", + "target": "xpath=//ol[contains(@class,'breadcrumb')]/li[contains(.,'Opportunities')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "a86104bb-f9af-403a-9882-881685b85a10", + "comment": "Open Kanban view", + "command": "click", + "target": "xpath=//button[contains(@data-view-type,'kanban')]", + "targets": [], + "value": "" + }, { + "id": "6e4daa2c-5046-4549-bf11-ca962c84944e", + "comment": "Search for existing record", + "command": "click", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "a41bca20-7d97-4e79-a914-d01d4e6ddaa8", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "3c26ae34-080e-408a-b69c-66232fe1d7a8", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "bc48c28f-83fd-4c65-b6e8-64f13e1d7a05", + "comment": "Searchbar", + "command": "click", + "target": "css=.o_searchview_input", + "targets": [], + "value": "" + }, { + "id": "c05f63c8-932b-4e41-866d-16f950c08f46", + "comment": "", + "command": "type", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "Eisenhowertestt" + }, { + "id": "238d5cda-f507-4f73-9b54-1f6bd96f2f4f", + "comment": "", + "command": "sendKeys", + "target": "css=.o_searchview_input", + "targets": [ + ["css=.o_searchview_input", "css:finder"], + ["xpath=//input[@type='text']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "8ece37e3-9147-4107-b357-23ca5eabdc4e", + "comment": "Wait for Loading Message", + "command": "waitForElementVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "3ea52677-271c-4bc7-ae58-c18eb8ea9f50", + "comment": "", + "command": "waitForElementNotVisible", + "target": "xpath=//div[contains(@class,'o_loading')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "c2c27a05-3173-4e0e-9531-5a4f63f0e20f", + "comment": "No existing record", + "command": "assertElementNotPresent", + "target": "css=.o_kanban_record", + "targets": [], + "value": "" + }, { + "id": "a9490692-f072-4473-a30d-e2709da28e7f", + "comment": "List view", + "command": "click", + "target": "xpath=//button[contains(@accesskey,'l')]", + "targets": [ + ["css=.o-kanban-button-new", "css:finder"], + ["xpath=(//button[@type='button'])[6]", "xpath:attributes"], + ["xpath=//div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "5aeae281-53b2-4c05-b733-592eec7fbb1a", + "comment": "Create new record", + "command": "click", + "target": "css=.o_list_button_add", + "targets": [ + ["css=.o-kanban-button-new", "css:finder"], + ["xpath=(//button[@type='button'])[6]", "xpath:attributes"], + ["xpath=//div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "aeac8da0-c133-4051-affc-26155c46b271", + "comment": "Subject", + "command": "click", + "target": "xpath=//h1/input[contains(@placeholder, 'e.g. Product Pricing')]", + "targets": [], + "value": "" + }, { + "id": "a4caf133-671c-4ec0-a683-2c017682f904", + "comment": "", + "command": "sendKeys", + "target": "xpath=//h1/input[contains(@placeholder, 'e.g. Product Pricing')]", + "targets": [], + "value": "100 Eisenhowertestt Ave" + }, { + "id": "5e2af64b-ceb5-4d4f-a746-0acc759c71aa", + "comment": "Revenue", + "command": "click", + "target": "xpath=//label[contains(text(),'Expected Revenue')]/../div/div/input", + "targets": [], + "value": "" + }, { + "id": "1fd0c217-6e35-4417-aa31-e626909f9a75", + "comment": "", + "command": "doubleClick", + "target": "xpath=//label[contains(text(),'Expected Revenue')]/../div/div/input", + "targets": [], + "value": "" + }, { + "id": "c4ddba98-6174-435a-a02e-95d1f7db0909", + "comment": "", + "command": "type", + "target": "xpath=//label[contains(text(),'Expected Revenue')]/../div/div/input", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "61532852-d42a-4b70-9af9-2e29cf94fa50", + "comment": "Billing", + "command": "click", + "target": "xpath=//label[contains(text(),'Contact for Billing')]/../../td/div/div/input", + "targets": [], + "value": "" + }, { + "id": "1dcc69ff-ee61-4434-8d20-499150c3ab78", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(text(),'Contact for Billing')]/../../td/div/div/input", + "targets": [], + "value": "presidentialtest" + }, { + "id": "640647d0-c914-4e46-8d82-b50980704e5b", + "comment": "", + "command": "click", + "target": "linkText=Presidentialtest AUS", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "72f45337-c14f-41ec-bb1f-55f442f7753f", + "comment": "Rating", + "command": "click", + "target": "xpath=//a[contains(@title,'High')]", + "targets": [ + ["css=.o_priority_star:nth-child(2)", "css:finder"], + ["xpath=(//a[contains(@href, '#')])[77]", "xpath:href"], + ["xpath=//a[2]", "xpath:position"] + ], + "value": "" + }, { + "id": "91c85698-6c1b-48a5-bc3c-dba62bf7ed49", + "comment": "Site", + "command": "click", + "target": "xpath=//label[contains(text(),'Site')]/../../td/div/div/input", + "targets": [], + "value": "" + }, { + "id": "89418c9b-68ae-4e73-8142-71702b2fdabc", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(text(),'Site')]/../../td/div/div/input", + "targets": [], + "value": "eisenhowertestt" + }, { + "id": "85692cab-7143-4cc6-8dbc-c52d80af8c68", + "comment": "", + "command": "click", + "target": "linkText=100 Eisenhowertestt Ave", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "7a936cda-733c-4a2d-9fa1-a969f4e28fe2", + "comment": "Contract Type", + "command": "click", + "target": "xpath=//label[contains(text(),'Contract Type')]/../../td/div/div/input", + "targets": [], + "value": "" + }, { + "id": "3596fad8-a9e5-483b-9fa2-ec708f916234", + "comment": "", + "command": "sendKeys", + "target": "xpath=//label[contains(text(),'Contract Type')]/../../td/div/div/input", + "targets": [], + "value": "buyer" + }, { + "id": "4fa3fcfa-770e-49c8-88e4-4af04d493c09", + "comment": "", + "command": "click", + "target": "linkText=Buyer/Sold Home", + "targets": [ + ["linkText=Texas", "linkText"], + ["css=#ui-id-62 > a", "css:finder"], + ["xpath=//a[contains(text(),'Texas')]", "xpath:link"], + ["xpath=//li[@id='ui-id-62']/a", "xpath:idRelative"], + ["xpath=//body/ul[2]/li/a", "xpath:position"], + ["xpath=//a[contains(.,'Texas')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "69b211e6-f4e9-408d-ab21-366063b3052a", + "comment": "Save record", + "command": "click", + "target": "xpath=//button[contains(@accesskey,'s')]", + "targets": [ + ["css=.o_form_button_save", "css:finder"], + ["xpath=(//button[@type='button'])[8]", "xpath:attributes"], + ["xpath=//div[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "27469a62-f726-4cbf-8861-2aa3c1fedc1e", + "comment": "Check for Edit button", + "command": "waitForElementPresent", + "target": "xpath=//button[contains(@accesskey,'a')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "81b65f93-021d-4001-ac2b-f9cc855b10f1", + "comment": "Check name", + "command": "waitForElementPresent", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "BASETIMEOUT" + }, { + "id": "2de41df9-65cb-4b08-980b-7d6968fc166b", + "comment": "Check name", + "command": "assertText", + "target": "xpath=//h1/span[contains(@class,'o_form_field')]", + "targets": [], + "value": "100 Eisenhowertestt Ave" + }] + }], + "suites": [{ + "id": "c8214252-2d03-4db0-a68e-d100bc0a6dea", + "name": "01_Create_Owner", + "persistSession": false, + "parallel": false, + "timeout": 900, + "tests": ["4d2b4a98-471c-4430-b05b-fb2d0342bee9"] + }, { + "id": "d4fc5554-e369-4790-8c3b-3f013d47ec68", + "name": "02_Create_Builder", + "persistSession": false, + "parallel": false, + "timeout": 900, + "tests": ["8f15db82-4f3b-4944-8ba9-6c16f2fb8645"] + }, { + "id": "3768caf0-3150-43a4-8130-4cc98208b647", + "name": "03_Create_Community", + "persistSession": false, + "parallel": false, + "timeout": 900, + "tests": ["78cec64d-9db8-4284-8cce-62179181740b"] + }, { + "id": "f21d1fe4-d518-4d71-b3a8-739df654bb68", + "name": "04_Create_Community_Line", + "persistSession": false, + "parallel": false, + "timeout": 900, + "tests": ["e96d4fb8-2c4f-46d3-8c5f-31aadc72a661"] + }, { + "id": "87801708-e850-40a4-a207-98b3f909a643", + "name": "05_Create_Site", + "persistSession": false, + "parallel": false, + "timeout": 900, + "tests": ["3c43015d-a816-48ba-ad69-1cd7b2d57324"] + }, { + "id": "ff65a4da-83fa-470a-8ddb-d5964552f0cd", + "name": "06_Create_Lead", + "persistSession": false, + "parallel": false, + "timeout": 900, + "tests": ["9326d358-c83d-489c-9b3c-f6b2784ea50a"] + }], + "urls": ["https://HOST/web?db=DATABASE"], + "plugins": [] +} diff --git a/odoo/tests/selenium/README.md b/odoo/tests/selenium/README.md new file mode 100644 index 0000000..e8feca9 --- /dev/null +++ b/odoo/tests/selenium/README.md @@ -0,0 +1,61 @@ +# Functional Tests using Selenium + +## Installation + +* Run +```shell script +python3 -m venv env +. env/bin/activate +pip install -r requirements.txt +``` +* Download Selenium IDE from the [Selenium website](https://www.selenium.dev/selenium-ide). + Choose the Chrome or Firefox version based on your web browser of choice + +## Configuration + +The Selenium tests require a database without previous test data. +If data from previous Selenium tests are found, the tests will fail. + +## Writing Tests in IDE + +* At the top left, near the run buttons, select the plus (+) sign to create a new test in the project +* At the top right, select the "Rec" button (rec circle) to begin recording a workflow +* A new browser window will open. Selenium will record your actions as you perform each step of the workflow +* When the workflow is complete, return to the Selenium window and select the Stop button (replaces Rec button) +* The Selenium tests require unique identifiers for each webpage element it interacts with. + + * Many of the default Targets selected by the recording will need to be modified to ensure that they are truly unique + * This typically includes replacing any use of indices (e.g. "//ul[2]") + * This may require referring to a parent element first, then the desired element by inheritance + +* It may be helpful to include assertions during the test to check if things are going well + + * Right click on an existing step and select "Insert new command" + * See the `Selenium Command API `_ for more details + +* Save the test when it is complete, and take it for a test run + +## Running Tests Manually through IDE + +* Click on the Se extension icon in the top right of your browser window +* Select "Open an existing project" +* Select a `.side` file from this directory +* Near the top left of the Selenium popup window, there are two Triangular ("Play") buttons. + Select the left button with lines to run all tests, or run a test individually with the second button + +## Interpreting Results + +The log will return a success message if every step is completed. + +Any step that fails, whether by a missed assertion or by timeout, will stop the test +with a message in the log explaining which step failed and why. + +If an Odoo error occurs at any point, the next step will likely time out because it +cannot find the target element. If this occurs, the test window will remain open with +the Odoo error popup to indicate what went wrong. + +## Exporting Tests to Python + +* In the Selenium IDE window, click the three dots (...) next to the test name on the left. +* Select Export. +* Choose Python pytest, export the test as a .py file and save it in the `tests` directory. diff --git a/odoo/tests/selenium/index.sh b/odoo/tests/selenium/index.sh new file mode 100644 index 0000000..280b4d5 --- /dev/null +++ b/odoo/tests/selenium/index.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Copyright (C) 2020 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# Environment variables - add in Jenkins config +# +# HOST: URL of odoo machine to run tests +# DATABASE: name of odoo database to run tests +# PASSWORD: password of admin user +# BASETIMEOUT: timeout (in sec) for test lines + +# Remove old screenshots in the workspace +rm -f screenshots/*.png || echo "No old screenshots found." + +# Run tests +cd tests +DISPLAY=:99 +python -m pytest --durations=0 --full-trace -rA --hosturl "$HOST" --dbname "$DATABASE" --adminpw "$PASSWORD" --basetimeout "$BASETIMEOUT" + +exit 0 diff --git a/odoo/tests/selenium/requirements.txt b/odoo/tests/selenium/requirements.txt new file mode 100644 index 0000000..3c0c8ca --- /dev/null +++ b/odoo/tests/selenium/requirements.txt @@ -0,0 +1,3 @@ +selenium==3.141.0 +pytest==4.6.11 +pytest-dependency==0.5.1 diff --git a/odoo/tests/selenium/screenshots/README.md b/odoo/tests/selenium/screenshots/README.md new file mode 100644 index 0000000..a9e08d7 --- /dev/null +++ b/odoo/tests/selenium/screenshots/README.md @@ -0,0 +1 @@ +Folder containing screenshots during Selenium tests. diff --git a/odoo/tests/selenium/tests/conftest.py b/odoo/tests/selenium/tests/conftest.py new file mode 100644 index 0000000..d89d05e --- /dev/null +++ b/odoo/tests/selenium/tests/conftest.py @@ -0,0 +1,69 @@ +# Copyright (C) 2020 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import os +from datetime import datetime + +import pytest +from selenium import webdriver + + +def pytest_addoption(parser): + # Collect parameters from console call + parser.addoption("--hosturl", action="store") + parser.addoption("--dbname", action="store") + parser.addoption("--adminpw", action="store") + parser.addoption("--basetimeout", action="store") + + +@pytest.fixture(scope="session") +def get_parameters(request): + strdict = { + "hosturl": request.config.option.hosturl, + "dbname": request.config.option.dbname, + "adminpw": request.config.option.adminpw, + "basetimeout": int(request.config.option.basetimeout), + } + return strdict + + +@pytest.fixture(scope="function") +def setup_hostpage(get_parameters): + # At beginning of test: open host homepage + chrops = webdriver.ChromeOptions() + chrops.add_argument("headless") + chrops.add_argument("disable-gpu") + chrops.add_argument("disable-popup-blocking") + + caps = webdriver.DesiredCapabilities.CHROME.copy() + caps["acceptSslCerts"] = True + caps["acceptInsecureCerts"] = True + + driver = webdriver.Chrome(options=chrops, desired_capabilities=caps) + + hosturl = get_parameters["hosturl"] + dbname = get_parameters["dbname"] + fullurl = "https://{}/web?db={}".format(hosturl, dbname) + # If host wasn't prefixed with http(s):// we do it here + if not fullurl[:4] == "http": + httpurl = "http://" + fullurl + driver.get(httpurl) + else: + driver.get(fullurl) + + # Set window size and timeout + driver.set_window_size(1200, 800) + driver.implicitly_wait(get_parameters["basetimeout"]) + + yield driver + + # Save a screenshot + fname = ( + os.path.dirname(os.path.abspath(__file__))[:-5] + + "screenshots/" + + datetime.now().strftime("%Y%m%d%H%M%S%f") + + ".png" + ) + driver.save_screenshot(fname) + + driver.quit() diff --git a/odoo/tests/selenium/tests/test_01CreateLead.py b/odoo/tests/selenium/tests/test_01CreateLead.py new file mode 100644 index 0000000..b1ae970 --- /dev/null +++ b/odoo/tests/selenium/tests/test_01CreateLead.py @@ -0,0 +1,161 @@ +# Copyright (C) 2020 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait + + +@pytest.mark.usefixtures("get_parameters", "setup_hostpage") +class Test06CreateLead: + @pytest.mark.dependency(scope="session", name="06", depends=["05"]) + def test_06_create_lead(self, get_parameters, setup_hostpage): + driver = setup_hostpage + basetimeout = get_parameters["basetimeout"] * 1000 + + # Check if user is logged in + homemenucount = len(driver.find_elements(By.XPATH, "//nav/a")) + appiconcount = len(driver.find_elements(By.CSS_SELECTOR, ".o_app_icon")) + # If user is not logged in, log in + if homemenucount < 1: + driver.find_element(By.XPATH, "//label[contains(.,'Employee')]").click() + driver.find_element(By.XPATH, "//a[contains(@href,'/web/login')]").click() + driver.find_element(By.XPATH, "//input[@id='login']").click() + driver.find_element(By.XPATH, "//input[@id='login']").send_keys("admin") + driver.find_element(By.XPATH, "//input[@id='password']").click() + # Password goes here + adminpw = get_parameters["adminpw"] + driver.find_element(By.XPATH, "//input[@id='password']").send_keys(adminpw) + driver.find_element(By.XPATH, "//button[contains(@type,'submit')]").click() + elif appiconcount > 0: + # User is already logged in and window was already open + pass + else: + # If user is logged in, go to App menu + driver.find_element(By.XPATH, "//nav/a").click() + + # Open Sales App + driver.find_element(By.XPATH, "//a/div[contains(.,'Sales')]").click() + # Open Pipeline + driver.find_element( + By.XPATH, + "//div[text()='Lead Generation']/../../..//button[text()='Pipeline']", + ).click() + # Wait for Loading Message + WebDriverWait(driver, basetimeout).until( + expected_conditions.visibility_of_element_located( + (By.XPATH, "//div[contains(@class,'o_loading')]") + ) + ) + WebDriverWait(driver, basetimeout).until( + expected_conditions.invisibility_of_element_located( + (By.XPATH, "//div[contains(@class,'o_loading')]") + ) + ) + # Wait for page to load + WebDriverWait(driver, basetimeout).until( + expected_conditions.visibility_of_element_located( + ( + By.XPATH, + "//ol[contains(@class,'breadcrumb')]/li" + "[contains(.,'Opportunities')]", + ) + ) + ) + # Open Kanban view + driver.find_element( + By.XPATH, "//button[contains(@data-view-type,'kanban')]" + ).click() + # Search for existing record + driver.find_element(By.CSS_SELECTOR, ".o_searchview_input").click() + # Wait for Loading Message + WebDriverWait(driver, basetimeout).until( + expected_conditions.visibility_of_element_located( + (By.XPATH, "//div[contains(@class,'o_loading')]") + ) + ) + WebDriverWait(driver, basetimeout).until( + expected_conditions.invisibility_of_element_located( + (By.XPATH, "//div[contains(@class,'o_loading')]") + ) + ) + # Searchbar + driver.find_element(By.CSS_SELECTOR, ".o_searchview_input").click() + driver.find_element(By.CSS_SELECTOR, ".o_searchview_input").send_keys( + "Eisenhower" + ) + driver.find_element(By.CSS_SELECTOR, ".o_searchview_input").send_keys( + Keys.ENTER + ) + # Wait for Loading Message + WebDriverWait(driver, basetimeout).until( + expected_conditions.visibility_of_element_located( + (By.XPATH, "//div[contains(@class,'o_loading')]") + ) + ) + WebDriverWait(driver, basetimeout).until( + expected_conditions.invisibility_of_element_located( + (By.XPATH, "//div[contains(@class,'o_loading')]") + ) + ) + # No existing record + elements = driver.find_elements(By.CSS_SELECTOR, ".o_kanban_record") + assert len(elements) == 0 + + # List view + driver.find_element(By.XPATH, "//button[contains(@accesskey,'l')]").click() + # Create new record + driver.find_element(By.CSS_SELECTOR, ".o_list_button_add").click() + # Subject + driver.find_element( + By.XPATH, "//h1/input[contains(@placeholder, 'e.g. Product Pricing')]" + ).click() + driver.find_element( + By.XPATH, "//h1/input[contains(@placeholder, 'e.g. Product Pricing')]" + ).send_keys("111 Eisenhowertestttt Ave") + # Revenue + driver.find_element( + By.XPATH, "//label[contains(text(),'Expected Revenue')]/../div/div/input" + ).click() + driver.find_element( + By.XPATH, "//label[contains(text(),'Expected Revenue')]/../div/div/input" + ).clear() + driver.find_element( + By.XPATH, "//label[contains(text(),'Expected Revenue')]/../div/div/input" + ).send_keys("11000") + # Billing + driver.find_element( + By.XPATH, + "//label[contains(text(),'Contact for Billing')]/../../td/div/div/input", + ).click() + driver.find_element( + By.XPATH, + "//label[contains(text(),'Contact for Billing')]/../../td/div/div/input", + ).send_keys("test") + driver.find_element(By.LINK_TEXT, "Test").click() + # Rating + driver.find_element(By.XPATH, "//a[contains(@title,'High')]").click() + # Save record + driver.find_element(By.XPATH, "//button[contains(@accesskey,'s')]").click() + + # Check for Edit button + WebDriverWait(driver, basetimeout).until( + expected_conditions.presence_of_element_located( + (By.XPATH, "//button[contains(@accesskey,'a')]") + ) + ) + # Check name + WebDriverWait(driver, basetimeout).until( + expected_conditions.presence_of_element_located( + (By.XPATH, "//h1/span[contains(@class,'o_form_field')]") + ) + ) + # Check name + assert ( + driver.find_element( + By.XPATH, "//h1/span[contains(@class,'o_form_field')]" + ).text + == "111 Eisenhower Ave" + ) diff --git a/repos.yml b/repos.yml new file mode 100644 index 0000000..225bd63 --- /dev/null +++ b/repos.yml @@ -0,0 +1,17 @@ +#./src/field-service: +# remotes: +# oca: https://github.com/OCA/field-service.git +# ursais: git+ssh://git@github.com/ursais/field-service.git +# merges: +# - oca 16.0 +# - oca refs/pull/552/head +# target: &default_target ursais XXX/16.0-RC1 +# +#./src/web: +# remotes: +# oca: https://github.com/OCA/web.git +# ursais: git+ssh://git@github.com/ursais/web.git +# merges: +# - oca 16.0 +# - oca refs/pull/552/head +# target: *default_target diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6349c5c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +git-aggregator==1.7.2 +markdown-toc==1.2.6 +pre-commit==2.2.0