Compare commits
6 Commits
18.0
...
1664bb043a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1664bb043a | ||
|
|
f9f1c98580 | ||
|
|
0a7814ad1a | ||
|
|
a97bd8ecca | ||
|
|
8ab14340f1 | ||
|
|
9192465ca2 |
@@ -1,42 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# Run the Python tests of the given Odoo modules.
|
|
||||||
#
|
|
||||||
# This script is meant to be executed *inside* an Elabore doodba image
|
|
||||||
# (docker.0k.io/mirror/odoo:rc_<version>-ELABORE-LIGHT), with a
|
|
||||||
# PostgreSQL service reachable at $PGHOST. The modules under test are
|
|
||||||
# read from the current working directory (the checked-out repository),
|
|
||||||
# which is prepended to the addons path so that the PR's code takes
|
|
||||||
# precedence over the modules already baked in the image.
|
|
||||||
#
|
|
||||||
# Usage: odoo-tests.sh <module1,module2,...>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
modules="${1:?usage: odoo-tests.sh <module1,module2,...>}"
|
|
||||||
|
|
||||||
export PGHOST=postgres
|
|
||||||
export PGUSER=odoo
|
|
||||||
export PGPASSWORD=odoopassword
|
|
||||||
export PGDATABASE="test_$(basename "$PWD" | tr '.-' '__')"
|
|
||||||
export WITHOUT_DEMO=all
|
|
||||||
export DB_FILTER='.*'
|
|
||||||
|
|
||||||
# Generate /opt/odoo/auto/odoo.conf from the environment and wait for
|
|
||||||
# the database to accept connections.
|
|
||||||
/opt/odoo/common/entrypoint
|
|
||||||
|
|
||||||
# Odoo test-tags format: "/module1,/module2" (one leading slash).
|
|
||||||
tags="/$(echo "$modules" | tr ',' ',/')"
|
|
||||||
|
|
||||||
exec odoo \
|
|
||||||
--config=/opt/odoo/auto/odoo.conf \
|
|
||||||
--max-cron-threads=0 \
|
|
||||||
--limit-time-real=10000 \
|
|
||||||
--workers=0 \
|
|
||||||
--test-enable \
|
|
||||||
--stop-after-init \
|
|
||||||
--addons-path="$PWD,/opt/odoo/auto/addons" \
|
|
||||||
-d "$PGDATABASE" \
|
|
||||||
-i "$modules" \
|
|
||||||
--test-tags "$tags"
|
|
||||||
@@ -2,6 +2,8 @@ name: pre-commit
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "16.0*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-commit:
|
pre-commit:
|
||||||
@@ -13,10 +15,10 @@ jobs:
|
|||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
- name: Get python version
|
- name: Get python version
|
||||||
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
|
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
|
||||||
- uses: https://gitea.com/actions/cache@v3
|
# - uses: actions/cache@v4
|
||||||
with:
|
# with:
|
||||||
path: ~/.cache/pre-commit
|
# path: ~/.cache/pre-commit
|
||||||
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
# key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
- name: Install pre-commit
|
- name: Install pre-commit
|
||||||
run: pip install pre-commit
|
run: pip install pre-commit
|
||||||
- name: Run pre-commit
|
- name: Run pre-commit
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
name: tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "18.0*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: odoo-ci
|
|
||||||
container:
|
|
||||||
image: docker.0k.io/mirror/odoo:rc_18.0-ELABORE-LIGHT
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: docker.0k.io/postgres:17.2.0-myc
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: odoo
|
|
||||||
POSTGRES_PASSWORD: odoopassword
|
|
||||||
POSTGRES_DB: postgres
|
|
||||||
steps:
|
|
||||||
# The doodba image ships no Node.js, so actions/checkout@v4 cannot
|
|
||||||
# run here. The repository is private, so the job token is required.
|
|
||||||
- name: Checkout
|
|
||||||
env:
|
|
||||||
SECRET_GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
||||||
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
token="${GITEA_TOKEN:-${GITHUB_TOKEN:-${SECRET_GITEA_TOKEN:-${SECRET_GITHUB_TOKEN:-}}}}"
|
|
||||||
if [ -z "$token" ]; then
|
|
||||||
echo "::error::No job token available to clone the repository"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
server="${GITHUB_SERVER_URL:-https://git.elabore.coop}"
|
|
||||||
repo="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is not set}"
|
|
||||||
ref="${GITHUB_REF:-${GITHUB_SHA:?neither GITHUB_REF nor GITHUB_SHA is set}}"
|
|
||||||
auth_url="$(printf '%s' "${server%/}/${repo}.git" |
|
|
||||||
sed -E "s#^https://#https://${token}@#")"
|
|
||||||
git init -q .
|
|
||||||
git config --global --add safe.directory "$PWD"
|
|
||||||
git remote add origin "$auth_url"
|
|
||||||
git fetch -q --depth=1 origin "$ref"
|
|
||||||
git checkout -q FETCH_HEAD
|
|
||||||
|
|
||||||
- name: Detect changed modules
|
|
||||||
id: changed
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
if [ -n "${GITHUB_BASE_REF:-}" ]; then
|
|
||||||
git fetch -q --depth=1 origin "refs/heads/$GITHUB_BASE_REF"
|
|
||||||
base_rev="FETCH_HEAD"
|
|
||||||
scope="diff against $GITHUB_BASE_REF"
|
|
||||||
else
|
|
||||||
base_rev=""
|
|
||||||
scope="all installable modules (manual run)"
|
|
||||||
fi
|
|
||||||
if [ -n "$base_rev" ]; then
|
|
||||||
modules="$(
|
|
||||||
git diff --name-only "$base_rev" HEAD |
|
|
||||||
awk -F/ '{print $1}' | sort -u |
|
|
||||||
while read -r dir; do
|
|
||||||
[ -f "$dir/__manifest__.py" ] && echo "$dir"
|
|
||||||
done | paste -sd, -
|
|
||||||
)"
|
|
||||||
else
|
|
||||||
modules="$(
|
|
||||||
for dir in */; do
|
|
||||||
dir="${dir%/}"
|
|
||||||
[ -f "$dir/__manifest__.py" ] && echo "$dir"
|
|
||||||
done | paste -sd, -
|
|
||||||
)"
|
|
||||||
fi
|
|
||||||
echo "modules=$modules" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Modules to test ($scope): ${modules:-<none>}"
|
|
||||||
|
|
||||||
- name: Run Odoo tests
|
|
||||||
if: steps.changed.outputs.modules != ''
|
|
||||||
run: bash .gitea/scripts/odoo-tests.sh "${{ steps.changed.outputs.modules }}"
|
|
||||||
90
.gitignore
vendored
90
.gitignore
vendored
@@ -1,88 +1,2 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
*.*~
|
||||||
__pycache__/
|
*.pyc
|
||||||
*.py[cod]
|
|
||||||
/.venv
|
|
||||||
/.pytest_cache
|
|
||||||
/.ruff_cache
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
env/
|
|
||||||
bin/
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
eggs/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
*.eggs
|
|
||||||
|
|
||||||
# Windows installers
|
|
||||||
*.msi
|
|
||||||
|
|
||||||
# Debian packages
|
|
||||||
*.deb
|
|
||||||
|
|
||||||
# Redhat packages
|
|
||||||
*.rpm
|
|
||||||
|
|
||||||
# MacOS packages
|
|
||||||
*.dmg
|
|
||||||
*.pkg
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.coverage
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Pycharm
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# Eclipse
|
|
||||||
.settings
|
|
||||||
|
|
||||||
# Visual Studio cache/options directory
|
|
||||||
.vs/
|
|
||||||
.vscode
|
|
||||||
|
|
||||||
# OSX Files
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Mr Developer
|
|
||||||
.mr.developer.cfg
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
|
|
||||||
# Rope
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# Backup files
|
|
||||||
*~
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
# OCA rules
|
|
||||||
!static/lib/
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ exclude: |
|
|||||||
(LICENSE.*|COPYING.*)
|
(LICENSE.*|COPYING.*)
|
||||||
default_language_version:
|
default_language_version:
|
||||||
python: python3
|
python: python3
|
||||||
node: "22.9.0"
|
node: "16.17.0"
|
||||||
repos:
|
repos:
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
@@ -38,12 +38,8 @@ repos:
|
|||||||
entry: found a en.po file
|
entry: found a en.po file
|
||||||
language: fail
|
language: fail
|
||||||
files: '[a-zA-Z0-9_]*/i18n/en\.po$'
|
files: '[a-zA-Z0-9_]*/i18n/en\.po$'
|
||||||
- repo: https://github.com/sbidoul/whool
|
|
||||||
rev: v1.3
|
|
||||||
hooks:
|
|
||||||
- id: whool-init
|
|
||||||
- repo: https://github.com/oca/maintainer-tools
|
- repo: https://github.com/oca/maintainer-tools
|
||||||
rev: b89f767503be6ab2b11e4f50a7557cb20066e667
|
rev: f9b919b9868143135a9c9cb03021089cabba8223
|
||||||
hooks:
|
hooks:
|
||||||
# update the NOT INSTALLABLE ADDONS section above
|
# update the NOT INSTALLABLE ADDONS section above
|
||||||
- id: oca-update-pre-commit-excluded-addons
|
- id: oca-update-pre-commit-excluded-addons
|
||||||
@@ -53,13 +49,16 @@ repos:
|
|||||||
$(git rev-parse --show-toplevel))"'
|
$(git rev-parse --show-toplevel))"'
|
||||||
- id: oca-gen-addon-readme
|
- id: oca-gen-addon-readme
|
||||||
entry:
|
entry:
|
||||||
bash -c 'oca-gen-addon-readme --addons-dir=. --branch=$(git symbolic-ref
|
bash -c 'oca-gen-addon-readme
|
||||||
|
--addons-dir=.
|
||||||
|
--branch=$(git symbolic-ref
|
||||||
refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@")
|
refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@")
|
||||||
--repo-name=$(basename $(git rev-parse --show-toplevel)) --org-name="Elabore"
|
--repo-name=$(basename $(git rev-parse --show-toplevel))
|
||||||
--if-source-changed --keep-source-digest --convert-fragments-to-markdown'
|
--org-name="Elabore"
|
||||||
- id: oca-gen-external-dependencies
|
--if-source-changed --keep-source-digest'
|
||||||
|
|
||||||
- repo: https://github.com/OCA/odoo-pre-commit-hooks
|
- repo: https://github.com/OCA/odoo-pre-commit-hooks
|
||||||
rev: v0.0.33
|
rev: v0.1.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: oca-checks-odoo-module
|
- id: oca-checks-odoo-module
|
||||||
- id: oca-checks-po
|
- id: oca-checks-po
|
||||||
@@ -78,8 +77,8 @@ repos:
|
|||||||
files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
|
files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
|
||||||
language: node
|
language: node
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- "prettier@3.3.3"
|
- "prettier@2.7.1"
|
||||||
- "@prettier/plugin-xml@3.4.1"
|
- "@prettier/plugin-xml@2.2.0"
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: eslint
|
- id: eslint
|
||||||
@@ -92,11 +91,10 @@ repos:
|
|||||||
types: [javascript]
|
types: [javascript]
|
||||||
language: node
|
language: node
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- "eslint@9.12.0"
|
- "eslint@8.24.0"
|
||||||
- "eslint-plugin-jsdoc@50.3.1"
|
- "eslint-plugin-jsdoc@"
|
||||||
- "globals@16.0.0"
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.6.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
# exclude autogenerated files
|
# exclude autogenerated files
|
||||||
@@ -117,8 +115,23 @@ repos:
|
|||||||
- id: check-xml
|
- id: check-xml
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
args: ["--fix=lf"]
|
args: ["--fix=lf"]
|
||||||
|
- repo: https://github.com/PyCQA/docformatter
|
||||||
|
rev: v1.7.7
|
||||||
|
hooks:
|
||||||
|
- id: docformatter
|
||||||
|
args: [
|
||||||
|
"--in-place", # modify the files
|
||||||
|
"--recursive", # run on all the files
|
||||||
|
"--wrap-summaries",
|
||||||
|
"88", # max length of 1st line
|
||||||
|
"--wrap-descriptions",
|
||||||
|
"88", # max length of other lines
|
||||||
|
"--pre-summary-newline", # new line before a long summary
|
||||||
|
"--make-summary-multi-line", # force summary on multilines
|
||||||
|
]
|
||||||
|
additional_dependencies: ["tomli"] # if Python <3.11
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.6.8
|
rev: v0.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|||||||
73
.pylintrc
73
.pylintrc
@@ -10,7 +10,7 @@ manifest-required-authors=Elabore
|
|||||||
manifest-required-keys=license
|
manifest-required-keys=license
|
||||||
manifest-deprecated-keys=description,active
|
manifest-deprecated-keys=description,active
|
||||||
license-allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
|
license-allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
|
||||||
valid-odoo-versions=18.0
|
valid-odoo-versions=16.0
|
||||||
|
|
||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
disable=all
|
disable=all
|
||||||
@@ -25,25 +25,19 @@ disable=all
|
|||||||
enable=anomalous-backslash-in-string,
|
enable=anomalous-backslash-in-string,
|
||||||
api-one-deprecated,
|
api-one-deprecated,
|
||||||
api-one-multi-together,
|
api-one-multi-together,
|
||||||
|
assignment-from-none,
|
||||||
|
attribute-deprecated,
|
||||||
class-camelcase,
|
class-camelcase,
|
||||||
|
dangerous-default-value,
|
||||||
dangerous-view-replace-wo-priority,
|
dangerous-view-replace-wo-priority,
|
||||||
|
development-status-allowed,
|
||||||
duplicate-id-csv,
|
duplicate-id-csv,
|
||||||
|
duplicate-key,
|
||||||
duplicate-xml-fields,
|
duplicate-xml-fields,
|
||||||
duplicate-xml-record-id,
|
duplicate-xml-record-id,
|
||||||
eval-referenced,
|
eval-referenced,
|
||||||
incoherent-interpreter-exec-perm,
|
|
||||||
openerp-exception-warning,
|
|
||||||
redundant-modulename-xml,
|
|
||||||
relative-import,
|
|
||||||
rst-syntax-error,
|
|
||||||
wrong-tabs-instead-of-spaces,
|
|
||||||
xml-syntax-error,
|
|
||||||
assignment-from-none,
|
|
||||||
attribute-deprecated,
|
|
||||||
dangerous-default-value,
|
|
||||||
development-status-allowed,
|
|
||||||
duplicate-key,
|
|
||||||
eval-used,
|
eval-used,
|
||||||
|
incoherent-interpreter-exec-perm,
|
||||||
license-allowed,
|
license-allowed,
|
||||||
manifest-author-string,
|
manifest-author-string,
|
||||||
manifest-deprecated-key,
|
manifest-deprecated-key,
|
||||||
@@ -54,68 +48,73 @@ enable=anomalous-backslash-in-string,
|
|||||||
method-inverse,
|
method-inverse,
|
||||||
method-required-super,
|
method-required-super,
|
||||||
method-search,
|
method-search,
|
||||||
|
openerp-exception-warning,
|
||||||
pointless-statement,
|
pointless-statement,
|
||||||
pointless-string-statement,
|
pointless-string-statement,
|
||||||
print-used,
|
print-used,
|
||||||
redundant-keyword-arg,
|
redundant-keyword-arg,
|
||||||
|
redundant-modulename-xml,
|
||||||
reimported,
|
reimported,
|
||||||
|
relative-import,
|
||||||
return-in-init,
|
return-in-init,
|
||||||
|
rst-syntax-error,
|
||||||
sql-injection,
|
sql-injection,
|
||||||
too-few-format-args,
|
too-few-format-args,
|
||||||
translation-field,
|
translation-field,
|
||||||
translation-required,
|
translation-required,
|
||||||
unreachable,
|
unreachable,
|
||||||
use-vim-comment,
|
use-vim-comment,
|
||||||
|
wrong-tabs-instead-of-spaces,
|
||||||
|
xml-syntax-error,
|
||||||
|
attribute-string-redundant,
|
||||||
character-not-valid-in-resource-link,
|
character-not-valid-in-resource-link,
|
||||||
|
consider-merging-classes-inherited,
|
||||||
|
context-overridden,
|
||||||
create-user-wo-reset-password,
|
create-user-wo-reset-password,
|
||||||
dangerous-filter-wo-user,
|
dangerous-filter-wo-user,
|
||||||
dangerous-qweb-replace-wo-priority,
|
dangerous-qweb-replace-wo-priority,
|
||||||
deprecated-data-xml-node,
|
deprecated-data-xml-node,
|
||||||
deprecated-openerp-xml-node,
|
deprecated-openerp-xml-node,
|
||||||
duplicate-po-message-definition,
|
duplicate-po-message-definition,
|
||||||
file-not-used,
|
|
||||||
missing-newline-extrafiles,
|
|
||||||
old-api7-method-defined,
|
|
||||||
po-msgstr-variables,
|
|
||||||
po-syntax-error,
|
|
||||||
str-format-used,
|
|
||||||
unnecessary-utf8-coding-comment,
|
|
||||||
xml-attribute-translatable,
|
|
||||||
xml-deprecated-qweb-directive,
|
|
||||||
xml-deprecated-tree-attribute,
|
|
||||||
attribute-string-redundant,
|
|
||||||
consider-merging-classes-inherited,
|
|
||||||
context-overridden,
|
|
||||||
except-pass,
|
except-pass,
|
||||||
|
file-not-used,
|
||||||
invalid-commit,
|
invalid-commit,
|
||||||
manifest-maintainers-list,
|
manifest-maintainers-list,
|
||||||
|
missing-newline-extrafiles,
|
||||||
missing-readme,
|
missing-readme,
|
||||||
missing-return,
|
missing-return,
|
||||||
odoo-addons-relative-import,
|
odoo-addons-relative-import,
|
||||||
|
old-api7-method-defined,
|
||||||
|
po-msgstr-variables,
|
||||||
|
po-syntax-error,
|
||||||
renamed-field-parameter,
|
renamed-field-parameter,
|
||||||
resource-not-exist,
|
resource-not-exist,
|
||||||
|
str-format-used,
|
||||||
test-folder-imported,
|
test-folder-imported,
|
||||||
translation-contains-variable,
|
translation-contains-variable,
|
||||||
translation-positional-used,
|
translation-positional-used,
|
||||||
website-manifest-key-not-valid-uri,
|
|
||||||
external-request-timeout,
|
|
||||||
missing-manifest-dependency,
|
|
||||||
too-complex,,
|
|
||||||
create-user-wo-reset-password,
|
|
||||||
dangerous-filter-wo-user,
|
|
||||||
file-not-used,
|
|
||||||
missing-newline-extrafiles,
|
|
||||||
no-utf8-coding-comment,
|
|
||||||
old-api7-method-defined,
|
|
||||||
unnecessary-utf8-coding-comment,
|
unnecessary-utf8-coding-comment,
|
||||||
|
website-manifest-key-not-valid-uri,
|
||||||
|
xml-attribute-translatable,
|
||||||
|
xml-deprecated-qweb-directive,
|
||||||
|
xml-deprecated-tree-attribute,
|
||||||
|
external-request-timeout,
|
||||||
# messages that do not cause the lint step to fail
|
# messages that do not cause the lint step to fail
|
||||||
consider-merging-classes-inherited,
|
consider-merging-classes-inherited,
|
||||||
|
create-user-wo-reset-password,
|
||||||
|
dangerous-filter-wo-user,
|
||||||
deprecated-module,
|
deprecated-module,
|
||||||
|
file-not-used,
|
||||||
invalid-commit,
|
invalid-commit,
|
||||||
|
missing-manifest-dependency,
|
||||||
|
missing-newline-extrafiles,
|
||||||
missing-readme,
|
missing-readme,
|
||||||
|
no-utf8-coding-comment,
|
||||||
odoo-addons-relative-import,
|
odoo-addons-relative-import,
|
||||||
|
old-api7-method-defined,
|
||||||
redefined-builtin,
|
redefined-builtin,
|
||||||
manifest-external-assets
|
too-complex,
|
||||||
|
unnecessary-utf8-coding-comment
|
||||||
|
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ manifest-required-authors=Elabore
|
|||||||
manifest-required-keys=license
|
manifest-required-keys=license
|
||||||
manifest-deprecated-keys=description,active
|
manifest-deprecated-keys=description,active
|
||||||
license-allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
|
license-allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
|
||||||
valid-odoo-versions=18.0
|
valid-odoo-versions=16.0
|
||||||
|
|
||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
disable=all
|
disable=all
|
||||||
@@ -17,25 +17,19 @@ disable=all
|
|||||||
enable=anomalous-backslash-in-string,
|
enable=anomalous-backslash-in-string,
|
||||||
api-one-deprecated,
|
api-one-deprecated,
|
||||||
api-one-multi-together,
|
api-one-multi-together,
|
||||||
|
assignment-from-none,
|
||||||
|
attribute-deprecated,
|
||||||
class-camelcase,
|
class-camelcase,
|
||||||
|
dangerous-default-value,
|
||||||
dangerous-view-replace-wo-priority,
|
dangerous-view-replace-wo-priority,
|
||||||
|
development-status-allowed,
|
||||||
duplicate-id-csv,
|
duplicate-id-csv,
|
||||||
|
duplicate-key,
|
||||||
duplicate-xml-fields,
|
duplicate-xml-fields,
|
||||||
duplicate-xml-record-id,
|
duplicate-xml-record-id,
|
||||||
eval-referenced,
|
eval-referenced,
|
||||||
incoherent-interpreter-exec-perm,
|
|
||||||
openerp-exception-warning,
|
|
||||||
redundant-modulename-xml,
|
|
||||||
relative-import,
|
|
||||||
rst-syntax-error,
|
|
||||||
wrong-tabs-instead-of-spaces,
|
|
||||||
xml-syntax-error,
|
|
||||||
assignment-from-none,
|
|
||||||
attribute-deprecated,
|
|
||||||
dangerous-default-value,
|
|
||||||
development-status-allowed,
|
|
||||||
duplicate-key,
|
|
||||||
eval-used,
|
eval-used,
|
||||||
|
incoherent-interpreter-exec-perm,
|
||||||
license-allowed,
|
license-allowed,
|
||||||
manifest-author-string,
|
manifest-author-string,
|
||||||
manifest-deprecated-key,
|
manifest-deprecated-key,
|
||||||
@@ -46,50 +40,56 @@ enable=anomalous-backslash-in-string,
|
|||||||
method-inverse,
|
method-inverse,
|
||||||
method-required-super,
|
method-required-super,
|
||||||
method-search,
|
method-search,
|
||||||
|
openerp-exception-warning,
|
||||||
pointless-statement,
|
pointless-statement,
|
||||||
pointless-string-statement,
|
pointless-string-statement,
|
||||||
print-used,
|
print-used,
|
||||||
redundant-keyword-arg,
|
redundant-keyword-arg,
|
||||||
|
redundant-modulename-xml,
|
||||||
reimported,
|
reimported,
|
||||||
|
relative-import,
|
||||||
return-in-init,
|
return-in-init,
|
||||||
|
rst-syntax-error,
|
||||||
sql-injection,
|
sql-injection,
|
||||||
too-few-format-args,
|
too-few-format-args,
|
||||||
translation-field,
|
translation-field,
|
||||||
translation-required,
|
translation-required,
|
||||||
unreachable,
|
unreachable,
|
||||||
use-vim-comment,
|
use-vim-comment,
|
||||||
|
wrong-tabs-instead-of-spaces,
|
||||||
|
xml-syntax-error,
|
||||||
|
attribute-string-redundant,
|
||||||
character-not-valid-in-resource-link,
|
character-not-valid-in-resource-link,
|
||||||
|
consider-merging-classes-inherited,
|
||||||
|
context-overridden,
|
||||||
create-user-wo-reset-password,
|
create-user-wo-reset-password,
|
||||||
dangerous-filter-wo-user,
|
dangerous-filter-wo-user,
|
||||||
dangerous-qweb-replace-wo-priority,
|
dangerous-qweb-replace-wo-priority,
|
||||||
deprecated-data-xml-node,
|
deprecated-data-xml-node,
|
||||||
deprecated-openerp-xml-node,
|
deprecated-openerp-xml-node,
|
||||||
duplicate-po-message-definition,
|
duplicate-po-message-definition,
|
||||||
file-not-used,
|
|
||||||
missing-newline-extrafiles,
|
|
||||||
old-api7-method-defined,
|
|
||||||
po-msgstr-variables,
|
|
||||||
po-syntax-error,
|
|
||||||
str-format-used,
|
|
||||||
unnecessary-utf8-coding-comment,
|
|
||||||
xml-attribute-translatable,
|
|
||||||
xml-deprecated-qweb-directive,
|
|
||||||
xml-deprecated-tree-attribute,
|
|
||||||
attribute-string-redundant,
|
|
||||||
consider-merging-classes-inherited,
|
|
||||||
context-overridden,
|
|
||||||
except-pass,
|
except-pass,
|
||||||
|
file-not-used,
|
||||||
invalid-commit,
|
invalid-commit,
|
||||||
manifest-maintainers-list,
|
manifest-maintainers-list,
|
||||||
|
missing-newline-extrafiles,
|
||||||
missing-readme,
|
missing-readme,
|
||||||
missing-return,
|
missing-return,
|
||||||
odoo-addons-relative-import,
|
odoo-addons-relative-import,
|
||||||
|
old-api7-method-defined,
|
||||||
|
po-msgstr-variables,
|
||||||
|
po-syntax-error,
|
||||||
renamed-field-parameter,
|
renamed-field-parameter,
|
||||||
resource-not-exist,
|
resource-not-exist,
|
||||||
|
str-format-used,
|
||||||
test-folder-imported,
|
test-folder-imported,
|
||||||
translation-contains-variable,
|
translation-contains-variable,
|
||||||
translation-positional-used,
|
translation-positional-used,
|
||||||
|
unnecessary-utf8-coding-comment,
|
||||||
website-manifest-key-not-valid-uri,
|
website-manifest-key-not-valid-uri,
|
||||||
|
xml-attribute-translatable,
|
||||||
|
xml-deprecated-qweb-directive,
|
||||||
|
xml-deprecated-tree-attribute,
|
||||||
external-request-timeout
|
external-request-timeout
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
var globals = require('globals');
|
|
||||||
jsdoc = require("eslint-plugin-jsdoc");
|
|
||||||
|
|
||||||
const config = [{
|
|
||||||
plugins: {
|
|
||||||
jsdoc,
|
|
||||||
},
|
|
||||||
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
_: "readonly",
|
|
||||||
$: "readonly",
|
|
||||||
fuzzy: "readonly",
|
|
||||||
jQuery: "readonly",
|
|
||||||
moment: "readonly",
|
|
||||||
odoo: "readonly",
|
|
||||||
openerp: "readonly",
|
|
||||||
owl: "readonly",
|
|
||||||
luxon: "readonly",
|
|
||||||
QUnit: "readonly",
|
|
||||||
...globals.browser,
|
|
||||||
},
|
|
||||||
|
|
||||||
ecmaVersion: 2024,
|
|
||||||
sourceType: "script",
|
|
||||||
},
|
|
||||||
|
|
||||||
rules: {
|
|
||||||
"accessor-pairs": "warn",
|
|
||||||
"array-callback-return": "warn",
|
|
||||||
"callback-return": "warn",
|
|
||||||
"capitalized-comments": ["warn", "always", {
|
|
||||||
ignoreConsecutiveComments: true,
|
|
||||||
ignoreInlineComments: true,
|
|
||||||
}],
|
|
||||||
complexity: ["warn", 15],
|
|
||||||
"constructor-super": "warn",
|
|
||||||
"dot-notation": "warn",
|
|
||||||
eqeqeq: "warn",
|
|
||||||
"global-require": "warn",
|
|
||||||
"handle-callback-err": "warn",
|
|
||||||
"id-blacklist": "warn",
|
|
||||||
"id-match": "warn",
|
|
||||||
"init-declarations": "error",
|
|
||||||
"max-depth": "warn",
|
|
||||||
"max-nested-callbacks": "warn",
|
|
||||||
"max-statements-per-line": "warn",
|
|
||||||
"no-alert": "warn",
|
|
||||||
"no-array-constructor": "warn",
|
|
||||||
"no-caller": "warn",
|
|
||||||
"no-case-declarations": "warn",
|
|
||||||
"no-class-assign": "warn",
|
|
||||||
"no-cond-assign": "error",
|
|
||||||
"no-const-assign": "error",
|
|
||||||
"no-constant-condition": "warn",
|
|
||||||
"no-control-regex": "warn",
|
|
||||||
"no-debugger": "error",
|
|
||||||
"no-delete-var": "warn",
|
|
||||||
"no-div-regex": "warn",
|
|
||||||
"no-dupe-args": "error",
|
|
||||||
"no-dupe-class-members": "error",
|
|
||||||
"no-dupe-keys": "error",
|
|
||||||
"no-duplicate-case": "error",
|
|
||||||
"no-duplicate-imports": "error",
|
|
||||||
"no-else-return": "warn",
|
|
||||||
"no-empty-character-class": "warn",
|
|
||||||
"no-empty-function": "error",
|
|
||||||
"no-empty-pattern": "error",
|
|
||||||
"no-empty": "warn",
|
|
||||||
"no-eq-null": "error",
|
|
||||||
"no-eval": "error",
|
|
||||||
"no-ex-assign": "error",
|
|
||||||
"no-extend-native": "warn",
|
|
||||||
"no-extra-bind": "warn",
|
|
||||||
"no-extra-boolean-cast": "warn",
|
|
||||||
"no-extra-label": "warn",
|
|
||||||
"no-fallthrough": "warn",
|
|
||||||
"no-func-assign": "error",
|
|
||||||
"no-global-assign": "error",
|
|
||||||
"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-irregular-whitespace": "warn",
|
|
||||||
"no-iterator": "warn",
|
|
||||||
"no-label-var": "warn",
|
|
||||||
"no-labels": "warn",
|
|
||||||
"no-lone-blocks": "warn",
|
|
||||||
"no-lonely-if": "error",
|
|
||||||
"no-mixed-requires": "error",
|
|
||||||
"no-multi-str": "warn",
|
|
||||||
"no-native-reassign": "error",
|
|
||||||
"no-negated-condition": "warn",
|
|
||||||
"no-negated-in-lhs": "error",
|
|
||||||
"no-new-func": "warn",
|
|
||||||
"no-new-object": "warn",
|
|
||||||
"no-new-require": "warn",
|
|
||||||
"no-new-symbol": "warn",
|
|
||||||
"no-new-wrappers": "warn",
|
|
||||||
"no-new": "warn",
|
|
||||||
"no-obj-calls": "warn",
|
|
||||||
"no-octal-escape": "warn",
|
|
||||||
"no-octal": "warn",
|
|
||||||
"no-param-reassign": "warn",
|
|
||||||
"no-path-concat": "warn",
|
|
||||||
"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": "error",
|
|
||||||
"no-script-url": "warn",
|
|
||||||
"no-self-assign": "warn",
|
|
||||||
"no-self-compare": "warn",
|
|
||||||
"no-sequences": "warn",
|
|
||||||
"no-shadow-restricted-names": "warn",
|
|
||||||
"no-shadow": "warn",
|
|
||||||
"no-sparse-arrays": "warn",
|
|
||||||
"no-sync": "warn",
|
|
||||||
"no-this-before-super": "warn",
|
|
||||||
"no-throw-literal": "warn",
|
|
||||||
"no-undef-init": "warn",
|
|
||||||
"no-undef": "error",
|
|
||||||
"no-unmodified-loop-condition": "warn",
|
|
||||||
"no-unneeded-ternary": "error",
|
|
||||||
"no-unreachable": "error",
|
|
||||||
"no-unsafe-finally": "error",
|
|
||||||
"no-unused-expressions": "error",
|
|
||||||
"no-unused-labels": "error",
|
|
||||||
"no-unused-vars": "error",
|
|
||||||
"no-use-before-define": "error",
|
|
||||||
"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-with": "warn",
|
|
||||||
"operator-assignment": ["error", "always"],
|
|
||||||
"prefer-const": "warn",
|
|
||||||
radix: "warn",
|
|
||||||
"require-yield": "warn",
|
|
||||||
"sort-imports": "warn",
|
|
||||||
"spaced-comment": ["error", "always"],
|
|
||||||
strict: ["error", "function"],
|
|
||||||
"use-isnan": "error",
|
|
||||||
|
|
||||||
"jsdoc/check-tag-names": "warn",
|
|
||||||
"jsdoc/check-types": "warn",
|
|
||||||
"jsdoc/require-param-description": "off",
|
|
||||||
"jsdoc/require-return": "off",
|
|
||||||
"jsdoc/require-return-description": "off",
|
|
||||||
"jsdoc/require-return-type": "off",
|
|
||||||
|
|
||||||
"valid-typeof": "warn",
|
|
||||||
yoda: "warn",
|
|
||||||
},
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
jsdoc: {
|
|
||||||
tagNamePreference: {
|
|
||||||
arg: "param",
|
|
||||||
argument: "param",
|
|
||||||
augments: "extends",
|
|
||||||
constructor: "class",
|
|
||||||
exception: "throws",
|
|
||||||
func: "function",
|
|
||||||
method: "function",
|
|
||||||
prop: "property",
|
|
||||||
return: "returns",
|
|
||||||
virtual: "abstract",
|
|
||||||
yield: "yields",
|
|
||||||
},
|
|
||||||
preferredTypes: {
|
|
||||||
array: "Array",
|
|
||||||
bool: "Boolean",
|
|
||||||
boolean: "Boolean",
|
|
||||||
number: "Number",
|
|
||||||
object: "Object",
|
|
||||||
str: "String",
|
|
||||||
string: "String",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
}, {
|
|
||||||
files: ["**/*.esm.js", "**/*test.js"],
|
|
||||||
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: 2024,
|
|
||||||
sourceType: "module",
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
|
|
||||||
module.exports = config
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/** @type {import('prettier').Config} */
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
// https://github.com/prettier/prettier/issues/15388#issuecomment-1717746872
|
|
||||||
plugins: [require.resolve("@prettier/plugin-xml")],
|
|
||||||
bracketSpacing: false,
|
|
||||||
printWidth: 88,
|
|
||||||
proseWrap: "always",
|
|
||||||
semi: true,
|
|
||||||
trailingComma: "es5",
|
|
||||||
xmlWhitespaceSensitivity: "preserve",
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
|
||||||
from . import models, controllers
|
from . import models, controllers
|
||||||
|
|||||||
@@ -3,15 +3,19 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "project_average_acceptable_time",
|
"name": "project_average_acceptable_time",
|
||||||
"version": "18.0.1.1.0",
|
"version": "18.0.1.0.0",
|
||||||
"author": "Elabore",
|
"author": "Elabore",
|
||||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
"website": "https://github.com/elabore-coop/project-tools",
|
||||||
"maintainer": "Clément Thomas",
|
"maintainer": "Clément Thomas",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"category": "Tools",
|
"category": "Tools",
|
||||||
"summary": "Task validation without customer agreement",
|
"summary": "Task validation without customer agreement",
|
||||||
# any module necessary for this one to work correctly
|
# any module necessary for this one to work correctly
|
||||||
"depends": ["base", "project", "project_user_default_project"],
|
"depends": [
|
||||||
|
"base",
|
||||||
|
"project",
|
||||||
|
"project_user_default_project"
|
||||||
|
],
|
||||||
"qweb": [
|
"qweb": [
|
||||||
# "static/src/xml/*.xml",
|
# "static/src/xml/*.xml",
|
||||||
],
|
],
|
||||||
@@ -19,7 +23,10 @@
|
|||||||
"python": [],
|
"python": [],
|
||||||
},
|
},
|
||||||
# always loaded
|
# always loaded
|
||||||
"data": ["views/project_project.xml", "views/portal_home_template.xml"],
|
"data": [
|
||||||
|
"views/project_project.xml",
|
||||||
|
"views/portal_home_template.xml"
|
||||||
|
],
|
||||||
# only loaded in demonstration mode
|
# only loaded in demonstration mode
|
||||||
"demo": [],
|
"demo": [],
|
||||||
"js": [],
|
"js": [],
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
from . import custom_portal
|
|
||||||
|
from . import custom_portal
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
# Copyright 2020 Lokavaluto ()
|
# Copyright 2020 Lokavaluto ()
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
from odoo.http import request, route
|
from odoo.http import request, route
|
||||||
|
|
||||||
from odoo.addons.portal.controllers.portal import CustomerPortal
|
from odoo.addons.portal.controllers.portal import CustomerPortal
|
||||||
|
|
||||||
|
|
||||||
class CustomCustomerPortal(CustomerPortal):
|
class CustomCustomerPortal(CustomerPortal):
|
||||||
@route(["/my/account"], type="http", auth="user", website=True)
|
@route(["/my/account"], type="http", auth="user", website=True)
|
||||||
def account(self, redirect=None, **post):
|
def account(self, redirect=None, **post):
|
||||||
self.OPTIONAL_BILLING_FIELDS.append(
|
self.OPTIONAL_BILLING_FIELDS.append("average_acceptable_time") #unecessary save in res partner, but necessary to avoid error on form post
|
||||||
"average_acceptable_time"
|
|
||||||
) # unecessary save in res partner, but necessary to avoid error on form post
|
|
||||||
|
|
||||||
response = super().account(redirect, **post)
|
|
||||||
|
|
||||||
|
response = super(CustomCustomerPortal, self).account(redirect, **post)
|
||||||
|
|
||||||
if post and request.httprequest.method == "POST":
|
if post and request.httprequest.method == "POST":
|
||||||
error, error_message = self.details_form_validate(post)
|
error, error_message = self.details_form_validate(post)
|
||||||
if not error:
|
if not error:
|
||||||
# sudo: a portal user has no write access at all on
|
user = request.env.user
|
||||||
# project.project, and no read access on its default project
|
if user.default_project_id and post["average_acceptable_time"]:
|
||||||
# unless it follows it
|
user.default_project_id.average_acceptable_time = post["average_acceptable_time"]
|
||||||
project_sudo = request.env.user.sudo().default_project_id
|
|
||||||
if project_sudo and post.get("average_acceptable_time"):
|
|
||||||
project_sudo.average_acceptable_time = post[
|
|
||||||
"average_acceptable_time"
|
|
||||||
]
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
|
||||||
from . import project_project
|
from . import project_project
|
||||||
from . import res_partner
|
from . import res_partner
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
from odoo import fields, models
|
|
||||||
|
from odoo import models, fields, _, api
|
||||||
|
|
||||||
|
|
||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
_inherit = "project.project"
|
_inherit = "project.project"
|
||||||
|
|
||||||
average_acceptable_time = fields.Float("Average acceptable time")
|
average_acceptable_time = fields.Float('Average acceptable time')
|
||||||
|
|
||||||
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
from odoo import fields, models
|
|
||||||
|
from odoo import models, fields, _, api
|
||||||
|
|
||||||
|
|
||||||
class ResPartner(models.Model):
|
class ResPartner(models.Model):
|
||||||
_inherit = "res.partner"
|
_inherit = "res.partner"
|
||||||
|
|
||||||
average_acceptable_time = fields.Float(
|
average_acceptable_time = fields.Float('Average acceptable time') # not used, but necessary to post custom field from /my/account
|
||||||
"Average acceptable time"
|
|
||||||
) # not used, but necessary to post custom field from /my/account
|
|
||||||
@@ -1,50 +1,25 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Template /my/home -->
|
<!-- Template /my/home -->
|
||||||
<template
|
<template id="portal_my_home_average_acceptable_time" name="Portal My Home: Average acceptable time" inherit_id="portal.side_content" priority="40">
|
||||||
id="portal_my_home_average_acceptable_time"
|
|
||||||
name="Portal My Home: Average acceptable time"
|
|
||||||
inherit_id="portal.side_content"
|
|
||||||
priority="40"
|
|
||||||
>
|
|
||||||
<xpath expr="//div[@class='o_portal_my_details']" position="inside">
|
<xpath expr="//div[@class='o_portal_my_details']" position="inside">
|
||||||
<!-- sudo: a portal user cannot read its own default project
|
<hr class="mt-1 mb-0"/>
|
||||||
unless it follows it (project.project_project_rule_portal) -->
|
<b>Average acceptable time</b>: <t t-esc="user_id.default_project_id.average_acceptable_time" t-options="{'widget': 'float_time'}"/>
|
||||||
<t
|
|
||||||
t-set="default_project_sudo"
|
|
||||||
t-value="user_id.sudo().default_project_id"
|
|
||||||
/>
|
|
||||||
<t t-if="default_project_sudo">
|
|
||||||
<hr class="mt-1 mb-0" />
|
|
||||||
<b>Average acceptable time</b>: <t
|
|
||||||
t-esc="default_project_sudo.average_acceptable_time"
|
|
||||||
t-options="{'widget': 'float_time'}"
|
|
||||||
/>
|
|
||||||
</t>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Template /my/account -->
|
<!-- Template /my/account -->
|
||||||
<template
|
<template id="portal_my_details_average_acceptable_time" name="Portal My details: Average acceptable time" inherit_id="portal.portal_my_details_fields">
|
||||||
id="portal_my_details_average_acceptable_time"
|
|
||||||
name="Portal My details: Average acceptable time"
|
|
||||||
inherit_id="portal.portal_my_details_fields"
|
|
||||||
>
|
|
||||||
<xpath expr="//input[@name='csrf_token']/.." position="inside">
|
<xpath expr="//input[@name='csrf_token']/.." position="inside">
|
||||||
<div
|
<div t-attf-class="form-group #{error.get('average_acceptable_time') and 'o_has_error' or ''} col-xl-6">
|
||||||
t-attf-class="form-group #{error.get('average_acceptable_time') and 'o_has_error' or ''} col-xl-6"
|
<label class="col-form-label" for="average_acceptable_time">Average acceptable time (h)</label>
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="col-form-label"
|
|
||||||
for="average_acceptable_time"
|
|
||||||
>Average acceptable time (h)</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="average_acceptable_time"
|
name="average_acceptable_time"
|
||||||
t-attf-class="form-control #{error.get('average_acceptable_time') and 'is-invalid' or ''}"
|
t-attf-class="form-control #{error.get('average_acceptable_time') and 'is-invalid' or ''}"
|
||||||
t-att-value="average_acceptable_time or user_id.sudo().default_project_id.average_acceptable_time"
|
t-att-value="average_acceptable_time or user_id.default_project_id.average_acceptable_time"
|
||||||
t-options="{'widget': 'float_time'}"
|
t-options="{'widget': 'float_time'}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,44 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!--Project KANBAN -->
|
<!--Project KANBAN -->
|
||||||
<record
|
<record id="project_project_view_kanban_inherit_average_acceptable_time" model="ir.ui.view">
|
||||||
id="project_project_view_kanban_inherit_average_acceptable_time"
|
<field name="name">project.project.kanban.inherit.average.acceptable.time</field>
|
||||||
model="ir.ui.view"
|
|
||||||
>
|
|
||||||
<field
|
|
||||||
name="name"
|
|
||||||
>project.project.kanban.inherit.average.acceptable.time</field>
|
|
||||||
<field name="model">project.project</field>
|
<field name="model">project.project</field>
|
||||||
<field name="inherit_id" ref="project.view_project_kanban" />
|
<field name="inherit_id" ref="project.view_project_kanban"/>
|
||||||
<field name="priority">999</field>
|
<field name="priority">999</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//div[hasclass('o_project_kanban_main')]" position="inside">
|
<xpath expr="//div[hasclass('o_project_kanban_main')]" position="inside">
|
||||||
<div class="o_project_kanban_boxes">
|
<div class="o_project_kanban_boxes">
|
||||||
<span>Average acceptable time:<![CDATA[ ]]></span>
|
<span>Average acceptable time:<![CDATA[ ]]></span>
|
||||||
<field name="average_acceptable_time" widget="float_time" />
|
<field name="average_acceptable_time" widget="float_time"/>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Project FORM -->
|
<!-- Project FORM -->
|
||||||
<record
|
<record id="project_project_form_inherit_average_acceptable_time" model="ir.ui.view">
|
||||||
id="project_project_form_inherit_average_acceptable_time"
|
|
||||||
model="ir.ui.view"
|
|
||||||
>
|
|
||||||
<field name="name">project.project.form.inherit.average.acceptable.time</field>
|
<field name="name">project.project.form.inherit.average.acceptable.time</field>
|
||||||
<field name="model">project.project</field>
|
<field name="model">project.project</field>
|
||||||
<field name="inherit_id" ref="project.edit_project" />
|
<field name="inherit_id" ref="project.edit_project"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//group[@name='extra_settings']" position="inside">
|
<xpath expr="//group[@name='extra_settings']" position="inside">
|
||||||
<div
|
<div class="o_settings_average_acceptable_time" style="margin-top:10px;">
|
||||||
class="o_settings_average_acceptable_time"
|
|
||||||
style="margin-top:10px;"
|
|
||||||
>
|
|
||||||
<!-- <span>Average acceptable time:<![CDATA[ ]]></span> -->
|
<!-- <span>Average acceptable time:<![CDATA[ ]]></span> -->
|
||||||
<label for="average_acceptable_time" />
|
<label for="average_acceptable_time"/>
|
||||||
<field name="average_acceptable_time" widget="float_time" />
|
<field name="average_acceptable_time" widget="float_time"/>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
# Project Link From Invoice
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Easily access your projects directly from invoices:
|
|
||||||
|
|
||||||
- Displays project name(s) in the invoice list view.
|
|
||||||
- Adds a smart button on invoice form view to open the related project(s).
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
This module depends on:
|
|
||||||
|
|
||||||
- `project` (Odoo core)
|
|
||||||
- `account` (Odoo core)
|
|
||||||
- `sale_project` (Odoo core)
|
|
||||||
|
|
||||||
Use Odoo's normal procedure to install add-ons.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
No specific configuration is required.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Open an invoice that is linked to a sale order. If the sale order has tasks
|
|
||||||
in a project, a "Project(s)" button appears in the header of the invoice form.
|
|
||||||
Clicking it opens the related project(s) in form or list view.
|
|
||||||
|
|
||||||
The invoice list view also displays the project name(s) in a dedicated column.
|
|
||||||
|
|
||||||
## Known issues / Roadmap
|
|
||||||
|
|
||||||
None yet.
|
|
||||||
|
|
||||||
## Bug Tracker
|
|
||||||
|
|
||||||
Bugs are tracked on [our issues website](https://github.com/elabore-coop/project-tools/issues).
|
|
||||||
In case of trouble, please check there if your issue has already been reported.
|
|
||||||
If you spotted it first, help us smash it by providing detailed and welcomed feedback.
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
### Images
|
|
||||||
|
|
||||||
- Elabore: [Icon](https://elabore.coop/web/image/res.company/1/logo)
|
|
||||||
|
|
||||||
### Contributors
|
|
||||||
|
|
||||||
- Clément Thomas
|
|
||||||
|
|
||||||
### Funders
|
|
||||||
|
|
||||||
The development of this module has been financially supported by:
|
|
||||||
|
|
||||||
- Elabore (<https://elabore.coop>)
|
|
||||||
|
|
||||||
### Maintainer
|
|
||||||
|
|
||||||
This module is maintained by Elabore.
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
from . import models
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Copyright 2022 Stéphan Sainléger (Elabore)
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "project_link_from_invoice",
|
|
||||||
"version": "18.0.1.0.0",
|
|
||||||
"author": "Elabore",
|
|
||||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
|
||||||
"maintainer": "Clément Thomas",
|
|
||||||
"license": "AGPL-3",
|
|
||||||
"category": "Tools",
|
|
||||||
"summary": "Add link from invoice to project",
|
|
||||||
# any module necessary for this one to work correctly
|
|
||||||
"depends": [
|
|
||||||
"base",
|
|
||||||
"project",
|
|
||||||
"account",
|
|
||||||
"sale_project",
|
|
||||||
],
|
|
||||||
"external_dependencies": {
|
|
||||||
"python": [],
|
|
||||||
},
|
|
||||||
# always loaded
|
|
||||||
"data": [
|
|
||||||
"views/account_move_view.xml",
|
|
||||||
],
|
|
||||||
# only loaded in demonstration mode
|
|
||||||
"demo": [],
|
|
||||||
"installable": True,
|
|
||||||
# Install this module automatically if all dependency have been previously
|
|
||||||
# and independently installed. Used for synergetic or glue modules.
|
|
||||||
"auto_install": False,
|
|
||||||
"application": False,
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Translation of Odoo Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * project_link_from_invoice
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Odoo Server 14.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2023-04-27 12:47+0000\n"
|
|
||||||
"PO-Revision-Date: 2023-04-27 12:47+0000\n"
|
|
||||||
"Last-Translator: \n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: project_link_from_invoice
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_move__display_name
|
|
||||||
msgid "Display Name"
|
|
||||||
msgstr "Nom affiché"
|
|
||||||
|
|
||||||
#. module: project_link_from_invoice
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_move__id
|
|
||||||
msgid "ID"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: project_link_from_invoice
|
|
||||||
#: model:ir.model,name:project_link_from_invoice.model_account_move
|
|
||||||
msgid "Journal Entry"
|
|
||||||
msgstr "Pièce comptable"
|
|
||||||
|
|
||||||
#. module: project_link_from_invoice
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_move____last_update
|
|
||||||
msgid "Last Modified on"
|
|
||||||
msgstr "Dernière modification le"
|
|
||||||
|
|
||||||
#. module: project_link_from_invoice
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_bank_statement_line__project_ids
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_move__project_ids
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_payment__project_ids
|
|
||||||
msgid "Project"
|
|
||||||
msgstr "Projet"
|
|
||||||
|
|
||||||
#. module: project_link_from_invoice
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_bank_statement_line__project_count
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_move__project_count
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_payment__project_count
|
|
||||||
msgid "Project Count"
|
|
||||||
msgstr "Numbre de projets"
|
|
||||||
|
|
||||||
#. module: project_link_from_invoice
|
|
||||||
#: model_terms:ir.ui.view,arch_db:project_link_from_invoice.view_move_form_project_link_from_invoice_inherit
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_bank_statement_line__projects_name
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_move__projects_name
|
|
||||||
#: model:ir.model.fields,field_description:project_link_from_invoice.field_account_payment__projects_name
|
|
||||||
msgid "Project(s)"
|
|
||||||
msgstr "Projet(s)"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
from . import account_move
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Copyright 2022 Stéphan Sainléger (Elabore)
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
from odoo import api, fields, models
|
|
||||||
|
|
||||||
|
|
||||||
class AccountMove(models.Model):
|
|
||||||
_inherit = "account.move"
|
|
||||||
|
|
||||||
project_ids = fields.Many2many(
|
|
||||||
"project.project", string="Projects", compute="_get_related_project_ids"
|
|
||||||
)
|
|
||||||
project_count = fields.Integer("Project Count", compute="_get_related_project_ids")
|
|
||||||
projects_name = fields.Char("Project(s)", compute="_get_related_project_ids")
|
|
||||||
|
|
||||||
def action_open_projects(self):
|
|
||||||
"""Open related projects, in form or list view depending on project numbers."""
|
|
||||||
project_ids = self.project_ids.ids
|
|
||||||
action = self.env["ir.actions.actions"]._for_xml_id(
|
|
||||||
"project.open_view_project_all"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.project_count == 1:
|
|
||||||
action["res_id"] = project_ids[0]
|
|
||||||
action["views"] = [[False, "form"]]
|
|
||||||
else:
|
|
||||||
action["views"] = [[False, "list"], [False, "form"]]
|
|
||||||
|
|
||||||
action["domain"] = [("id", "in", project_ids)]
|
|
||||||
|
|
||||||
del action["target"] # to display breadcrumbs
|
|
||||||
|
|
||||||
return action
|
|
||||||
|
|
||||||
@api.depends("line_ids.sale_line_ids")
|
|
||||||
def _get_related_project_ids(self):
|
|
||||||
for move in self:
|
|
||||||
projects = self.env["project.task"].search(
|
|
||||||
[
|
|
||||||
(
|
|
||||||
"sale_order_id",
|
|
||||||
"in",
|
|
||||||
move.line_ids.sale_line_ids.order_id.ids,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
).project_id
|
|
||||||
move.project_ids = projects.ids
|
|
||||||
move.projects_name = " ; ".join([p.name for p in projects])
|
|
||||||
move.project_count = len(projects)
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Copyright 2022 Stéphan Sainléger (Elabore)
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
from . import test_account_move
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
# Copyright 2022 Stéphan Sainléger (Elabore)
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
from odoo import Command
|
|
||||||
from odoo.tests import tagged
|
|
||||||
from odoo.addons.sale_project.tests.common import TestSaleProjectCommon
|
|
||||||
|
|
||||||
|
|
||||||
@tagged("post_install", "-at_install")
|
|
||||||
class TestProjectLinkFromInvoice(TestSaleProjectCommon):
|
|
||||||
"""Tests for the project_link_from_invoice module.
|
|
||||||
|
|
||||||
Verifies that invoices correctly expose related project(s) via
|
|
||||||
the computed fields project_ids, project_count and projects_name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
|
||||||
|
|
||||||
# Service product that creates a task in a new project on SO confirmation
|
|
||||||
cls.service_product = cls.env["product.product"].create(
|
|
||||||
{
|
|
||||||
"name": "Service with Project",
|
|
||||||
"type": "service",
|
|
||||||
"invoice_policy": "order",
|
|
||||||
"service_tracking": "task_in_project",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Partner used for orders and invoices
|
|
||||||
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
|
|
||||||
|
|
||||||
def _confirm_sale_order(self, product, qty=1):
|
|
||||||
"""Create and confirm a sale order with one service line."""
|
|
||||||
so = self.env["sale.order"].create(
|
|
||||||
{
|
|
||||||
"partner_id": self.partner.id,
|
|
||||||
"order_line": [
|
|
||||||
Command.create(
|
|
||||||
{
|
|
||||||
"product_id": product.id,
|
|
||||||
"product_uom_qty": qty,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
so.action_confirm()
|
|
||||||
return so
|
|
||||||
|
|
||||||
def _invoice_sale_order(self, sale_order):
|
|
||||||
"""Create and post an invoice from a confirmed sale order."""
|
|
||||||
invoice_wizard = (
|
|
||||||
self.env["sale.advance.payment.inv"]
|
|
||||||
.with_context(
|
|
||||||
active_model="sale.order",
|
|
||||||
active_ids=sale_order.ids,
|
|
||||||
active_id=sale_order.id,
|
|
||||||
)
|
|
||||||
.create({"advance_payment_method": "delivered"})
|
|
||||||
)
|
|
||||||
invoices = invoice_wizard._create_invoices(sale_order)
|
|
||||||
invoices.action_post()
|
|
||||||
return invoices
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Tests
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
def test_no_project_on_unrelated_invoice(self):
|
|
||||||
"""An invoice not linked to any sale order has no related project."""
|
|
||||||
invoice = self.env["account.move"].create(
|
|
||||||
{
|
|
||||||
"move_type": "out_invoice",
|
|
||||||
"partner_id": self.partner.id,
|
|
||||||
"invoice_line_ids": [
|
|
||||||
Command.create(
|
|
||||||
{
|
|
||||||
"name": "Manual line",
|
|
||||||
"quantity": 1,
|
|
||||||
"price_unit": 100.0,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.assertEqual(invoice.project_count, 0)
|
|
||||||
self.assertFalse(invoice.project_ids)
|
|
||||||
self.assertEqual(invoice.projects_name, "")
|
|
||||||
|
|
||||||
def test_single_project_linked_to_invoice(self):
|
|
||||||
"""An invoice from a SO that created a task has one related project."""
|
|
||||||
so = self._confirm_sale_order(self.service_product)
|
|
||||||
# The SO must have generated a task in a project
|
|
||||||
task = so.tasks_ids[:1]
|
|
||||||
self.assertTrue(task, "Sale order confirmation should have created a task")
|
|
||||||
project = task.project_id
|
|
||||||
self.assertTrue(project, "Task should belong to a project")
|
|
||||||
|
|
||||||
invoice = self._invoice_sale_order(so)
|
|
||||||
self.assertEqual(len(invoice), 1)
|
|
||||||
self.assertEqual(invoice.project_count, 1)
|
|
||||||
self.assertIn(project, invoice.project_ids)
|
|
||||||
self.assertIn(project.name, invoice.projects_name)
|
|
||||||
|
|
||||||
def test_multiple_projects_linked_to_invoice(self):
|
|
||||||
"""An invoice from two SOs with different projects shows both projects."""
|
|
||||||
so1 = self._confirm_sale_order(self.service_product)
|
|
||||||
so2 = self._confirm_sale_order(self.service_product)
|
|
||||||
|
|
||||||
project1 = so1.tasks_ids[:1].project_id
|
|
||||||
project2 = so2.tasks_ids[:1].project_id
|
|
||||||
self.assertTrue(project1 and project2)
|
|
||||||
# Ensure the two SOs created different projects
|
|
||||||
self.assertNotEqual(project1, project2)
|
|
||||||
|
|
||||||
# Create a single invoice covering both sale orders
|
|
||||||
invoice_wizard = (
|
|
||||||
self.env["sale.advance.payment.inv"]
|
|
||||||
.with_context(
|
|
||||||
active_model="sale.order",
|
|
||||||
active_ids=(so1 + so2).ids,
|
|
||||||
)
|
|
||||||
.create({"advance_payment_method": "delivered"})
|
|
||||||
)
|
|
||||||
invoices = invoice_wizard._create_invoices(so1 + so2)
|
|
||||||
invoices.action_post()
|
|
||||||
|
|
||||||
# Find the invoice that covers both SO lines
|
|
||||||
combined_invoice = invoices[:1]
|
|
||||||
self.assertGreaterEqual(combined_invoice.project_count, 2)
|
|
||||||
self.assertIn(project1, combined_invoice.project_ids)
|
|
||||||
self.assertIn(project2, combined_invoice.project_ids)
|
|
||||||
|
|
||||||
def test_action_open_projects_single(self):
|
|
||||||
"""action_open_projects returns a form view when there is one project."""
|
|
||||||
so = self._confirm_sale_order(self.service_product)
|
|
||||||
invoice = self._invoice_sale_order(so)
|
|
||||||
self.assertEqual(invoice.project_count, 1)
|
|
||||||
|
|
||||||
action = invoice.action_open_projects()
|
|
||||||
self.assertEqual(action["res_id"], invoice.project_ids.id)
|
|
||||||
view_types = [v[1] for v in action["views"]]
|
|
||||||
self.assertIn("form", view_types)
|
|
||||||
self.assertNotIn("list", view_types)
|
|
||||||
|
|
||||||
def test_action_open_projects_multiple(self):
|
|
||||||
"""action_open_projects returns a list view when there are multiple projects."""
|
|
||||||
so1 = self._confirm_sale_order(self.service_product)
|
|
||||||
so2 = self._confirm_sale_order(self.service_product)
|
|
||||||
|
|
||||||
invoice_wizard = (
|
|
||||||
self.env["sale.advance.payment.inv"]
|
|
||||||
.with_context(
|
|
||||||
active_model="sale.order",
|
|
||||||
active_ids=(so1 + so2).ids,
|
|
||||||
)
|
|
||||||
.create({"advance_payment_method": "delivered"})
|
|
||||||
)
|
|
||||||
invoices = invoice_wizard._create_invoices(so1 + so2)
|
|
||||||
invoices.action_post()
|
|
||||||
|
|
||||||
combined_invoice = invoices[:1]
|
|
||||||
if combined_invoice.project_count < 2:
|
|
||||||
self.skipTest("Combined invoice does not span multiple projects")
|
|
||||||
|
|
||||||
action = combined_invoice.action_open_projects()
|
|
||||||
view_types = [v[1] for v in action["views"]]
|
|
||||||
self.assertIn("list", view_types)
|
|
||||||
self.assertIn("form", view_types)
|
|
||||||
self.assertNotIn("target", action)
|
|
||||||
|
|
||||||
def test_projects_name_separator(self):
|
|
||||||
"""projects_name joins multiple project names with ' ; '."""
|
|
||||||
so1 = self._confirm_sale_order(self.service_product)
|
|
||||||
so2 = self._confirm_sale_order(self.service_product)
|
|
||||||
|
|
||||||
invoice_wizard = (
|
|
||||||
self.env["sale.advance.payment.inv"]
|
|
||||||
.with_context(
|
|
||||||
active_model="sale.order",
|
|
||||||
active_ids=(so1 + so2).ids,
|
|
||||||
)
|
|
||||||
.create({"advance_payment_method": "delivered"})
|
|
||||||
)
|
|
||||||
invoices = invoice_wizard._create_invoices(so1 + so2)
|
|
||||||
invoices.action_post()
|
|
||||||
|
|
||||||
combined_invoice = invoices[:1]
|
|
||||||
if combined_invoice.project_count >= 2:
|
|
||||||
self.assertIn(" ; ", combined_invoice.projects_name)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<odoo>
|
|
||||||
<record id="view_move_form_project_link_from_invoice_inherit" model="ir.ui.view">
|
|
||||||
<field name="name">account.move.form.project.link</field>
|
|
||||||
<field name="model">account.move</field>
|
|
||||||
<field name="inherit_id" ref="account.view_move_form" />
|
|
||||||
<field name="priority" eval="99" />
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//div[@name='button_box']" position="inside">
|
|
||||||
<button
|
|
||||||
name="action_open_projects"
|
|
||||||
class="oe_stat_button"
|
|
||||||
icon="fa-puzzle-piece"
|
|
||||||
type="object"
|
|
||||||
invisible="project_count == 0"
|
|
||||||
>
|
|
||||||
<field name="project_count" widget="statinfo" string="Project(s)" />
|
|
||||||
</button>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="view_invoice_tree_project_link_from_invoice_inherit" model="ir.ui.view">
|
|
||||||
<field name="name">account.invoice.list.project.link</field>
|
|
||||||
<field name="model">account.move</field>
|
|
||||||
<field name="inherit_id" ref="account.view_invoice_tree"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//list/field[@name='invoice_date']" position="before">
|
|
||||||
<field name="projects_name" />
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</odoo>
|
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"name": "project_only_current_project_tasks_as_parent",
|
"name": "project_only_current_project_tasks_as_parent",
|
||||||
"version": "18.0.1.0.0",
|
"version": "18.0.1.0.0",
|
||||||
"author": "Elabore",
|
"author": "Elabore",
|
||||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
"website": "https://elabore.coop",
|
||||||
"maintainer": "Laetitia Da Costa",
|
"maintainer": "Laetitia Da Costa",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"category": "Project",
|
"category": "Project",
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record model="ir.ui.view" id="view_tasks_form2_parent_project_inherited">
|
<record model="ir.ui.view" id="view_tasks_form2_parent_project_inherited">
|
||||||
<field name="name">view.tasks.form2.parent.project.inherited</field>
|
<field name="name">view.tasks.form2.parent.project.inherited</field>
|
||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
<field name="inherit_id" ref="project.view_task_form2" />
|
<field name="inherit_id" ref="project.view_task_form2" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath
|
<xpath expr="//page[@name='extra_info']//field[@name='parent_id']" position="attributes">
|
||||||
expr="//page[@name='extra_info']//field[@name='parent_id']"
|
<attribute name="domain">[('project_id','=', project_id), ('stage_id.fold', '=', False)]</attribute>
|
||||||
position="attributes"
|
|
||||||
>
|
|
||||||
<attribute
|
|
||||||
name="domain"
|
|
||||||
>[('project_id','=', project_id), ('stage_id.fold', '=', False)]</attribute>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"name": "project_request_data",
|
"name": "project_request_data",
|
||||||
"version": "18.0.1.0.0",
|
"version": "18.0.1.0.0",
|
||||||
"author": "Elabore",
|
"author": "Elabore",
|
||||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
"website": "https://elabore.coop",
|
||||||
"maintainer": "Stéphan Sainléger",
|
"maintainer": "Stéphan Sainléger",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"category": "Project",
|
"category": "Project",
|
||||||
@@ -89,4 +89,4 @@ This module is maintained by Elabore.
|
|||||||
# and independently installed. Used for synergetic or glue modules.
|
# and independently installed. Used for synergetic or glue modules.
|
||||||
"auto_install": False,
|
"auto_install": False,
|
||||||
"application": False,
|
"application": False,
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
from . import task_service
|
from . import task_service
|
||||||
from . import request_type
|
from . import request_type
|
||||||
from . import project_task
|
from . import project_task
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
from odoo import fields, models
|
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
class Task(models.Model):
|
class Task(models.Model):
|
||||||
_inherit = "project.task"
|
_inherit = "project.task"
|
||||||
|
|
||||||
service_id = fields.Many2one("task.service", string="Service")
|
service_id = fields.Many2one('task.service', string='Service')
|
||||||
request_type_id = fields.Many2one("request.type", string="Request Type")
|
request_type_id = fields.Many2one('request.type', string='Request Type')
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from odoo import fields, models
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
class RequestType(models.Model):
|
class RequestType(models.Model):
|
||||||
_name = "request.type"
|
_name = "request.type"
|
||||||
_description = "Request Type"
|
_description = "Request Type"
|
||||||
|
|
||||||
name = fields.Char("name", required=True)
|
name = fields.Char('name', required=True)
|
||||||
sequence = fields.Integer()
|
sequence = fields.Integer()
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
from odoo import fields, models
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
class TaskService(models.Model):
|
class TaskService(models.Model):
|
||||||
_name = "task.service"
|
_name = "task.service"
|
||||||
_description = "Task service"
|
_description = "Task service"
|
||||||
|
|
||||||
name = fields.Char("name", required=True)
|
name = fields.Char('name', required=True)
|
||||||
sequence = fields.Integer()
|
sequence = fields.Integer()
|
||||||
@@ -2,4 +2,4 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
|||||||
access_task_service_user,project.task_service.user,model_task_service,,1,0,0,0
|
access_task_service_user,project.task_service.user,model_task_service,,1,0,0,0
|
||||||
access_task_service_manager,project.task_service.manager,model_task_service,project.group_project_manager,1,1,1,1
|
access_task_service_manager,project.task_service.manager,model_task_service,project.group_project_manager,1,1,1,1
|
||||||
access_request_type_user,project.request_type.user,model_request_type,,1,0,0,0
|
access_request_type_user,project.request_type.user,model_request_type,,1,0,0,0
|
||||||
access_request_type_manager,project.request_type.manager,model_request_type,project.group_project_manager,1,1,1,1
|
access_request_type_manager,project.request_type.manager,model_request_type,project.group_project_manager,1,1,1,1
|
||||||
|
@@ -1,15 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<template
|
<template id="portal_my_task_request_data" name="My Task: Request Data" inherit_id="project.portal_my_task" priority="40">
|
||||||
id="portal_my_task_request_data"
|
<xpath expr="//div[@id='card_body']/div[hasclass('row','mb-4','container')]" position="after">
|
||||||
name="My Task: Request Data"
|
|
||||||
inherit_id="project.portal_my_task"
|
|
||||||
priority="40"
|
|
||||||
>
|
|
||||||
<xpath
|
|
||||||
expr="//div[@id='card_body']/div[hasclass('row','mb-4','container')]"
|
|
||||||
position="after"
|
|
||||||
>
|
|
||||||
<div id="request_data" class="row mb-4">
|
<div id="request_data" class="row mb-4">
|
||||||
<div class="col-12 col-md-6" t-if="task.service_id">
|
<div class="col-12 col-md-6" t-if="task.service_id">
|
||||||
<strong>Service:</strong>
|
<strong>Service:</strong>
|
||||||
@@ -22,4 +14,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_task_form2_request_data" model="ir.ui.view">
|
<record id="view_task_form2_request_data" model="ir.ui.view">
|
||||||
<field name="name">project.task.form.request.data</field>
|
<field name="name">project.task.form.request.data</field>
|
||||||
@@ -18,19 +18,12 @@
|
|||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='name']" position="before">
|
<xpath expr="//field[@name='name']" position="before">
|
||||||
<div style="font-size:11px; margin-bottom: 8px;"><field
|
<div style="font-size:11px; margin-bottom: 8px;"><field name="create_date" widget="date"/></div>
|
||||||
name="create_date"
|
|
||||||
widget="date"
|
|
||||||
/></div>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='tag_ids']" position="before">
|
<xpath expr="//field[@name='tag_ids']" position="before">
|
||||||
<span style="background:lightblue; font-size:11px"><field
|
<span style="background:lightblue; font-size:11px"><field name="service_id"/></span>
|
||||||
name="service_id"
|
<span style="background:lightsteelblue; font-size:11px"><field name="request_type_id"/></span>
|
||||||
/></span>
|
|
||||||
<span style="background:lightsteelblue; font-size:11px"><field
|
|
||||||
name="request_type_id"
|
|
||||||
/></span>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="request_type_view_tree" model="ir.ui.view">
|
<record id="request_type_view_tree" model="ir.ui.view">
|
||||||
@@ -18,12 +18,6 @@
|
|||||||
<field name="view_mode">list</field>
|
<field name="view_mode">list</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem
|
<menuitem id="menu_request_types" action="act_request_types_list" parent="project.menu_project_config" sequence="99" name="Request Types" />
|
||||||
id="menu_request_types"
|
|
||||||
action="act_request_types_list"
|
|
||||||
parent="project.menu_project_config"
|
|
||||||
sequence="99"
|
|
||||||
name="Request Types"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="task_service_view_tree" model="ir.ui.view">
|
<record id="task_service_view_tree" model="ir.ui.view">
|
||||||
@@ -18,12 +18,6 @@
|
|||||||
<field name="view_mode">list</field>
|
<field name="view_mode">list</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem
|
<menuitem id="menu_task_services" action="act_task_services_list" parent="project.menu_project_config" sequence="99" name="Task Services" />
|
||||||
id="menu_task_services"
|
|
||||||
action="act_task_services_list"
|
|
||||||
parent="project.menu_project_config"
|
|
||||||
sequence="99"
|
|
||||||
name="Task Services"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -5,15 +5,19 @@
|
|||||||
"name": "Project task billable hours",
|
"name": "Project task billable hours",
|
||||||
"version": "18.0.1.0.0",
|
"version": "18.0.1.0.0",
|
||||||
"author": "Elabore",
|
"author": "Elabore",
|
||||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
"website": "https://github.com/elabore-coop/project-tools",
|
||||||
"maintainer": "Laetitia Da Costa",
|
"maintainer": "Laetitia Da Costa",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"category": "Tools",
|
"category": "Tools",
|
||||||
"summary": "In task kanban view, display remaining billable hours instead of remaining hours",
|
"summary": "In task kanban view, display remaining billable hours instead of remaining hours",
|
||||||
"description": "",
|
"description": "",
|
||||||
"depends": ["project", "hr_timesheet", "project_working_time_task_portal"],
|
"depends": [
|
||||||
|
"project",
|
||||||
|
"hr_timesheet",
|
||||||
|
"project_working_time_task_portal"
|
||||||
|
],
|
||||||
"data": [
|
"data": [
|
||||||
"views/project_task_views.xml",
|
"views/project_task_views.xml",
|
||||||
],
|
],
|
||||||
"installable": True,
|
"installable": True,
|
||||||
"application": False,
|
"application": False,
|
||||||
|
|||||||
@@ -1,36 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Replace remaining hours by billable remaining hours in kanban view -->
|
<!-- Replace remaining hours by billable remaining hours in kanban view -->
|
||||||
<record id="view_task_kanban_billable_remaining_hours_inherit" model="ir.ui.view">
|
<record id="view_task_kanban_billable_remaining_hours_inherit" model="ir.ui.view">
|
||||||
<field name="name">project.task.kanban.billable.remaining.hours.inherit</field>
|
<field name="name">project.task.kanban.billable.remaining.hours.inherit</field>
|
||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
<field
|
<field name="inherit_id" ref="hr_timesheet.view_task_kanban_inherited_progress"/>
|
||||||
name="inherit_id"
|
|
||||||
ref="hr_timesheet.view_task_kanban_inherited_progress"
|
|
||||||
/>
|
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//t[@name='allocated_hours']" position="replace">
|
<xpath expr="//t[@name='allocated_hours']" position="replace">
|
||||||
<t t-set="badge" t-value="" />
|
<t t-set="badge" t-value=""/>
|
||||||
<t
|
<t t-set="badge" t-value="'badge-warning'" t-if="record.billable_progress >= 80 and record.billable_progress <= 100"/>
|
||||||
t-set="badge"
|
<t t-set="badge" t-value="'badge-danger'" t-if="record.billable_remaining_hours.raw_value < 0"/>
|
||||||
t-value="'badge-warning'"
|
<t t-set="title" t-value="'Remaining days'" t-if="record.encode_uom_in_days.raw_value"/>
|
||||||
t-if="record.billable_progress >= 80 and record.billable_progress <= 100"
|
<t t-set="title" t-value="'Billable remaining hours'" t-else=""/>
|
||||||
/>
|
<div t-attf-class="oe_kanban_align badge {{ badge }}" t-att-title="title">
|
||||||
<t
|
|
||||||
t-set="badge"
|
|
||||||
t-value="'badge-danger'"
|
|
||||||
t-if="record.billable_remaining_hours.raw_value < 0"
|
|
||||||
/>
|
|
||||||
<t
|
|
||||||
t-set="title"
|
|
||||||
t-value="'Remaining days'"
|
|
||||||
t-if="record.encode_uom_in_days.raw_value"
|
|
||||||
/>
|
|
||||||
<t t-set="title" t-value="'Billable remaining hours'" t-else="" />
|
|
||||||
<div
|
|
||||||
t-attf-class="oe_kanban_align badge {{ badge }}"
|
|
||||||
t-att-title="title"
|
|
||||||
>
|
|
||||||
<field name="billable_remaining_hours" widget="timesheet_uom" />
|
<field name="billable_remaining_hours" widget="timesheet_uom" />
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"name": "project_user_default_project",
|
"name": "project_user_default_project",
|
||||||
"version": "18.0.1.0.0",
|
"version": "18.0.1.0.0",
|
||||||
"author": "Elabore",
|
"author": "Elabore",
|
||||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
"website": "https://elabore.coop",
|
||||||
"maintainer": "Stéphan Sainléger",
|
"maintainer": "Stéphan Sainléger",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"category": "Tools",
|
"category": "Tools",
|
||||||
@@ -62,8 +62,7 @@ This module is maintained by Elabore.
|
|||||||
""",
|
""",
|
||||||
# any module necessary for this one to work correctly
|
# any module necessary for this one to work correctly
|
||||||
"depends": [
|
"depends": [
|
||||||
"base",
|
"base", "project",
|
||||||
"project",
|
|
||||||
],
|
],
|
||||||
"qweb": [
|
"qweb": [
|
||||||
# "static/src/xml/*.xml",
|
# "static/src/xml/*.xml",
|
||||||
@@ -84,4 +83,4 @@ This module is maintained by Elabore.
|
|||||||
# and independently installed. Used for synergetic or glue modules.
|
# and independently installed. Used for synergetic or glue modules.
|
||||||
"auto_install": False,
|
"auto_install": False,
|
||||||
"application": False,
|
"application": False,
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
from . import res_users
|
from . import res_users
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
from odoo import fields, models
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class Users(models.Model):
|
class Users(models.Model):
|
||||||
_inherit = "res.users"
|
_inherit = "res.users"
|
||||||
|
|
||||||
default_project_id = fields.Many2one("project.project", string="Default Project")
|
default_project_id = fields.Many2one('project.project', string='Default Project')
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_users_form_inherit_default_project" model="ir.ui.view">
|
<record id="view_users_form_inherit_default_project" model="ir.ui.view">
|
||||||
<field name="name">view.users.form.inherit.default.project</field>
|
<field name="name">view.users.form.inherit.default.project</field>
|
||||||
@@ -10,4 +10,4 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
2
project_working_time_task_portal/.gitignore
vendored
2
project_working_time_task_portal/.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
*.*~
|
*.*~
|
||||||
*pyc
|
*pyc
|
||||||
@@ -1 +1 @@
|
|||||||
from . import models
|
from . import models
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "project_working_time_task_portal",
|
"name": "project_working_time_task_portal",
|
||||||
"version": "18.0.1.1.0",
|
"version": "18.0.1.0.0",
|
||||||
"author": "Elabore",
|
"author": "Elabore",
|
||||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
"website": "https://elabore.coop",
|
||||||
"maintainer": "Boris Gallet",
|
"maintainer": "Boris Gallet",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"category": "tools",
|
"category": "tools",
|
||||||
@@ -23,10 +23,9 @@
|
|||||||
},
|
},
|
||||||
# always loaded
|
# always loaded
|
||||||
"data": [
|
"data": [
|
||||||
# views/hr_timesheet_portal.xml",
|
#views/hr_timesheet_portal.xml",
|
||||||
"views/hr_timesheet_view_task_form2.xml",
|
"views/hr_timesheet_view_task_form2.xml",
|
||||||
"views/portal_tasks_list.xml",
|
"views/portal_tasks_list.xml"
|
||||||
"views/portal_my_task_with_subtasks.xml"
|
|
||||||
],
|
],
|
||||||
"assets": {
|
"assets": {
|
||||||
"web.assets_frontend": [
|
"web.assets_frontend": [
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
from odoo import models
|
from odoo import models
|
||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
|
|
||||||
|
|
||||||
class AccountAnalyticLine(models.Model):
|
class AccountAnalyticLine(models.Model):
|
||||||
_inherit = "account.analytic.line"
|
_inherit = 'account.analytic.line'
|
||||||
|
|
||||||
def _timesheet_get_portal_domain(self):
|
def _timesheet_get_portal_domain(self):
|
||||||
domain = super()._timesheet_get_portal_domain()
|
domain = super()._timesheet_get_portal_domain()
|
||||||
return expression.AND([domain, [("exclude_from_sale_order", "=", False)]])
|
return expression.AND([domain, [('exclude_from_sale_order', '=', False)]])
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Main
|
* Main
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.oe-project_working_time_task_portal.progressbar {
|
.oe-project_working_time_task_portal.progressbar {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #7c7cac; /* Couleur de la barre de progression */
|
background-color: #7c7cac; /* Couleur de la barre de progression */
|
||||||
@@ -9,3 +11,4 @@
|
|||||||
color: white; /* Couleur du texte dans la barre de progression */
|
color: white; /* Couleur du texte dans la barre de progression */
|
||||||
line-height: 20px; /* Aligner le texte verticalement */
|
line-height: 20px; /* Aligner le texte verticalement */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<template
|
<template id="portal_my_task_allocated_hours" name="My Task: planned hours" inherit_id="hr_timesheet.portal_my_task" priority="40">
|
||||||
id="portal_my_task_allocated_hours"
|
<xpath expr="//t[@t-call='hr_timesheet.portal_timesheet_table']" position="before">
|
||||||
name="My Task: planned hours"
|
|
||||||
inherit_id="hr_timesheet.portal_my_task"
|
|
||||||
priority="40"
|
|
||||||
>
|
|
||||||
<xpath
|
|
||||||
expr="//t[@t-call='hr_timesheet.portal_timesheet_table']"
|
|
||||||
position="before"
|
|
||||||
>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6 pb-2">
|
<div class="col-12 col-md-6 pb-2">
|
||||||
<span style="font-weight:bold;">Planned Hours: </span>
|
<span style="font-weight:bold;">Planned Hours: </span>
|
||||||
<span
|
<span t-field="task.allocated_hours" t-options="{"widget": "timesheet_uom_no_toggle"}"/>
|
||||||
t-field="task.allocated_hours"
|
|
||||||
t-options="{"widget": "timesheet_uom_no_toggle"}"
|
|
||||||
/>
|
|
||||||
<t t-if="task.allow_subtasks and task.subtask_count > 0">
|
<t t-if="task.allow_subtasks and task.subtask_count > 0">
|
||||||
<div>
|
<div>
|
||||||
<strong>Subtask Planned Hours: </strong>
|
<strong>Subtask Planned Hours: </strong>
|
||||||
<span
|
<span t-field="task.subtask_allocated_hours" t-options="{"widget": "timesheet_uom_no_toggle"}"/>
|
||||||
t-field="task.subtask_allocated_hours"
|
|
||||||
t-options="{"widget": "timesheet_uom_no_toggle"}"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<t t-if="task.billable_progress > 0">
|
<t t-if="task.billable_progress > 0">
|
||||||
<div class="col-12 col-md-6 pb-2">
|
<div class="col-12 col-md-6 pb-2">
|
||||||
<strong>Progress: </strong>
|
<strong>Progress: </strong>
|
||||||
<t t-set="progress" t-value="task.billable_progress" />
|
<t t-set="progress" t-value="task.billable_progress"/>
|
||||||
<div
|
<div class="oe-project_working_time_task_portal progressbar" t-attf-style="width: #{progress}%;">
|
||||||
class="oe-project_working_time_task_portal progressbar"
|
<t t-esc="progress"/>%
|
||||||
t-attf-style="width: #{progress}%;"
|
|
||||||
>
|
|
||||||
<t t-esc="progress" />%
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
@@ -43,25 +26,15 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template
|
<template id="portal_my_task_remaining_hours" name="My Task: remaining hours" inherit_id="hr_timesheet.portal_timesheet_table" priority="40">
|
||||||
id="portal_my_task_remaining_hours"
|
|
||||||
name="My Task: remaining hours"
|
|
||||||
inherit_id="hr_timesheet.portal_timesheet_table"
|
|
||||||
priority="40"
|
|
||||||
>
|
|
||||||
<xpath expr="//tr[@t-foreach='timesheets']" position="attributes">
|
<xpath expr="//tr[@t-foreach='timesheets']" position="attributes">
|
||||||
<attribute name="t-if">not timesheet.exclude_from_sale_order</attribute>
|
<attribute name="t-if">not timesheet.exclude_from_sale_order</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//tfoot/tr/th[last()]" position="replace">
|
<xpath expr="//tfoot/tr/th[last()]" position="replace">
|
||||||
<th class="text-end">
|
<th class="text-end">
|
||||||
<div
|
<div t-esc="round(sum(timesheets.filtered(lambda t: not t.exclude_from_sale_order).mapped('unit_amount')), 2)"
|
||||||
t-esc="round(sum(timesheets.filtered(lambda t: not t.exclude_from_sale_order).mapped('unit_amount')), 2)"
|
t-options="{"widget": "float_time"}"/>
|
||||||
t-options="{"widget": "float_time"}"
|
<div t-if="task.allocated_hours > 0">Remaining Hours: <span t-field="task.billable_remaining_hours" t-options="{"widget": "float_time"}"/></div>
|
||||||
/>
|
|
||||||
<div t-if="task.allocated_hours > 0">Remaining Hours: <span
|
|
||||||
t-field="task.billable_remaining_hours"
|
|
||||||
t-options="{"widget": "float_time"}"
|
|
||||||
/></div>
|
|
||||||
</th>
|
</th>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="project_working_time_view_task_form" model="ir.ui.view">
|
<record id="project_working_time_view_task_form" model="ir.ui.view">
|
||||||
@@ -9,165 +9,54 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='effective_hours']" position="after">
|
<xpath expr="//field[@name='effective_hours']" position="after">
|
||||||
<span>
|
<span>
|
||||||
<label
|
<label class="font-weight-bold" for="billable_effective_hours" string="Billable Effective Hours" invisible="non_billable_effective_hours == 0.0 or encode_uom_in_days"/>
|
||||||
class="font-weight-bold"
|
<label class="font-weight-bold" for="billable_effective_hours" string="Billable Effective Days" invisible="non_billable_effective_hours == 0.0 or not encode_uom_in_days"/>
|
||||||
for="billable_effective_hours"
|
|
||||||
string="Billable Effective Hours"
|
|
||||||
invisible="non_billable_effective_hours == 0.0 or encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="font-weight-bold"
|
|
||||||
for="billable_effective_hours"
|
|
||||||
string="Billable Effective Days"
|
|
||||||
invisible="non_billable_effective_hours == 0.0 or not encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<field
|
<field name="billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="non_billable_effective_hours == 0.0" nolabel="1"/>
|
||||||
name="billable_effective_hours"
|
|
||||||
widget="timesheet_uom"
|
|
||||||
class="mt-2"
|
|
||||||
invisible="non_billable_effective_hours == 0.0"
|
|
||||||
nolabel="1"
|
|
||||||
/>
|
|
||||||
<span>
|
<span>
|
||||||
<label
|
<label class="font-weight-bold" for="non_billable_effective_hours" string="Non Billable Effective Hours" invisible="non_billable_effective_hours == 0.0 or encode_uom_in_days"/>
|
||||||
class="font-weight-bold"
|
<label class="font-weight-bold" for="non_billable_effective_hours" string="Non Billable Effective Days" invisible="non_billable_effective_hours == 0.0 or not encode_uom_in_days"/>
|
||||||
for="non_billable_effective_hours"
|
|
||||||
string="Non Billable Effective Hours"
|
|
||||||
invisible="non_billable_effective_hours == 0.0 or encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="font-weight-bold"
|
|
||||||
for="non_billable_effective_hours"
|
|
||||||
string="Non Billable Effective Days"
|
|
||||||
invisible="non_billable_effective_hours == 0.0 or not encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<field
|
<field name="non_billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="non_billable_effective_hours == 0.0" nolabel="1"/>
|
||||||
name="non_billable_effective_hours"
|
|
||||||
widget="timesheet_uom"
|
|
||||||
class="mt-2"
|
|
||||||
invisible="non_billable_effective_hours == 0.0"
|
|
||||||
nolabel="1"
|
|
||||||
/>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='subtask_effective_hours']" position="after">
|
<xpath expr="//field[@name='subtask_effective_hours']" position="after">
|
||||||
<span>
|
<span>
|
||||||
<label
|
<label class="font-weight-bold" for="subtask_billable_effective_hours" string="Subtask Billable Effective Hours" invisible="subtask_effective_hours == 0.0 or encode_uom_in_days"/>
|
||||||
class="font-weight-bold"
|
<label class="font-weight-bold" for="subtask_billable_effective_hours" string="Subtask Billable Effective Days" invisible="subtask_effective_hours == 0.0 or not encode_uom_in_days"/>
|
||||||
for="subtask_billable_effective_hours"
|
|
||||||
string="Subtask Billable Effective Hours"
|
|
||||||
invisible="subtask_effective_hours == 0.0 or encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="font-weight-bold"
|
|
||||||
for="subtask_billable_effective_hours"
|
|
||||||
string="Subtask Billable Effective Days"
|
|
||||||
invisible="subtask_effective_hours == 0.0 or not encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<field
|
<field name="subtask_billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="subtask_effective_hours == 0.0" nolabel="1"/>
|
||||||
name="subtask_billable_effective_hours"
|
|
||||||
widget="timesheet_uom"
|
|
||||||
class="mt-2"
|
|
||||||
invisible="subtask_effective_hours == 0.0"
|
|
||||||
nolabel="1"
|
|
||||||
/>
|
|
||||||
<span>
|
<span>
|
||||||
<label
|
<label class="font-weight-bold" for="subtask_non_billable_effective_hours" string="Subtask Non Billable Effective Hours" invisible="subtask_effective_hours == 0.0 or encode_uom_in_days"/>
|
||||||
class="font-weight-bold"
|
<label class="font-weight-bold" for="subtask_non_billable_effective_hours" string="Subtask Non Billable Effective Days" invisible="subtask_effective_hours == 0.0 or not encode_uom_in_days"/>
|
||||||
for="subtask_non_billable_effective_hours"
|
|
||||||
string="Subtask Non Billable Effective Hours"
|
|
||||||
invisible="subtask_effective_hours == 0.0 or encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="font-weight-bold"
|
|
||||||
for="subtask_non_billable_effective_hours"
|
|
||||||
string="Subtask Non Billable Effective Days"
|
|
||||||
invisible="subtask_effective_hours == 0.0 or not encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<field
|
<field name="subtask_non_billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="subtask_effective_hours == 0.0" nolabel="1"/>
|
||||||
name="subtask_non_billable_effective_hours"
|
|
||||||
widget="timesheet_uom"
|
|
||||||
class="mt-2"
|
|
||||||
invisible="subtask_effective_hours == 0.0"
|
|
||||||
nolabel="1"
|
|
||||||
/>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='total_hours_spent']" position="after">
|
<xpath expr="//field[@name='total_hours_spent']" position="after">
|
||||||
<span invisible="subtask_effective_hours == 0.0">
|
<span invisible="subtask_effective_hours == 0.0">
|
||||||
<label
|
<label for="total_billable_hours_spent" string="Total Billable Hours"
|
||||||
for="total_billable_hours_spent"
|
invisible="encode_uom_in_days"/>
|
||||||
string="Total Billable Hours"
|
<label for="total_billable_hours_spent" string="Total Billable Days"
|
||||||
invisible="encode_uom_in_days"
|
invisible="not encode_uom_in_days"/>
|
||||||
/>
|
|
||||||
<label
|
|
||||||
for="total_billable_hours_spent"
|
|
||||||
string="Total Billable Days"
|
|
||||||
invisible="not encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<field
|
<field name="total_billable_hours_spent" widget="timesheet_uom" nolabel="1"
|
||||||
name="total_billable_hours_spent"
|
invisible="subtask_effective_hours == 0.0"/>
|
||||||
widget="timesheet_uom"
|
|
||||||
nolabel="1"
|
|
||||||
invisible="subtask_effective_hours == 0.0"
|
|
||||||
/>
|
|
||||||
<span invisible="subtask_effective_hours == 0.0">
|
<span invisible="subtask_effective_hours == 0.0">
|
||||||
<label
|
<label for="total_non_billable_hours_spent" string="Total Non Billable Hours"
|
||||||
for="total_non_billable_hours_spent"
|
invisible="encode_uom_in_days"/>
|
||||||
string="Total Non Billable Hours"
|
<label for="total_non_billable_hours_spent" string="Total Non Billable Days"
|
||||||
invisible="encode_uom_in_days"
|
invisible="not encode_uom_in_days"/>
|
||||||
/>
|
|
||||||
<label
|
|
||||||
for="total_non_billable_hours_spent"
|
|
||||||
string="Total Non Billable Days"
|
|
||||||
invisible="not encode_uom_in_days"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<field
|
<field name="total_non_billable_hours_spent" widget="timesheet_uom" nolabel="1"
|
||||||
name="total_non_billable_hours_spent"
|
invisible="subtask_effective_hours == 0.0"/>
|
||||||
widget="timesheet_uom"
|
|
||||||
nolabel="1"
|
|
||||||
invisible="subtask_effective_hours == 0.0"
|
|
||||||
/>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='remaining_hours']" position="after">
|
<xpath expr="//field[@name='remaining_hours']" position="after">
|
||||||
<span>
|
<span>
|
||||||
<label
|
<label class="font-weight-bold text-danger" for="billable_remaining_hours" string="Billable Remaining Hours" invisible="allocated_hours == 0.0 or encode_uom_in_days or billable_remaining_hours >= 0"/>
|
||||||
class="font-weight-bold text-danger"
|
<label class="font-weight-bold" for="billable_remaining_hours" string="Billable Remaining Hours" invisible="allocated_hours == 0.0 or encode_uom_in_days or billable_remaining_hours < 0"/>
|
||||||
for="billable_remaining_hours"
|
<label class="font-weight-bold text-danger" for="billable_remaining_hours" string="Billable Remaining Days" invisible="allocated_hours == 0.0 or not encode_uom_in_days or billable_remaining_hours >= 0"/>
|
||||||
string="Billable Remaining Hours"
|
<label class="font-weight-bold" for="billable_remaining_hours" string="Billable Remaining Days" invisible="allocated_hours == 0.0 or not encode_uom_in_days or billable_remaining_hours < 0"/>
|
||||||
invisible="allocated_hours == 0.0 or encode_uom_in_days or billable_remaining_hours >= 0"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="font-weight-bold"
|
|
||||||
for="billable_remaining_hours"
|
|
||||||
string="Billable Remaining Hours"
|
|
||||||
invisible="allocated_hours == 0.0 or encode_uom_in_days or billable_remaining_hours < 0"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="font-weight-bold text-danger"
|
|
||||||
for="billable_remaining_hours"
|
|
||||||
string="Billable Remaining Days"
|
|
||||||
invisible="allocated_hours == 0.0 or not encode_uom_in_days or billable_remaining_hours >= 0"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="font-weight-bold"
|
|
||||||
for="billable_remaining_hours"
|
|
||||||
string="Billable Remaining Days"
|
|
||||||
invisible="allocated_hours == 0.0 or not encode_uom_in_days or billable_remaining_hours < 0"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<field
|
<field name="billable_remaining_hours" widget="timesheet_uom" class="oe_subtotal_footer_separator" invisible="allocated_hours == 0.0" decoration-danger="billable_remaining_hours < 0" nolabel="1"/>
|
||||||
name="billable_remaining_hours"
|
|
||||||
widget="timesheet_uom"
|
|
||||||
class="oe_subtotal_footer_separator"
|
|
||||||
invisible="allocated_hours == 0.0"
|
|
||||||
decoration-danger="billable_remaining_hours < 0"
|
|
||||||
nolabel="1"
|
|
||||||
/>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
|
|
||||||
<template id="hr_timesheet_portal_timesheet_table_inherit"
|
|
||||||
inherit_id="hr_timesheet.portal_timesheet_table">
|
|
||||||
<xpath expr="//table[hasclass('o_portal_my_doc_table')]" position="after">
|
|
||||||
<t t-foreach="task.child_ids" t-as="subtask">
|
|
||||||
<t t-if="subtask.billable_effective_hours > 0">
|
|
||||||
<a t-attf-href="/my/tasks/#{subtask.id}">
|
|
||||||
<span t-att-title="subtask.name" t-field="subtask.name" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<t t-set="timesheets" t-value="subtask.timesheet_ids" />
|
|
||||||
|
|
||||||
<table class="o_portal_my_doc_table table table-sm table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Employee</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th class="text-end" name="t_label">Time Spent</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tr t-foreach="timesheets" t-as="timesheet" valign="middle">
|
|
||||||
<td>
|
|
||||||
<t t-esc="timesheet.date" t-options='{"widget": "date"}' />
|
|
||||||
</td>
|
|
||||||
<td t-attf-title="#{timesheet.employee_id.name}">
|
|
||||||
<t t-esc="timesheet.employee_id.name" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<t t-esc="timesheet.name" />
|
|
||||||
</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<span t-if="is_uom_day" t-esc="timesheet._get_timesheet_time_day()"
|
|
||||||
t-options='{"widget": "timesheet_uom"}' />
|
|
||||||
<span t-else="" t-field="timesheet.unit_amount"
|
|
||||||
t-options='{"widget": "float_time"}' />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</xpath>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</odoo>
|
|
||||||
|
|
||||||
@@ -1,32 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<template
|
<template id="portal_tasks_list_inherit" inherit_id="project.portal_tasks_list">
|
||||||
id="portal_tasks_list_inherit"
|
<xpath expr="//thead/tr/th[7]" position="replace">
|
||||||
inherit_id="hr_timesheet.portal_tasks_list_inherit"
|
<th t-if="not is_uom_day" class="text-end">Billable Hours</th>
|
||||||
>
|
|
||||||
<xpath
|
|
||||||
expr="//thead//t[@t-if='not project or project.allow_timesheets']/th[@class='text-end']"
|
|
||||||
position="replace"
|
|
||||||
>
|
|
||||||
<th class="text-end">Billable hours</th>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath
|
<xpath expr="//tbody/t/tr/td[7]" position="replace">
|
||||||
expr="//tbody//td[@t-if='not project or project.allow_timesheets']"
|
<td class="text-end">
|
||||||
position="replace"
|
<t t-if="not is_uom_day">
|
||||||
>
|
<span t-field="task.billable_effective_hours"
|
||||||
<td t-if="not project or project.allow_timesheets" class="text-end">
|
t-options='{"widget": "float_time"}' />
|
||||||
<t t-if="task.allow_timesheets and not is_uom_day">
|
<t t-if="task.allocated_hours > 0"> / <span t-field="task.allocated_hours"
|
||||||
<span
|
t-options='{"widget": "float_time"}' />
|
||||||
t-field="task.billable_effective_hours"
|
|
||||||
t-options='{"widget": "float_time"}'
|
|
||||||
/>
|
|
||||||
<t t-if="task.allocated_hours > 0"> / <span
|
|
||||||
t-field="task.allocated_hours"
|
|
||||||
t-options='{"widget": "float_time"}'
|
|
||||||
/>
|
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
</td>
|
</td>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
</odoo>
|
</odoo>
|
||||||
Reference in New Issue
Block a user