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:
|
||||
pull_request:
|
||||
branches:
|
||||
- "16.0*"
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
@@ -13,10 +15,10 @@ jobs:
|
||||
python-version: "3.11"
|
||||
- name: Get python version
|
||||
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
|
||||
- uses: https://gitea.com/actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
# - uses: actions/cache@v4
|
||||
# with:
|
||||
# path: ~/.cache/pre-commit
|
||||
# key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
- name: Install pre-commit
|
||||
run: pip install 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__/
|
||||
*.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/
|
||||
*.*~
|
||||
*.pyc
|
||||
|
||||
@@ -22,7 +22,7 @@ exclude: |
|
||||
(LICENSE.*|COPYING.*)
|
||||
default_language_version:
|
||||
python: python3
|
||||
node: "22.9.0"
|
||||
node: "16.17.0"
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
@@ -38,12 +38,8 @@ repos:
|
||||
entry: found a en.po file
|
||||
language: fail
|
||||
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
|
||||
rev: b89f767503be6ab2b11e4f50a7557cb20066e667
|
||||
rev: f9b919b9868143135a9c9cb03021089cabba8223
|
||||
hooks:
|
||||
# update the NOT INSTALLABLE ADDONS section above
|
||||
- id: oca-update-pre-commit-excluded-addons
|
||||
@@ -53,13 +49,16 @@ repos:
|
||||
$(git rev-parse --show-toplevel))"'
|
||||
- id: oca-gen-addon-readme
|
||||
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/@@")
|
||||
--repo-name=$(basename $(git rev-parse --show-toplevel)) --org-name="Elabore"
|
||||
--if-source-changed --keep-source-digest --convert-fragments-to-markdown'
|
||||
- id: oca-gen-external-dependencies
|
||||
--repo-name=$(basename $(git rev-parse --show-toplevel))
|
||||
--org-name="Elabore"
|
||||
--if-source-changed --keep-source-digest'
|
||||
|
||||
- repo: https://github.com/OCA/odoo-pre-commit-hooks
|
||||
rev: v0.0.33
|
||||
rev: v0.1.4
|
||||
hooks:
|
||||
- id: oca-checks-odoo-module
|
||||
- id: oca-checks-po
|
||||
@@ -78,8 +77,8 @@ repos:
|
||||
files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
|
||||
language: node
|
||||
additional_dependencies:
|
||||
- "prettier@3.3.3"
|
||||
- "@prettier/plugin-xml@3.4.1"
|
||||
- "prettier@2.7.1"
|
||||
- "@prettier/plugin-xml@2.2.0"
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: eslint
|
||||
@@ -92,11 +91,10 @@ repos:
|
||||
types: [javascript]
|
||||
language: node
|
||||
additional_dependencies:
|
||||
- "eslint@9.12.0"
|
||||
- "eslint-plugin-jsdoc@50.3.1"
|
||||
- "globals@16.0.0"
|
||||
- "eslint@8.24.0"
|
||||
- "eslint-plugin-jsdoc@"
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.6.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
# exclude autogenerated files
|
||||
@@ -117,8 +115,23 @@ repos:
|
||||
- id: check-xml
|
||||
- id: mixed-line-ending
|
||||
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
|
||||
rev: v0.6.8
|
||||
rev: v0.12.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
73
.pylintrc
73
.pylintrc
@@ -10,7 +10,7 @@ manifest-required-authors=Elabore
|
||||
manifest-required-keys=license
|
||||
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
|
||||
valid-odoo-versions=18.0
|
||||
valid-odoo-versions=16.0
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=all
|
||||
@@ -25,25 +25,19 @@ disable=all
|
||||
enable=anomalous-backslash-in-string,
|
||||
api-one-deprecated,
|
||||
api-one-multi-together,
|
||||
assignment-from-none,
|
||||
attribute-deprecated,
|
||||
class-camelcase,
|
||||
dangerous-default-value,
|
||||
dangerous-view-replace-wo-priority,
|
||||
development-status-allowed,
|
||||
duplicate-id-csv,
|
||||
duplicate-key,
|
||||
duplicate-xml-fields,
|
||||
duplicate-xml-record-id,
|
||||
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,
|
||||
incoherent-interpreter-exec-perm,
|
||||
license-allowed,
|
||||
manifest-author-string,
|
||||
manifest-deprecated-key,
|
||||
@@ -54,68 +48,73 @@ enable=anomalous-backslash-in-string,
|
||||
method-inverse,
|
||||
method-required-super,
|
||||
method-search,
|
||||
openerp-exception-warning,
|
||||
pointless-statement,
|
||||
pointless-string-statement,
|
||||
print-used,
|
||||
redundant-keyword-arg,
|
||||
redundant-modulename-xml,
|
||||
reimported,
|
||||
relative-import,
|
||||
return-in-init,
|
||||
rst-syntax-error,
|
||||
sql-injection,
|
||||
too-few-format-args,
|
||||
translation-field,
|
||||
translation-required,
|
||||
unreachable,
|
||||
use-vim-comment,
|
||||
wrong-tabs-instead-of-spaces,
|
||||
xml-syntax-error,
|
||||
attribute-string-redundant,
|
||||
character-not-valid-in-resource-link,
|
||||
consider-merging-classes-inherited,
|
||||
context-overridden,
|
||||
create-user-wo-reset-password,
|
||||
dangerous-filter-wo-user,
|
||||
dangerous-qweb-replace-wo-priority,
|
||||
deprecated-data-xml-node,
|
||||
deprecated-openerp-xml-node,
|
||||
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,
|
||||
file-not-used,
|
||||
invalid-commit,
|
||||
manifest-maintainers-list,
|
||||
missing-newline-extrafiles,
|
||||
missing-readme,
|
||||
missing-return,
|
||||
odoo-addons-relative-import,
|
||||
old-api7-method-defined,
|
||||
po-msgstr-variables,
|
||||
po-syntax-error,
|
||||
renamed-field-parameter,
|
||||
resource-not-exist,
|
||||
str-format-used,
|
||||
test-folder-imported,
|
||||
translation-contains-variable,
|
||||
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,
|
||||
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
|
||||
consider-merging-classes-inherited,
|
||||
create-user-wo-reset-password,
|
||||
dangerous-filter-wo-user,
|
||||
deprecated-module,
|
||||
file-not-used,
|
||||
invalid-commit,
|
||||
missing-manifest-dependency,
|
||||
missing-newline-extrafiles,
|
||||
missing-readme,
|
||||
no-utf8-coding-comment,
|
||||
odoo-addons-relative-import,
|
||||
old-api7-method-defined,
|
||||
redefined-builtin,
|
||||
manifest-external-assets
|
||||
too-complex,
|
||||
unnecessary-utf8-coding-comment
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
@@ -9,7 +9,7 @@ manifest-required-authors=Elabore
|
||||
manifest-required-keys=license
|
||||
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
|
||||
valid-odoo-versions=18.0
|
||||
valid-odoo-versions=16.0
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=all
|
||||
@@ -17,25 +17,19 @@ disable=all
|
||||
enable=anomalous-backslash-in-string,
|
||||
api-one-deprecated,
|
||||
api-one-multi-together,
|
||||
assignment-from-none,
|
||||
attribute-deprecated,
|
||||
class-camelcase,
|
||||
dangerous-default-value,
|
||||
dangerous-view-replace-wo-priority,
|
||||
development-status-allowed,
|
||||
duplicate-id-csv,
|
||||
duplicate-key,
|
||||
duplicate-xml-fields,
|
||||
duplicate-xml-record-id,
|
||||
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,
|
||||
incoherent-interpreter-exec-perm,
|
||||
license-allowed,
|
||||
manifest-author-string,
|
||||
manifest-deprecated-key,
|
||||
@@ -46,50 +40,56 @@ enable=anomalous-backslash-in-string,
|
||||
method-inverse,
|
||||
method-required-super,
|
||||
method-search,
|
||||
openerp-exception-warning,
|
||||
pointless-statement,
|
||||
pointless-string-statement,
|
||||
print-used,
|
||||
redundant-keyword-arg,
|
||||
redundant-modulename-xml,
|
||||
reimported,
|
||||
relative-import,
|
||||
return-in-init,
|
||||
rst-syntax-error,
|
||||
sql-injection,
|
||||
too-few-format-args,
|
||||
translation-field,
|
||||
translation-required,
|
||||
unreachable,
|
||||
use-vim-comment,
|
||||
wrong-tabs-instead-of-spaces,
|
||||
xml-syntax-error,
|
||||
attribute-string-redundant,
|
||||
character-not-valid-in-resource-link,
|
||||
consider-merging-classes-inherited,
|
||||
context-overridden,
|
||||
create-user-wo-reset-password,
|
||||
dangerous-filter-wo-user,
|
||||
dangerous-qweb-replace-wo-priority,
|
||||
deprecated-data-xml-node,
|
||||
deprecated-openerp-xml-node,
|
||||
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,
|
||||
file-not-used,
|
||||
invalid-commit,
|
||||
manifest-maintainers-list,
|
||||
missing-newline-extrafiles,
|
||||
missing-readme,
|
||||
missing-return,
|
||||
odoo-addons-relative-import,
|
||||
old-api7-method-defined,
|
||||
po-msgstr-variables,
|
||||
po-syntax-error,
|
||||
renamed-field-parameter,
|
||||
resource-not-exist,
|
||||
str-format-used,
|
||||
test-folder-imported,
|
||||
translation-contains-variable,
|
||||
translation-positional-used,
|
||||
unnecessary-utf8-coding-comment,
|
||||
website-manifest-key-not-valid-uri,
|
||||
xml-attribute-translatable,
|
||||
xml-deprecated-qweb-directive,
|
||||
xml-deprecated-tree-attribute,
|
||||
external-request-timeout
|
||||
|
||||
[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
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
|
||||
{
|
||||
"name": "project_average_acceptable_time",
|
||||
"version": "18.0.1.1.0",
|
||||
"version": "18.0.1.0.0",
|
||||
"author": "Elabore",
|
||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
||||
"website": "https://github.com/elabore-coop/project-tools",
|
||||
"maintainer": "Clément Thomas",
|
||||
"license": "AGPL-3",
|
||||
"category": "Tools",
|
||||
"summary": "Task validation without customer agreement",
|
||||
# any module necessary for this one to work correctly
|
||||
"depends": ["base", "project", "project_user_default_project"],
|
||||
"depends": [
|
||||
"base",
|
||||
"project",
|
||||
"project_user_default_project"
|
||||
],
|
||||
"qweb": [
|
||||
# "static/src/xml/*.xml",
|
||||
],
|
||||
@@ -19,7 +23,10 @@
|
||||
"python": [],
|
||||
},
|
||||
# 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
|
||||
"demo": [],
|
||||
"js": [],
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from . import custom_portal
|
||||
|
||||
from . import custom_portal
|
||||
@@ -1,29 +1,21 @@
|
||||
# Copyright 2020 Lokavaluto ()
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from odoo.http import request, route
|
||||
|
||||
from odoo.addons.portal.controllers.portal import CustomerPortal
|
||||
|
||||
|
||||
class CustomCustomerPortal(CustomerPortal):
|
||||
@route(["/my/account"], type="http", auth="user", website=True)
|
||||
def account(self, redirect=None, **post):
|
||||
self.OPTIONAL_BILLING_FIELDS.append(
|
||||
"average_acceptable_time"
|
||||
) # unecessary save in res partner, but necessary to avoid error on form post
|
||||
|
||||
response = super().account(redirect, **post)
|
||||
self.OPTIONAL_BILLING_FIELDS.append("average_acceptable_time") #unecessary save in res partner, but necessary to avoid error on form post
|
||||
|
||||
response = super(CustomCustomerPortal, self).account(redirect, **post)
|
||||
|
||||
if post and request.httprequest.method == "POST":
|
||||
error, error_message = self.details_form_validate(post)
|
||||
if not error:
|
||||
# sudo: a portal user has no write access at all on
|
||||
# project.project, and no read access on its default project
|
||||
# unless it follows it
|
||||
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"
|
||||
]
|
||||
|
||||
user = request.env.user
|
||||
if user.default_project_id and post["average_acceptable_time"]:
|
||||
user.default_project_id.average_acceptable_time = post["average_acceptable_time"]
|
||||
|
||||
return response
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
|
||||
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):
|
||||
_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):
|
||||
_inherit = "res.partner"
|
||||
|
||||
average_acceptable_time = fields.Float(
|
||||
"Average acceptable time"
|
||||
) # not used, but necessary to post custom field from /my/account
|
||||
average_acceptable_time = fields.Float('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>
|
||||
<!-- Template /my/home -->
|
||||
<template
|
||||
id="portal_my_home_average_acceptable_time"
|
||||
name="Portal My Home: Average acceptable time"
|
||||
inherit_id="portal.side_content"
|
||||
priority="40"
|
||||
>
|
||||
<template 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">
|
||||
<!-- sudo: a portal user cannot read its own default project
|
||||
unless it follows it (project.project_project_rule_portal) -->
|
||||
<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>
|
||||
<hr class="mt-1 mb-0"/>
|
||||
<b>Average acceptable time</b>: <t t-esc="user_id.default_project_id.average_acceptable_time" t-options="{'widget': 'float_time'}"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Template /my/account -->
|
||||
<template
|
||||
id="portal_my_details_average_acceptable_time"
|
||||
name="Portal My details: Average acceptable time"
|
||||
inherit_id="portal.portal_my_details_fields"
|
||||
>
|
||||
<template 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">
|
||||
<div
|
||||
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>
|
||||
<div 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>
|
||||
<input
|
||||
type="text"
|
||||
name="average_acceptable_time"
|
||||
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'}"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -1,44 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<!--Project KANBAN -->
|
||||
<record
|
||||
id="project_project_view_kanban_inherit_average_acceptable_time"
|
||||
model="ir.ui.view"
|
||||
>
|
||||
<field
|
||||
name="name"
|
||||
>project.project.kanban.inherit.average.acceptable.time</field>
|
||||
<record id="project_project_view_kanban_inherit_average_acceptable_time" model="ir.ui.view">
|
||||
<field name="name">project.project.kanban.inherit.average.acceptable.time</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="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('o_project_kanban_main')]" position="inside">
|
||||
<div class="o_project_kanban_boxes">
|
||||
<span>Average acceptable time:<![CDATA[ ]]></span>
|
||||
<field name="average_acceptable_time" widget="float_time" />
|
||||
<field name="average_acceptable_time" widget="float_time"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Project FORM -->
|
||||
<record
|
||||
id="project_project_form_inherit_average_acceptable_time"
|
||||
model="ir.ui.view"
|
||||
>
|
||||
<record 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="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">
|
||||
<xpath expr="//group[@name='extra_settings']" position="inside">
|
||||
<div
|
||||
class="o_settings_average_acceptable_time"
|
||||
style="margin-top:10px;"
|
||||
>
|
||||
<div class="o_settings_average_acceptable_time" style="margin-top:10px;">
|
||||
<!-- <span>Average acceptable time:<![CDATA[ ]]></span> -->
|
||||
<label for="average_acceptable_time" />
|
||||
<field name="average_acceptable_time" widget="float_time" />
|
||||
<label for="average_acceptable_time"/>
|
||||
<field name="average_acceptable_time" widget="float_time"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</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",
|
||||
"version": "18.0.1.0.0",
|
||||
"author": "Elabore",
|
||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
||||
"website": "https://elabore.coop",
|
||||
"maintainer": "Laetitia Da Costa",
|
||||
"license": "AGPL-3",
|
||||
"category": "Project",
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record model="ir.ui.view" id="view_tasks_form2_parent_project_inherited">
|
||||
<field name="name">view.tasks.form2.parent.project.inherited</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath
|
||||
expr="//page[@name='extra_info']//field[@name='parent_id']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="domain"
|
||||
>[('project_id','=', project_id), ('stage_id.fold', '=', False)]</attribute>
|
||||
<xpath expr="//page[@name='extra_info']//field[@name='parent_id']" position="attributes">
|
||||
<attribute name="domain">[('project_id','=', project_id), ('stage_id.fold', '=', False)]</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"name": "project_request_data",
|
||||
"version": "18.0.1.0.0",
|
||||
"author": "Elabore",
|
||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
||||
"website": "https://elabore.coop",
|
||||
"maintainer": "Stéphan Sainléger",
|
||||
"license": "AGPL-3",
|
||||
"category": "Project",
|
||||
@@ -89,4 +89,4 @@ This module is maintained by Elabore.
|
||||
# and independently installed. Used for synergetic or glue modules.
|
||||
"auto_install": False,
|
||||
"application": False,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
from . import task_service
|
||||
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):
|
||||
_inherit = "project.task"
|
||||
|
||||
service_id = fields.Many2one("task.service", string="Service")
|
||||
request_type_id = fields.Many2one("request.type", string="Request Type")
|
||||
service_id = fields.Many2one('task.service', string='Service')
|
||||
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):
|
||||
_name = "request.type"
|
||||
_description = "Request Type"
|
||||
|
||||
name = fields.Char("name", required=True)
|
||||
sequence = fields.Integer()
|
||||
name = fields.Char('name', required=True)
|
||||
sequence = fields.Integer()
|
||||
@@ -1,9 +1,9 @@
|
||||
from odoo import fields, models
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class TaskService(models.Model):
|
||||
_name = "task.service"
|
||||
_description = "Task service"
|
||||
|
||||
name = fields.Char("name", required=True)
|
||||
sequence = fields.Integer()
|
||||
name = fields.Char('name', required=True)
|
||||
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_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_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>
|
||||
<template
|
||||
id="portal_my_task_request_data"
|
||||
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"
|
||||
>
|
||||
<template id="portal_my_task_request_data" 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 class="col-12 col-md-6" t-if="task.service_id">
|
||||
<strong>Service:</strong>
|
||||
@@ -22,4 +14,4 @@
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
</odoo>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="view_task_form2_request_data" model="ir.ui.view">
|
||||
<field name="name">project.task.form.request.data</field>
|
||||
@@ -18,19 +18,12 @@
|
||||
<field name="model">project.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="before">
|
||||
<div style="font-size:11px; margin-bottom: 8px;"><field
|
||||
name="create_date"
|
||||
widget="date"
|
||||
/></div>
|
||||
<div style="font-size:11px; margin-bottom: 8px;"><field name="create_date" widget="date"/></div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='tag_ids']" position="before">
|
||||
<span style="background:lightblue; font-size:11px"><field
|
||||
name="service_id"
|
||||
/></span>
|
||||
<span style="background:lightsteelblue; font-size:11px"><field
|
||||
name="request_type_id"
|
||||
/></span>
|
||||
<span style="background:lightblue; font-size:11px"><field name="service_id"/></span>
|
||||
<span style="background:lightsteelblue; font-size:11px"><field name="request_type_id"/></span>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
</odoo>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="request_type_view_tree" model="ir.ui.view">
|
||||
@@ -18,12 +18,6 @@
|
||||
<field name="view_mode">list</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_request_types"
|
||||
action="act_request_types_list"
|
||||
parent="project.menu_project_config"
|
||||
sequence="99"
|
||||
name="Request Types"
|
||||
/>
|
||||
<menuitem 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>
|
||||
|
||||
<record id="task_service_view_tree" model="ir.ui.view">
|
||||
@@ -18,12 +18,6 @@
|
||||
<field name="view_mode">list</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_task_services"
|
||||
action="act_task_services_list"
|
||||
parent="project.menu_project_config"
|
||||
sequence="99"
|
||||
name="Task Services"
|
||||
/>
|
||||
<menuitem 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",
|
||||
"version": "18.0.1.0.0",
|
||||
"author": "Elabore",
|
||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
||||
"website": "https://github.com/elabore-coop/project-tools",
|
||||
"maintainer": "Laetitia Da Costa",
|
||||
"license": "AGPL-3",
|
||||
"category": "Tools",
|
||||
"summary": "In task kanban view, display remaining billable hours instead of remaining hours",
|
||||
"description": "",
|
||||
"depends": ["project", "hr_timesheet", "project_working_time_task_portal"],
|
||||
"depends": [
|
||||
"project",
|
||||
"hr_timesheet",
|
||||
"project_working_time_task_portal"
|
||||
],
|
||||
"data": [
|
||||
"views/project_task_views.xml",
|
||||
"views/project_task_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"application": False,
|
||||
|
||||
@@ -1,36 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<!-- Replace remaining hours by billable remaining hours in kanban 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="model">project.task</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="hr_timesheet.view_task_kanban_inherited_progress"
|
||||
/>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_task_kanban_inherited_progress"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//t[@name='allocated_hours']" position="replace">
|
||||
<t t-set="badge" t-value="" />
|
||||
<t
|
||||
t-set="badge"
|
||||
t-value="'badge-warning'"
|
||||
t-if="record.billable_progress >= 80 and record.billable_progress <= 100"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
<t t-set="badge" t-value=""/>
|
||||
<t t-set="badge" t-value="'badge-warning'" t-if="record.billable_progress >= 80 and record.billable_progress <= 100"/>
|
||||
<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" />
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
|
||||
from . import models
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"name": "project_user_default_project",
|
||||
"version": "18.0.1.0.0",
|
||||
"author": "Elabore",
|
||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
||||
"website": "https://elabore.coop",
|
||||
"maintainer": "Stéphan Sainléger",
|
||||
"license": "AGPL-3",
|
||||
"category": "Tools",
|
||||
@@ -62,8 +62,7 @@ This module is maintained by Elabore.
|
||||
""",
|
||||
# any module necessary for this one to work correctly
|
||||
"depends": [
|
||||
"base",
|
||||
"project",
|
||||
"base", "project",
|
||||
],
|
||||
"qweb": [
|
||||
# "static/src/xml/*.xml",
|
||||
@@ -84,4 +83,4 @@ This module is maintained by Elabore.
|
||||
# and independently installed. Used for synergetic or glue modules.
|
||||
"auto_install": 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):
|
||||
_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>
|
||||
<record id="view_users_form_inherit_default_project" model="ir.ui.view">
|
||||
<field name="name">view.users.form.inherit.default.project</field>
|
||||
@@ -10,4 +10,4 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</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",
|
||||
"version": "18.0.1.1.0",
|
||||
"version": "18.0.1.0.0",
|
||||
"author": "Elabore",
|
||||
"website": "https://git.elabore.coop/elabore/project-tools",
|
||||
"website": "https://elabore.coop",
|
||||
"maintainer": "Boris Gallet",
|
||||
"license": "AGPL-3",
|
||||
"category": "tools",
|
||||
@@ -23,10 +23,9 @@
|
||||
},
|
||||
# always loaded
|
||||
"data": [
|
||||
# views/hr_timesheet_portal.xml",
|
||||
#views/hr_timesheet_portal.xml",
|
||||
"views/hr_timesheet_view_task_form2.xml",
|
||||
"views/portal_tasks_list.xml",
|
||||
"views/portal_my_task_with_subtasks.xml"
|
||||
"views/portal_tasks_list.xml"
|
||||
],
|
||||
"assets": {
|
||||
"web.assets_frontend": [
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
from odoo import models
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = "account.analytic.line"
|
||||
_inherit = 'account.analytic.line'
|
||||
|
||||
def _timesheet_get_portal_domain(self):
|
||||
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 {
|
||||
height: 20px;
|
||||
background-color: #7c7cac; /* Couleur de la barre de progression */
|
||||
@@ -9,3 +11,4 @@
|
||||
color: white; /* Couleur du texte dans la barre de progression */
|
||||
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>
|
||||
<template
|
||||
id="portal_my_task_allocated_hours"
|
||||
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"
|
||||
>
|
||||
<template id="portal_my_task_allocated_hours" 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="col-12 col-md-6 pb-2">
|
||||
<span style="font-weight:bold;">Planned Hours: </span>
|
||||
<span
|
||||
t-field="task.allocated_hours"
|
||||
t-options="{"widget": "timesheet_uom_no_toggle"}"
|
||||
/>
|
||||
<span t-field="task.allocated_hours" t-options="{"widget": "timesheet_uom_no_toggle"}"/>
|
||||
<t t-if="task.allow_subtasks and task.subtask_count > 0">
|
||||
<div>
|
||||
<strong>Subtask Planned Hours: </strong>
|
||||
<span
|
||||
t-field="task.subtask_allocated_hours"
|
||||
t-options="{"widget": "timesheet_uom_no_toggle"}"
|
||||
/>
|
||||
<span t-field="task.subtask_allocated_hours" t-options="{"widget": "timesheet_uom_no_toggle"}"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="task.billable_progress > 0">
|
||||
<div class="col-12 col-md-6 pb-2">
|
||||
<strong>Progress: </strong>
|
||||
<t t-set="progress" t-value="task.billable_progress" />
|
||||
<div
|
||||
class="oe-project_working_time_task_portal progressbar"
|
||||
t-attf-style="width: #{progress}%;"
|
||||
>
|
||||
<t t-esc="progress" />%
|
||||
<t t-set="progress" t-value="task.billable_progress"/>
|
||||
<div class="oe-project_working_time_task_portal progressbar" t-attf-style="width: #{progress}%;">
|
||||
<t t-esc="progress"/>%
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
@@ -43,25 +26,15 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template
|
||||
id="portal_my_task_remaining_hours"
|
||||
name="My Task: remaining hours"
|
||||
inherit_id="hr_timesheet.portal_timesheet_table"
|
||||
priority="40"
|
||||
>
|
||||
<template 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">
|
||||
<attribute name="t-if">not timesheet.exclude_from_sale_order</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//tfoot/tr/th[last()]" position="replace">
|
||||
<th class="text-end">
|
||||
<div
|
||||
t-esc="round(sum(timesheets.filtered(lambda t: not t.exclude_from_sale_order).mapped('unit_amount')), 2)"
|
||||
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-esc="round(sum(timesheets.filtered(lambda t: not t.exclude_from_sale_order).mapped('unit_amount')), 2)"
|
||||
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>
|
||||
</th>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="project_working_time_view_task_form" model="ir.ui.view">
|
||||
@@ -9,165 +9,54 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='effective_hours']" position="after">
|
||||
<span>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<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"/>
|
||||
<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>
|
||||
<field
|
||||
name="billable_effective_hours"
|
||||
widget="timesheet_uom"
|
||||
class="mt-2"
|
||||
invisible="non_billable_effective_hours == 0.0"
|
||||
nolabel="1"
|
||||
/>
|
||||
<field name="billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="non_billable_effective_hours == 0.0" nolabel="1"/>
|
||||
<span>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<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"/>
|
||||
<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>
|
||||
<field
|
||||
name="non_billable_effective_hours"
|
||||
widget="timesheet_uom"
|
||||
class="mt-2"
|
||||
invisible="non_billable_effective_hours == 0.0"
|
||||
nolabel="1"
|
||||
/>
|
||||
<field name="non_billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="non_billable_effective_hours == 0.0" nolabel="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='subtask_effective_hours']" position="after">
|
||||
<span>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<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"/>
|
||||
<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>
|
||||
<field
|
||||
name="subtask_billable_effective_hours"
|
||||
widget="timesheet_uom"
|
||||
class="mt-2"
|
||||
invisible="subtask_effective_hours == 0.0"
|
||||
nolabel="1"
|
||||
/>
|
||||
<field name="subtask_billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="subtask_effective_hours == 0.0" nolabel="1"/>
|
||||
<span>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<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"/>
|
||||
<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>
|
||||
<field
|
||||
name="subtask_non_billable_effective_hours"
|
||||
widget="timesheet_uom"
|
||||
class="mt-2"
|
||||
invisible="subtask_effective_hours == 0.0"
|
||||
nolabel="1"
|
||||
/>
|
||||
<field name="subtask_non_billable_effective_hours" widget="timesheet_uom" class="mt-2" invisible="subtask_effective_hours == 0.0" nolabel="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='total_hours_spent']" position="after">
|
||||
<span invisible="subtask_effective_hours == 0.0">
|
||||
<label
|
||||
for="total_billable_hours_spent"
|
||||
string="Total Billable Hours"
|
||||
invisible="encode_uom_in_days"
|
||||
/>
|
||||
<label
|
||||
for="total_billable_hours_spent"
|
||||
string="Total Billable Days"
|
||||
invisible="not encode_uom_in_days"
|
||||
/>
|
||||
<label for="total_billable_hours_spent" string="Total Billable Hours"
|
||||
invisible="encode_uom_in_days"/>
|
||||
<label for="total_billable_hours_spent" string="Total Billable Days"
|
||||
invisible="not encode_uom_in_days"/>
|
||||
</span>
|
||||
<field
|
||||
name="total_billable_hours_spent"
|
||||
widget="timesheet_uom"
|
||||
nolabel="1"
|
||||
invisible="subtask_effective_hours == 0.0"
|
||||
/>
|
||||
<field name="total_billable_hours_spent" widget="timesheet_uom" nolabel="1"
|
||||
invisible="subtask_effective_hours == 0.0"/>
|
||||
<span invisible="subtask_effective_hours == 0.0">
|
||||
<label
|
||||
for="total_non_billable_hours_spent"
|
||||
string="Total Non Billable Hours"
|
||||
invisible="encode_uom_in_days"
|
||||
/>
|
||||
<label
|
||||
for="total_non_billable_hours_spent"
|
||||
string="Total Non Billable Days"
|
||||
invisible="not encode_uom_in_days"
|
||||
/>
|
||||
<label for="total_non_billable_hours_spent" string="Total Non Billable Hours"
|
||||
invisible="encode_uom_in_days"/>
|
||||
<label for="total_non_billable_hours_spent" string="Total Non Billable Days"
|
||||
invisible="not encode_uom_in_days"/>
|
||||
</span>
|
||||
<field
|
||||
name="total_non_billable_hours_spent"
|
||||
widget="timesheet_uom"
|
||||
nolabel="1"
|
||||
invisible="subtask_effective_hours == 0.0"
|
||||
/>
|
||||
<field name="total_non_billable_hours_spent" widget="timesheet_uom" nolabel="1"
|
||||
invisible="subtask_effective_hours == 0.0"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='remaining_hours']" position="after">
|
||||
<span>
|
||||
<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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<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"/>
|
||||
<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>
|
||||
<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"
|
||||
/>
|
||||
<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"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</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>
|
||||
<template
|
||||
id="portal_tasks_list_inherit"
|
||||
inherit_id="hr_timesheet.portal_tasks_list_inherit"
|
||||
>
|
||||
<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>
|
||||
<template id="portal_tasks_list_inherit" inherit_id="project.portal_tasks_list">
|
||||
<xpath expr="//thead/tr/th[7]" position="replace">
|
||||
<th t-if="not is_uom_day" class="text-end">Billable Hours</th>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//tbody//td[@t-if='not project or project.allow_timesheets']"
|
||||
position="replace"
|
||||
>
|
||||
<td t-if="not project or project.allow_timesheets" class="text-end">
|
||||
<t t-if="task.allow_timesheets and not is_uom_day">
|
||||
<span
|
||||
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"}'
|
||||
/>
|
||||
<xpath expr="//tbody/t/tr/td[7]" position="replace">
|
||||
<td class="text-end">
|
||||
<t t-if="not is_uom_day">
|
||||
<span 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>
|
||||
</td>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user