Compare commits
26 Commits
14.0-imp-m
...
14.0-copie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
167aefee13 | ||
|
|
a04b8980e1 | ||
|
|
54d6e6ac9d | ||
|
|
f3d6b67043 | ||
|
|
1963af114b | ||
|
|
1da4c40927 | ||
|
|
edc9db5839 | ||
|
|
882d068f1a | ||
|
|
878db1d0a1 | ||
|
|
600acd2f26 | ||
|
|
059c3b4a09 | ||
|
|
895e1d9dd0 | ||
|
|
0b3ffc804f | ||
|
|
c0a03dbb0e | ||
|
|
6e5f263283 | ||
|
|
d4ebbb28d9 | ||
|
|
7dd204e57e | ||
|
|
e1a84973da | ||
|
|
13e68ac0f5 | ||
|
|
3b17c2e5fb | ||
|
|
0be112dc84 | ||
|
|
2854d4fdda | ||
|
|
6c51a92acc | ||
|
|
f3910ab528 | ||
|
|
05374c4b4a | ||
|
|
45500f5bd8 |
14
.copier-answers.yml
Normal file
14
.copier-answers.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Do NOT update manually; changes here will be overwritten by Copier
|
||||
_commit: v1.3.6
|
||||
_src_path: https://github.com/OCA/oca-addons-repo-template.git
|
||||
ci: Travis
|
||||
dependency_installation_mode: OCA
|
||||
generate_requirements_txt: true
|
||||
include_wkhtmltopdf: false
|
||||
odoo_version: 14.0
|
||||
rebel_module_groups: []
|
||||
repo_description: Modules that improve UX sensibly
|
||||
repo_name: Odoo Usability modules
|
||||
repo_slug: odoo-usability
|
||||
travis_apt_packages: []
|
||||
travis_apt_sources: []
|
||||
20
.editorconfig
Normal file
20
.editorconfig
Normal file
@@ -0,0 +1,20 @@
|
||||
# Configuration for known file extensions
|
||||
[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{json,yml,yaml,rst,md}]
|
||||
indent_size = 2
|
||||
|
||||
# Do not configure editor for libs and autogenerated content
|
||||
[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
|
||||
charset = unset
|
||||
end_of_line = unset
|
||||
indent_size = unset
|
||||
indent_style = unset
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
187
.eslintrc.yml
Normal file
187
.eslintrc.yml
Normal file
@@ -0,0 +1,187 @@
|
||||
env:
|
||||
browser: true
|
||||
es6: true
|
||||
|
||||
# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449
|
||||
parserOptions:
|
||||
ecmaVersion: 2017
|
||||
|
||||
overrides:
|
||||
- files:
|
||||
- "**/*.esm.js"
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
|
||||
# Globals available in Odoo that shouldn't produce errorings
|
||||
globals:
|
||||
_: readonly
|
||||
$: readonly
|
||||
fuzzy: readonly
|
||||
jQuery: readonly
|
||||
moment: readonly
|
||||
odoo: readonly
|
||||
openerp: readonly
|
||||
owl: readonly
|
||||
|
||||
# Styling is handled by Prettier, so we only need to enable AST rules;
|
||||
# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890
|
||||
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
|
||||
valid-jsdoc:
|
||||
- warn
|
||||
- prefer:
|
||||
arg: param
|
||||
argument: param
|
||||
augments: extends
|
||||
constructor: class
|
||||
exception: throws
|
||||
func: function
|
||||
method: function
|
||||
prop: property
|
||||
return: returns
|
||||
virtual: abstract
|
||||
yield: yields
|
||||
preferType:
|
||||
array: Array
|
||||
bool: Boolean
|
||||
boolean: Boolean
|
||||
number: Number
|
||||
object: Object
|
||||
str: String
|
||||
string: String
|
||||
requireParamDescription: false
|
||||
requireReturn: false
|
||||
requireReturnDescription: false
|
||||
requireReturnType: false
|
||||
valid-typeof: warn
|
||||
yoda: warn
|
||||
12
.flake8
Normal file
12
.flake8
Normal file
@@ -0,0 +1,12 @@
|
||||
[flake8]
|
||||
max-line-length = 88
|
||||
max-complexity = 16
|
||||
# B = bugbear
|
||||
# B9 = bugbear opinionated (incl line length)
|
||||
select = C,E,F,W,B,B9
|
||||
# E203: whitespace before ':' (black behaviour)
|
||||
# E501: flake8 line length (covered by bugbear B950)
|
||||
# W503: line break before binary operator (black behaviour)
|
||||
ignore = E203,E501,W503
|
||||
per-file-ignores=
|
||||
__init__.py:F401
|
||||
20
.github/workflows/pre-commit.yml
vendored
Normal file
20
.github/workflows/pre-commit.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: pre-commit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
# The pylint-odoo version we use here does not support python 3.10
|
||||
# https://github.com/OCA/oca-addons-repo-template/issues/80
|
||||
# We also need to pin to an older version of python for older odoo versions
|
||||
# where we are not using black > 21. Older black versions won't work with
|
||||
# Python 3.9.8+, and we can't bump black without reformatting.
|
||||
python-version: "3.9.7"
|
||||
- uses: pre-commit/action@v2.0.0
|
||||
69
.github/workflows/stale.yml
vendored
Normal file
69
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 12 * * 0"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Stale PRs and issues policy
|
||||
uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# General settings.
|
||||
ascending: true
|
||||
remove-stale-when-updated: true
|
||||
# Pull Requests settings.
|
||||
# 120+30 day stale policy for PRs
|
||||
# * Except PRs marked as "no stale"
|
||||
days-before-pr-stale: 120
|
||||
days-before-pr-close: 30
|
||||
exempt-pr-labels: "no stale"
|
||||
stale-pr-label: "stale"
|
||||
stale-pr-message: >
|
||||
There hasn't been any activity on this pull request in the past 4 months, so
|
||||
it has been marked as stale and it will be closed automatically if no
|
||||
further activity occurs in the next 30 days.
|
||||
|
||||
If you want this PR to never become stale, please ask a PSC member to apply
|
||||
the "no stale" label.
|
||||
# Issues settings.
|
||||
# 180+30 day stale policy for open issues
|
||||
# * Except Issues marked as "no stale"
|
||||
days-before-issue-stale: 180
|
||||
days-before-issue-close: 30
|
||||
exempt-issue-labels: "no stale,needs more information"
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: >
|
||||
There hasn't been any activity on this issue in the past 6 months, so it has
|
||||
been marked as stale and it will be closed automatically if no further
|
||||
activity occurs in the next 30 days.
|
||||
|
||||
If you want this issue to never become stale, please ask a PSC member to
|
||||
apply the "no stale" label.
|
||||
|
||||
# 15+30 day stale policy for issues pending more information
|
||||
# * Issues that are pending more information
|
||||
# * Except Issues marked as "no stale"
|
||||
- name: Needs more information stale issues policy
|
||||
uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
ascending: true
|
||||
only-labels: "needs more information"
|
||||
exempt-issue-labels: "no stale"
|
||||
days-before-stale: 15
|
||||
days-before-close: 30
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
remove-stale-when-updated: true
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: >
|
||||
This issue needs more information and there hasn't been any activity
|
||||
recently, so it has been marked as stale and it will be closed automatically
|
||||
if no further activity occurs in the next 30 days.
|
||||
|
||||
If you think this is a mistake, please ask a PSC member to remove the "needs
|
||||
more information" label.
|
||||
19
.gitignore
vendored
19
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
/.venv
|
||||
/.pytest_cache
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
@@ -21,6 +23,7 @@ var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
*.eggs
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
@@ -40,6 +43,19 @@ coverage.xml
|
||||
# Pycharm
|
||||
.idea
|
||||
|
||||
# Eclipse
|
||||
.settings
|
||||
|
||||
# Visual Studio cache/options directory
|
||||
.vs/
|
||||
.vscode
|
||||
|
||||
# OSX Files
|
||||
.DS_Store
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
@@ -54,3 +70,6 @@ docs/_build/
|
||||
# Backup files
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# OCA rules
|
||||
!static/lib/
|
||||
|
||||
13
.isort.cfg
Normal file
13
.isort.cfg
Normal file
@@ -0,0 +1,13 @@
|
||||
[settings]
|
||||
; see https://github.com/psf/black
|
||||
multi_line_output=3
|
||||
include_trailing_comma=True
|
||||
force_grid_wrap=0
|
||||
combine_as_imports=True
|
||||
use_parentheses=True
|
||||
line_length=88
|
||||
known_odoo=odoo
|
||||
known_odoo_addons=odoo.addons
|
||||
sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER
|
||||
default_section=THIRDPARTY
|
||||
ensure_newline_before_comments = True
|
||||
160
.pre-commit-config.yaml
Normal file
160
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,160 @@
|
||||
exclude: |
|
||||
(?x)
|
||||
# NOT INSTALLABLE ADDONS
|
||||
^account_fiscal_position_payable_receivable/|
|
||||
^account_invoice_margin/|
|
||||
^account_invoice_update_wizard/|
|
||||
^company_code/|
|
||||
^developer_menu/|
|
||||
^intrastat_product_type/|
|
||||
^mass_mailing_usability/|
|
||||
^mrp_average_cost/|
|
||||
^mrp_no_product_template_menu/|
|
||||
^mrp_product_tree_default/|
|
||||
^sale_down_payment/|
|
||||
^sale_margin_no_onchange/|
|
||||
^sale_no_configurator_button/|
|
||||
^sale_quotation_title/|
|
||||
^service_line_qty_update_base/|
|
||||
^service_line_qty_update_purchase/|
|
||||
^service_line_qty_update_sale/|
|
||||
^stock_no_product_template_menu/|
|
||||
^stock_user_default_warehouse_base/|
|
||||
^stock_user_default_warehouse_mrp/|
|
||||
^stock_user_default_warehouse_purchase/|
|
||||
^stock_user_default_warehouse_sale/|
|
||||
^volume_precision/|
|
||||
# END NOT INSTALLABLE ADDONS
|
||||
# Files and folders generated by bots, to avoid loops
|
||||
^setup/|/static/description/index\.html$|
|
||||
# We don't want to mess with tool-generated files
|
||||
.svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
|
||||
# Maybe reactivate this when all README files include prettier ignore tags?
|
||||
^README\.md$|
|
||||
# Library files can have extraneous formatting (even minimized)
|
||||
/static/(src/)?lib/|
|
||||
# Repos using Sphinx to generate docs don't need prettying
|
||||
^docs/_templates/.*\.html$|
|
||||
# You don't usually want a bot to modify your legal texts
|
||||
(LICENSE.*|COPYING.*)
|
||||
default_language_version:
|
||||
python: python3
|
||||
node: "14.13.0"
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
# These files are most likely copier diff rejection junks; if found,
|
||||
# review them manually, fix the problem (if needed) and remove them
|
||||
- id: forbidden-files
|
||||
name: forbidden files
|
||||
entry: found forbidden files; remove them
|
||||
language: fail
|
||||
files: "\\.rej$"
|
||||
- repo: https://github.com/oca/maintainer-tools
|
||||
rev: ab1d7f6
|
||||
hooks:
|
||||
# update the NOT INSTALLABLE ADDONS section above
|
||||
- id: oca-update-pre-commit-excluded-addons
|
||||
- id: oca-fix-manifest-website
|
||||
args: ["https://github.com/OCA/odoo-usability"]
|
||||
- repo: https://github.com/myint/autoflake
|
||||
rev: v1.4
|
||||
hooks:
|
||||
- id: autoflake
|
||||
args:
|
||||
- --expand-star-imports
|
||||
- --ignore-init-module-imports
|
||||
- --in-place
|
||||
- --remove-all-unused-imports
|
||||
- --remove-duplicate-keys
|
||||
- --remove-unused-variables
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.1.2
|
||||
hooks:
|
||||
- id: prettier
|
||||
name: prettier (with plugin-xml)
|
||||
additional_dependencies:
|
||||
- "prettier@2.1.2"
|
||||
- "@prettier/plugin-xml@0.12.0"
|
||||
args:
|
||||
- --plugin=@prettier/plugin-xml
|
||||
files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v7.8.1
|
||||
hooks:
|
||||
- id: eslint
|
||||
verbose: true
|
||||
args:
|
||||
- --color
|
||||
- --fix
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
# exclude autogenerated files
|
||||
exclude: /README\.rst$|\.pot?$
|
||||
- id: end-of-file-fixer
|
||||
# exclude autogenerated files
|
||||
exclude: /README\.rst$|\.pot?$
|
||||
- id: debug-statements
|
||||
- id: fix-encoding-pragma
|
||||
args: ["--remove"]
|
||||
- id: check-case-conflict
|
||||
- id: check-docstring-first
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
# exclude files where underlines are not distinguishable from merge conflicts
|
||||
exclude: /README\.rst$|^docs/.*\.rst$
|
||||
- id: check-symlinks
|
||||
- id: check-xml
|
||||
- id: mixed-line-ending
|
||||
args: ["--fix=lf"]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.7.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ["--keep-percent-format"]
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.5.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort except __init__.py
|
||||
args:
|
||||
- --settings=.
|
||||
exclude: /__init__\.py$
|
||||
- repo: https://github.com/acsone/setuptools-odoo
|
||||
rev: 2.6.0
|
||||
hooks:
|
||||
- id: setuptools-odoo-make-default
|
||||
- id: setuptools-odoo-get-requirements
|
||||
args:
|
||||
- --output
|
||||
- requirements.txt
|
||||
- --header
|
||||
- "# generated from manifests external_dependencies"
|
||||
- repo: https://gitlab.com/PyCQA/flake8
|
||||
rev: 3.8.3
|
||||
hooks:
|
||||
- id: flake8
|
||||
name: flake8
|
||||
additional_dependencies: ["flake8-bugbear==20.1.4"]
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: pylint-2.5.3
|
||||
hooks:
|
||||
- id: pylint
|
||||
name: pylint with optional checks
|
||||
args:
|
||||
- --rcfile=.pylintrc
|
||||
- --exit-zero
|
||||
verbose: true
|
||||
additional_dependencies: &pylint_deps
|
||||
- pylint-odoo==3.5.0
|
||||
- id: pylint
|
||||
name: pylint with mandatory checks
|
||||
args:
|
||||
- --rcfile=.pylintrc-mandatory
|
||||
additional_dependencies: *pylint_deps
|
||||
8
.prettierrc.yml
Normal file
8
.prettierrc.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
# Defaults for all prettier-supported languages.
|
||||
# Prettier will complete this with settings from .editorconfig file.
|
||||
bracketSpacing: false
|
||||
printWidth: 88
|
||||
proseWrap: always
|
||||
semi: true
|
||||
trailingComma: "es5"
|
||||
xmlWhitespaceSensitivity: "strict"
|
||||
86
.pylintrc
Normal file
86
.pylintrc
Normal file
@@ -0,0 +1,86 @@
|
||||
[MASTER]
|
||||
load-plugins=pylint_odoo
|
||||
score=n
|
||||
|
||||
[ODOOLINT]
|
||||
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
|
||||
manifest_required_authors=Odoo Community Association (OCA)
|
||||
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=14.0
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=all
|
||||
|
||||
# This .pylintrc contains optional AND mandatory checks and is meant to be
|
||||
# loaded in an IDE to have it check everything, in the hope this will make
|
||||
# optional checks more visible to contributors who otherwise never look at a
|
||||
# green travis to see optional checks that failed.
|
||||
# .pylintrc-mandatory containing only mandatory checks is used the pre-commit
|
||||
# config as a blocking check.
|
||||
|
||||
enable=anomalous-backslash-in-string,
|
||||
api-one-deprecated,
|
||||
api-one-multi-together,
|
||||
assignment-from-none,
|
||||
attribute-deprecated,
|
||||
class-camelcase,
|
||||
dangerous-default-value,
|
||||
dangerous-view-replace-wo-priority,
|
||||
development-status-allowed,
|
||||
duplicate-id-csv,
|
||||
duplicate-key,
|
||||
duplicate-xml-fields,
|
||||
duplicate-xml-record-id,
|
||||
eval-referenced,
|
||||
eval-used,
|
||||
incoherent-interpreter-exec-perm,
|
||||
license-allowed,
|
||||
manifest-author-string,
|
||||
manifest-deprecated-key,
|
||||
manifest-required-key,
|
||||
method-compute,
|
||||
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,
|
||||
# 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,
|
||||
too-complex,
|
||||
unnecessary-utf8-coding-comment
|
||||
|
||||
|
||||
[REPORTS]
|
||||
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
||||
output-format=colorized
|
||||
reports=no
|
||||
62
.pylintrc-mandatory
Normal file
62
.pylintrc-mandatory
Normal file
@@ -0,0 +1,62 @@
|
||||
[MASTER]
|
||||
load-plugins=pylint_odoo
|
||||
score=n
|
||||
|
||||
[ODOOLINT]
|
||||
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
|
||||
manifest_required_authors=Odoo Community Association (OCA)
|
||||
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=14.0
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=all
|
||||
|
||||
enable=anomalous-backslash-in-string,
|
||||
api-one-deprecated,
|
||||
api-one-multi-together,
|
||||
assignment-from-none,
|
||||
attribute-deprecated,
|
||||
class-camelcase,
|
||||
dangerous-default-value,
|
||||
dangerous-view-replace-wo-priority,
|
||||
development-status-allowed,
|
||||
duplicate-id-csv,
|
||||
duplicate-key,
|
||||
duplicate-xml-fields,
|
||||
duplicate-xml-record-id,
|
||||
eval-referenced,
|
||||
eval-used,
|
||||
incoherent-interpreter-exec-perm,
|
||||
license-allowed,
|
||||
manifest-author-string,
|
||||
manifest-deprecated-key,
|
||||
manifest-required-key,
|
||||
method-compute,
|
||||
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
|
||||
|
||||
[REPORTS]
|
||||
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
||||
output-format=colorized
|
||||
reports=no
|
||||
41
.travis.yml
Normal file
41
.travis.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
language: python
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/.cache/pre-commit
|
||||
|
||||
python:
|
||||
- "3.6"
|
||||
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
apt:
|
||||
packages:
|
||||
- expect-dev # provides unbuffer utility
|
||||
|
||||
stages:
|
||||
- test
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
env:
|
||||
- TESTS=1 ODOO_REPO="odoo/odoo" MAKEPOT="1"
|
||||
- stage: test
|
||||
env:
|
||||
- TESTS=1 ODOO_REPO="OCA/OCB"
|
||||
env:
|
||||
global:
|
||||
- VERSION="14.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0"
|
||||
|
||||
install:
|
||||
- git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git
|
||||
${HOME}/maintainer-quality-tools
|
||||
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
|
||||
- travis_install_nightly
|
||||
|
||||
script:
|
||||
- travis_run_tests
|
||||
|
||||
after_success:
|
||||
- travis_after_tests_success
|
||||
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
36
README.md
Normal file
36
README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
[](https://runbot.odoo-community.org/runbot/repo/github-com-oca-odoo-usability-)
|
||||
[](https://travis-ci.com/OCA/odoo-usability)
|
||||
[](https://codecov.io/gh/OCA/odoo-usability)
|
||||
[](https://translation.odoo-community.org/engage/odoo-usability-14-0/?utm_source=widget)
|
||||
|
||||
<!-- /!\ do not modify above this line -->
|
||||
|
||||
# Odoo Usability modules
|
||||
|
||||
Modules that improve UX sensibly
|
||||
|
||||
<!-- /!\ do not modify below this line -->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[//]: # (addons)
|
||||
|
||||
This part will be replaced when running the oca-gen-addons-table script from OCA/maintainer-tools.
|
||||
|
||||
[//]: # (end addons)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Licenses
|
||||
|
||||
This repository is licensed under [AGPL-3.0](LICENSE).
|
||||
|
||||
However, each module can have a totally different license, as long as they adhere to OCA
|
||||
policy. Consult each module's `__manifest__.py` file, which contains a `license` key
|
||||
that explains its license.
|
||||
|
||||
----
|
||||
|
||||
OCA, or the [Odoo Community Association](http://odoo-community.org/), is a nonprofit
|
||||
organization whose mission is to support the collaborative development of Odoo features
|
||||
and promote its widespread use.
|
||||
@@ -16,7 +16,7 @@ This module allows to configure a special *Partner Receivable Account* and a spe
|
||||
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
||||
""",
|
||||
"author": "Akretion",
|
||||
"website": "http://www.akretion.com",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": ["account"],
|
||||
"data": ["views/account_fiscal_position_view.xml"],
|
||||
"installable": False,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# © 2016-2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, fields
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountFiscalPosition(models.Model):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# © 2016-2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, api
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
@@ -13,8 +12,8 @@
|
||||
<field name="inherit_id" ref="account.view_account_position_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="after">
|
||||
<field name="receivable_account_id"/>
|
||||
<field name="payable_account_id"/>
|
||||
<field name="receivable_account_id" />
|
||||
<field name="payable_account_id" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Account Invoice Margin',
|
||||
'version': '12.0.1.0.0',
|
||||
'category': 'Invoicing Management',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Copy standard price on invoice line and compute margins',
|
||||
'description': """
|
||||
"name": "Account Invoice Margin",
|
||||
"version": "12.0.1.0.0",
|
||||
"category": "Invoicing Management",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Copy standard price on invoice line and compute margins",
|
||||
"description": """
|
||||
This module copies the field *standard_price* of the product on the invoice line when the invoice line is created. The allows the computation of the margin of the invoice.
|
||||
|
||||
This module has been written by Alexis de Lattre from Akretion
|
||||
<alexis.delattre@akretion.com>.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['account'],
|
||||
'data': [
|
||||
'account_invoice_view.xml',
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": ["account"],
|
||||
"data": [
|
||||
"account_invoice_view.xml",
|
||||
],
|
||||
'installable': False,
|
||||
"installable": False,
|
||||
}
|
||||
|
||||
@@ -3,39 +3,62 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = 'account.invoice.line'
|
||||
_inherit = "account.invoice.line"
|
||||
|
||||
standard_price_company_currency = fields.Float(
|
||||
string='Cost Price in Company Currency', readonly=True,
|
||||
digits=dp.get_precision('Product Price'),
|
||||
string="Cost Price in Company Currency",
|
||||
readonly=True,
|
||||
digits=dp.get_precision("Product Price"),
|
||||
help="Cost price in company currency in the unit of measure "
|
||||
"of the invoice line (which may be different from the unit "
|
||||
"of measure of the product).")
|
||||
"of measure of the product).",
|
||||
)
|
||||
standard_price_invoice_currency = fields.Float(
|
||||
string='Cost Price in Invoice Currency', readonly=True,
|
||||
compute='_compute_margin', store=True,
|
||||
digits=dp.get_precision('Product Price'),
|
||||
string="Cost Price in Invoice Currency",
|
||||
readonly=True,
|
||||
compute="_compute_margin",
|
||||
store=True,
|
||||
digits=dp.get_precision("Product Price"),
|
||||
help="Cost price in invoice currency in the unit of measure "
|
||||
"of the invoice line")
|
||||
"of the invoice line",
|
||||
)
|
||||
margin_invoice_currency = fields.Monetary(
|
||||
string='Margin in Invoice Currency', readonly=True, store=True,
|
||||
compute='_compute_margin', currency_field='currency_id')
|
||||
string="Margin in Invoice Currency",
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute="_compute_margin",
|
||||
currency_field="currency_id",
|
||||
)
|
||||
margin_company_currency = fields.Monetary(
|
||||
string='Margin in Company Currency', readonly=True, store=True,
|
||||
compute='_compute_margin', currency_field='company_currency_id')
|
||||
string="Margin in Company Currency",
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute="_compute_margin",
|
||||
currency_field="company_currency_id",
|
||||
)
|
||||
margin_rate = fields.Float(
|
||||
string="Margin Rate", readonly=True, store=True,
|
||||
compute='_compute_margin',
|
||||
digits=(16, 2), help="Margin rate in percentage of the sale price")
|
||||
string="Margin Rate",
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute="_compute_margin",
|
||||
digits=(16, 2),
|
||||
help="Margin rate in percentage of the sale price",
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'standard_price_company_currency', 'invoice_id.currency_id',
|
||||
'invoice_id.type', 'invoice_id.company_id',
|
||||
'invoice_id.date_invoice', 'quantity', 'price_subtotal')
|
||||
"standard_price_company_currency",
|
||||
"invoice_id.currency_id",
|
||||
"invoice_id.type",
|
||||
"invoice_id.company_id",
|
||||
"invoice_id.date_invoice",
|
||||
"quantity",
|
||||
"price_subtotal",
|
||||
)
|
||||
def _compute_margin(self):
|
||||
for il in self:
|
||||
standard_price_inv_cur = 0.0
|
||||
@@ -43,27 +66,27 @@ class AccountInvoiceLine(models.Model):
|
||||
margin_comp_cur = 0.0
|
||||
margin_rate = 0.0
|
||||
inv = il.invoice_id
|
||||
if inv and inv.type in ('out_invoice', 'out_refund'):
|
||||
if inv and inv.type in ("out_invoice", "out_refund"):
|
||||
# it works in _get_current_rate
|
||||
# even if we set date = False in context
|
||||
# standard_price_inv_cur is in the UoM of the invoice line
|
||||
date = inv._get_currency_rate_date() or\
|
||||
fields.Date.context_today(self)
|
||||
date = inv._get_currency_rate_date() or fields.Date.context_today(self)
|
||||
company = inv.company_id
|
||||
company_currency = company.currency_id
|
||||
standard_price_inv_cur =\
|
||||
company_currency._convert(
|
||||
il.standard_price_company_currency,
|
||||
inv.currency_id, company, date)
|
||||
margin_inv_cur =\
|
||||
standard_price_inv_cur = company_currency._convert(
|
||||
il.standard_price_company_currency, inv.currency_id, company, date
|
||||
)
|
||||
margin_inv_cur = (
|
||||
il.price_subtotal - il.quantity * standard_price_inv_cur
|
||||
)
|
||||
margin_comp_cur = inv.currency_id._convert(
|
||||
margin_inv_cur, company_currency, company, date)
|
||||
margin_inv_cur, company_currency, company, date
|
||||
)
|
||||
if il.price_subtotal:
|
||||
margin_rate = 100 * margin_inv_cur / il.price_subtotal
|
||||
# for a refund, margin should be negative
|
||||
# but margin rate should stay positive
|
||||
if inv.type == 'out_refund':
|
||||
if inv.type == "out_refund":
|
||||
margin_inv_cur *= -1
|
||||
margin_comp_cur *= -1
|
||||
il.standard_price_invoice_currency = standard_price_inv_cur
|
||||
@@ -79,35 +102,32 @@ class AccountInvoiceLine(models.Model):
|
||||
# because we don't have access to the 'type' of the invoice
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('product_id'):
|
||||
pp = self.env['product.product'].browse(vals['product_id'])
|
||||
if vals.get("product_id"):
|
||||
pp = self.env["product.product"].browse(vals["product_id"])
|
||||
std_price = pp.standard_price
|
||||
inv_uom_id = vals.get('uom_id')
|
||||
inv_uom_id = vals.get("uom_id")
|
||||
if inv_uom_id and inv_uom_id != pp.uom_id.id:
|
||||
inv_uom = self.env['uom.uom'].browse(inv_uom_id)
|
||||
std_price = pp.uom_id._compute_price(
|
||||
std_price, inv_uom)
|
||||
vals['standard_price_company_currency'] = std_price
|
||||
inv_uom = self.env["uom.uom"].browse(inv_uom_id)
|
||||
std_price = pp.uom_id._compute_price(std_price, inv_uom)
|
||||
vals["standard_price_company_currency"] = std_price
|
||||
return super(AccountInvoiceLine, self).create(vals)
|
||||
|
||||
def write(self, vals):
|
||||
if not vals:
|
||||
vals = {}
|
||||
if 'product_id' in vals or 'uom_id' in vals:
|
||||
if "product_id" in vals or "uom_id" in vals:
|
||||
for il in self:
|
||||
if 'product_id' in vals:
|
||||
if vals.get('product_id'):
|
||||
pp = self.env['product.product'].browse(
|
||||
vals['product_id'])
|
||||
if "product_id" in vals:
|
||||
if vals.get("product_id"):
|
||||
pp = self.env["product.product"].browse(vals["product_id"])
|
||||
else:
|
||||
pp = False
|
||||
else:
|
||||
pp = il.product_id or False
|
||||
# uom_id is NOT a required field
|
||||
if 'uom_id' in vals:
|
||||
if vals.get('uom_id'):
|
||||
inv_uom = self.env['uom.uom'].browse(
|
||||
vals['uom_id'])
|
||||
if "uom_id" in vals:
|
||||
if vals.get("uom_id"):
|
||||
inv_uom = self.env["uom.uom"].browse(vals["uom_id"])
|
||||
else:
|
||||
inv_uom = False
|
||||
else:
|
||||
@@ -116,37 +136,43 @@ class AccountInvoiceLine(models.Model):
|
||||
if pp:
|
||||
std_price = pp.standard_price
|
||||
if inv_uom and inv_uom != pp.uom_id:
|
||||
std_price = pp.uom_id._compute_price(
|
||||
std_price, inv_uom)
|
||||
il.write({'standard_price_company_currency': std_price})
|
||||
std_price = pp.uom_id._compute_price(std_price, inv_uom)
|
||||
il.write({"standard_price_company_currency": std_price})
|
||||
return super(AccountInvoiceLine, self).write(vals)
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
_inherit = "account.invoice"
|
||||
|
||||
margin_invoice_currency = fields.Monetary(
|
||||
string='Margin in Invoice Currency',
|
||||
compute='_compute_margin', store=True, readonly=True,
|
||||
currency_field='currency_id')
|
||||
string="Margin in Invoice Currency",
|
||||
compute="_compute_margin",
|
||||
store=True,
|
||||
readonly=True,
|
||||
currency_field="currency_id",
|
||||
)
|
||||
margin_company_currency = fields.Monetary(
|
||||
string='Margin in Company Currency',
|
||||
compute='_compute_margin', store=True, readonly=True,
|
||||
currency_field='company_currency_id')
|
||||
string="Margin in Company Currency",
|
||||
compute="_compute_margin",
|
||||
store=True,
|
||||
readonly=True,
|
||||
currency_field="company_currency_id",
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'type',
|
||||
'invoice_line_ids.margin_invoice_currency',
|
||||
'invoice_line_ids.margin_company_currency')
|
||||
"type",
|
||||
"invoice_line_ids.margin_invoice_currency",
|
||||
"invoice_line_ids.margin_company_currency",
|
||||
)
|
||||
def _compute_margin(self):
|
||||
res = self.env['account.invoice.line'].read_group(
|
||||
[('invoice_id', 'in', self.ids)],
|
||||
['invoice_id', 'margin_invoice_currency',
|
||||
'margin_company_currency'],
|
||||
['invoice_id'])
|
||||
res = self.env["account.invoice.line"].read_group(
|
||||
[("invoice_id", "in", self.ids)],
|
||||
["invoice_id", "margin_invoice_currency", "margin_company_currency"],
|
||||
["invoice_id"],
|
||||
)
|
||||
for re in res:
|
||||
if re['invoice_id']:
|
||||
inv = self.browse(re['invoice_id'][0])
|
||||
if inv.type in ('out_invoice', 'out_refund'):
|
||||
inv.margin_invoice_currency = re['margin_invoice_currency']
|
||||
inv.margin_company_currency = re['margin_company_currency']
|
||||
if re["invoice_id"]:
|
||||
inv = self.browse(re["invoice_id"][0])
|
||||
if inv.type in ("out_invoice", "out_refund"):
|
||||
inv.margin_invoice_currency = re["margin_invoice_currency"]
|
||||
inv.margin_company_currency = re["margin_company_currency"]
|
||||
|
||||
@@ -6,47 +6,70 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountInvoiceReport(models.Model):
|
||||
_inherit = 'account.invoice.report'
|
||||
_inherit = "account.invoice.report"
|
||||
|
||||
margin = fields.Float(string='Margin', readonly=True)
|
||||
margin = fields.Float(string="Margin", readonly=True)
|
||||
# why digits=0 ??? Why is it like that in the native "account" module
|
||||
user_currency_margin = fields.Float(
|
||||
string="Margin", compute='_compute_user_currency_margin', digits=0)
|
||||
string="Margin", compute="_compute_user_currency_margin", digits=0
|
||||
)
|
||||
|
||||
_depends = {
|
||||
'account.invoice': [
|
||||
'account_id', 'amount_total_company_signed',
|
||||
'commercial_partner_id', 'company_id',
|
||||
'currency_id', 'date_due', 'date_invoice', 'fiscal_position_id',
|
||||
'journal_id', 'number', 'partner_bank_id', 'partner_id',
|
||||
'payment_term_id', 'residual', 'state', 'type', 'user_id',
|
||||
"account.invoice": [
|
||||
"account_id",
|
||||
"amount_total_company_signed",
|
||||
"commercial_partner_id",
|
||||
"company_id",
|
||||
"currency_id",
|
||||
"date_due",
|
||||
"date_invoice",
|
||||
"fiscal_position_id",
|
||||
"journal_id",
|
||||
"number",
|
||||
"partner_bank_id",
|
||||
"partner_id",
|
||||
"payment_term_id",
|
||||
"residual",
|
||||
"state",
|
||||
"type",
|
||||
"user_id",
|
||||
],
|
||||
'account.invoice.line': [
|
||||
'account_id', 'invoice_id', 'price_subtotal', 'product_id',
|
||||
'quantity', 'uom_id', 'account_analytic_id',
|
||||
'margin_company_currency',
|
||||
"account.invoice.line": [
|
||||
"account_id",
|
||||
"invoice_id",
|
||||
"price_subtotal",
|
||||
"product_id",
|
||||
"quantity",
|
||||
"uom_id",
|
||||
"account_analytic_id",
|
||||
"margin_company_currency",
|
||||
],
|
||||
'product.product': ['product_tmpl_id'],
|
||||
'product.template': ['categ_id'],
|
||||
'uom.uom': ['category_id', 'factor', 'name', 'uom_type'],
|
||||
'res.currency.rate': ['currency_id', 'name'],
|
||||
'res.partner': ['country_id'],
|
||||
"product.product": ["product_tmpl_id"],
|
||||
"product.template": ["categ_id"],
|
||||
"uom.uom": ["category_id", "factor", "name", "uom_type"],
|
||||
"res.currency.rate": ["currency_id", "name"],
|
||||
"res.partner": ["country_id"],
|
||||
}
|
||||
|
||||
@api.depends('currency_id', 'date', 'margin')
|
||||
@api.depends("currency_id", "date", "margin")
|
||||
def _compute_user_currency_margin(self):
|
||||
user_currency = self.env.user.company_id.currency_id
|
||||
currency_rate = self.env['res.currency.rate'].search([
|
||||
('rate', '=', 1),
|
||||
'|',
|
||||
('company_id', '=', self.env.user.company_id.id),
|
||||
('company_id', '=', False)], limit=1)
|
||||
currency_rate = self.env["res.currency.rate"].search(
|
||||
[
|
||||
("rate", "=", 1),
|
||||
"|",
|
||||
("company_id", "=", self.env.user.company_id.id),
|
||||
("company_id", "=", False),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
base_currency = currency_rate.currency_id
|
||||
for record in self:
|
||||
date = record.date or fields.Date.today()
|
||||
company = record.company_id
|
||||
record.user_currency_margin = base_currency._convert(
|
||||
record.margin, user_currency, company, date)
|
||||
record.margin, user_currency, company, date
|
||||
)
|
||||
|
||||
# TODO check for refunds
|
||||
def _sub_select(self):
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
© 2015-2017 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_invoice_line_form" model="ir.ui.view">
|
||||
<field name="name">margin.account.invoice.line.form</field>
|
||||
<field name="model">account.invoice.line</field>
|
||||
<field name="inherit_id" ref="account.view_invoice_line_form"/>
|
||||
<field name="inherit_id" ref="account.view_invoice_line_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='analytic_tag_ids']/.." position="inside">
|
||||
<field name="standard_price_company_currency"
|
||||
string="Cost Price in Comp. Cur."
|
||||
groups="base.group_no_one"/>
|
||||
<field name="standard_price_invoice_currency"
|
||||
string="Cost Price in Inv. Cur."
|
||||
groups="base.group_no_one"/>
|
||||
<field name="margin_invoice_currency"
|
||||
string="Margin in Inv. Cur."
|
||||
groups="base.group_no_one"/>
|
||||
<field name="margin_company_currency"
|
||||
string="Margin in Comp. Cur."
|
||||
groups="base.group_no_one"/>
|
||||
<label for="margin_rate" groups="base.group_no_one"/>
|
||||
<field
|
||||
name="standard_price_company_currency"
|
||||
string="Cost Price in Comp. Cur."
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<field
|
||||
name="standard_price_invoice_currency"
|
||||
string="Cost Price in Inv. Cur."
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<field
|
||||
name="margin_invoice_currency"
|
||||
string="Margin in Inv. Cur."
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<field
|
||||
name="margin_company_currency"
|
||||
string="Margin in Comp. Cur."
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<label for="margin_rate" groups="base.group_no_one" />
|
||||
<div name="margin_rate" groups="base.group_no_one">
|
||||
<field name="margin_rate" class="oe_inline"/> %
|
||||
<field name="margin_rate" class="oe_inline" /> %
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
@@ -36,13 +43,19 @@
|
||||
<record id="invoice_form" model="ir.ui.view">
|
||||
<field name="name">margin.account.invoice.form</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.invoice_form"/>
|
||||
<field name="inherit_id" ref="account.invoice_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="move_id" position="after">
|
||||
<field name="margin_invoice_currency"
|
||||
string="Margin in Inv. Cur." groups="base.group_no_one"/>
|
||||
<field name="margin_company_currency"
|
||||
string="Margin in Comp. Cur." groups="base.group_no_one"/>
|
||||
<field
|
||||
name="margin_invoice_currency"
|
||||
string="Margin in Inv. Cur."
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<field
|
||||
name="margin_company_currency"
|
||||
string="Margin in Comp. Cur."
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Account Invoice Update Wizard',
|
||||
'version': '12.0.1.0.0',
|
||||
'category': 'Accounting & Finance',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Wizard to update non-legal fields of an open/paid invoice',
|
||||
'author': 'Akretion',
|
||||
'website': 'https://github.com/akretion/odoo-usability',
|
||||
'depends': [
|
||||
'account',
|
||||
"name": "Account Invoice Update Wizard",
|
||||
"version": "12.0.1.0.0",
|
||||
"category": "Accounting & Finance",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Wizard to update non-legal fields of an open/paid invoice",
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": [
|
||||
"account",
|
||||
],
|
||||
'data': [
|
||||
'wizard/account_invoice_update_view.xml',
|
||||
'views/account_invoice.xml',
|
||||
],
|
||||
'installable': False,
|
||||
"data": [
|
||||
"wizard/account_invoice_update_view.xml",
|
||||
"views/account_invoice.xml",
|
||||
],
|
||||
"installable": False,
|
||||
}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
# Copyright 2019 Camptocamp
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
import odoo.addons.decimal_precision as dp
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
_inherit = "account.invoice"
|
||||
|
||||
def prepare_update_wizard(self):
|
||||
self.ensure_one()
|
||||
wizard = self.env['account.invoice.update']
|
||||
wizard = self.env["account.invoice.update"]
|
||||
res = wizard._prepare_default_get(self)
|
||||
action = self.env.ref(
|
||||
'account_invoice_update_wizard.account_invoice_update_action'
|
||||
"account_invoice_update_wizard.account_invoice_update_action"
|
||||
).read()[0]
|
||||
action['name'] = "Update Wizard"
|
||||
action['res_id'] = wizard.create(res).id
|
||||
action["name"] = "Update Wizard"
|
||||
action["res_id"] = wizard.create(res).id
|
||||
return action
|
||||
|
||||
|
||||
@@ -1,54 +1,61 @@
|
||||
# Copyright 2018-2019 Camptocamp
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo.tests.common import SavepointCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.customer12 = cls.env.ref('base.res_partner_12')
|
||||
cls.product16 = cls.env.ref('product.product_product_16')
|
||||
cls.product24 = cls.env.ref('product.product_product_24')
|
||||
uom_unit = cls.env.ref('uom.product_uom_categ_unit')
|
||||
cls.customer12 = cls.env.ref("base.res_partner_12")
|
||||
cls.product16 = cls.env.ref("product.product_product_16")
|
||||
cls.product24 = cls.env.ref("product.product_product_24")
|
||||
uom_unit = cls.env.ref("uom.product_uom_categ_unit")
|
||||
|
||||
cls.invoice1 = cls.env['account.invoice'].create({
|
||||
'name': 'Test invoice',
|
||||
'partner_id': cls.customer12.id,
|
||||
})
|
||||
cls.inv_line1 = cls.env['account.invoice.line'].create({
|
||||
'invoice_id': cls.invoice1.id,
|
||||
'name': "Line1",
|
||||
'product_id': cls.product16.id,
|
||||
'product_uom_id': uom_unit.id,
|
||||
'account_id': cls.invoice1.account_id.id,
|
||||
'price_unit': 42.0,
|
||||
})
|
||||
cls.inv_line2 = cls.env['account.invoice.line'].create({
|
||||
'invoice_id': cls.invoice1.id,
|
||||
'name': "Line2",
|
||||
'product_id': cls.product24.id,
|
||||
'product_uom_id': uom_unit.id,
|
||||
'account_id': cls.invoice1.account_id.id,
|
||||
'price_unit': 1111.1,
|
||||
})
|
||||
cls.invoice1 = cls.env["account.invoice"].create(
|
||||
{
|
||||
"name": "Test invoice",
|
||||
"partner_id": cls.customer12.id,
|
||||
}
|
||||
)
|
||||
cls.inv_line1 = cls.env["account.invoice.line"].create(
|
||||
{
|
||||
"invoice_id": cls.invoice1.id,
|
||||
"name": "Line1",
|
||||
"product_id": cls.product16.id,
|
||||
"product_uom_id": uom_unit.id,
|
||||
"account_id": cls.invoice1.account_id.id,
|
||||
"price_unit": 42.0,
|
||||
}
|
||||
)
|
||||
cls.inv_line2 = cls.env["account.invoice.line"].create(
|
||||
{
|
||||
"invoice_id": cls.invoice1.id,
|
||||
"name": "Line2",
|
||||
"product_id": cls.product24.id,
|
||||
"product_uom_id": uom_unit.id,
|
||||
"account_id": cls.invoice1.account_id.id,
|
||||
"price_unit": 1111.1,
|
||||
}
|
||||
)
|
||||
|
||||
cls.aa1 = cls.env.ref('analytic.analytic_partners_camp_to_camp')
|
||||
cls.aa2 = cls.env.ref('analytic.analytic_nebula')
|
||||
cls.atag1 = cls.env.ref('analytic.tag_contract')
|
||||
cls.atag2 = cls.env['account.analytic.tag'].create({
|
||||
'name': 'の',
|
||||
})
|
||||
cls.aa1 = cls.env.ref("analytic.analytic_partners_camp_to_camp")
|
||||
cls.aa2 = cls.env.ref("analytic.analytic_nebula")
|
||||
cls.atag1 = cls.env.ref("analytic.tag_contract")
|
||||
cls.atag2 = cls.env["account.analytic.tag"].create(
|
||||
{
|
||||
"name": "の",
|
||||
}
|
||||
)
|
||||
|
||||
def create_wizard(self, invoice):
|
||||
res = self.invoice1.prepare_update_wizard()
|
||||
self.wiz = self.env['account.invoice.update'].browse(res['res_id'])
|
||||
self.wiz = self.env["account.invoice.update"].browse(res["res_id"])
|
||||
|
||||
def test_add_analytic_account_line1(self):
|
||||
""" Add analytic account on an invoice line
|
||||
"""Add analytic account on an invoice line
|
||||
after the invoice has been approved.
|
||||
|
||||
This will:
|
||||
@@ -59,17 +66,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
self.create_wizard(self.invoice1)
|
||||
|
||||
wiz_line = self.wiz.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1
|
||||
)
|
||||
wiz_line.account_analytic_id = self.aa1
|
||||
self.wiz.run()
|
||||
|
||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
||||
lambda rec: rec.product_id == self.product16)
|
||||
lambda rec: rec.product_id == self.product16
|
||||
)
|
||||
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
||||
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
||||
|
||||
def test_change_analytic_account_line1(self):
|
||||
""" Change analytic account on an invoice line
|
||||
"""Change analytic account on an invoice line
|
||||
after the invoice has been approved.
|
||||
|
||||
This will:
|
||||
@@ -81,17 +90,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
self.create_wizard(self.invoice1)
|
||||
|
||||
wiz_line = self.wiz.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1
|
||||
)
|
||||
wiz_line.account_analytic_id = self.aa1
|
||||
self.wiz.run()
|
||||
|
||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
||||
lambda rec: rec.product_id == self.product16)
|
||||
lambda rec: rec.product_id == self.product16
|
||||
)
|
||||
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
||||
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
||||
|
||||
def test_error_grouped_move_lines(self):
|
||||
""" Change analytic account on an invoice line
|
||||
"""Change analytic account on an invoice line
|
||||
after the invoice has been approved where both
|
||||
lines were grouped in the same move line.
|
||||
|
||||
@@ -111,7 +122,7 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
self.wiz.run()
|
||||
|
||||
def test_add_analytic_tags_line1(self):
|
||||
""" Add analytic tags on an invoice line
|
||||
"""Add analytic tags on an invoice line
|
||||
after the invoice has been approved.
|
||||
|
||||
This will update move line.
|
||||
@@ -120,17 +131,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
self.create_wizard(self.invoice1)
|
||||
|
||||
wiz_line = self.wiz.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1
|
||||
)
|
||||
wiz_line.analytic_tag_ids = self.atag2
|
||||
self.wiz.run()
|
||||
|
||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
||||
lambda rec: rec.product_id == self.product16)
|
||||
lambda rec: rec.product_id == self.product16
|
||||
)
|
||||
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
||||
self.assertFalse(related_ml.analytic_line_ids)
|
||||
|
||||
def test_change_analytic_tags_line1(self):
|
||||
""" Change analytic tags on an invoice line
|
||||
"""Change analytic tags on an invoice line
|
||||
after the invoice has been approved.
|
||||
|
||||
It will update move line and analytic line
|
||||
@@ -142,17 +155,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
self.create_wizard(self.invoice1)
|
||||
|
||||
wiz_line = self.wiz.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1
|
||||
)
|
||||
wiz_line.analytic_tag_ids = self.atag2
|
||||
self.wiz.run()
|
||||
|
||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
||||
lambda rec: rec.product_id == self.product16)
|
||||
lambda rec: rec.product_id == self.product16
|
||||
)
|
||||
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
||||
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
|
||||
|
||||
def test_add_analytic_info_line1(self):
|
||||
""" Add analytic account and tags on an invoice line
|
||||
"""Add analytic account and tags on an invoice line
|
||||
after the invoice has been approved.
|
||||
|
||||
This will:
|
||||
@@ -163,20 +178,22 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
self.create_wizard(self.invoice1)
|
||||
|
||||
wiz_line = self.wiz.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1
|
||||
)
|
||||
wiz_line.account_analytic_id = self.aa1
|
||||
wiz_line.analytic_tag_ids = self.atag2
|
||||
self.wiz.run()
|
||||
|
||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
||||
lambda rec: rec.product_id == self.product16)
|
||||
lambda rec: rec.product_id == self.product16
|
||||
)
|
||||
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
||||
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
||||
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
||||
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
|
||||
|
||||
def test_empty_analytic_account_line1(self):
|
||||
""" Remove analytic account
|
||||
"""Remove analytic account
|
||||
after the invoice has been approved.
|
||||
|
||||
This will raise an error as it is not implemented.
|
||||
@@ -187,10 +204,12 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||
self.create_wizard(self.invoice1)
|
||||
|
||||
wiz_line = self.wiz.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
||||
lambda rec: rec.invoice_line_id == self.inv_line1
|
||||
)
|
||||
wiz_line.account_analytic_id = False
|
||||
self.wiz.run()
|
||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
||||
lambda rec: rec.product_id == self.product16)
|
||||
lambda rec: rec.product_id == self.product16
|
||||
)
|
||||
self.assertFalse(related_ml.analytic_account_id)
|
||||
self.assertFalse(related_ml.analytic_line_ids)
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="invoice_supplier_form" model="ir.ui.view">
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||
<field name="inherit_id" ref="account.invoice_supplier_form" />
|
||||
<field name="arch" type="xml">
|
||||
<button name="action_invoice_draft" position="before">
|
||||
<button name="prepare_update_wizard" type="object" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/>
|
||||
<button
|
||||
name="prepare_update_wizard"
|
||||
type="object"
|
||||
string="Update Invoice"
|
||||
states="open,paid"
|
||||
groups="account.group_account_invoice"
|
||||
/>
|
||||
</button>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="invoice_form" model="ir.ui.view">
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.invoice_form"/>
|
||||
<field name="inherit_id" ref="account.invoice_form" />
|
||||
<field name="arch" type="xml">
|
||||
<button name="action_invoice_draft" position="before">
|
||||
<button name="prepare_update_wizard" type="object" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/>
|
||||
<button
|
||||
name="prepare_update_wizard"
|
||||
type="object"
|
||||
string="Update Invoice"
|
||||
states="open,paid"
|
||||
groups="account.group_account_invoice"
|
||||
/>
|
||||
</button>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -2,47 +2,45 @@
|
||||
# Copyright 2018-2019 Camptocamp
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class AccountInvoiceUpdate(models.TransientModel):
|
||||
_name = 'account.invoice.update'
|
||||
_description = 'Wizard to update non-legal fields of invoice'
|
||||
_name = "account.invoice.update"
|
||||
_description = "Wizard to update non-legal fields of invoice"
|
||||
|
||||
invoice_id = fields.Many2one(
|
||||
'account.invoice', string='Invoice', required=True,
|
||||
readonly=True)
|
||||
type = fields.Selection(related='invoice_id.type', readonly=True)
|
||||
company_id = fields.Many2one(
|
||||
related='invoice_id.company_id', readonly=True)
|
||||
partner_id = fields.Many2one(
|
||||
related='invoice_id.partner_id', readonly=True)
|
||||
user_id = fields.Many2one('res.users', string='Salesperson')
|
||||
payment_term_id = fields.Many2one(
|
||||
'account.payment.term', string='Payment Term')
|
||||
reference = fields.Char(string='Invoice Reference')
|
||||
name = fields.Char(string='Reference/Description')
|
||||
origin = fields.Char(string='Source Document')
|
||||
comment = fields.Text('Additional Information')
|
||||
partner_bank_id = fields.Many2one(
|
||||
'res.partner.bank', string='Bank Account')
|
||||
"account.invoice", string="Invoice", required=True, readonly=True
|
||||
)
|
||||
type = fields.Selection(related="invoice_id.type", readonly=True)
|
||||
company_id = fields.Many2one(related="invoice_id.company_id", readonly=True)
|
||||
partner_id = fields.Many2one(related="invoice_id.partner_id", readonly=True)
|
||||
user_id = fields.Many2one("res.users", string="Salesperson")
|
||||
payment_term_id = fields.Many2one("account.payment.term", string="Payment Term")
|
||||
reference = fields.Char(string="Invoice Reference")
|
||||
name = fields.Char(string="Reference/Description")
|
||||
origin = fields.Char(string="Source Document")
|
||||
comment = fields.Text("Additional Information")
|
||||
partner_bank_id = fields.Many2one("res.partner.bank", string="Bank Account")
|
||||
line_ids = fields.One2many(
|
||||
'account.invoice.line.update', 'parent_id', string='Invoice Lines')
|
||||
"account.invoice.line.update", "parent_id", string="Invoice Lines"
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _simple_fields2update(self):
|
||||
'''List boolean, date, datetime, char, text fields'''
|
||||
return ['reference', 'name', 'origin', 'comment']
|
||||
"""List boolean, date, datetime, char, text fields"""
|
||||
return ["reference", "name", "origin", "comment"]
|
||||
|
||||
@api.model
|
||||
def _m2o_fields2update(self):
|
||||
return ['payment_term_id', 'user_id', 'partner_bank_id']
|
||||
return ["payment_term_id", "user_id", "partner_bank_id"]
|
||||
|
||||
@api.model
|
||||
def _prepare_default_get(self, invoice):
|
||||
res = {'invoice_id': invoice.id, 'line_ids': []}
|
||||
res = {"invoice_id": invoice.id, "line_ids": []}
|
||||
for sfield in self._simple_fields2update():
|
||||
res[sfield] = invoice[sfield]
|
||||
for m2ofield in self._m2o_fields2update():
|
||||
@@ -50,26 +48,32 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
for line in invoice.invoice_line_ids:
|
||||
aa_tags = line.analytic_tag_ids
|
||||
aa_tags = [(6, 0, aa_tags.ids)] if aa_tags else False
|
||||
res['line_ids'].append([0, 0, {
|
||||
'invoice_line_id': line.id,
|
||||
'name': line.name,
|
||||
'quantity': line.quantity,
|
||||
'price_subtotal': line.price_subtotal,
|
||||
'account_analytic_id': line.account_analytic_id.id,
|
||||
'analytic_tag_ids': aa_tags,
|
||||
'display_type': line.display_type,
|
||||
}])
|
||||
res["line_ids"].append(
|
||||
[
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"invoice_line_id": line.id,
|
||||
"name": line.name,
|
||||
"quantity": line.quantity,
|
||||
"price_subtotal": line.price_subtotal,
|
||||
"account_analytic_id": line.account_analytic_id.id,
|
||||
"analytic_tag_ids": aa_tags,
|
||||
"display_type": line.display_type,
|
||||
},
|
||||
]
|
||||
)
|
||||
return res
|
||||
|
||||
@api.onchange('type')
|
||||
@api.onchange("type")
|
||||
def type_on_change(self):
|
||||
res = {'domain': {}}
|
||||
if self.type in ('out_invoice', 'out_refund'):
|
||||
res['domain']['partner_bank_id'] =\
|
||||
"[('partner_id.ref_company_ids', 'in', [company_id])]"
|
||||
res = {"domain": {}}
|
||||
if self.type in ("out_invoice", "out_refund"):
|
||||
res["domain"][
|
||||
"partner_bank_id"
|
||||
] = "[('partner_id.ref_company_ids', 'in', [company_id])]"
|
||||
else:
|
||||
res['domain']['partner_bank_id'] =\
|
||||
"[('partner_id', '=', partner_id)]"
|
||||
res["domain"]["partner_bank_id"] = "[('partner_id', '=', partner_id)]"
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@@ -82,24 +86,31 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
for m2ofield in self._m2o_fields2update():
|
||||
if self[m2ofield] != inv[m2ofield]:
|
||||
vals[m2ofield] = self[m2ofield].id or False
|
||||
if 'payment_term_id' in vals:
|
||||
if "payment_term_id" in vals:
|
||||
pterm_list = self.payment_term_id.compute(
|
||||
value=1, date_ref=inv.date_invoice)[0]
|
||||
value=1, date_ref=inv.date_invoice
|
||||
)[0]
|
||||
if pterm_list:
|
||||
vals['date_due'] = max(line[0] for line in pterm_list)
|
||||
vals["date_due"] = max(line[0] for line in pterm_list)
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def _line_simple_fields2update(self):
|
||||
return ["name",]
|
||||
return [
|
||||
"name",
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _line_m2o_fields2update(self):
|
||||
return ["account_analytic_id",]
|
||||
return [
|
||||
"account_analytic_id",
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _line_m2m_fields2update(self):
|
||||
return ["analytic_tag_ids",]
|
||||
return [
|
||||
"analytic_tag_ids",
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _prepare_invoice_line(self, line):
|
||||
@@ -122,7 +133,7 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
ini_ref = inv.move_id.ref
|
||||
ref = inv.reference or inv.name
|
||||
if ini_ref != ref:
|
||||
mvals['ref'] = ref
|
||||
mvals["ref"] = ref
|
||||
return mvals
|
||||
|
||||
@api.multi
|
||||
@@ -131,49 +142,52 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
# TODO make it accept more case as lines won't
|
||||
# be grouped unless journal.group_invoice_line is True
|
||||
inv_line = self.invoice_id.invoice_line_ids.filtered(
|
||||
lambda rec: rec.product_id == move_line.product_id)
|
||||
lambda rec: rec.product_id == move_line.product_id
|
||||
)
|
||||
if len(inv_line) != 1:
|
||||
raise UserError(
|
||||
"Cannot match a single invoice line to move line %s" %
|
||||
move_line.name)
|
||||
"Cannot match a single invoice line to move line %s" % move_line.name
|
||||
)
|
||||
return inv_line
|
||||
|
||||
@api.multi
|
||||
def _prepare_move_line(self, inv_line):
|
||||
mlvals = {}
|
||||
inv_line_upd = self.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == inv_line)
|
||||
lambda rec: rec.invoice_line_id == inv_line
|
||||
)
|
||||
|
||||
ini_aa = inv_line.account_analytic_id
|
||||
new_aa = inv_line_upd.account_analytic_id
|
||||
|
||||
if ini_aa != new_aa:
|
||||
mlvals['analytic_account_id'] = new_aa.id
|
||||
mlvals["analytic_account_id"] = new_aa.id
|
||||
|
||||
ini_aa_tags = inv_line.analytic_tag_ids
|
||||
new_aa_tags = inv_line_upd.analytic_tag_ids
|
||||
|
||||
if ini_aa_tags != new_aa_tags:
|
||||
mlvals['analytic_tag_ids'] = [(6, None, new_aa_tags.ids)]
|
||||
mlvals["analytic_tag_ids"] = [(6, None, new_aa_tags.ids)]
|
||||
return mlvals
|
||||
|
||||
@api.multi
|
||||
def _prepare_analytic_line(self, inv_line):
|
||||
alvals = {}
|
||||
inv_line_upd = self.line_ids.filtered(
|
||||
lambda rec: rec.invoice_line_id == inv_line)
|
||||
lambda rec: rec.invoice_line_id == inv_line
|
||||
)
|
||||
|
||||
ini_aa = inv_line.account_analytic_id
|
||||
new_aa = inv_line_upd.account_analytic_id
|
||||
|
||||
if ini_aa != new_aa:
|
||||
alvals['account_id'] = new_aa.id
|
||||
alvals["account_id"] = new_aa.id
|
||||
|
||||
ini_aa_tags = inv_line.analytic_tag_ids
|
||||
new_aa_tags = inv_line_upd.analytic_tag_ids
|
||||
|
||||
if ini_aa_tags != new_aa_tags:
|
||||
alvals['tag_ids'] = [(6, None, new_aa_tags.ids)]
|
||||
alvals["tag_ids"] = [(6, None, new_aa_tags.ids)]
|
||||
return alvals
|
||||
|
||||
@api.multi
|
||||
@@ -181,22 +195,27 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
self.ensure_one()
|
||||
inv = self.invoice_id
|
||||
if (
|
||||
self.payment_term_id and
|
||||
self.payment_term_id != inv.payment_term_id and
|
||||
inv.move_id):
|
||||
self.payment_term_id
|
||||
and self.payment_term_id != inv.payment_term_id
|
||||
and inv.move_id
|
||||
):
|
||||
# I don't update pay term when the invoice is partially (or fully)
|
||||
# paid because if you have a payment term with several lines
|
||||
# of the same amount, you would also have to take into account
|
||||
# the reconcile marks to put the new maturity date on the right
|
||||
# lines
|
||||
if inv.payment_ids:
|
||||
raise UserError(_(
|
||||
"This wizard doesn't support the update of payment "
|
||||
"terms on an invoice which is partially or fully "
|
||||
"paid."))
|
||||
prec = self.env['decimal.precision'].precision_get('Account')
|
||||
term_res = self.payment_term_id.compute(
|
||||
inv.amount_total, inv.date_invoice)[0]
|
||||
raise UserError(
|
||||
_(
|
||||
"This wizard doesn't support the update of payment "
|
||||
"terms on an invoice which is partially or fully "
|
||||
"paid."
|
||||
)
|
||||
)
|
||||
prec = self.env["decimal.precision"].precision_get("Account")
|
||||
term_res = self.payment_term_id.compute(inv.amount_total, inv.date_invoice)[
|
||||
0
|
||||
]
|
||||
new_pterm = {} # key = int(amount * 100), value = [date1, date2]
|
||||
for entry in term_res:
|
||||
amount = int(entry[1] * 10 * prec)
|
||||
@@ -214,13 +233,16 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
mlines[amount] = [line]
|
||||
for iamount, lines in mlines.items():
|
||||
if len(lines) != len(new_pterm.get(iamount, [])):
|
||||
raise UserError(_(
|
||||
"The original payment term '%s' doesn't have the "
|
||||
"same terms (number of terms and/or amount) as the "
|
||||
"new payment term '%s'. You can only switch to a "
|
||||
"payment term that has the same number of terms "
|
||||
"with the same amount.") % (
|
||||
inv.payment_term_id.name, self.payment_term_id.name))
|
||||
raise UserError(
|
||||
_(
|
||||
"The original payment term '%s' doesn't have the "
|
||||
"same terms (number of terms and/or amount) as the "
|
||||
"new payment term '%s'. You can only switch to a "
|
||||
"payment term that has the same number of terms "
|
||||
"with the same amount."
|
||||
)
|
||||
% (inv.payment_term_id.name, self.payment_term_id.name)
|
||||
)
|
||||
for line in lines:
|
||||
line.date_maturity = new_pterm[iamount].pop()
|
||||
|
||||
@@ -240,8 +262,8 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
if mvals:
|
||||
inv.move_id.write(mvals)
|
||||
for ml in inv.move_id.line_ids.filtered(
|
||||
# we are only interested in invoice lines, not tax lines
|
||||
lambda rec: bool(rec.product_id)
|
||||
# we are only interested in invoice lines, not tax lines
|
||||
lambda rec: bool(rec.product_id)
|
||||
):
|
||||
if ml.credit == 0.0:
|
||||
continue
|
||||
@@ -254,16 +276,16 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
alvals = self._prepare_analytic_line(inv_line)
|
||||
if aalines and alvals:
|
||||
updated = True
|
||||
if ('account_id' in alvals and
|
||||
alvals['account_id'] is False):
|
||||
if "account_id" in alvals and alvals["account_id"] is False:
|
||||
former_aa = inv_line.account_analytic_id
|
||||
to_remove_aalines = aalines.filtered(
|
||||
lambda rec: rec.account_id == former_aa)
|
||||
lambda rec: rec.account_id == former_aa
|
||||
)
|
||||
# remove existing analytic line
|
||||
to_remove_aalines.unlink()
|
||||
else:
|
||||
aalines.write(alvals)
|
||||
elif 'account_id' in alvals:
|
||||
elif "account_id" in alvals:
|
||||
# Create analytic lines if analytic account
|
||||
# is added later
|
||||
ml.create_analytic_lines()
|
||||
@@ -273,30 +295,40 @@ class AccountInvoiceUpdate(models.TransientModel):
|
||||
updated = True
|
||||
line.invoice_line_id.write(ilvals)
|
||||
if updated:
|
||||
inv.message_post(body=_(
|
||||
'Non-legal fields of invoice updated via the Invoice Update '
|
||||
'wizard.'))
|
||||
inv.message_post(
|
||||
body=_(
|
||||
"Non-legal fields of invoice updated via the Invoice Update "
|
||||
"wizard."
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class AccountInvoiceLineUpdate(models.TransientModel):
|
||||
_name = 'account.invoice.line.update'
|
||||
_description = 'Update non-legal fields of invoice lines'
|
||||
_name = "account.invoice.line.update"
|
||||
_description = "Update non-legal fields of invoice lines"
|
||||
|
||||
parent_id = fields.Many2one(
|
||||
'account.invoice.update', string='Wizard', ondelete='cascade')
|
||||
"account.invoice.update", string="Wizard", ondelete="cascade"
|
||||
)
|
||||
invoice_line_id = fields.Many2one(
|
||||
'account.invoice.line', string='Invoice Line', readonly=True)
|
||||
name = fields.Text(string='Description', required=True)
|
||||
display_type = fields.Selection([
|
||||
('line_section', "Section"),
|
||||
('line_note', "Note")], default=False, help="Technical field for UX purpose.")
|
||||
"account.invoice.line", string="Invoice Line", readonly=True
|
||||
)
|
||||
name = fields.Text(string="Description", required=True)
|
||||
display_type = fields.Selection(
|
||||
[("line_section", "Section"), ("line_note", "Note")],
|
||||
default=False,
|
||||
help="Technical field for UX purpose.",
|
||||
)
|
||||
quantity = fields.Float(
|
||||
string='Quantity', digits=dp.get_precision('Product Unit of Measure'),
|
||||
readonly=True)
|
||||
string="Quantity",
|
||||
digits=dp.get_precision("Product Unit of Measure"),
|
||||
readonly=True,
|
||||
)
|
||||
price_subtotal = fields.Float(
|
||||
string='Amount', readonly=True, digits=dp.get_precision('Account'))
|
||||
string="Amount", readonly=True, digits=dp.get_precision("Account")
|
||||
)
|
||||
account_analytic_id = fields.Many2one(
|
||||
'account.analytic.account', string='Analytic Account')
|
||||
analytic_tag_ids = fields.Many2many(
|
||||
'account.analytic.tag', string='Analytic Tags')
|
||||
"account.analytic.account", string="Analytic Account"
|
||||
)
|
||||
analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags")
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
© 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="account_invoice_update_form" model="ir.ui.view">
|
||||
@@ -11,34 +10,62 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Update Invoice Wizard">
|
||||
<group name="main">
|
||||
<field name="invoice_id" invisible="1"/>
|
||||
<field name="type" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="partner_id" invisible="1"/>
|
||||
<field name="reference" attrs="{'invisible': [('type', 'not in', ('in_invoice', 'in_refund'))]}"/>
|
||||
<field name="origin"/>
|
||||
<field name="name"/>
|
||||
<field name="payment_term_id" widget="selection"/>
|
||||
<field name="partner_bank_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="comment"/>
|
||||
<field name="invoice_id" invisible="1" />
|
||||
<field name="type" invisible="1" />
|
||||
<field name="company_id" invisible="1" />
|
||||
<field name="partner_id" invisible="1" />
|
||||
<field
|
||||
name="reference"
|
||||
attrs="{'invisible': [('type', 'not in', ('in_invoice', 'in_refund'))]}"
|
||||
/>
|
||||
<field name="origin" />
|
||||
<field name="name" />
|
||||
<field name="payment_term_id" widget="selection" />
|
||||
<field name="partner_bank_id" />
|
||||
<field name="user_id" />
|
||||
<field name="comment" />
|
||||
</group>
|
||||
<group name="lines">
|
||||
<field name="line_ids" nolabel="1">
|
||||
<tree editable="bottom" create="false" delete="false" edit="true">
|
||||
<field name="invoice_line_id" invisible="1"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="quantity" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
||||
<field name="price_subtotal" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
||||
<field name="account_analytic_id" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="analytic_tag_ids" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting" widget="many2many_tags"/>
|
||||
<tree
|
||||
editable="bottom"
|
||||
create="false"
|
||||
delete="false"
|
||||
edit="true"
|
||||
>
|
||||
<field name="invoice_line_id" invisible="1" />
|
||||
<field name="display_type" invisible="1" />
|
||||
<field name="name" />
|
||||
<field
|
||||
name="quantity"
|
||||
attrs="{'invisible': [('display_type', '!=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="price_subtotal"
|
||||
attrs="{'invisible': [('display_type', '!=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="account_analytic_id"
|
||||
attrs="{'invisible': [('display_type', '!=', False)]}"
|
||||
groups="analytic.group_analytic_accounting"
|
||||
/>
|
||||
<field
|
||||
name="analytic_tag_ids"
|
||||
attrs="{'invisible': [('display_type', '!=', False)]}"
|
||||
groups="analytic.group_analytic_accounting"
|
||||
widget="many2many_tags"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="run" type="object" class="oe_highlight" string="Update"/>
|
||||
<button special="cancel" string="Cancel" class="oe_link"/>
|
||||
<button
|
||||
name="run"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
string="Update"
|
||||
/>
|
||||
<button special="cancel" string="Cancel" class="oe_link" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -3,35 +3,36 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Account Usability',
|
||||
'version': '14.0.1.0.0',
|
||||
'category': 'Accounting & Finance',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Small usability enhancements in account module',
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': [
|
||||
'account',
|
||||
'base_view_inheritance_extension',
|
||||
'base_usability', # needed only to access base_usability.group_nobody
|
||||
# in v12, I may create a module only for group_nobody
|
||||
],
|
||||
'data': [
|
||||
'views/account_account_type.xml',
|
||||
'views/account_account.xml',
|
||||
'views/account_bank_statement.xml',
|
||||
'views/account_invoice_report.xml',
|
||||
'views/account_journal.xml',
|
||||
'views/account_move.xml',
|
||||
'views/account_menu.xml',
|
||||
'views/account_tax.xml',
|
||||
'views/product.xml',
|
||||
'views/res_config_settings.xml',
|
||||
'views/res_partner.xml',
|
||||
'views/account_report.xml',
|
||||
'wizard/account_invoice_mark_sent_view.xml',
|
||||
'wizard/account_group_generate_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
"name": "Account Usability",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Accounting & Finance",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Small usability enhancements in account module",
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": [
|
||||
"account",
|
||||
"base_view_inheritance_extension",
|
||||
"base_usability", # needed only to access base_usability.group_nobody
|
||||
# in v12, I may create a module only for group_nobody
|
||||
],
|
||||
"data": [
|
||||
"views/account_account_type.xml",
|
||||
"views/account_account.xml",
|
||||
"views/account_bank_statement.xml",
|
||||
"views/account_invoice_report.xml",
|
||||
"views/account_journal.xml",
|
||||
"views/account_move.xml",
|
||||
"views/account_menu.xml",
|
||||
"views/account_tax.xml",
|
||||
"views/product.xml",
|
||||
"views/res_config_settings.xml",
|
||||
"views/res_partner.xml",
|
||||
"views/account_report.xml",
|
||||
"wizard/account_invoice_mark_sent_view.xml",
|
||||
"wizard/account_group_generate_view.xml",
|
||||
"wizard/account_payment_register_views.xml",
|
||||
"security/ir.model.access.csv",
|
||||
],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ index 2dd1f9cef83..62275fca65e 100644
|
||||
+ 'analytic_account_id': write_off_line_vals.get('analytic_account_id'),
|
||||
})
|
||||
return line_vals_list
|
||||
|
||||
|
||||
@@ -699,6 +700,7 @@ class AccountPayment(models.Model):
|
||||
'name': writeoff_lines[0].name,
|
||||
'amount': writeoff_amount * sign,
|
||||
@@ -29,7 +29,7 @@ index 3fc91f716ad..35636774c7e 100644
|
||||
+ writeoff_analytic_account_id = fields.Many2one('account.analytic.account', string="Difference Analytic Account", copy=False, domain="[('company_id', '=', company_id)]")
|
||||
writeoff_label = fields.Char(string='Journal Item Label', default='Write-Off',
|
||||
help='Change label of the counterpart that will hold the payment difference')
|
||||
|
||||
|
||||
@@ -422,6 +423,7 @@ class AccountPaymentRegister(models.TransientModel):
|
||||
'name': self.writeoff_label,
|
||||
'amount': self.payment_difference,
|
||||
@@ -37,7 +37,7 @@ index 3fc91f716ad..35636774c7e 100644
|
||||
+ 'analytic_account_id': self.writeoff_analytic_account_id.id or False,
|
||||
}
|
||||
return payment_vals
|
||||
|
||||
|
||||
diff --git a/addons/account/wizard/account_payment_register_views.xml b/addons/account/wizard/account_payment_register_views.xml
|
||||
index 16eec30e265..b9386567baa 100644
|
||||
--- a/addons/account/wizard/account_payment_register_views.xml
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models
|
||||
import logging
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountAccount(models.Model):
|
||||
_inherit = 'account.account'
|
||||
_inherit = "account.account"
|
||||
|
||||
@api.depends('name', 'code')
|
||||
@api.depends("name", "code")
|
||||
def name_get(self):
|
||||
if self._context.get('account_account_show_code_only'):
|
||||
if self._context.get("account_account_show_code_only"):
|
||||
res = []
|
||||
for record in self:
|
||||
res.append((record.id, record.code))
|
||||
@@ -24,36 +25,42 @@ class AccountAccount(models.Model):
|
||||
# https://github.com/odoo/odoo/issues/23040
|
||||
# TODO mig to v14 ?
|
||||
def fix_bank_account_types(self):
|
||||
aao = self.env['account.account']
|
||||
companies = self.env['res.company'].search([])
|
||||
aao = self.env["account.account"]
|
||||
companies = self.env["res.company"].search([])
|
||||
if len(companies) > 1:
|
||||
self = self.sudo()
|
||||
logger.info("START the script 'fix bank and cash account types'")
|
||||
bank_type = self.env.ref('account.data_account_type_liquidity')
|
||||
asset_type = self.env.ref('account.data_account_type_current_assets')
|
||||
journals = self.env['account.journal'].search(
|
||||
[('type', 'in', ('bank', 'cash'))], order='company_id')
|
||||
bank_type = self.env.ref("account.data_account_type_liquidity")
|
||||
asset_type = self.env.ref("account.data_account_type_current_assets")
|
||||
journals = self.env["account.journal"].search(
|
||||
[("type", "in", ("bank", "cash"))], order="company_id"
|
||||
)
|
||||
journal_accounts_bank_type = aao
|
||||
for journal in journals:
|
||||
for account in [
|
||||
journal.default_credit_account_id,
|
||||
journal.default_debit_account_id]:
|
||||
journal.default_credit_account_id,
|
||||
journal.default_debit_account_id,
|
||||
]:
|
||||
if account:
|
||||
if account.user_type_id != bank_type:
|
||||
account.user_type_id = bank_type.id
|
||||
logger.info(
|
||||
'Company %s: Account %s updated to Bank '
|
||||
'and Cash type',
|
||||
account.company_id.display_name, account.code)
|
||||
"Company %s: Account %s updated to Bank " "and Cash type",
|
||||
account.company_id.display_name,
|
||||
account.code,
|
||||
)
|
||||
if account not in journal_accounts_bank_type:
|
||||
journal_accounts_bank_type += account
|
||||
accounts = aao.search([
|
||||
('user_type_id', '=', bank_type.id)], order='company_id, code')
|
||||
accounts = aao.search(
|
||||
[("user_type_id", "=", bank_type.id)], order="company_id, code"
|
||||
)
|
||||
for account in accounts:
|
||||
if account not in journal_accounts_bank_type:
|
||||
account.user_type_id = asset_type.id
|
||||
logger.info(
|
||||
'Company %s: Account %s updated to Current Asset type',
|
||||
account.company_id.display_name, account.code)
|
||||
"Company %s: Account %s updated to Current Asset type",
|
||||
account.company_id.display_name,
|
||||
account.code,
|
||||
)
|
||||
logger.info("END of the script 'fix bank and cash account types'")
|
||||
return True
|
||||
|
||||
@@ -6,10 +6,10 @@ from odoo import models
|
||||
|
||||
|
||||
class AccountAnalyticAccount(models.Model):
|
||||
_inherit = 'account.analytic.account'
|
||||
_inherit = "account.analytic.account"
|
||||
|
||||
def name_get(self):
|
||||
if self._context.get('analytic_account_show_code_only'):
|
||||
if self._context.get("analytic_account_show_code_only"):
|
||||
res = []
|
||||
for record in self:
|
||||
res.append((record.id, record.code or record.name))
|
||||
@@ -17,8 +17,11 @@ class AccountAnalyticAccount(models.Model):
|
||||
else:
|
||||
return super().name_get()
|
||||
|
||||
_sql_constraints = [(
|
||||
'code_company_unique',
|
||||
'unique(code, company_id)',
|
||||
'An analytic account with the same code already '
|
||||
'exists in the same company!')]
|
||||
_sql_constraints = [
|
||||
(
|
||||
"code_company_unique",
|
||||
"unique(code, company_id)",
|
||||
"An analytic account with the same code already "
|
||||
"exists in the same company!",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -7,18 +7,19 @@ from odoo.tools.misc import format_date
|
||||
|
||||
|
||||
class AccountBankStatement(models.Model):
|
||||
_inherit = 'account.bank.statement'
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
start_date = fields.Date(
|
||||
compute='_compute_dates', string='Start Date', readonly=True,
|
||||
store=True)
|
||||
compute="_compute_dates", string="Start Date", readonly=True, store=True
|
||||
)
|
||||
end_date = fields.Date(
|
||||
compute='_compute_dates', string='End Date', readonly=True,
|
||||
store=True)
|
||||
compute="_compute_dates", string="End Date", readonly=True, store=True
|
||||
)
|
||||
hide_bank_statement_balance = fields.Boolean(
|
||||
related='journal_id.hide_bank_statement_balance', readonly=True)
|
||||
related="journal_id.hide_bank_statement_balance", readonly=True
|
||||
)
|
||||
|
||||
@api.depends('line_ids.date')
|
||||
@api.depends("line_ids.date")
|
||||
def _compute_dates(self):
|
||||
for st in self:
|
||||
dates = [line.date for line in st.line_ids]
|
||||
@@ -30,26 +31,31 @@ class AccountBankStatement(models.Model):
|
||||
if stmt.hide_bank_statement_balance:
|
||||
continue
|
||||
else:
|
||||
super(AccountBankStatement, stmt)._check_balance_end_real_same_as_computed()
|
||||
super(
|
||||
AccountBankStatement, stmt
|
||||
)._check_balance_end_real_same_as_computed()
|
||||
return True
|
||||
|
||||
@api.depends('name', 'start_date', 'end_date')
|
||||
@api.depends("name", "start_date", "end_date")
|
||||
def name_get(self):
|
||||
res = []
|
||||
for statement in self:
|
||||
name = "%s (%s => %s)" % (
|
||||
statement.name,
|
||||
statement.start_date and format_date(self.env, statement.start_date) or '',
|
||||
statement.end_date and format_date(self.env, statement.end_date) or '')
|
||||
statement.start_date
|
||||
and format_date(self.env, statement.start_date)
|
||||
or "",
|
||||
statement.end_date and format_date(self.env, statement.end_date) or "",
|
||||
)
|
||||
res.append((statement.id, name))
|
||||
return res
|
||||
|
||||
|
||||
class AccountBankStatementLine(models.Model):
|
||||
_inherit = 'account.bank.statement.line'
|
||||
_inherit = "account.bank.statement.line"
|
||||
# Native order is:
|
||||
# _order = 'statement_id desc, sequence, id desc'
|
||||
_order = 'statement_id desc, date desc, sequence, id desc'
|
||||
_order = "statement_id desc, date desc, sequence, id desc"
|
||||
|
||||
# Disable guessing for reconciliation
|
||||
# because my experience with several customers shows that it is a problem
|
||||
@@ -80,12 +86,14 @@ class AccountBankStatementLine(models.Model):
|
||||
|
||||
def show_account_move(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('account.action_move_line_form').read()[0]
|
||||
action = self.env.ref("account.action_move_line_form").read()[0]
|
||||
# Note: this action is on account.move, not account.move.line !
|
||||
action.update({
|
||||
'views': False,
|
||||
'view_id': False,
|
||||
'view_mode': 'form,tree',
|
||||
'res_id': self.move_id.id,
|
||||
})
|
||||
action.update(
|
||||
{
|
||||
"views": False,
|
||||
"view_id": False,
|
||||
"view_mode": "form,tree",
|
||||
"res_id": self.move_id.id,
|
||||
}
|
||||
)
|
||||
return action
|
||||
|
||||
@@ -6,11 +6,11 @@ from odoo import api, models
|
||||
|
||||
|
||||
class AccountIncoterms(models.Model):
|
||||
_inherit = 'account.incoterms'
|
||||
_inherit = "account.incoterms"
|
||||
|
||||
@api.depends('code', 'name')
|
||||
@api.depends("code", "name")
|
||||
def name_get(self):
|
||||
res = []
|
||||
for rec in self:
|
||||
res.append((rec.id, '[%s] %s' % (rec.code, rec.name)))
|
||||
res.append((rec.id, "[%s] %s" % (rec.code, rec.name)))
|
||||
return res
|
||||
|
||||
@@ -6,22 +6,22 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_inherit = 'account.journal'
|
||||
_inherit = "account.journal"
|
||||
|
||||
hide_bank_statement_balance = fields.Boolean(
|
||||
string='Hide Bank Statement Balance',
|
||||
string="Hide Bank Statement Balance",
|
||||
help="You may want to enable this option when your bank "
|
||||
"journal is generated from a bank statement file that "
|
||||
"doesn't handle start/end balance (QIF for instance) and "
|
||||
"you don't want to enter the start/end balance manually: it "
|
||||
"will prevent the display of wrong information in the accounting "
|
||||
"dashboard and on bank statements.")
|
||||
"dashboard and on bank statements.",
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'name', 'currency_id', 'company_id', 'company_id.currency_id', 'code')
|
||||
@api.depends("name", "currency_id", "company_id", "company_id.currency_id", "code")
|
||||
def name_get(self):
|
||||
res = []
|
||||
if self._context.get('journal_show_code_only'):
|
||||
if self._context.get("journal_show_code_only"):
|
||||
for journal in self:
|
||||
res.append((journal.id, journal.code))
|
||||
return res
|
||||
@@ -29,12 +29,14 @@ class AccountJournal(models.Model):
|
||||
for journal in self:
|
||||
name = "[%s] %s" % (journal.code, journal.name)
|
||||
if (
|
||||
journal.currency_id and
|
||||
journal.currency_id != journal.company_id.currency_id):
|
||||
journal.currency_id
|
||||
and journal.currency_id != journal.company_id.currency_id
|
||||
):
|
||||
name = "%s (%s)" % (name, journal.currency_id.name)
|
||||
res.append((journal.id, name))
|
||||
return res
|
||||
|
||||
|
||||
# @api.constrains('default_credit_account_id', 'default_debit_account_id')
|
||||
# def _check_account_type_on_bank_journal(self):
|
||||
# bank_acc_type = self.env.ref('account.data_account_type_liquidity')
|
||||
|
||||
@@ -3,19 +3,17 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import float_is_zero
|
||||
from odoo.tools.misc import format_date
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
_inherit = "account.move"
|
||||
|
||||
default_move_line_name = fields.Char(
|
||||
string='Default Label', states={'posted': [('readonly', True)]})
|
||||
# By default, we can still modify "ref" when account move is posted
|
||||
# which seems a bit lazy for me...
|
||||
ref = fields.Char(states={'posted': [('readonly', True)]})
|
||||
ref = fields.Char(states={"posted": [("readonly", True)]})
|
||||
date = fields.Date(tracking=True)
|
||||
invoice_date_due = fields.Date(tracking=True)
|
||||
invoice_payment_term_id = fields.Many2one(tracking=True)
|
||||
@@ -24,51 +22,63 @@ class AccountMove(models.Model):
|
||||
fiscal_position_id = fields.Many2one(tracking=True)
|
||||
amount_total = fields.Monetary(tracking=True)
|
||||
# for invoice report
|
||||
has_discount = fields.Boolean(
|
||||
compute='_compute_has_discount', readonly=True)
|
||||
has_discount = fields.Boolean(compute="_compute_has_discount", readonly=True)
|
||||
# has_attachment is useful for those who use attachment to archive
|
||||
# supplier invoices. It allows them to find supplier invoices
|
||||
# that don't have any attachment
|
||||
has_attachment = fields.Boolean(
|
||||
compute='_compute_has_attachment',
|
||||
search='_search_has_attachment', readonly=True)
|
||||
compute="_compute_has_attachment",
|
||||
search="_search_has_attachment",
|
||||
readonly=True,
|
||||
)
|
||||
sale_dates = fields.Char(
|
||||
compute="_compute_sales_dates", readonly=True,
|
||||
compute="_compute_sales_dates",
|
||||
readonly=True,
|
||||
help="This information appears on invoice qweb report "
|
||||
"(you may use it for your own report)")
|
||||
"(you may use it for your own report)",
|
||||
)
|
||||
|
||||
def _compute_has_discount(self):
|
||||
prec = self.env['decimal.precision'].precision_get('Discount')
|
||||
prec = self.env["decimal.precision"].precision_get("Discount")
|
||||
for inv in self:
|
||||
has_discount = False
|
||||
for line in inv.invoice_line_ids:
|
||||
if not line.display_type and not float_is_zero(line.discount, precision_digits=prec):
|
||||
if not line.display_type and not float_is_zero(
|
||||
line.discount, precision_digits=prec
|
||||
):
|
||||
has_discount = True
|
||||
break
|
||||
inv.has_discount = has_discount
|
||||
|
||||
def _compute_has_attachment(self):
|
||||
iao = self.env['ir.attachment']
|
||||
iao = self.env["ir.attachment"]
|
||||
for move in self:
|
||||
if iao.search_count([
|
||||
('res_model', '=', 'account.move'),
|
||||
('res_id', '=', move.id),
|
||||
('type', '=', 'binary'),
|
||||
('company_id', '=', move.company_id.id)]):
|
||||
if iao.search_count(
|
||||
[
|
||||
("res_model", "=", "account.move"),
|
||||
("res_id", "=", move.id),
|
||||
("type", "=", "binary"),
|
||||
("company_id", "=", move.company_id.id),
|
||||
]
|
||||
):
|
||||
move.has_attachment = True
|
||||
else:
|
||||
move.has_attachment = False
|
||||
|
||||
def _search_has_attachment(self, operator, value):
|
||||
att_inv_ids = {}
|
||||
if operator == '=':
|
||||
search_res = self.env['ir.attachment'].search_read([
|
||||
('res_model', '=', 'account.move'),
|
||||
('type', '=', 'binary'),
|
||||
('res_id', '!=', False)], ['res_id'])
|
||||
if operator == "=":
|
||||
search_res = self.env["ir.attachment"].search_read(
|
||||
[
|
||||
("res_model", "=", "account.move"),
|
||||
("type", "=", "binary"),
|
||||
("res_id", "!=", False),
|
||||
],
|
||||
["res_id"],
|
||||
)
|
||||
for att in search_res:
|
||||
att_inv_ids[att['res_id']] = True
|
||||
res = [('id', value and 'in' or 'not in', list(att_inv_ids))]
|
||||
att_inv_ids[att["res_id"]] = True
|
||||
res = [("id", value and "in" or "not in", list(att_inv_ids))]
|
||||
return res
|
||||
|
||||
# when you have an invoice created from a lot of sale orders, the 'name'
|
||||
@@ -83,10 +93,10 @@ class AccountMove(models.Model):
|
||||
name = old_re[1]
|
||||
if name and len(name) > 100:
|
||||
# nice cut
|
||||
name = '%s ...' % ', '.join(name.split(', ')[:3])
|
||||
name = "%s ..." % ", ".join(name.split(", ")[:3])
|
||||
# if not enough, hard cut
|
||||
if len(name) > 120:
|
||||
name = '%s ...' % old_re[1][:120]
|
||||
name = "%s ..." % old_re[1][:120]
|
||||
res.append((old_re[0], name))
|
||||
return res
|
||||
|
||||
@@ -96,23 +106,26 @@ class AccountMove(models.Model):
|
||||
# write a rubbish '/' in it !
|
||||
# 2) the 'name' field of the account.move.line is used in the overdue
|
||||
# letter, and '/' is not meaningful for our customer !
|
||||
# TODO mig to v12
|
||||
# def action_move_create(self):
|
||||
# res = super().action_move_create()
|
||||
# for inv in self:
|
||||
# self._cr.execute(
|
||||
# "UPDATE account_move_line SET name= "
|
||||
# "CASE WHEN name='/' THEN %s "
|
||||
# "ELSE %s||' - '||name END "
|
||||
# "WHERE move_id=%s", (inv.number, inv.number, inv.move_id.id))
|
||||
# self.invalidate_cache()
|
||||
# return res
|
||||
# TODO mig to v12
|
||||
# def action_move_create(self):
|
||||
# res = super().action_move_create()
|
||||
# for inv in self:
|
||||
# self._cr.execute(
|
||||
# "UPDATE account_move_line SET name= "
|
||||
# "CASE WHEN name='/' THEN %s "
|
||||
# "ELSE %s||' - '||name END "
|
||||
# "WHERE move_id=%s", (inv.number, inv.number, inv.move_id.id))
|
||||
# self.invalidate_cache()
|
||||
# return res
|
||||
|
||||
def delete_lines_qty_zero(self):
|
||||
lines = self.env['account.move.line'].search([
|
||||
('display_type', '=', False),
|
||||
('move_id', 'in', self.ids),
|
||||
('quantity', '=', 0)])
|
||||
lines = self.env["account.move.line"].search(
|
||||
[
|
||||
("display_type", "=", False),
|
||||
("move_id", "in", self.ids),
|
||||
("quantity", "=", 0),
|
||||
]
|
||||
)
|
||||
lines.unlink()
|
||||
return True
|
||||
|
||||
@@ -122,24 +135,27 @@ class AccountMove(models.Model):
|
||||
res = []
|
||||
has_sections = False
|
||||
subtotal = 0.0
|
||||
sign = self.move_type == 'out_refund' and -1 or 1
|
||||
sign = self.move_type == "out_refund" and -1 or 1
|
||||
# Warning: the order of invoice line is forced in the view
|
||||
# <tree editable="bottom" default_order="sequence, date desc, move_name desc, id"
|
||||
# it's not the same as the _order in the class AccountMoveLine
|
||||
lines = self.env['account.move.line'].search([('exclude_from_invoice_tab', '=', False), ('move_id', '=', self.id)], order="sequence, date desc, move_name desc, id")
|
||||
lines = self.env["account.move.line"].search(
|
||||
[("exclude_from_invoice_tab", "=", False), ("move_id", "=", self.id)],
|
||||
order="sequence, date desc, move_name desc, id",
|
||||
)
|
||||
for line in lines:
|
||||
if line.display_type == 'line_section':
|
||||
if line.display_type == "line_section":
|
||||
# insert line
|
||||
if has_sections:
|
||||
res.append({'subtotal': subtotal})
|
||||
res.append({"subtotal": subtotal})
|
||||
subtotal = 0.0 # reset counter
|
||||
has_sections = True
|
||||
else:
|
||||
if not line.display_type:
|
||||
subtotal += line.price_subtotal * sign
|
||||
res.append({'line': line})
|
||||
res.append({"line": line})
|
||||
if has_sections: # insert last subtotal line
|
||||
res.append({'subtotal': subtotal})
|
||||
res.append({"subtotal": subtotal})
|
||||
# res:
|
||||
# [
|
||||
# {'line': account_invoice_line(1) with display_type=='line_section'},
|
||||
@@ -151,38 +167,81 @@ class AccountMove(models.Model):
|
||||
return res
|
||||
|
||||
def _compute_sales_dates(self):
|
||||
""" French law requires to set sale order dates into invoice
|
||||
returned string: "sale1 (date1), sale2 (date2) ..."
|
||||
"""French law requires to set sale order dates into invoice
|
||||
returned string: "sale1 (date1), sale2 (date2) ..."
|
||||
"""
|
||||
for inv in self:
|
||||
sales = inv.invoice_line_ids.mapped(
|
||||
'sale_line_ids').mapped('order_id')
|
||||
dates = ["%s (%s)" % (
|
||||
x.name, format_date(inv.env, self.date_order))
|
||||
for x in sales]
|
||||
sales = inv.invoice_line_ids.mapped("sale_line_ids").mapped("order_id")
|
||||
dates = [
|
||||
"%s (%s)" % (x.name, format_date(inv.env, self.date_order))
|
||||
for x in sales
|
||||
]
|
||||
inv.sale_dates = ", ".join(dates)
|
||||
|
||||
# allow to manually create moves not only in general journals,
|
||||
# but also in cash journal and check journals (= bank journals not linked to a bank account)
|
||||
@api.depends('company_id', 'invoice_filter_type_domain')
|
||||
@api.depends("company_id", "invoice_filter_type_domain")
|
||||
def _compute_suitable_journal_ids(self):
|
||||
for move in self:
|
||||
if move.invoice_filter_type_domain:
|
||||
super(AccountMove, move)._compute_suitable_journal_ids()
|
||||
else:
|
||||
company_id = move.company_id.id or self.env.company.id
|
||||
domain = expression.AND([
|
||||
[('company_id', '=', company_id)],
|
||||
expression.OR([
|
||||
[('type', 'in', ('general', 'cash'))],
|
||||
[('type', '=', 'bank'), ('bank_account_id', '=', False)]
|
||||
])
|
||||
])
|
||||
move.suitable_journal_ids = self.env['account.journal'].search(domain)
|
||||
domain = expression.AND(
|
||||
[
|
||||
[("company_id", "=", company_id)],
|
||||
expression.OR(
|
||||
[
|
||||
[("type", "in", ("general", "cash"))],
|
||||
[
|
||||
("type", "=", "bank"),
|
||||
("bank_account_id", "=", False),
|
||||
],
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
move.suitable_journal_ids = self.env["account.journal"].search(domain)
|
||||
|
||||
def button_draft(self):
|
||||
super().button_draft()
|
||||
# Delete attached pdf invoice
|
||||
try:
|
||||
report_invoice = self.env["ir.actions.report"]._get_report_from_name(
|
||||
"account.report_invoice"
|
||||
)
|
||||
except IndexError:
|
||||
report_invoice = False
|
||||
if report_invoice and report_invoice.attachment:
|
||||
for move in self.filtered(
|
||||
lambda x: x.move_type in ("out_invoice", "out_refund")
|
||||
):
|
||||
# The pb is that the filename is dynamic and related to move.state
|
||||
# in v12, the feature was native and they used that kind of code:
|
||||
# with invoice.env.do_in_draft():
|
||||
# invoice.number, invoice.state = invoice.move_name, 'open'
|
||||
# attachment = self.env.ref('account.account_invoices').retrieve_attachment(invoice)
|
||||
# But do_in_draft() doesn't exists in v14
|
||||
# If you know how we could do that, please update the code below
|
||||
attachment = self.env["ir.attachment"].search(
|
||||
[
|
||||
("name", "=", self._get_invoice_attachment_name()),
|
||||
("res_id", "=", move.id),
|
||||
("res_model", "=", self._name),
|
||||
("type", "=", "binary"),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
if attachment:
|
||||
attachment.unlink()
|
||||
|
||||
def _get_invoice_attachment_name(self):
|
||||
self.ensure_one()
|
||||
return "%s.pdf" % (self.name and self.name.replace("/", "_") or "INV")
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
_inherit = "account.move.line"
|
||||
# Native order:
|
||||
# _order = "date desc, move_name desc, id"
|
||||
# Problem: when you manually create a journal entry, the
|
||||
@@ -193,34 +252,42 @@ class AccountMoveLine(models.Model):
|
||||
# In the 'account' module, we have related stored field for:
|
||||
# name (move_name), date, ref, state (parent_state),
|
||||
# journal_id, company_id, payment_id, statement_line_id,
|
||||
account_reconcile = fields.Boolean(related='account_id.reconcile')
|
||||
full_reconcile_id = fields.Many2one(string='Full Reconcile')
|
||||
matched_debit_ids = fields.One2many(string='Partial Reconcile Debit')
|
||||
matched_credit_ids = fields.One2many(string='Partial Reconcile Credit')
|
||||
account_reconcile = fields.Boolean(related="account_id.reconcile")
|
||||
full_reconcile_id = fields.Many2one(string="Full Reconcile")
|
||||
matched_debit_ids = fields.One2many(string="Partial Reconcile Debit")
|
||||
matched_credit_ids = fields.One2many(string="Partial Reconcile Credit")
|
||||
reconcile_string = fields.Char(
|
||||
compute='_compute_reconcile_string', string='Reconcile', store=True)
|
||||
compute="_compute_reconcile_string", string="Reconcile", store=True
|
||||
)
|
||||
# for optional display in tree view
|
||||
product_barcode = fields.Char(related='product_id.barcode', string="Product Barcode")
|
||||
product_barcode = fields.Char(
|
||||
related="product_id.barcode", string="Product Barcode"
|
||||
)
|
||||
|
||||
def show_account_move_form(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('account.action_move_line_form').read()[0]
|
||||
action.update({
|
||||
'res_id': self.move_id.id,
|
||||
'view_id': False,
|
||||
'views': False,
|
||||
'view_mode': 'form,tree',
|
||||
})
|
||||
action = self.env.ref("account.action_move_line_form").read()[0]
|
||||
action.update(
|
||||
{
|
||||
"res_id": self.move_id.id,
|
||||
"view_id": False,
|
||||
"views": False,
|
||||
"view_mode": "form,tree",
|
||||
}
|
||||
)
|
||||
return action
|
||||
|
||||
@api.depends(
|
||||
'full_reconcile_id', 'matched_debit_ids', 'matched_credit_ids')
|
||||
@api.depends("full_reconcile_id", "matched_debit_ids", "matched_credit_ids")
|
||||
def _compute_reconcile_string(self):
|
||||
for line in self:
|
||||
rec_str = False
|
||||
if line.full_reconcile_id:
|
||||
rec_str = line.full_reconcile_id.name
|
||||
else:
|
||||
rec_str = ', '.join([
|
||||
'a%d' % pr.id for pr in line.matched_debit_ids + line.matched_credit_ids])
|
||||
rec_str = ", ".join(
|
||||
[
|
||||
"a%d" % pr.id
|
||||
for pr in line.matched_debit_ids + line.matched_credit_ids
|
||||
]
|
||||
)
|
||||
line.reconcile_string = rec_str
|
||||
|
||||
@@ -17,7 +17,8 @@ class AccountPartialReconcile(models.Model):
|
||||
# Prefix for full rec: 'A' (upper case)
|
||||
# Prefix for partial rec: 'a' (lower case)
|
||||
amount_fmt = formatLang(
|
||||
self.env, rec.amount, currency_obj=rec.company_currency_id)
|
||||
name = 'a%d (%s)' % (rec.id, amount_fmt)
|
||||
self.env, rec.amount, currency_obj=rec.company_currency_id
|
||||
)
|
||||
name = "a%d (%s)" % (rec.id, amount_fmt)
|
||||
res.append((rec.id, name))
|
||||
return res
|
||||
|
||||
@@ -2,44 +2,61 @@
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
_inherit = "product.template"
|
||||
|
||||
# DON'T put store=True on those fields, because they are company dependent
|
||||
sale_price_type = fields.Selection(
|
||||
'_sale_purchase_price_type_sel', compute='_compute_sale_price_type',
|
||||
string='Sale Price Type', compute_sudo=False, readonly=True)
|
||||
"_sale_purchase_price_type_sel",
|
||||
compute="_compute_sale_price_type",
|
||||
string="Sale Price Type",
|
||||
compute_sudo=False,
|
||||
readonly=True,
|
||||
)
|
||||
purchase_price_type = fields.Selection(
|
||||
'_sale_purchase_price_type_sel', compute='_compute_purchase_price_type',
|
||||
string='Purchase Price Type', compute_sudo=False, readonly=True)
|
||||
"_sale_purchase_price_type_sel",
|
||||
compute="_compute_purchase_price_type",
|
||||
string="Purchase Price Type",
|
||||
compute_sudo=False,
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _sale_purchase_price_type_sel(self):
|
||||
return [('incl', _('Tax incl.')), ('excl', _('Tax excl.'))]
|
||||
return [("incl", _("Tax incl.")), ("excl", _("Tax excl."))]
|
||||
|
||||
@api.depends('taxes_id')
|
||||
@api.depends("taxes_id")
|
||||
def _compute_sale_price_type(self):
|
||||
for pt in self:
|
||||
sale_price_type = 'incl'
|
||||
if pt.taxes_id and all([not t.price_include for t in pt.taxes_id if t.amount_type == 'percent']):
|
||||
sale_price_type = 'excl'
|
||||
sale_price_type = "incl"
|
||||
if pt.taxes_id and all(
|
||||
[not t.price_include for t in pt.taxes_id if t.amount_type == "percent"]
|
||||
):
|
||||
sale_price_type = "excl"
|
||||
pt.sale_price_type = sale_price_type
|
||||
|
||||
@api.depends('supplier_taxes_id')
|
||||
@api.depends("supplier_taxes_id")
|
||||
def _compute_purchase_price_type(self):
|
||||
for pt in self:
|
||||
purchase_price_type = 'incl'
|
||||
if pt.supplier_taxes_id and all([not t.price_include for t in pt.supplier_taxes_id if t.amount_type == 'percent']):
|
||||
purchase_price_type = 'excl'
|
||||
purchase_price_type = "incl"
|
||||
if pt.supplier_taxes_id and all(
|
||||
[
|
||||
not t.price_include
|
||||
for t in pt.supplier_taxes_id
|
||||
if t.amount_type == "percent"
|
||||
]
|
||||
):
|
||||
purchase_price_type = "excl"
|
||||
pt.purchase_price_type = purchase_price_type
|
||||
|
||||
|
||||
class ProductSupplierinfo(models.Model):
|
||||
_inherit = 'product.supplierinfo'
|
||||
_inherit = "product.supplierinfo"
|
||||
|
||||
# DON'T put store=True on those fields, because they are company dependent
|
||||
purchase_price_type = fields.Selection(
|
||||
related='product_tmpl_id.purchase_price_type', related_sudo=False)
|
||||
related="product_tmpl_id.purchase_price_type", related_sudo=False
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
_inherit = "res.partner"
|
||||
|
||||
invoice_warn = fields.Selection(tracking=True)
|
||||
property_account_position_id = fields.Many2one(tracking=True)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
This modules adds the following functions:
|
||||
|
||||
* Add an *Overdue* filter on invoice search view (this feature was previously
|
||||
* Add an *Overdue* filter on invoice search view (this feature was previously
|
||||
located in te module *account_invoice_overdue_filter*)
|
||||
* Increase the default limit of 80 lines in account move and account move line view.
|
||||
* disable reconciliation "guessing"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<template id="report_invoice_document" inherit_id="account.report_invoice_document">
|
||||
<xpath expr="//div[@name='origin']/p" position="replace">
|
||||
<p class="m-0" t-field="o.sale_dates"/>
|
||||
<p class="m-0" t-field="o.sale_dates" />
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2021 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
<record id="view_account_form" model="ir.ui.view">
|
||||
<field name="name">account.account.form</field>
|
||||
<field name="model">account.account</field>
|
||||
<field name="inherit_id" ref="account.view_account_form"/>
|
||||
<field name="inherit_id" ref="account.view_account_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="deprecated" position="before">
|
||||
<field name="reconcile" attrs="{'invisible': ['|', ('internal_type','=','liquidity'), ('internal_group', '=', 'off_balance')]}"/>
|
||||
<field
|
||||
name="reconcile"
|
||||
attrs="{'invisible': ['|', ('internal_type','=','liquidity'), ('internal_group', '=', 'off_balance')]}"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<record id="view_account_search" model="ir.ui.view">
|
||||
<field name="name">account.account.search</field>
|
||||
<field name="model">account.account</field>
|
||||
<field name="inherit_id" ref="account.view_account_search"/>
|
||||
<field name="inherit_id" ref="account.view_account_search" />
|
||||
<field name="arch" type="xml">
|
||||
<!-- The native "name" filter uses a domain ['|', ('name','ilike',self), ('code','=like',str(self)+'%')]
|
||||
This is good because it uses '=like' on 'code', but sometimes there are digits in account names,
|
||||
so you get additionnal unexpected accounts in the result of the search -->
|
||||
<field name="name" position="after">
|
||||
<field name="code" filter_domain="[('code', '=like', str(self)+'%')]" string="Code"/>
|
||||
<field
|
||||
name="code"
|
||||
filter_domain="[('code', '=like', str(self)+'%')]"
|
||||
string="Code"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_type_tree" model="ir.ui.view">
|
||||
<field name="name">account_usability.account_type_tree</field>
|
||||
<field name="model">account.account.type</field>
|
||||
<field name="inherit_id" ref="account.view_account_type_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="arch" type="xml">
|
||||
<field name="type" position="after">
|
||||
<field name="include_initial_balance" optional="show"/>
|
||||
<field name="include_initial_balance" optional="show" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,45 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
<record id="view_bank_statement_form" model="ir.ui.view">
|
||||
<field name="name">usability.account.bank.statement.form</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form"/>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='line_ids']/tree/button[@name='button_undo_reconciliation']" position="after">
|
||||
<field name="move_id" invisible="1"/>
|
||||
<button name="show_account_move" type="object"
|
||||
title="View Journal Entry" icon="fa-arrow-right"/>
|
||||
<xpath
|
||||
expr="//field[@name='line_ids']/tree/button[@name='button_undo_reconciliation']"
|
||||
position="after"
|
||||
>
|
||||
<field name="move_id" invisible="1" />
|
||||
<button
|
||||
name="show_account_move"
|
||||
type="object"
|
||||
title="View Journal Entry"
|
||||
icon="fa-arrow-right"
|
||||
/>
|
||||
</xpath>
|
||||
<field name="date" position="after">
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="hide_bank_statement_balance" invisible="1"/>
|
||||
<field name="start_date" />
|
||||
<field name="end_date" />
|
||||
<field name="hide_bank_statement_balance" invisible="1" />
|
||||
</field>
|
||||
<field name="date" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</field>
|
||||
<label for="balance_start" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
</label>
|
||||
<label for="balance_end_real" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
</label>
|
||||
<xpath expr="//field[@name='balance_start']/.." position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='balance_end_real']/.." position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
</xpath>
|
||||
<group name="sale_total" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
@@ -47,14 +63,14 @@
|
||||
<record id="view_bank_statement_tree" model="ir.ui.view">
|
||||
<field name="name">usability.account.bank.statement.tree</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_tree"/>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="date" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</field>
|
||||
<field name="journal_id" position="after">
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="start_date" />
|
||||
<field name="end_date" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -62,7 +78,7 @@
|
||||
<record id="view_bank_statement_search" model="ir.ui.view">
|
||||
<field name="name">usability.account.bank.statement.search</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_search"/>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_search" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="date" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
@@ -74,14 +90,20 @@
|
||||
<attribute name="invisible">1</attribute>
|
||||
</filter>
|
||||
<field name="date" position="after">
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="start_date" />
|
||||
<field name="end_date" />
|
||||
</field>
|
||||
<filter name="date" position="after">
|
||||
<filter name="start_date_groupby" string="Start Date"
|
||||
context="{'group_by': 'start_date'}"/>
|
||||
<filter name="end_date_groupby" string="End Date"
|
||||
context="{'group_by': 'end_date'}"/>
|
||||
<filter
|
||||
name="start_date_groupby"
|
||||
string="Start Date"
|
||||
context="{'group_by': 'start_date'}"
|
||||
/>
|
||||
<filter
|
||||
name="end_date_groupby"
|
||||
string="End Date"
|
||||
context="{'group_by': 'end_date'}"
|
||||
/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2018-2020 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
@@ -13,36 +12,43 @@
|
||||
<field name="model">account.invoice.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Invoices Analysis">
|
||||
<field name="move_id"/>
|
||||
<field name="invoice_date"/>
|
||||
<field name="invoice_date_due"/>
|
||||
<field name="move_type"/>
|
||||
<field name="commercial_partner_id"/>
|
||||
<field name="invoice_user_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="quantity" sum="1"/>
|
||||
<field name="product_uom_id" groups="uom.group_uom"/>
|
||||
<field name="price_subtotal" sum="1"/>
|
||||
<field name="state"/>
|
||||
<field name="move_id" />
|
||||
<field name="invoice_date" />
|
||||
<field name="invoice_date_due" />
|
||||
<field name="move_type" />
|
||||
<field name="commercial_partner_id" />
|
||||
<field name="invoice_user_id" />
|
||||
<field name="product_id" />
|
||||
<field name="quantity" sum="1" />
|
||||
<field name="product_uom_id" groups="uom.group_uom" />
|
||||
<field name="price_subtotal" sum="1" />
|
||||
<field name="state" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account.action_account_invoice_report_all_supp" model="ir.actions.act_window">
|
||||
<field name="context">{'search_default_current': 1, 'search_default_supplier': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
|
||||
<record
|
||||
id="account.action_account_invoice_report_all_supp"
|
||||
model="ir.actions.act_window"
|
||||
>
|
||||
<field
|
||||
name="context"
|
||||
>{'search_default_current': 1, 'search_default_supplier': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
|
||||
</record>
|
||||
|
||||
<record id="account.action_account_invoice_report_all" model="ir.actions.act_window">
|
||||
<field name="context">{'search_default_current': 1, 'search_default_customer': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
|
||||
<field
|
||||
name="context"
|
||||
>{'search_default_current': 1, 'search_default_customer': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
|
||||
</record>
|
||||
|
||||
<record id="view_account_invoice_report_pivot" model="ir.ui.view">
|
||||
<field name="name">usability.account.invoice.report</field>
|
||||
<field name="model">account.invoice.report</field>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_report_pivot"/>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_report_pivot" />
|
||||
<field name="arch" type="xml">
|
||||
<pivot position="attributes">
|
||||
<attribute name="disable_linking"></attribute>
|
||||
<attribute name="disable_linking" />
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_journal_form" model="ir.ui.view">
|
||||
<field name="name">usability.account.journal.form</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.view_account_journal_form"/>
|
||||
<field name="inherit_id" ref="account.view_account_journal_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="bank_statements_source" position="after">
|
||||
<field name="hide_bank_statement_balance" groups="account.group_account_readonly"/>
|
||||
<field
|
||||
name="hide_bank_statement_balance"
|
||||
groups="account.group_account_readonly"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -21,13 +23,15 @@
|
||||
<record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
|
||||
<field name="name">usability.account.journal.dashboard</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.account_journal_dashboard_kanban_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="inherit_id" ref="account.account_journal_dashboard_kanban_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="kanban_dashboard" position="after">
|
||||
<field name="hide_bank_statement_balance"/>
|
||||
<field name="hide_bank_statement_balance" />
|
||||
</field>
|
||||
<xpath expr="//div[@name='latest_statement']/.." position="attributes">
|
||||
<attribute name="t-if">dashboard.has_at_least_one_statement and dashboard.account_balance != dashboard.last_balance and !record.hide_bank_statement_balance.raw_value</attribute>
|
||||
<attribute
|
||||
name="t-if"
|
||||
>dashboard.has_at_least_one_statement and dashboard.account_balance != dashboard.last_balance and !record.hide_bank_statement_balance.raw_value</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -35,10 +39,10 @@
|
||||
<record id="view_account_journal_tree" model="ir.ui.view">
|
||||
<field name="name">usability.account.journal.tree</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.view_account_journal_tree"/>
|
||||
<field name="inherit_id" ref="account.view_account_journal_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="code" optional="show"/>
|
||||
<field name="code" optional="show" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -46,11 +50,15 @@
|
||||
<record id="view_account_journal_search" model="ir.ui.view">
|
||||
<field name="name">usability.account.journal.search</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field name="inherit_id" ref="account.view_account_journal_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="inherit_id" ref="account.view_account_journal_search" />
|
||||
<field name="arch" type="xml">
|
||||
<filter name="inactive" position="after">
|
||||
<group name="groupby" string="Group By">
|
||||
<filter name="type_groupby" string="Type" context="{'group_by': 'type'}"/>
|
||||
<filter
|
||||
name="type_groupby"
|
||||
string="Type"
|
||||
context="{'group_by': 'type'}"
|
||||
/>
|
||||
</group>
|
||||
</filter>
|
||||
</field>
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<!-- Duplicate the menu "Sales > Configuration > Contacts > Bank Accounts"
|
||||
under "Accounting > Configuration", because most users will try to find it there -->
|
||||
<menuitem id="res_bank_account_config_menu" action="base.action_res_bank_form" parent="account.account_banks_menu" sequence="10"/>
|
||||
<menuitem
|
||||
id="res_bank_account_config_menu"
|
||||
action="base.action_res_bank_form"
|
||||
parent="account.account_banks_menu"
|
||||
sequence="10"
|
||||
/>
|
||||
|
||||
<menuitem id="res_partner_bank_account_config_menu" action="base.action_res_partner_bank_account_form" parent="account.account_banks_menu" sequence="20"/>
|
||||
<menuitem
|
||||
id="res_partner_bank_account_config_menu"
|
||||
action="base.action_res_partner_bank_account_form"
|
||||
parent="account.account_banks_menu"
|
||||
sequence="20"
|
||||
/>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_move_form" model="ir.ui.view">
|
||||
<field name="name">account_usability.account.move.form</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="inherit_id" ref="account.view_move_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="fiscal_position_id" position="attributes">
|
||||
<attribute name="widget">selection</attribute>
|
||||
@@ -22,28 +21,45 @@
|
||||
<attribute name="class">btn-default</attribute>
|
||||
</button>
|
||||
<button name="action_register_payment" position="before">
|
||||
<button name="%(account.account_invoices)d" type="action" string="Print" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
|
||||
<button
|
||||
name="%(account.account_invoices)d"
|
||||
type="action"
|
||||
string="Print"
|
||||
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"
|
||||
/>
|
||||
</button>
|
||||
<button name="preview_invoice" position="attributes">
|
||||
<attribute name="attrs">{'invisible': 1}</attribute>
|
||||
</button>
|
||||
<!-- move sent field and make it visible -->
|
||||
<field name="is_move_sent" position="replace"/>
|
||||
<field name="is_move_sent" position="replace" />
|
||||
<field name="invoice_origin" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</field>
|
||||
<field name="invoice_origin" position="after">
|
||||
<field name="is_move_sent" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
|
||||
<field
|
||||
name="is_move_sent"
|
||||
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"
|
||||
/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='analytic_account_id']" position="attributes">
|
||||
<xpath
|
||||
expr="//field[@name='line_ids']/tree/field[@name='analytic_account_id']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="optional">show</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='tax_tag_ids']" position="after">
|
||||
<field name="matching_number" optional="hide"/>
|
||||
<field name="reconcile_string" optional="show"/>
|
||||
<xpath
|
||||
expr="//field[@name='line_ids']/tree/field[@name='tax_tag_ids']"
|
||||
position="after"
|
||||
>
|
||||
<field name="matching_number" optional="hide" />
|
||||
<field name="reconcile_string" optional="show" />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='product_id']" position="after">
|
||||
<field name="product_barcode" optional="hide"/>
|
||||
<xpath
|
||||
expr="//field[@name='invoice_line_ids']/tree/field[@name='product_id']"
|
||||
position="after"
|
||||
>
|
||||
<field name="product_barcode" optional="hide" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -51,24 +67,41 @@
|
||||
<record id="view_account_invoice_filter" model="ir.ui.view">
|
||||
<field name="name">account_usability.account.move.search</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_filter" />
|
||||
<field name="arch" type="xml">
|
||||
<filter name="due_date" position="after">
|
||||
<separator/>
|
||||
<filter name="to_send" string="To Send" domain="[('is_move_sent', '=', False), ('state', '=', 'posted'), ('move_type', 'in', ('out_invoice', 'out_refund'))]"/>
|
||||
<filter name="sent" string="Sent" domain="[('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund'))]"/>
|
||||
<separator/>
|
||||
<filter name="no_attachment" string="Missing Attachment" domain="[('has_attachment', '=', False)]"/>
|
||||
<separator />
|
||||
<filter
|
||||
name="to_send"
|
||||
string="To Send"
|
||||
domain="[('is_move_sent', '=', False), ('state', '=', 'posted'), ('move_type', 'in', ('out_invoice', 'out_refund'))]"
|
||||
/>
|
||||
<filter
|
||||
name="sent"
|
||||
string="Sent"
|
||||
domain="[('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund'))]"
|
||||
/>
|
||||
<separator />
|
||||
<filter
|
||||
name="no_attachment"
|
||||
string="Missing Attachment"
|
||||
domain="[('has_attachment', '=', False)]"
|
||||
/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_line_tree" model="ir.ui.view">
|
||||
<field name="model">account.move.line</field>
|
||||
<field name="inherit_id" ref="account.view_move_line_tree"/>
|
||||
<field name="inherit_id" ref="account.view_move_line_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="matching_number" position="after">
|
||||
<button title="View Journal Entry Form" type="object" name="show_account_move_form" icon="fa-arrow-right"/>
|
||||
<button
|
||||
title="View Journal Entry Form"
|
||||
type="object"
|
||||
name="show_account_move_form"
|
||||
icon="fa-arrow-right"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -76,19 +109,35 @@
|
||||
<record id="view_account_move_line_filter" model="ir.ui.view">
|
||||
<field name="name">account_usability.account_move_line_search</field>
|
||||
<field name="model">account.move.line</field>
|
||||
<field name="inherit_id" ref="account.view_account_move_line_filter"/>
|
||||
<field name="inherit_id" ref="account.view_account_move_line_filter" />
|
||||
<field name="arch" type="xml">
|
||||
<filter name="unposted" position="before">
|
||||
<filter name="current_year" string="Current Year" domain="[('date', '>=', (context_today().strftime('%Y-01-01'))), ('date', '<=', (context_today().strftime('%Y-12-31')))]"/>
|
||||
<filter name="previous_year" string="Previous Year" domain="[('date', '>=', (context_today() + relativedelta(day=1, month=1, years=-1)).strftime('%Y-%m-%d')), ('date', '<=', (context_today() + relativedelta(day=31, month=12, years=-1)).strftime('%Y-%m-%d'))]"/>
|
||||
<separator/>
|
||||
<filter
|
||||
name="current_year"
|
||||
string="Current Year"
|
||||
domain="[('date', '>=', (context_today().strftime('%Y-01-01'))), ('date', '<=', (context_today().strftime('%Y-12-31')))]"
|
||||
/>
|
||||
<filter
|
||||
name="previous_year"
|
||||
string="Previous Year"
|
||||
domain="[('date', '>=', (context_today() + relativedelta(day=1, month=1, years=-1)).strftime('%Y-%m-%d')), ('date', '<=', (context_today() + relativedelta(day=31, month=12, years=-1)).strftime('%Y-%m-%d'))]"
|
||||
/>
|
||||
<separator />
|
||||
</filter>
|
||||
<field name="partner_id" position="after">
|
||||
<field name="reconcile_string" />
|
||||
<field name="debit" filter_domain="['|', ('debit', '=', self), ('credit', '=', self)]" string="Debit or Credit"/>
|
||||
<field
|
||||
name="debit"
|
||||
filter_domain="['|', ('debit', '=', self), ('credit', '=', self)]"
|
||||
string="Debit or Credit"
|
||||
/>
|
||||
</field>
|
||||
<filter name="unreconciled" position="before">
|
||||
<filter name="reconciled" string="Fully Reconciled" domain="[('full_reconcile_id', '!=', False)]"/>
|
||||
<filter
|
||||
name="reconciled"
|
||||
string="Fully Reconciled"
|
||||
domain="[('full_reconcile_id', '!=', False)]"
|
||||
/>
|
||||
</filter>
|
||||
<filter name="unreconciled" position="attributes">
|
||||
<attribute name="string">Unreconciled or Partially Reconciled</attribute>
|
||||
@@ -98,13 +147,17 @@
|
||||
<attribute name="string">Name or Reference</attribute>
|
||||
</field> -->
|
||||
<field name="partner_id" position="attributes">
|
||||
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
|
||||
<attribute
|
||||
name="domain"
|
||||
>['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account.action_move_journal_line" model="ir.actions.act_window">
|
||||
<field name="context">{'default_move_type': 'entry', 'view_no_maturity': True}</field>
|
||||
<field
|
||||
name="context"
|
||||
>{'default_move_type': 'entry', 'view_no_maturity': True}</field>
|
||||
<!-- Remove 'search_default_misc_filter': 1 -->
|
||||
</record>
|
||||
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2018-2020 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="account.account_invoices" model="ir.actions.report">
|
||||
<!-- Attach only on customer invoices/refunds -->
|
||||
<field name="attachment">(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field>
|
||||
<field
|
||||
name="attachment"
|
||||
>(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field>
|
||||
</record>
|
||||
|
||||
<record id="account.account_invoices_without_payment" model="ir.actions.report">
|
||||
<!-- Attach only on customer invoices/refunds -->
|
||||
<field name="attachment">(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field>
|
||||
<field
|
||||
name="attachment"
|
||||
>(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_tax_tree" model="ir.ui.view">
|
||||
<field name="model">account.tax</field>
|
||||
<field name="inherit_id" ref="account.view_tax_tree"/>
|
||||
<field name="inherit_id" ref="account.view_tax_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="description" position="after">
|
||||
<field name="price_include" optional="show"/>
|
||||
<field name="price_include" optional="show" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2017-2020 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<!-- In the official account module, on product category and product template,
|
||||
@@ -16,8 +15,10 @@ Here, we set all those fields on account.group_account_invoice
|
||||
<record id="product_template_form_view" model="ir.ui.view">
|
||||
<field name="name">account_usability.product.template.form</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="priority">100</field> <!-- when you replace a field, it's always better to inherit at the end -->
|
||||
<field name="inherit_id" ref="account.product_template_form_view"/>
|
||||
<field
|
||||
name="priority"
|
||||
>100</field> <!-- when you replace a field, it's always better to inherit at the end -->
|
||||
<field name="inherit_id" ref="account.product_template_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="property_account_income_id" position="attributes">
|
||||
<attribute name="groups">account.group_account_invoice</attribute>
|
||||
@@ -27,9 +28,14 @@ Here, we set all those fields on account.group_account_invoice
|
||||
</field>
|
||||
<field name="list_price" position="replace">
|
||||
<div name="list_price">
|
||||
<field name="list_price" widget='monetary' options="{'currency_field': 'currency_id'}" class="oe_inline"/>
|
||||
<label for="sale_price_type" string=" "/>
|
||||
<field name="sale_price_type"/>
|
||||
<field
|
||||
name="list_price"
|
||||
widget='monetary'
|
||||
options="{'currency_field': 'currency_id', 'field_digits': True}"
|
||||
class="oe_inline"
|
||||
/>
|
||||
<label for="sale_price_type" string=" " />
|
||||
<field name="sale_price_type" />
|
||||
</div>
|
||||
</field>
|
||||
</field>
|
||||
@@ -38,7 +44,7 @@ Here, we set all those fields on account.group_account_invoice
|
||||
<record id="view_category_property_form" model="ir.ui.view">
|
||||
<field name="name">account_usability.product.category.form</field>
|
||||
<field name="model">product.category</field>
|
||||
<field name="inherit_id" ref="account.view_category_property_form"/>
|
||||
<field name="inherit_id" ref="account.view_category_property_form" />
|
||||
<field name="arch" type="xml">
|
||||
<group name="account_property" position="attributes">
|
||||
<attribute name="groups">account.group_account_invoice</attribute>
|
||||
@@ -49,10 +55,10 @@ Here, we set all those fields on account.group_account_invoice
|
||||
<record id="product_supplierinfo_form_view" model="ir.ui.view">
|
||||
<field name="name">account_usability.product.supplierinfo.form</field>
|
||||
<field name="model">product.supplierinfo</field>
|
||||
<field name="inherit_id" ref="product.product_supplierinfo_form_view"/>
|
||||
<field name="inherit_id" ref="product.product_supplierinfo_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="currency_id" position="after">
|
||||
<field name="purchase_price_type"/>
|
||||
<field name="purchase_price_type" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -60,10 +66,10 @@ Here, we set all those fields on account.group_account_invoice
|
||||
<record id="product_supplierinfo_tree_view" model="ir.ui.view">
|
||||
<field name="name">account_usability.product.supplierinfo.tree</field>
|
||||
<field name="model">product.supplierinfo</field>
|
||||
<field name="inherit_id" ref="product.product_supplierinfo_tree_view"/>
|
||||
<field name="inherit_id" ref="product.product_supplierinfo_tree_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="price" position="after">
|
||||
<field name="purchase_price_type" string="Tax"/>
|
||||
<field name="purchase_price_type" string="Tax" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">account_usability account config page</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
|
||||
<field name="inherit_id" ref="account.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='bank_cash']" position="inside">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" id="transfer_account">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_left_pane" />
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="transfer_account_id"/>
|
||||
<label for="transfer_account_id" />
|
||||
<div class="text-muted">
|
||||
Transit account when you transfer money from a bank account of your company to another bank account of your company.
|
||||
</div>
|
||||
<field name="transfer_account_id"/>
|
||||
<field name="transfer_account_id" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2017-2020 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
<record id="view_partner_property_form" model="ir.ui.view">
|
||||
<field name="name">account_usability.res.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="property_account_position_id" position="attributes">
|
||||
<attribute name="widget">selection</attribute>
|
||||
</field>
|
||||
<xpath expr="//field[@name='bank_ids']/tree/field[@name='acc_number']" position="after">
|
||||
<field name="acc_type"/>
|
||||
<xpath
|
||||
expr="//field[@name='bank_ids']/tree/field[@name='acc_number']"
|
||||
position="after"
|
||||
>
|
||||
<field name="acc_type" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -2,58 +2,66 @@
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountGroupGenerate(models.TransientModel):
|
||||
_name = 'account.group.generate'
|
||||
_description = 'Generate Account Groups'
|
||||
_name = "account.group.generate"
|
||||
_description = "Generate Account Groups"
|
||||
|
||||
name_prefix = fields.Char(string='Prefix', required=True, default='Comptes')
|
||||
name_prefix = fields.Char(string="Prefix", required=True, default="Comptes")
|
||||
level = fields.Integer(default=2, required=True)
|
||||
|
||||
def run(self):
|
||||
if self.level < 1:
|
||||
raise UserError(_("The level must be >= 1."))
|
||||
ago = self.env['account.group']
|
||||
aao = self.env['account.account']
|
||||
ago = self.env["account.group"]
|
||||
aao = self.env["account.account"]
|
||||
company = self.env.company
|
||||
groups = ago.search([('company_id', '=', company.id)])
|
||||
groups = ago.search([("company_id", "=", company.id)])
|
||||
if groups:
|
||||
raise UserError(_(
|
||||
"%d account groups already exists in company '%s'. This wizard is "
|
||||
"designed to generate account groups from scratch.")
|
||||
% (len(groups), company.display_name))
|
||||
accounts = aao.search([('company_id', '=', company.id)])
|
||||
struct = {'childs': {}}
|
||||
raise UserError(
|
||||
_(
|
||||
"%d account groups already exists in company '%s'. This wizard is "
|
||||
"designed to generate account groups from scratch."
|
||||
)
|
||||
% (len(groups), company.display_name)
|
||||
)
|
||||
accounts = aao.search([("company_id", "=", company.id)])
|
||||
struct = {"childs": {}}
|
||||
for account in accounts:
|
||||
if len(account.code) <= self.level:
|
||||
raise UserError(_(
|
||||
"The code of account '%s' is %d caracters. "
|
||||
"It cannot be inferior to level (%d).")
|
||||
% (account.display_name, len(account.code), self.level))
|
||||
raise UserError(
|
||||
_(
|
||||
"The code of account '%s' is %d caracters. "
|
||||
"It cannot be inferior to level (%d)."
|
||||
)
|
||||
% (account.display_name, len(account.code), self.level)
|
||||
)
|
||||
n = 1
|
||||
parent = struct
|
||||
gparent = False
|
||||
while n <= self.level:
|
||||
group_code = account.code[:n]
|
||||
if group_code not in parent['childs']:
|
||||
new_group = ago.create({
|
||||
'name': '%s %s' % (self.name_prefix or '', group_code),
|
||||
'code_prefix_start': group_code,
|
||||
'parent_id': gparent and gparent.id or False,
|
||||
'company_id': company.id,
|
||||
})
|
||||
parent['childs'][group_code] = {'obj': new_group, 'childs': {}}
|
||||
parent = parent['childs'][group_code]
|
||||
gparent = parent['obj']
|
||||
if group_code not in parent["childs"]:
|
||||
new_group = ago.create(
|
||||
{
|
||||
"name": "%s %s" % (self.name_prefix or "", group_code),
|
||||
"code_prefix_start": group_code,
|
||||
"parent_id": gparent and gparent.id or False,
|
||||
"company_id": company.id,
|
||||
}
|
||||
)
|
||||
parent["childs"][group_code] = {"obj": new_group, "childs": {}}
|
||||
parent = parent["childs"][group_code]
|
||||
gparent = parent["obj"]
|
||||
n += 1
|
||||
account.write({'group_id': gparent.id})
|
||||
account.write({"group_id": gparent.id})
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Account Groups'),
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.group',
|
||||
}
|
||||
"type": "ir.actions.act_window",
|
||||
"name": _("Account Groups"),
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "account.group",
|
||||
}
|
||||
return action
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="account_group_generate_form" model="ir.ui.view">
|
||||
<field name="name">account.group.generate.form</field>
|
||||
<field name="model">account.group.generate</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="arch" type="xml">
|
||||
<form string="Generate account groups">
|
||||
<p>
|
||||
This wizard is designed to auto-generate account groups from the chart of account.
|
||||
</p>
|
||||
<group name="main">
|
||||
<field name="name_prefix"/>
|
||||
<field name="level"/>
|
||||
<field name="name_prefix" />
|
||||
<field name="level" />
|
||||
</group>
|
||||
<footer>
|
||||
<button type="object" name="run" string="Generate" class="btn-primary"/>
|
||||
<button special="cancel" string="Cancel"/>
|
||||
<button
|
||||
type="object"
|
||||
name="run"
|
||||
string="Generate"
|
||||
class="btn-primary"
|
||||
/>
|
||||
<button special="cancel" string="Cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
@@ -34,9 +38,11 @@
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="account_group_generate_menu"
|
||||
<menuitem
|
||||
id="account_group_generate_menu"
|
||||
action="account_group_generate_action"
|
||||
parent="account.account_account_menu"
|
||||
sequence="51"/>
|
||||
sequence="51"
|
||||
/>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -2,23 +2,29 @@
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models
|
||||
import logging
|
||||
|
||||
from odoo import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountInvoiceMarkSent(models.TransientModel):
|
||||
_name = 'account.invoice.mark.sent'
|
||||
_description = 'Mark invoices as sent'
|
||||
_name = "account.invoice.mark.sent"
|
||||
_description = "Mark invoices as sent"
|
||||
|
||||
def run(self):
|
||||
assert self.env.context.get('active_model') == 'account.move',\
|
||||
'Source model must be invoices'
|
||||
assert self.env.context.get('active_ids'), 'No invoices selected'
|
||||
invoices = self.env['account.move'].search([
|
||||
('id', 'in', self.env.context.get('active_ids')),
|
||||
('move_type', 'in', ('out_invoice', 'out_refund')),
|
||||
('state', '=', 'posted')])
|
||||
invoices.write({'is_move_sent': True})
|
||||
logger.info('Marking invoices with ID %s as sent', invoices.ids)
|
||||
assert (
|
||||
self.env.context.get("active_model") == "account.move"
|
||||
), "Source model must be invoices"
|
||||
assert self.env.context.get("active_ids"), "No invoices selected"
|
||||
invoices = self.env["account.move"].search(
|
||||
[
|
||||
("id", "in", self.env.context.get("active_ids")),
|
||||
("move_type", "in", ("out_invoice", "out_refund")),
|
||||
("state", "=", "posted"),
|
||||
]
|
||||
)
|
||||
invoices.write({"is_move_sent": True})
|
||||
logger.info("Marking invoices with ID %s as sent", invoices.ids)
|
||||
return
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2017-2020 Akretion France
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="account_invoice_mark_sent_form" model="ir.ui.view">
|
||||
<field name="name">account.invoice.mark.sent.form</field>
|
||||
<field name="model">account.invoice.mark.sent</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mark invoices as sent">
|
||||
<p>
|
||||
This wizard will mark as <i>sent</i> all the selected invoices in open or paid state.
|
||||
This wizard will mark as <i
|
||||
>sent</i> all the selected invoices in open or paid state.
|
||||
</p>
|
||||
<footer>
|
||||
<button type="object" name="run" string="Mark as Sent" class="btn-primary"/>
|
||||
<button special="cancel" string="Cancel"/>
|
||||
<button
|
||||
type="object"
|
||||
name="run"
|
||||
string="Mark as Sent"
|
||||
class="btn-primary"
|
||||
/>
|
||||
<button special="cancel" string="Cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountMoveReversal(models.TransientModel):
|
||||
_inherit = 'account.move.reversal'
|
||||
_inherit = "account.move.reversal"
|
||||
|
||||
@api.model
|
||||
def _default_date(self):
|
||||
date_dt = None
|
||||
if (
|
||||
self._context.get('active_model') == 'account.move' and
|
||||
self._context.get('active_id')):
|
||||
move = self.env['account.move'].browse(self._context['active_id'])
|
||||
if self._context.get("active_model") == "account.move" and self._context.get(
|
||||
"active_id"
|
||||
):
|
||||
move = self.env["account.move"].browse(self._context["active_id"])
|
||||
date_dt = move.date + relativedelta(days=1)
|
||||
return date_dt
|
||||
|
||||
|
||||
22
account_usability/wizard/account_payment_register_views.xml
Normal file
22
account_usability/wizard/account_payment_register_views.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2021 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<!-- When you change the date, it resets the amount via the onchange
|
||||
So, in the view, the date should be BEFORE the amount -->
|
||||
<record id="view_account_payment_register_form" model="ir.ui.view">
|
||||
<field name="model">account.payment.register</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_register_form" />
|
||||
<field name="arch" type="xml">
|
||||
<label for="amount" position="before">
|
||||
<field name="payment_date" position="move" />
|
||||
</label>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
@@ -6,7 +6,8 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
transfer_account_id = fields.Many2one(
|
||||
related='company_id.transfer_account_id', readonly=False)
|
||||
related="company_id.transfer_account_id", readonly=False
|
||||
)
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Base Company Extension',
|
||||
'version': '14.0.1.0.0',
|
||||
'category': 'Partner',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Adds capital and title on company',
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
"name": "Base Company Extension",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Partner",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Adds capital and title on company",
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
# I depend on base_usability only for _report_company_legal_name()
|
||||
'depends': ['base_usability'],
|
||||
'data': ['company_view.xml'],
|
||||
'installable': True,
|
||||
"depends": ["base_usability"],
|
||||
"data": ["company_view.xml"],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -2,27 +2,30 @@
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, fields
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
capital_amount = fields.Monetary(string='Capital Amount')
|
||||
capital_amount = fields.Monetary(string="Capital Amount")
|
||||
# in v9, title is only for contacts, not for companies
|
||||
legal_type = fields.Char(
|
||||
string="Legal Type", help="Type of Company, e.g. SARL, SAS, ...")
|
||||
string="Legal Type", help="Type of Company, e.g. SARL, SAS, ..."
|
||||
)
|
||||
|
||||
def _report_company_legal_name(self):
|
||||
self.ensure_one()
|
||||
if self.legal_type:
|
||||
name = '%s %s' % (self.name, self.legal_type)
|
||||
name = "%s %s" % (self.name, self.legal_type)
|
||||
else:
|
||||
name = self.name
|
||||
return name
|
||||
|
||||
_sql_constraints = [(
|
||||
'capital_amount_positive',
|
||||
'CHECK (capital_amount >= 0)',
|
||||
"The value of the field 'Capital Amount' must be positive."
|
||||
)]
|
||||
_sql_constraints = [
|
||||
(
|
||||
"capital_amount_positive",
|
||||
"CHECK (capital_amount >= 0)",
|
||||
"The value of the field 'Capital Amount' must be positive.",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2014-2020 Akretion (http://www.akretion.com/)
|
||||
@author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_company_form" model="ir.ui.view">
|
||||
@@ -13,8 +12,8 @@
|
||||
<field name="inherit_id" ref="base.view_company_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_registry" position="after">
|
||||
<field name="capital_amount"/>
|
||||
<field name="legal_type"/>
|
||||
<field name="capital_amount" />
|
||||
<field name="legal_type" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Base Dynamic List',
|
||||
'version': '14.0.1.0.0',
|
||||
'category': 'Tools',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Dynamic lists',
|
||||
'description': """
|
||||
"name": "Base Dynamic List",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Tools",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Dynamic lists",
|
||||
"description": """
|
||||
Base Dynamic List
|
||||
=================
|
||||
|
||||
@@ -51,12 +51,12 @@ parent="parent_menu_xmlid"/>
|
||||
|
||||
Limitation: when you want to have different access rights on these lists depending on the source object, you should prefer to use dedicated objects.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['base'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/dynamic_list.xml',
|
||||
],
|
||||
'installable': True,
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": ["base"],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"views/dynamic_list.xml",
|
||||
],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -6,110 +6,96 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class DynamicList(models.Model):
|
||||
_name = 'dynamic.list'
|
||||
_description = 'Dynamic List (non translatable)'
|
||||
_order = 'sequence, id'
|
||||
_name = "dynamic.list"
|
||||
_description = "Dynamic List (non translatable)"
|
||||
_order = "sequence, id"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True, index=True)
|
||||
domain = fields.Selection([], string="Domain", required=True, index=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_name_uniq',
|
||||
'unique(domain, name)',
|
||||
'This entry already exists!'
|
||||
)]
|
||||
_sql_constraint = [
|
||||
("domain_name_uniq", "unique(domain, name)", "This entry already exists!")
|
||||
]
|
||||
|
||||
|
||||
class DynamicListTranslate(models.Model):
|
||||
_name = 'dynamic.list.translate'
|
||||
_description = 'Translatable Dynamic List'
|
||||
_order = 'sequence, id'
|
||||
_name = "dynamic.list.translate"
|
||||
_description = "Translatable Dynamic List"
|
||||
_order = "sequence, id"
|
||||
|
||||
name = fields.Char(translate=True, required=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True, index=True)
|
||||
domain = fields.Selection([], string="Domain", required=True, index=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_name_uniq',
|
||||
'unique(domain, name)',
|
||||
'This entry already exists!'
|
||||
)]
|
||||
_sql_constraint = [
|
||||
("domain_name_uniq", "unique(domain, name)", "This entry already exists!")
|
||||
]
|
||||
|
||||
|
||||
class DynamicListCode(models.Model):
|
||||
_name = 'dynamic.list.code'
|
||||
_description = 'Dynamic list with code'
|
||||
_order = 'sequence, id'
|
||||
_name = "dynamic.list.code"
|
||||
_description = "Dynamic list with code"
|
||||
_order = "sequence, id"
|
||||
|
||||
code = fields.Char(required=True)
|
||||
name = fields.Char(translate=True, required=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True, index=True)
|
||||
domain = fields.Selection([], string="Domain", required=True, index=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_code_uniq',
|
||||
'unique(domain, code)',
|
||||
'This code already exists!'
|
||||
)]
|
||||
_sql_constraint = [
|
||||
("domain_code_uniq", "unique(domain, code)", "This code already exists!")
|
||||
]
|
||||
|
||||
@api.depends('code', 'name')
|
||||
@api.depends("code", "name")
|
||||
def name_get(self):
|
||||
res = []
|
||||
for rec in self:
|
||||
res.append((rec.id, '[%s] %s' % (rec.code, rec.name)))
|
||||
res.append((rec.id, "[%s] %s" % (rec.code, rec.name)))
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def name_search(
|
||||
self, name='', args=None, operator='ilike', limit=80):
|
||||
def name_search(self, name="", args=None, operator="ilike", limit=80):
|
||||
if args is None:
|
||||
args = []
|
||||
if name and operator == 'ilike':
|
||||
recs = self.search(
|
||||
[('code', '=', name)] + args, limit=limit)
|
||||
if name and operator == "ilike":
|
||||
recs = self.search([("code", "=", name)] + args, limit=limit)
|
||||
if recs:
|
||||
return recs.name_get()
|
||||
return super().name_search(
|
||||
name=name, args=args, operator=operator, limit=limit)
|
||||
return super().name_search(name=name, args=args, operator=operator, limit=limit)
|
||||
|
||||
|
||||
class DynamicListCodeTranslate(models.Model):
|
||||
_name = 'dynamic.list.code.translate'
|
||||
_description = 'Translatable dynamic list with code'
|
||||
_order = 'sequence, id'
|
||||
_name = "dynamic.list.code.translate"
|
||||
_description = "Translatable dynamic list with code"
|
||||
_order = "sequence, id"
|
||||
|
||||
code = fields.Char(required=True)
|
||||
name = fields.Char(translate=True, required=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
active = fields.Boolean(default=True)
|
||||
domain = fields.Selection([], string='Domain', required=True, index=True)
|
||||
domain = fields.Selection([], string="Domain", required=True, index=True)
|
||||
|
||||
_sql_constraint = [(
|
||||
'domain_code_uniq',
|
||||
'unique(domain, code)',
|
||||
'This code already exists!'
|
||||
)]
|
||||
_sql_constraint = [
|
||||
("domain_code_uniq", "unique(domain, code)", "This code already exists!")
|
||||
]
|
||||
|
||||
@api.depends('code', 'name')
|
||||
@api.depends("code", "name")
|
||||
def name_get(self):
|
||||
res = []
|
||||
for rec in self:
|
||||
res.append((rec.id, '[%s] %s' % (rec.code, rec.name)))
|
||||
res.append((rec.id, "[%s] %s" % (rec.code, rec.name)))
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def name_search(
|
||||
self, name='', args=None, operator='ilike', limit=80):
|
||||
def name_search(self, name="", args=None, operator="ilike", limit=80):
|
||||
if args is None:
|
||||
args = []
|
||||
if name and operator == 'ilike':
|
||||
recs = self.search(
|
||||
[('code', '=', name)] + args, limit=limit)
|
||||
if name and operator == "ilike":
|
||||
recs = self.search([("code", "=", name)] + args, limit=limit)
|
||||
if recs:
|
||||
return recs.name_get()
|
||||
return super().name_search(
|
||||
name=name, args=args, operator=operator, limit=limit)
|
||||
return super().name_search(name=name, args=args, operator=operator, limit=limit)
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2020-2021 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
<menuitem id="dynamic_list_root_menu" name="Dynamic Lists" parent="base.menu_custom" sequence="100"/>
|
||||
<menuitem
|
||||
id="dynamic_list_root_menu"
|
||||
name="Dynamic Lists"
|
||||
parent="base.menu_custom"
|
||||
sequence="100"
|
||||
/>
|
||||
|
||||
<record id="dynamic_list_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Archived"
|
||||
bg_color="bg-danger"
|
||||
attrs="{'invisible': [('active', '=', True)]}"
|
||||
/>
|
||||
<group name="main">
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_main_view')"
|
||||
/>
|
||||
<field name="active" invisible="1" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
@@ -30,9 +42,12 @@
|
||||
<field name="model">dynamic.list</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/>
|
||||
<field name="sequence" widget="handle" />
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_main_view')"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -41,11 +56,19 @@
|
||||
<field name="model">dynamic.list</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<field name="name" />
|
||||
<separator />
|
||||
<filter
|
||||
string="Archived"
|
||||
name="inactive"
|
||||
domain="[('active', '=', False)]"
|
||||
/>
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
<filter
|
||||
name="domain_groupby"
|
||||
string="Domain"
|
||||
context="{'group_by': 'domain'}"
|
||||
/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -55,21 +78,36 @@
|
||||
<field name="name">Simple List</field>
|
||||
<field name="res_model">dynamic.list</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
<field
|
||||
name="context"
|
||||
>{'dynamic_list_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_menu" action="dynamic_list_action" parent="dynamic_list_root_menu" sequence="10"/>
|
||||
<menuitem
|
||||
id="dynamic_list_menu"
|
||||
action="dynamic_list_action"
|
||||
parent="dynamic_list_root_menu"
|
||||
sequence="10"
|
||||
/>
|
||||
|
||||
<record id="dynamic_list_translate_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Archived"
|
||||
bg_color="bg-danger"
|
||||
attrs="{'invisible': [('active', '=', True)]}"
|
||||
/>
|
||||
<group name="main">
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_translate_main_view')"
|
||||
/>
|
||||
<field name="active" invisible="1" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
@@ -80,9 +118,12 @@
|
||||
<field name="model">dynamic.list.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/>
|
||||
<field name="sequence" widget="handle" />
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_translate_main_view')"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -91,11 +132,19 @@
|
||||
<field name="model">dynamic.list.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<field name="name" />
|
||||
<separator />
|
||||
<filter
|
||||
string="Archived"
|
||||
name="inactive"
|
||||
domain="[('active', '=', False)]"
|
||||
/>
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
<filter
|
||||
name="domain_groupby"
|
||||
string="Domain"
|
||||
context="{'group_by': 'domain'}"
|
||||
/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -105,22 +154,37 @@
|
||||
<field name="name">Translatable Simple List</field>
|
||||
<field name="res_model">dynamic.list.translate</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_translate_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
<field
|
||||
name="context"
|
||||
>{'dynamic_list_translate_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_translate_menu" action="dynamic_list_translate_action" parent="dynamic_list_root_menu" sequence="20"/>
|
||||
<menuitem
|
||||
id="dynamic_list_translate_menu"
|
||||
action="dynamic_list_translate_action"
|
||||
parent="dynamic_list_root_menu"
|
||||
sequence="20"
|
||||
/>
|
||||
|
||||
<record id="dynamic_list_code_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Archived"
|
||||
bg_color="bg-danger"
|
||||
attrs="{'invisible': [('active', '=', True)]}"
|
||||
/>
|
||||
<group name="main">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" />
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_code_main_view')"
|
||||
/>
|
||||
<field name="active" invisible="1" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
@@ -131,10 +195,13 @@
|
||||
<field name="model">dynamic.list.code</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/>
|
||||
<field name="sequence" widget="handle" />
|
||||
<field name="code" />
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_code_main_view')"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -143,12 +210,24 @@
|
||||
<field name="model">dynamic.list.code</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<field name="code"/>
|
||||
<field
|
||||
name="name"
|
||||
string="Name or Code"
|
||||
filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"
|
||||
/>
|
||||
<separator />
|
||||
<filter
|
||||
string="Archived"
|
||||
name="inactive"
|
||||
domain="[('active', '=', False)]"
|
||||
/>
|
||||
<field name="code" />
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
<filter
|
||||
name="domain_groupby"
|
||||
string="Domain"
|
||||
context="{'group_by': 'domain'}"
|
||||
/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -158,22 +237,37 @@
|
||||
<field name="name">Code List</field>
|
||||
<field name="res_model">dynamic.list.code</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_code_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
<field
|
||||
name="context"
|
||||
>{'dynamic_list_code_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_code_menu" action="dynamic_list_code_action" parent="dynamic_list_root_menu" sequence="30"/>
|
||||
<menuitem
|
||||
id="dynamic_list_code_menu"
|
||||
action="dynamic_list_code_action"
|
||||
parent="dynamic_list_root_menu"
|
||||
sequence="30"
|
||||
/>
|
||||
|
||||
<record id="dynamic_list_code_translate_form" model="ir.ui.view">
|
||||
<field name="model">dynamic.list.code.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Archived"
|
||||
bg_color="bg-danger"
|
||||
attrs="{'invisible': [('active', '=', True)]}"
|
||||
/>
|
||||
<group name="main">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" />
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_code_translate_main_view')"
|
||||
/>
|
||||
<field name="active" invisible="1" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
@@ -184,10 +278,13 @@
|
||||
<field name="model">dynamic.list.code.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/>
|
||||
<field name="sequence" widget="handle" />
|
||||
<field name="code" />
|
||||
<field name="name" />
|
||||
<field
|
||||
name="domain"
|
||||
invisible="not context.get('dynamic_list_code_translate_main_view')"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -196,12 +293,24 @@
|
||||
<field name="model">dynamic.list.code.translate</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/>
|
||||
<field name="code"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<field
|
||||
name="name"
|
||||
string="Name or Code"
|
||||
filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"
|
||||
/>
|
||||
<field name="code" />
|
||||
<separator />
|
||||
<filter
|
||||
string="Archived"
|
||||
name="inactive"
|
||||
domain="[('active', '=', False)]"
|
||||
/>
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/>
|
||||
<filter
|
||||
name="domain_groupby"
|
||||
string="Domain"
|
||||
context="{'group_by': 'domain'}"
|
||||
/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -211,10 +320,17 @@
|
||||
<field name="name">Translatable Code List</field>
|
||||
<field name="res_model">dynamic.list.code.translate</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'dynamic_list_code_translate_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
<field
|
||||
name="context"
|
||||
>{'dynamic_list_code_translate_main_view': True, 'search_default_domain_groupby': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="dynamic_list_code_translate_menu" action="dynamic_list_code_translate_action" parent="dynamic_list_root_menu" sequence="40"/>
|
||||
<menuitem
|
||||
id="dynamic_list_code_translate_menu"
|
||||
action="dynamic_list_code_translate_action"
|
||||
parent="dynamic_list_root_menu"
|
||||
sequence="40"
|
||||
/>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Base Partner One2many Phone',
|
||||
'version': '14.0.1.0.0',
|
||||
'category': 'Phone',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'One2many link between partners and phone numbers/emails',
|
||||
'description': """
|
||||
"name": "Base Partner One2many Phone",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Phone",
|
||||
"license": "AGPL-3",
|
||||
"summary": "One2many link between partners and phone numbers/emails",
|
||||
"description": """
|
||||
Base Partner One2many Phone
|
||||
===========================
|
||||
|
||||
@@ -18,14 +18,14 @@ With this module, one partner can have several phone numbers and several emails.
|
||||
|
||||
It has been developped by brother Bernard from Barroux Abbey and Alexis de Lattre from Akretion.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'https://akretion.com/',
|
||||
'depends': ['contacts', 'base_usability', 'phone_validation'],
|
||||
'excludes': ['sms'], # because sms introduces big changes in partner form view
|
||||
'data': [
|
||||
'partner_phone_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'post_init_hook': 'migrate_to_partner_phone',
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": ["contacts", "base_usability", "phone_validation"],
|
||||
"excludes": ["sms"], # because sms introduces big changes in partner form view
|
||||
"data": [
|
||||
"partner_phone_view.xml",
|
||||
"security/ir.model.access.csv",
|
||||
],
|
||||
"installable": True,
|
||||
"post_init_hook": "migrate_to_partner_phone",
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * base_partner_one2many_phone
|
||||
# * base_partner_one2many_phone
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.0\n"
|
||||
"Project-Id-Version: Odoo Server 14.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-01-27 18:03+0000\n"
|
||||
"PO-Revision-Date: 2020-01-27 18:03+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"POT-Creation-Date: 2021-10-29 21:12+0000\n"
|
||||
"PO-Revision-Date: 2021-10-29 21:12+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -16,92 +16,120 @@ msgstr ""
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_uid
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner
|
||||
msgid "Contact"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__create_uid
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_date
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__create_date
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_display_name
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__display_name
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_email
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__email
|
||||
msgid "E-Mail"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:61
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgid ""
|
||||
"E-mail field must be empty when type is Primary/Secondary Phone, "
|
||||
"Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:51
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "E-mail field must have a value when type is Primary E-mail or Secondary E-mail."
|
||||
msgid ""
|
||||
"E-mail field must have a value when type is Primary E-mail or Secondary "
|
||||
"E-mail."
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_id
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__email
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__email
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__id
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone___last_update
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner____last_update
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_uid
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_date
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_note
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__mobile
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__mobile
|
||||
msgid "Mobile"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner_phone
|
||||
msgid "Multiple emails and phones for partners"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__note
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner
|
||||
msgid "Partner"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__phone
|
||||
msgid "Phone"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:54
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_form
|
||||
msgid "Phone and E-mail"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "Phone field must be empty when type is Primary E-mail or Secondary E-mail."
|
||||
msgid ""
|
||||
"Phone field must be empty when type is Primary E-mail or Secondary E-mail."
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:58
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgid ""
|
||||
"Phone field must have a value when type is Primary/Secondary Phone, "
|
||||
"Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_ids
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users_phone_ids
|
||||
msgid "Phones"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_tree
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_tree
|
||||
msgid "Phones and E-mail"
|
||||
msgstr ""
|
||||
|
||||
@@ -112,63 +140,63 @@ msgid "Phones/E-mails"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__phone_ids
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__phone_ids
|
||||
msgid "Phones/Emails"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__1_email_primary
|
||||
msgid "Primary E-mail"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__7_fax_primary
|
||||
msgid "Primary Fax"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__5_mobile_primary
|
||||
msgid "Primary Mobile"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__3_phone_primary
|
||||
msgid "Primary Phone"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_partner_id
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__partner_id
|
||||
msgid "Related Partner"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
msgid "Search Phones/E-mail"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__2_email_secondary
|
||||
msgid "Secondary E-mail"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__8_fax_secondary
|
||||
msgid "Secondary Fax"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__6_mobile_secondary
|
||||
msgid "Secondary Mobile"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__4_phone_secondary
|
||||
msgid "Secondary Phone"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_type
|
||||
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__type
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner_phone
|
||||
msgid "res.partner.phone"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * base_partner_one2many_phone
|
||||
# * base_partner_one2many_phone
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.0\n"
|
||||
"Project-Id-Version: Odoo Server 14.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-01-27 17:56+0000\n"
|
||||
"PO-Revision-Date: 2020-01-27 17:56+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"POT-Creation-Date: 2021-10-29 21:12+0000\n"
|
||||
"PO-Revision-Date: 2021-10-29 21:12+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -16,159 +16,187 @@ msgstr ""
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_uid
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner
|
||||
msgid "Contact"
|
||||
msgstr "Contact"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Créé par"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_create_date
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__create_date
|
||||
msgid "Created on"
|
||||
msgstr "Créé le"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_display_name
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__display_name
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Nom à afficher"
|
||||
msgstr "Nom affiché"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_email
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__email
|
||||
msgid "E-Mail"
|
||||
msgstr "Courriel"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:61
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgstr "Le champ courriel doit être vide quand le type est tél. primaire/secondaire, portable primaire/secondaire ou fax primaire/secondaire."
|
||||
msgid ""
|
||||
"E-mail field must be empty when type is Primary/Secondary Phone, "
|
||||
"Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgstr "Le champ E-mail doit être vide quand le type est Tél. principal/secondaire, Portable principal/secondaire ou Fax principal/secondaire."
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:51
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "E-mail field must have a value when type is Primary E-mail or Secondary E-mail."
|
||||
msgstr "Le champ courriel doit être renseigné quand le type est courriel primaire ou courriel secondaire."
|
||||
msgid ""
|
||||
"E-mail field must have a value when type is Primary E-mail or Secondary "
|
||||
"E-mail."
|
||||
msgstr "Le champ E-mail doit avoir une valeur quand le type est E-mail principal ou secondaire."
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_id
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__email
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__email
|
||||
msgid "Email"
|
||||
msgstr "E-mail"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__id
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone___last_update
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner____last_update
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Dernière modification le"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_uid
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Dernière mise à jour par"
|
||||
msgstr "Dernière modification par"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_write_date
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Dernière mise à jour le"
|
||||
msgstr "Dernière modification le"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_note
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__mobile
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__mobile
|
||||
msgid "Mobile"
|
||||
msgstr "Portable"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner_phone
|
||||
msgid "Multiple emails and phones for partners"
|
||||
msgstr "Multiples e-mails et téléphones pour les partenaires"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__note
|
||||
msgid "Note"
|
||||
msgstr "Note"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner
|
||||
msgid "Partner"
|
||||
msgstr "Partenaire"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__phone
|
||||
msgid "Phone"
|
||||
msgstr "Téléphone"
|
||||
msgstr "Tél."
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:54
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_form
|
||||
msgid "Phone and E-mail"
|
||||
msgstr "Tél. et E-mail"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "Phone field must be empty when type is Primary E-mail or Secondary E-mail."
|
||||
msgstr "Le champ téléphone doit être vide quand le type est courriel primaire ou courriel secondaire."
|
||||
msgid ""
|
||||
"Phone field must be empty when type is Primary E-mail or Secondary E-mail."
|
||||
msgstr "Le champ Tél. doit être vide quand le type est E-mail principal ou E-mail secondaire."
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:58
|
||||
#: code:addons/base_partner_one2many_phone/partner_phone.py:0
|
||||
#, python-format
|
||||
msgid "Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgstr "Le champ téléphone doit être renseigné quand le type est tél. primaire/secondaire, portable primaire/secondaire ou fax primaire/secondaire.."
|
||||
msgid ""
|
||||
"Phone field must have a value when type is Primary/Secondary Phone, "
|
||||
"Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
msgstr "Le champ Tél. doit avoir une valeur quand le type est Tél. principal/secondaire, Portable principal/secondaire ou Fax principal/secondaire."
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_ids
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users_phone_ids
|
||||
msgid "Phones"
|
||||
msgstr "Téléphones"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_tree
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_tree
|
||||
msgid "Phones and E-mail"
|
||||
msgstr "Téls et courriels"
|
||||
msgstr "Téls et E-mail"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.actions.act_window,name:base_partner_one2many_phone.res_partner_phone_action
|
||||
#: model:ir.ui.menu,name:base_partner_one2many_phone.res_partner_phone_menu
|
||||
msgid "Phones/E-mails"
|
||||
msgstr "Téls/Courriels"
|
||||
msgstr "Téls/E-mails"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner__phone_ids
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_users__phone_ids
|
||||
msgid "Phones/Emails"
|
||||
msgstr "Téls/E-mails"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__1_email_primary
|
||||
msgid "Primary E-mail"
|
||||
msgstr "Courriel principal"
|
||||
msgstr "E-mail principal"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__7_fax_primary
|
||||
msgid "Primary Fax"
|
||||
msgstr "Fax principal"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__5_mobile_primary
|
||||
msgid "Primary Mobile"
|
||||
msgstr "Portable principal"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__3_phone_primary
|
||||
msgid "Primary Phone"
|
||||
msgstr "Tél principal"
|
||||
msgstr "Tél. principal"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_partner_id
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__partner_id
|
||||
msgid "Related Partner"
|
||||
msgstr "Partenaire associé"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
msgid "Search Phones/E-mail"
|
||||
msgstr "Search Phones/E-mail"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__2_email_secondary
|
||||
msgid "Secondary E-mail"
|
||||
msgstr "Courriel secondaire"
|
||||
msgstr "E-mail secondaire"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__8_fax_secondary
|
||||
msgid "Secondary Fax"
|
||||
msgstr "Fax secondaire"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__6_mobile_secondary
|
||||
msgid "Secondary Mobile"
|
||||
msgstr "Portable secondaire"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: selection:res.partner.phone,type:0
|
||||
#: model:ir.model.fields.selection,name:base_partner_one2many_phone.selection__res_partner_phone__type__4_phone_secondary
|
||||
msgid "Secondary Phone"
|
||||
msgstr "Tél. secondaire"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone_type
|
||||
#: model:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
#: model:ir.model.fields,field_description:base_partner_one2many_phone.field_res_partner_phone__type
|
||||
#: model_terms:ir.ui.view,arch_db:base_partner_one2many_phone.res_partner_phone_search
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#. module: base_partner_one2many_phone
|
||||
#: model:ir.model,name:base_partner_one2many_phone.model_res_partner_phone
|
||||
msgid "res.partner.phone"
|
||||
msgstr "res.partner.phone"
|
||||
|
||||
|
||||
@@ -4,38 +4,50 @@
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
EMAIL_TYPES = ('1_email_primary', '2_email_secondary')
|
||||
PHONE_TYPES = ('3_phone_primary', '4_phone_secondary', '5_mobile_primary', '6_mobile_secondary', '7_fax_primary', '8_fax_secondary')
|
||||
EMAIL_TYPES = ("1_email_primary", "2_email_secondary")
|
||||
PHONE_TYPES = (
|
||||
"3_phone_primary",
|
||||
"4_phone_secondary",
|
||||
"5_mobile_primary",
|
||||
"6_mobile_secondary",
|
||||
"7_fax_primary",
|
||||
"8_fax_secondary",
|
||||
)
|
||||
|
||||
|
||||
class ResPartnerPhone(models.Model):
|
||||
_name = 'res.partner.phone'
|
||||
_order = 'partner_id, type'
|
||||
_name = "res.partner.phone"
|
||||
_order = "partner_id, type"
|
||||
_phone_name_sequence = 8
|
||||
_inherit = ['phone.validation.mixin']
|
||||
_description = 'Multiple emails and phones for partners'
|
||||
_inherit = ["phone.validation.mixin"]
|
||||
_description = "Multiple emails and phones for partners"
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', string='Related Partner', index=True, ondelete='cascade')
|
||||
type = fields.Selection([
|
||||
('1_email_primary', 'Primary E-mail'),
|
||||
('2_email_secondary', 'Secondary E-mail'),
|
||||
('3_phone_primary', 'Primary Phone'),
|
||||
('4_phone_secondary', 'Secondary Phone'),
|
||||
('5_mobile_primary', 'Primary Mobile'),
|
||||
('6_mobile_secondary', 'Secondary Mobile'),
|
||||
('7_fax_primary', 'Primary Fax'),
|
||||
('8_fax_secondary', 'Secondary Fax'),
|
||||
"res.partner", string="Related Partner", index=True, ondelete="cascade"
|
||||
)
|
||||
type = fields.Selection(
|
||||
[
|
||||
("1_email_primary", "Primary E-mail"),
|
||||
("2_email_secondary", "Secondary E-mail"),
|
||||
("3_phone_primary", "Primary Phone"),
|
||||
("4_phone_secondary", "Secondary Phone"),
|
||||
("5_mobile_primary", "Primary Mobile"),
|
||||
("6_mobile_secondary", "Secondary Mobile"),
|
||||
("7_fax_primary", "Primary Fax"),
|
||||
("8_fax_secondary", "Secondary Fax"),
|
||||
],
|
||||
string='Type', required=True, index=True)
|
||||
phone = fields.Char(string='Phone')
|
||||
email = fields.Char(string='E-Mail')
|
||||
note = fields.Char('Note')
|
||||
string="Type",
|
||||
required=True,
|
||||
index=True,
|
||||
)
|
||||
phone = fields.Char(string="Phone")
|
||||
email = fields.Char(string="E-Mail")
|
||||
note = fields.Char("Note")
|
||||
|
||||
@api.onchange('type')
|
||||
@api.onchange("type")
|
||||
def type_change(self):
|
||||
if self.type:
|
||||
if self.type in EMAIL_TYPES:
|
||||
@@ -43,67 +55,89 @@ class ResPartnerPhone(models.Model):
|
||||
elif self.type in PHONE_TYPES:
|
||||
self.email = False
|
||||
|
||||
@api.onchange('phone', 'partner_id')
|
||||
@api.onchange("phone", "partner_id")
|
||||
def _onchange_phone_validation(self):
|
||||
if self.phone:
|
||||
self.phone = self.phone_format(self.phone, country=self.partner_id.country_id)
|
||||
self.phone = self.phone_format(
|
||||
self.phone, country=self.partner_id.country_id
|
||||
)
|
||||
|
||||
@api.constrains('type', 'phone', 'email')
|
||||
@api.constrains("type", "phone", "email")
|
||||
def _check_partner_phone(self):
|
||||
for rec in self:
|
||||
if rec.type in EMAIL_TYPES:
|
||||
if not rec.email:
|
||||
raise ValidationError(_(
|
||||
"E-mail field must have a value when type is Primary E-mail or Secondary E-mail."))
|
||||
raise ValidationError(
|
||||
_(
|
||||
"E-mail field must have a value when type is Primary E-mail or Secondary E-mail."
|
||||
)
|
||||
)
|
||||
if rec.phone:
|
||||
raise ValidationError(_(
|
||||
"Phone field must be empty when type is Primary E-mail or Secondary E-mail."))
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Phone field must be empty when type is Primary E-mail or Secondary E-mail."
|
||||
)
|
||||
)
|
||||
elif rec.type in PHONE_TYPES:
|
||||
if not rec.phone:
|
||||
raise ValidationError(_(
|
||||
"Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."))
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
)
|
||||
)
|
||||
if rec.email:
|
||||
raise ValidationError(_(
|
||||
"E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."))
|
||||
raise ValidationError(
|
||||
_(
|
||||
"E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
|
||||
)
|
||||
)
|
||||
|
||||
def name_get(self):
|
||||
res = []
|
||||
for pphone in self:
|
||||
if pphone.partner_id:
|
||||
if self._context.get('callerid'):
|
||||
if self._context.get("callerid"):
|
||||
name = pphone.partner_id.display_name
|
||||
else:
|
||||
name = u'%s (%s)' % (pphone.phone, pphone.partner_id.name)
|
||||
name = u"%s (%s)" % (pphone.phone, pphone.partner_id.name)
|
||||
else:
|
||||
name = pphone.phone
|
||||
res.append((pphone.id, name))
|
||||
return res
|
||||
|
||||
def init(self):
|
||||
self._cr.execute('''
|
||||
self._cr.execute(
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS single_email_primary
|
||||
ON res_partner_phone (partner_id, type)
|
||||
WHERE (type='1_email_primary')
|
||||
''')
|
||||
self._cr.execute('''
|
||||
"""
|
||||
)
|
||||
self._cr.execute(
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS single_phone_primary
|
||||
ON res_partner_phone (partner_id, type)
|
||||
WHERE (type='3_phone_primary')
|
||||
''')
|
||||
self._cr.execute('''
|
||||
"""
|
||||
)
|
||||
self._cr.execute(
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS single_mobile_primary
|
||||
ON res_partner_phone (partner_id, type)
|
||||
WHERE (type='5_mobile_primary')
|
||||
''')
|
||||
self._cr.execute('''
|
||||
"""
|
||||
)
|
||||
self._cr.execute(
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS single_fax_primary
|
||||
ON res_partner_phone (partner_id, type)
|
||||
WHERE (type='7_fax_primary')
|
||||
''')
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
_inherit = "res.partner"
|
||||
|
||||
# in v10, we are supposed to have in DB E.164 format
|
||||
# with the current implementation, we have:
|
||||
@@ -115,78 +149,85 @@ class ResPartner(models.Model):
|
||||
# for the future :)
|
||||
|
||||
phone_ids = fields.One2many(
|
||||
'res.partner.phone', 'partner_id', string='Phones/Emails')
|
||||
"res.partner.phone", "partner_id", string="Phones/Emails"
|
||||
)
|
||||
phone = fields.Char(
|
||||
compute='_compute_partner_phone',
|
||||
store=True, readonly=True, compute_sudo=True)
|
||||
compute="_compute_partner_phone", store=True, readonly=True, compute_sudo=True
|
||||
)
|
||||
mobile = fields.Char(
|
||||
compute='_compute_partner_phone',
|
||||
store=True, readonly=True, compute_sudo=True)
|
||||
compute="_compute_partner_phone", store=True, readonly=True, compute_sudo=True
|
||||
)
|
||||
email = fields.Char(
|
||||
compute='_compute_partner_phone',
|
||||
store=True, readonly=True, compute_sudo=True)
|
||||
compute="_compute_partner_phone", store=True, readonly=True, compute_sudo=True
|
||||
)
|
||||
|
||||
@api.depends('phone_ids.phone', 'phone_ids.type', 'phone_ids.email')
|
||||
@api.depends("phone_ids.phone", "phone_ids.type", "phone_ids.email")
|
||||
def _compute_partner_phone(self):
|
||||
for partner in self:
|
||||
phone = mobile = email = False
|
||||
for pphone in partner.phone_ids:
|
||||
if pphone.type == '1_email_primary' and pphone.email:
|
||||
if pphone.type == "1_email_primary" and pphone.email:
|
||||
email = pphone.email
|
||||
elif pphone.phone:
|
||||
if pphone.type == '5_mobile_primary':
|
||||
if pphone.type == "5_mobile_primary":
|
||||
mobile = pphone.phone
|
||||
elif pphone.type == '3_phone_primary':
|
||||
elif pphone.type == "3_phone_primary":
|
||||
phone = pphone.phone
|
||||
partner.phone = phone
|
||||
partner.mobile = mobile
|
||||
partner.email = email
|
||||
|
||||
def _update_create_vals(
|
||||
self, vals, type, partner_field, partner_phone_field):
|
||||
def _update_create_vals(self, vals, type, partner_field, partner_phone_field):
|
||||
if vals.get(partner_field):
|
||||
vals['phone_ids'].append(
|
||||
(0, 0, {'type': type, partner_phone_field: vals[partner_field]}))
|
||||
vals["phone_ids"].append(
|
||||
(0, 0, {"type": type, partner_phone_field: vals[partner_field]})
|
||||
)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if 'phone_ids' not in vals:
|
||||
vals['phone_ids'] = []
|
||||
self._update_create_vals(vals, '1_email_primary', 'email', 'email')
|
||||
self._update_create_vals(vals, '3_phone_primary', 'phone', 'phone')
|
||||
self._update_create_vals(vals, '5_mobile_primary', 'mobile', 'phone')
|
||||
if "phone_ids" not in vals:
|
||||
vals["phone_ids"] = []
|
||||
self._update_create_vals(vals, "1_email_primary", "email", "email")
|
||||
self._update_create_vals(vals, "3_phone_primary", "phone", "phone")
|
||||
self._update_create_vals(vals, "5_mobile_primary", "mobile", "phone")
|
||||
# self._update_create_vals(vals, '7_fax_primary', 'fax', 'phone')
|
||||
return super().create(vals)
|
||||
|
||||
def _update_write_vals(
|
||||
self, vals, type, partner_field, partner_phone_field):
|
||||
def _update_write_vals(self, vals, type, partner_field, partner_phone_field):
|
||||
self.ensure_one()
|
||||
rppo = self.env['res.partner.phone']
|
||||
rppo = self.env["res.partner.phone"]
|
||||
if partner_field in vals:
|
||||
pphone = rppo.search([
|
||||
('partner_id', '=', self.id),
|
||||
('type', '=', type)], limit=1)
|
||||
pphone = rppo.search(
|
||||
[("partner_id", "=", self.id), ("type", "=", type)], limit=1
|
||||
)
|
||||
if vals[partner_field]:
|
||||
if pphone:
|
||||
vals['phone_ids'].append((1, pphone.id, {
|
||||
partner_phone_field: vals[partner_field]}))
|
||||
vals["phone_ids"].append(
|
||||
(1, pphone.id, {partner_phone_field: vals[partner_field]})
|
||||
)
|
||||
else:
|
||||
vals['phone_ids'].append((0, 0, {
|
||||
'type': type,
|
||||
partner_phone_field: vals[partner_field],
|
||||
}))
|
||||
vals["phone_ids"].append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"type": type,
|
||||
partner_phone_field: vals[partner_field],
|
||||
},
|
||||
)
|
||||
)
|
||||
else:
|
||||
if pphone:
|
||||
vals['phone_ids'].append((2, pphone.id))
|
||||
vals["phone_ids"].append((2, pphone.id))
|
||||
|
||||
def write(self, vals):
|
||||
if 'phone_ids' not in vals:
|
||||
if "phone_ids" not in vals:
|
||||
for rec in self:
|
||||
vals['phone_ids'] = []
|
||||
rec._update_write_vals(vals, '1_email_primary', 'email', 'email')
|
||||
rec._update_write_vals(vals, '3_phone_primary', 'phone', 'phone')
|
||||
rec._update_write_vals(vals, '5_mobile_primary', 'mobile', 'phone')
|
||||
rec._update_write_vals(vals, '7_fax_primary', 'fax', 'phone')
|
||||
vals["phone_ids"] = []
|
||||
rec._update_write_vals(vals, "1_email_primary", "email", "email")
|
||||
rec._update_write_vals(vals, "3_phone_primary", "phone", "phone")
|
||||
rec._update_write_vals(vals, "5_mobile_primary", "mobile", "phone")
|
||||
rec._update_write_vals(vals, "7_fax_primary", "fax", "phone")
|
||||
super(ResPartner, rec).write(vals)
|
||||
return True
|
||||
else:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2014-2020 Abbaye du Barroux (http://www.barroux.org)
|
||||
Copyright 2016-2020 Akretion (http://www.akretion.com>)
|
||||
@@ -6,20 +6,31 @@
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<!-- Partner phones -->
|
||||
<record id="res_partner_phone_tree" model="ir.ui.view">
|
||||
<field name="name">res.partner.phone.tree</field>
|
||||
<field name="model">res.partner.phone</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Phones and E-mail" editable="bottom">
|
||||
<field name="partner_id" invisible="not context.get('partner_phone_main_view')"/>
|
||||
<field name="type"/>
|
||||
<field name="phone" widget="phone" options="{'enable_sms': false}" attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'readonly': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
|
||||
<field name="email" widget="email" attrs="{'readonly': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
|
||||
<field name="note"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom">
|
||||
<field
|
||||
name="partner_id"
|
||||
invisible="not context.get('partner_phone_main_view')"
|
||||
/>
|
||||
<field name="type" />
|
||||
<field
|
||||
name="phone"
|
||||
widget="phone"
|
||||
options="{'enable_sms': false}"
|
||||
attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'readonly': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
|
||||
/>
|
||||
<field
|
||||
name="email"
|
||||
widget="email"
|
||||
attrs="{'readonly': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
|
||||
/>
|
||||
<field name="note" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -27,14 +38,26 @@
|
||||
<record id="res_partner_phone_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.phone.form</field>
|
||||
<field name="model">res.partner.phone</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Phone and E-mail">
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group name="main">
|
||||
<field name="partner_id" invisible="not context.get('partner_phone_main_view')"/>
|
||||
<field name="type"/>
|
||||
<field name="phone" widget="phone" options="{'enable_sms': false}" attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'invisible': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
|
||||
<field name="email" widget="email" attrs="{'invisible': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/>
|
||||
<field name="note"/>
|
||||
<field
|
||||
name="partner_id"
|
||||
invisible="not context.get('partner_phone_main_view')"
|
||||
/>
|
||||
<field name="type" />
|
||||
<field
|
||||
name="phone"
|
||||
widget="phone"
|
||||
options="{'enable_sms': false}"
|
||||
attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'invisible': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
|
||||
/>
|
||||
<field
|
||||
name="email"
|
||||
widget="email"
|
||||
attrs="{'invisible': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
|
||||
/>
|
||||
<field name="note" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
@@ -43,12 +66,16 @@
|
||||
<record id="res_partner_phone_search" model="ir.ui.view">
|
||||
<field name="name">res.partner.phone.search</field>
|
||||
<field name="model">res.partner.phone</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Phones/E-mail">
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="phone" />
|
||||
<field name="email" />
|
||||
<group name="groupby">
|
||||
<filter name="type_groupby" string="Type" context="{'group_by': 'type'}"/>
|
||||
<filter
|
||||
name="type_groupby"
|
||||
string="Type"
|
||||
context="{'group_by': 'type'}"
|
||||
/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -61,8 +88,12 @@
|
||||
<field name="context">{'partner_phone_main_view': True}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="res_partner_phone_menu" action="res_partner_phone_action"
|
||||
parent="contacts.menu_contacts" sequence="10"/>
|
||||
<menuitem
|
||||
id="res_partner_phone_menu"
|
||||
action="res_partner_phone_action"
|
||||
parent="contacts.menu_contacts"
|
||||
sequence="10"
|
||||
/>
|
||||
|
||||
<record id="contacts.res_partner_menu_config" model="ir.ui.menu">
|
||||
<field name="sequence">20</field>
|
||||
@@ -72,12 +103,12 @@
|
||||
<record id="view_partner_form" model="ir.ui.view">
|
||||
<field name="name">add.phone_ids.on.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="mail.res_partner_view_form_inherit_mail"/>
|
||||
<field name="inherit_id" ref="mail.res_partner_view_form_inherit_mail" />
|
||||
<!-- This module depends on contacts which depends on mail
|
||||
and the mail module replaces the email field -->
|
||||
<field name="arch" type="xml">
|
||||
<field name="arch" type="xml">
|
||||
<field name="phone" position="after">
|
||||
<field name="phone_ids" nolabel="1" colspan="2"/>
|
||||
<field name="phone_ids" nolabel="1" colspan="2" />
|
||||
</field>
|
||||
<field name="phone" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
@@ -101,13 +132,22 @@
|
||||
<field name="phone_ids" nolabel="1" colspan="2" widget="many2many_tags"/>
|
||||
</xpath>
|
||||
-->
|
||||
<xpath expr="//field[@name='child_ids']/form//field[@name='phone']" position="attributes">
|
||||
<xpath
|
||||
expr="//field[@name='child_ids']/form//field[@name='phone']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='child_ids']/form//field[@name='mobile']" position="attributes">
|
||||
<xpath
|
||||
expr="//field[@name='child_ids']/form//field[@name='mobile']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='child_ids']/form//field[@name='email']" position="attributes">
|
||||
<xpath
|
||||
expr="//field[@name='child_ids']/form//field[@name='email']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
@@ -116,10 +156,10 @@
|
||||
<record id="view_partner_simple_form" model="ir.ui.view">
|
||||
<field name="name">add.phone_ids.on.res.partner.simplified.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_simple_form"/>
|
||||
<field name="inherit_id" ref="base.view_partner_simple_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="phone" position="after">
|
||||
<field name="phone_ids" nolabel="1" colspan="2"/>
|
||||
<field name="phone_ids" nolabel="1" colspan="2" />
|
||||
</field>
|
||||
<field name="phone" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
@@ -136,10 +176,10 @@
|
||||
<record id="res_partner_view_form_private" model="ir.ui.view">
|
||||
<field name="name">add.phone_ids.on.res.partner.private.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.res_partner_view_form_private"/>
|
||||
<field name="inherit_id" ref="base.res_partner_view_form_private" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="phone" position="after">
|
||||
<field name="phone_ids" nolabel="1" colspan="2"/>
|
||||
<field name="phone_ids" nolabel="1" colspan="2" />
|
||||
</field>
|
||||
<field name="phone" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
@@ -156,10 +196,12 @@
|
||||
<record id="view_res_partner_filter" model="ir.ui.view">
|
||||
<field name="name">phone.one2many.res.partner.search</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base_usability.view_res_partner_filter"/>
|
||||
<field name="inherit_id" ref="base_usability.view_res_partner_filter" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="attributes">
|
||||
<attribute name="filter_domain">['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute>
|
||||
<attribute
|
||||
name="filter_domain"
|
||||
>['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -2,48 +2,61 @@
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, SUPERUSER_ID
|
||||
import logging
|
||||
|
||||
from odoo import SUPERUSER_ID, api
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_partner_phone(cr, phone_field, phone_type):
|
||||
cr.execute(
|
||||
'SELECT id, ' + phone_field + ' FROM res_partner WHERE ' +
|
||||
phone_field + ' IS NOT null AND ' + phone_field + "!= ''")
|
||||
"SELECT id, "
|
||||
+ phone_field
|
||||
+ " FROM res_partner WHERE "
|
||||
+ phone_field
|
||||
+ " IS NOT null AND "
|
||||
+ phone_field
|
||||
+ "!= ''"
|
||||
)
|
||||
to_create = []
|
||||
for partner in cr.fetchall():
|
||||
to_create.append({
|
||||
'partner_id': partner[0],
|
||||
'type': phone_type,
|
||||
'phone': partner[1],
|
||||
})
|
||||
to_create.append(
|
||||
{
|
||||
"partner_id": partner[0],
|
||||
"type": phone_type,
|
||||
"phone": partner[1],
|
||||
}
|
||||
)
|
||||
return to_create
|
||||
|
||||
|
||||
def create_partner_email(cr):
|
||||
cr.execute(
|
||||
"SELECT id, email FROM res_partner WHERE email IS NOT null AND email != ''")
|
||||
"SELECT id, email FROM res_partner WHERE email IS NOT null AND email != ''"
|
||||
)
|
||||
to_create = []
|
||||
for partner in cr.fetchall():
|
||||
to_create.append({
|
||||
'partner_id': partner[0],
|
||||
'type': '1_email_primary',
|
||||
'email': partner[1],
|
||||
})
|
||||
to_create.append(
|
||||
{
|
||||
"partner_id": partner[0],
|
||||
"type": "1_email_primary",
|
||||
"email": partner[1],
|
||||
}
|
||||
)
|
||||
return to_create
|
||||
|
||||
|
||||
def migrate_to_partner_phone(cr, registry):
|
||||
logger.info('start data migration for one2many_phone')
|
||||
logger.info("start data migration for one2many_phone")
|
||||
with api.Environment.manage():
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
rppo = env['res.partner.phone']
|
||||
rppo = env["res.partner.phone"]
|
||||
to_create = []
|
||||
to_create += create_partner_phone(cr, 'phone', '3_phone_primary')
|
||||
to_create += create_partner_phone(cr, 'mobile', '5_mobile_primary')
|
||||
to_create += create_partner_phone(cr, "phone", "3_phone_primary")
|
||||
to_create += create_partner_phone(cr, "mobile", "5_mobile_primary")
|
||||
to_create += create_partner_email(cr)
|
||||
# I need to create all at the end for invalidation purposes
|
||||
rppo.create(to_create)
|
||||
logger.info('end data migration for one2many_phone')
|
||||
logger.info("end data migration for one2many_phone")
|
||||
return
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Barroux Abbey
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
@@ -7,39 +6,40 @@ from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestPartnerPhone(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPartnerPhone, self).setUp()
|
||||
|
||||
def _check_result(self, partner, result):
|
||||
rppo = self.env['res.partner.phone']
|
||||
rppo = self.env["res.partner.phone"]
|
||||
pphone_email = rppo.search(
|
||||
[('type', '=', '1_email_primary'), ('partner_id', '=', partner.id)])
|
||||
if result['email']:
|
||||
self.assertEqual(partner.email, result['email'])
|
||||
[("type", "=", "1_email_primary"), ("partner_id", "=", partner.id)]
|
||||
)
|
||||
if result["email"]:
|
||||
self.assertEqual(partner.email, result["email"])
|
||||
self.assertEqual(len(pphone_email), 1)
|
||||
self.assertEqual(pphone_email.email, result['email'])
|
||||
self.assertEqual(pphone_email.email, result["email"])
|
||||
else:
|
||||
self.assertFalse(partner.email)
|
||||
self.assertFalse(pphone_email)
|
||||
if result['phone']:
|
||||
self.assertEqual(partner.phone, result['phone'])
|
||||
if result["phone"]:
|
||||
self.assertEqual(partner.phone, result["phone"])
|
||||
else:
|
||||
self.assertFalse(partner.phone)
|
||||
if result['mobile']:
|
||||
self.assertEqual(partner.mobile, result['mobile'])
|
||||
if result["mobile"]:
|
||||
self.assertEqual(partner.mobile, result["mobile"])
|
||||
else:
|
||||
self.assertFalse(partner.mobile)
|
||||
field2type = {
|
||||
'phone': '3_phone_primary',
|
||||
'mobile': '5_mobile_primary',
|
||||
'fax': '7_fax_primary',
|
||||
}
|
||||
"phone": "3_phone_primary",
|
||||
"mobile": "5_mobile_primary",
|
||||
"fax": "7_fax_primary",
|
||||
}
|
||||
for field, value in result.items():
|
||||
if field in field2type:
|
||||
type = field2type[field]
|
||||
pphone = rppo.search(
|
||||
[('type', '=', type), ('partner_id', '=', partner.id)])
|
||||
[("type", "=", type), ("partner_id", "=", partner.id)]
|
||||
)
|
||||
if value:
|
||||
self.assertEqual(len(pphone), 1)
|
||||
self.assertEqual(pphone.phone, value)
|
||||
@@ -47,88 +47,105 @@ class TestPartnerPhone(TransactionCase):
|
||||
self.assertFalse(pphone)
|
||||
|
||||
def test_create_partner(self):
|
||||
rpo = self.env['res.partner']
|
||||
p = rpo.create({
|
||||
'name': 'Test Me',
|
||||
'email': 'testme@example.com',
|
||||
'phone': '+33198089246',
|
||||
'mobile': '+33198089247',
|
||||
})
|
||||
result = {
|
||||
'email': 'testme@example.com',
|
||||
'phone': '+33198089246',
|
||||
'mobile': '+33198089247',
|
||||
rpo = self.env["res.partner"]
|
||||
p = rpo.create(
|
||||
{
|
||||
"name": "Test Me",
|
||||
"email": "testme@example.com",
|
||||
"phone": "+33198089246",
|
||||
"mobile": "+33198089247",
|
||||
}
|
||||
)
|
||||
result = {
|
||||
"email": "testme@example.com",
|
||||
"phone": "+33198089246",
|
||||
"mobile": "+33198089247",
|
||||
}
|
||||
self._check_result(p, result)
|
||||
p2 = rpo.create({
|
||||
'name': 'Test me now',
|
||||
'email': 'testmenow@example.com',
|
||||
'phone': '+33972727272',
|
||||
})
|
||||
result = {
|
||||
'email': 'testmenow@example.com',
|
||||
'phone': '+33972727272',
|
||||
'mobile': False,
|
||||
p2 = rpo.create(
|
||||
{
|
||||
"name": "Test me now",
|
||||
"email": "testmenow@example.com",
|
||||
"phone": "+33972727272",
|
||||
}
|
||||
)
|
||||
result = {
|
||||
"email": "testmenow@example.com",
|
||||
"phone": "+33972727272",
|
||||
"mobile": False,
|
||||
}
|
||||
self._check_result(p2, result)
|
||||
p3 = rpo.create({
|
||||
'name': 'Test me now',
|
||||
'phone_ids': [
|
||||
(0, 0, {'type': '3_phone_primary', 'phone': '+33972727272'}),
|
||||
(0, 0, {'type': '1_email_primary', 'email': 'tutu@example.fr'})],
|
||||
})
|
||||
result = {
|
||||
'email': 'tutu@example.fr',
|
||||
'phone': '+33972727272',
|
||||
'mobile': False,
|
||||
p3 = rpo.create(
|
||||
{
|
||||
"name": "Test me now",
|
||||
"phone_ids": [
|
||||
(0, 0, {"type": "3_phone_primary", "phone": "+33972727272"}),
|
||||
(0, 0, {"type": "1_email_primary", "email": "tutu@example.fr"}),
|
||||
],
|
||||
}
|
||||
)
|
||||
result = {
|
||||
"email": "tutu@example.fr",
|
||||
"phone": "+33972727272",
|
||||
"mobile": False,
|
||||
}
|
||||
self._check_result(p3, result)
|
||||
|
||||
def test_write_partner(self):
|
||||
p1 = self.env['res.partner'].create({
|
||||
'name': 'test me now',
|
||||
'country_id': self.env.ref('base.fr').id,
|
||||
})
|
||||
p1 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "test me now",
|
||||
"country_id": self.env.ref("base.fr").id,
|
||||
}
|
||||
)
|
||||
result_none = {
|
||||
'email': False,
|
||||
'phone': False,
|
||||
'mobile': False,
|
||||
}
|
||||
"email": False,
|
||||
"phone": False,
|
||||
"mobile": False,
|
||||
}
|
||||
self._check_result(p1, result_none)
|
||||
p1.write({
|
||||
'mobile': '+33198089247',
|
||||
'email': 'testmenow@example.com',
|
||||
})
|
||||
result = {
|
||||
'email': 'testmenow@example.com',
|
||||
'phone': False,
|
||||
'mobile': '+33198089247',
|
||||
p1.write(
|
||||
{
|
||||
"mobile": "+33198089247",
|
||||
"email": "testmenow@example.com",
|
||||
}
|
||||
self._check_result(p1, result)
|
||||
p1.write({
|
||||
'email': 'testmenow2@example.com',
|
||||
'phone': False,
|
||||
'mobile': '+33472727272',
|
||||
})
|
||||
)
|
||||
result = {
|
||||
'email': 'testmenow2@example.com',
|
||||
'phone': False,
|
||||
'mobile': '+33472727272',
|
||||
}
|
||||
"email": "testmenow@example.com",
|
||||
"phone": False,
|
||||
"mobile": "+33198089247",
|
||||
}
|
||||
self._check_result(p1, result)
|
||||
p1.write({
|
||||
'phone': False,
|
||||
'mobile': False,
|
||||
'email': False,
|
||||
})
|
||||
p1.write(
|
||||
{
|
||||
"email": "testmenow2@example.com",
|
||||
"phone": False,
|
||||
"mobile": "+33472727272",
|
||||
}
|
||||
)
|
||||
result = {
|
||||
"email": "testmenow2@example.com",
|
||||
"phone": False,
|
||||
"mobile": "+33472727272",
|
||||
}
|
||||
self._check_result(p1, result)
|
||||
p1.write(
|
||||
{
|
||||
"phone": False,
|
||||
"mobile": False,
|
||||
"email": False,
|
||||
}
|
||||
)
|
||||
self._check_result(p1, result_none)
|
||||
p2 = self.env['res.partner'].create({'name': 'Toto', 'email': 'toto@example.com'})
|
||||
p2 = self.env["res.partner"].create(
|
||||
{"name": "Toto", "email": "toto@example.com"}
|
||||
)
|
||||
p_multi = p1 + p2
|
||||
p_multi.write({'email': 'all@example.com', 'phone': '+33560606070'})
|
||||
p_multi.write({"email": "all@example.com", "phone": "+33560606070"})
|
||||
result = {
|
||||
'email': 'all@example.com',
|
||||
'phone': '+33560606070',
|
||||
'mobile': False,
|
||||
}
|
||||
"email": "all@example.com",
|
||||
"phone": "+33560606070",
|
||||
"mobile": False,
|
||||
}
|
||||
self._check_result(p1, result)
|
||||
self._check_result(p2, result)
|
||||
|
||||
@@ -1 +1 @@
|
||||
from . import partner
|
||||
from . import models
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Copyright 2017-2019 Akretion (http://www.akretion.com)
|
||||
# Copyright 2017-2021 Akretion (http://www.akretion.com)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Base Partner Reference',
|
||||
'version': '12.0.1.0.0',
|
||||
'category': 'Partner',
|
||||
'license': 'AGPL-3',
|
||||
'summary': "Improve usage of partner's Internal Reference",
|
||||
'description': """
|
||||
"name": "Base Partner Reference",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Partner",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Improve usage of partner's Internal Reference",
|
||||
"description": """
|
||||
Base Partner Reference
|
||||
======================
|
||||
|
||||
@@ -18,9 +18,9 @@ Base Partner Reference
|
||||
|
||||
* Adds unicity constraint on Internal Reference
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['base'],
|
||||
'data': ['partner_view.xml'],
|
||||
'installable': False,
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": ["base"],
|
||||
"data": ["views/res_partner.xml"],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
1
base_partner_ref/models/__init__.py
Normal file
1
base_partner_ref/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import res_partner
|
||||
73
base_partner_ref/models/res_partner.py
Normal file
73
base_partner_ref/models/res_partner.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Copyright 2017-2021 Akretion
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
ref = fields.Char(copy=False) # To avoid blocking duplicate
|
||||
invalidate_display_name = fields.Boolean()
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"ref_unique",
|
||||
"unique(ref)",
|
||||
"A partner already exists with this internal reference!",
|
||||
)
|
||||
]
|
||||
|
||||
# add 'ref' in depends
|
||||
@api.depends("ref", "invalidate_display_name")
|
||||
def _compute_display_name(self):
|
||||
super()._compute_display_name()
|
||||
|
||||
def _get_name(self):
|
||||
partner = self
|
||||
name = partner.name or ""
|
||||
|
||||
# START modif of native method
|
||||
if partner.ref:
|
||||
name = "[%s] %s" % (partner.ref, name)
|
||||
# END modif of native method
|
||||
if partner.company_name or partner.parent_id:
|
||||
if not name and partner.type in ["invoice", "delivery", "other"]:
|
||||
name = dict(self.fields_get(["type"])["type"]["selection"])[
|
||||
partner.type
|
||||
]
|
||||
if not partner.is_company:
|
||||
# START modif of native name_get() method
|
||||
company_name = partner.commercial_company_name or partner.parent_id.name
|
||||
if partner.parent_id.ref:
|
||||
company_name = "[%s] %s" % (partner.parent_id.ref, company_name)
|
||||
name = "%s, %s" % (company_name, name)
|
||||
# END modif of native name_get() method
|
||||
if self._context.get("show_address_only"):
|
||||
name = partner._display_address(without_company=True)
|
||||
if self._context.get("show_address"):
|
||||
name = name + "\n" + partner._display_address(without_company=True)
|
||||
name = name.replace("\n\n", "\n")
|
||||
name = name.replace("\n\n", "\n")
|
||||
if self._context.get("address_inline"):
|
||||
splitted_names = name.split("\n")
|
||||
name = ", ".join([n for n in splitted_names if n.strip()])
|
||||
if self._context.get("show_email") and partner.email:
|
||||
name = "%s <%s>" % (name, partner.email)
|
||||
if self._context.get("html_format"):
|
||||
name = name.replace("\n", "<br/>")
|
||||
if self._context.get("show_vat") and partner.vat:
|
||||
name = "%s ‒ %s" % (name, partner.vat)
|
||||
return name
|
||||
|
||||
@api.model
|
||||
def name_search(self, name="", args=None, operator="ilike", limit=100):
|
||||
if args is None:
|
||||
args = []
|
||||
if name and operator == "ilike":
|
||||
recs = self.search([("ref", "=", name)] + args, limit=limit)
|
||||
if recs:
|
||||
rec_childs = self.search([("id", "child_of", recs.ids)])
|
||||
return rec_childs.name_get()
|
||||
return super().name_search(name=name, args=args, operator=operator, limit=limit)
|
||||
@@ -1,67 +0,0 @@
|
||||
# Copyright 2017-2019 Akretion
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
ref = fields.Char(copy=False) # To avoid blocking duplicate
|
||||
invalidate_display_name = fields.Boolean()
|
||||
|
||||
_sql_constraints = [(
|
||||
'ref_unique',
|
||||
'unique(ref)',
|
||||
'A partner already exists with this internal reference!'
|
||||
)]
|
||||
|
||||
# add 'ref' in depends
|
||||
@api.depends('is_company', 'name', 'parent_id.name', 'type', 'company_name', 'ref', 'invalidate_display_name')
|
||||
def _compute_display_name(self):
|
||||
super(ResPartner, self)._compute_display_name()
|
||||
|
||||
def _get_name(self):
|
||||
partner = self
|
||||
name = partner.name or ''
|
||||
|
||||
# START modif of native method
|
||||
if partner.ref:
|
||||
name = "[%s] %s" % (partner.ref, name)
|
||||
# END modif of native method
|
||||
if partner.company_name or partner.parent_id:
|
||||
if not name and partner.type in ['invoice', 'delivery', 'other']:
|
||||
name = dict(self.fields_get(['type'])['type']['selection'])[partner.type]
|
||||
if not partner.is_company:
|
||||
# START modif of native name_get() method
|
||||
company_name = partner.commercial_company_name or partner.parent_id.name
|
||||
if partner.parent_id.ref:
|
||||
company_name = u"[%s] %s" % (partner.parent_id.ref, company_name)
|
||||
name = "%s, %s" % (company_name, name)
|
||||
# END modif of native name_get() method
|
||||
if self._context.get('show_address_only'):
|
||||
name = partner._display_address(without_company=True)
|
||||
if self._context.get('show_address'):
|
||||
name = name + "\n" + partner._display_address(without_company=True)
|
||||
name = name.replace('\n\n', '\n')
|
||||
name = name.replace('\n\n', '\n')
|
||||
if self._context.get('address_inline'):
|
||||
name = name.replace('\n', ', ')
|
||||
if self._context.get('show_email') and partner.email:
|
||||
name = "%s <%s>" % (name, partner.email)
|
||||
if self._context.get('html_format'):
|
||||
name = name.replace('\n', '<br/>')
|
||||
if self._context.get('show_vat') and partner.vat:
|
||||
name = "%s ‒ %s" % (name, partner.vat)
|
||||
return name
|
||||
|
||||
@api.model
|
||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
if args is None:
|
||||
args = []
|
||||
if name and operator == 'ilike':
|
||||
recs = self.search([('ref', '=', name)] + args, limit=limit)
|
||||
if recs:
|
||||
return recs.name_get()
|
||||
return super().name_search(name=name, args=args, operator=operator, limit=limit)
|
||||
@@ -1,50 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2017-2019 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_partner_form" model="ir.ui.view">
|
||||
<field name="name">Move ref in partner form to make it more visible</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="inherit_id" ref="base.view_partner_form" />
|
||||
<field name="priority">1000</field> <!-- inherit after l10n_fr -->
|
||||
<field name="arch" type="xml">
|
||||
<field name="type" position="after">
|
||||
<field name="ref"/>
|
||||
<field name="ref" />
|
||||
</field>
|
||||
<xpath expr="//page[@name='sales_purchases']//field[@name='ref']" position="replace"/>
|
||||
<xpath
|
||||
expr="//page[@name='sales_purchases']//field[@name='ref']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- show name and ref in separate columns -->
|
||||
<!-- ref is added in tree view by base_usability with optional="hide"
|
||||
<record id="view_partner_tree" model="ir.ui.view">
|
||||
<field name="name">Add ref in partner tree view</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- show name and ref in separate columns -->
|
||||
<field name="display_name" position="after">
|
||||
<field name="name"/>
|
||||
<field name="ref"/>
|
||||
<field name="ref" optional="hide"/>
|
||||
</field>
|
||||
<field name="display_name" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<record id="res_partner_kanban_view" model="ir.ui.view">
|
||||
<field name="name">Add ref in partner kanban view</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
|
||||
<field name="inherit_id" ref="base.res_partner_kanban_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="display_name" position="after">
|
||||
<field name="ref"/>
|
||||
<field name="ref" />
|
||||
</field>
|
||||
<li t-if="record.email.raw_value" position="after">
|
||||
<li t-if="record.ref.raw_value">Ref: <field name="ref"/></li>
|
||||
<li t-if="record.ref.raw_value">Ref: <field name="ref" /></li>
|
||||
</li>
|
||||
</field>
|
||||
</record>
|
||||
@@ -3,22 +3,22 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Base Usability',
|
||||
'version': '14.0.1.0.0',
|
||||
'category': 'Partner',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Better usability in base module',
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['base'],
|
||||
'data': [
|
||||
'security/group.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/res_partner.xml',
|
||||
'views/res_partner_bank.xml',
|
||||
'views/res_country.xml',
|
||||
'views/ir_module.xml',
|
||||
'views/ir_sequence.xml',
|
||||
],
|
||||
'installable': True,
|
||||
"name": "Base Usability",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Partner",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Better usability in base module",
|
||||
"author": "Akretion",
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"depends": ["base"],
|
||||
"data": [
|
||||
"security/group.xml",
|
||||
"security/ir.model.access.csv",
|
||||
"views/res_partner.xml",
|
||||
"views/res_partner_bank.xml",
|
||||
"views/res_country.xml",
|
||||
"views/ir_module.xml",
|
||||
"views/ir_sequence.xml",
|
||||
],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -16,4 +16,3 @@ index a3baf47c615..e546d450107 100644
|
||||
+ user.partner_id.write({'company_id': False})
|
||||
user.partner_id.active = user.active
|
||||
return users
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models
|
||||
from odoo.addons.base.models.ir_mail_server import extract_rfc2822_addresses
|
||||
import logging
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
from odoo.addons.base.models.ir_mail_server import extract_rfc2822_addresses
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -14,23 +16,42 @@ class IrMailServer(models.Model):
|
||||
|
||||
@api.model
|
||||
def send_email(
|
||||
self, message, mail_server_id=None, smtp_server=None,
|
||||
smtp_port=None, smtp_user=None, smtp_password=None,
|
||||
smtp_encryption=None, smtp_debug=False, smtp_session=None):
|
||||
self,
|
||||
message,
|
||||
mail_server_id=None,
|
||||
smtp_server=None,
|
||||
smtp_port=None,
|
||||
smtp_user=None,
|
||||
smtp_password=None,
|
||||
smtp_encryption=None,
|
||||
smtp_debug=False,
|
||||
smtp_session=None,
|
||||
):
|
||||
# Start copy from native method
|
||||
smtp_from = message['Return-Path'] or\
|
||||
self._get_default_bounce_address() or message['From']
|
||||
smtp_from = (
|
||||
message["Return-Path"]
|
||||
or self._get_default_bounce_address()
|
||||
or message["From"]
|
||||
)
|
||||
from_rfc2822 = extract_rfc2822_addresses(smtp_from)
|
||||
smtp_from = from_rfc2822[-1]
|
||||
# End copy from native method
|
||||
logger.info(
|
||||
"Sending email from '%s' to '%s' Cc '%s' Bcc '%s' "
|
||||
"with subject '%s'",
|
||||
smtp_from, message.get('To'), message.get('Cc'),
|
||||
message.get('Bcc'), message.get('Subject'))
|
||||
"Sending email from '%s' to '%s' Cc '%s' Bcc '%s' " "with subject '%s'",
|
||||
smtp_from,
|
||||
message.get("To"),
|
||||
message.get("Cc"),
|
||||
message.get("Bcc"),
|
||||
message.get("Subject"),
|
||||
)
|
||||
return super().send_email(
|
||||
message, mail_server_id=mail_server_id,
|
||||
smtp_server=smtp_server, smtp_port=smtp_port,
|
||||
smtp_user=smtp_user, smtp_password=smtp_password,
|
||||
smtp_encryption=smtp_encryption, smtp_debug=smtp_debug,
|
||||
smtp_session=smtp_session)
|
||||
message,
|
||||
mail_server_id=mail_server_id,
|
||||
smtp_server=smtp_server,
|
||||
smtp_port=smtp_port,
|
||||
smtp_user=smtp_user,
|
||||
smtp_password=smtp_password,
|
||||
smtp_encryption=smtp_encryption,
|
||||
smtp_debug=smtp_debug,
|
||||
smtp_session=smtp_session,
|
||||
)
|
||||
|
||||
@@ -6,11 +6,11 @@ from odoo import api, models
|
||||
|
||||
|
||||
class IrModel(models.Model):
|
||||
_inherit = 'ir.model'
|
||||
_inherit = "ir.model"
|
||||
|
||||
@api.depends('name', 'model')
|
||||
@api.depends("name", "model")
|
||||
def name_get(self):
|
||||
res = []
|
||||
for rec in self:
|
||||
res.append((rec.id, '%s (%s)' % (rec.name, rec.model)))
|
||||
res.append((rec.id, "%s (%s)" % (rec.name, rec.model)))
|
||||
return res
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models, _
|
||||
from odoo import _, api, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
_inherit = "res.company"
|
||||
|
||||
@api.model
|
||||
def generate_line(self, fields, options, icon=True, separator=' - '):
|
||||
def generate_line(self, fields, options, icon=True, separator=" - "):
|
||||
assert fields
|
||||
assert options
|
||||
content = []
|
||||
@@ -20,13 +20,13 @@ class ResCompany(models.Model):
|
||||
label = field[1]
|
||||
uicon = False
|
||||
elif isinstance(field, str) and field in options:
|
||||
value = options[field]['value']
|
||||
label = options[field].get('label')
|
||||
uicon = options[field].get('icon')
|
||||
value = options[field]["value"]
|
||||
label = options[field].get("label")
|
||||
uicon = options[field].get("icon")
|
||||
if value:
|
||||
prefix = icon and uicon or label
|
||||
if prefix:
|
||||
content.append('%s %s' % (prefix, value))
|
||||
content.append("%s %s" % (prefix, value))
|
||||
else:
|
||||
content.append(value)
|
||||
line = separator.join(content)
|
||||
@@ -35,46 +35,51 @@ class ResCompany(models.Model):
|
||||
def _prepare_header_options(self):
|
||||
self.ensure_one()
|
||||
options = {
|
||||
'phone': {
|
||||
'value': self.phone,
|
||||
"phone": {
|
||||
"value": self.phone,
|
||||
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm
|
||||
'icon': '\U0001F4DE',
|
||||
'label': _('Tel:')},
|
||||
'email': {
|
||||
'value': self.email,
|
||||
"icon": "\U0001F4DE",
|
||||
"label": _("Tel:"),
|
||||
},
|
||||
"email": {
|
||||
"value": self.email,
|
||||
# http://www.fileformat.info/info/unicode/char/2709/index.htm
|
||||
'icon': '\u2709',
|
||||
'label': _('E-mail:')},
|
||||
'website': {
|
||||
'value': self.website,
|
||||
'icon': '\U0001f310',
|
||||
'label': _('Website:')},
|
||||
'vat': {
|
||||
'value': self.vat,
|
||||
'label': _('VAT:')},
|
||||
}
|
||||
"icon": "\u2709",
|
||||
"label": _("E-mail:"),
|
||||
},
|
||||
"website": {
|
||||
"value": self.website,
|
||||
"icon": "\U0001f310",
|
||||
"label": _("Website:"),
|
||||
},
|
||||
"vat": {"value": self.vat, "label": _("VAT:")},
|
||||
}
|
||||
return options
|
||||
|
||||
def _report_company_legal_name(self):
|
||||
'''Method inherited in the module base_company_extension'''
|
||||
"""Method inherited in the module base_company_extension"""
|
||||
self.ensure_one()
|
||||
return self.name
|
||||
|
||||
# for reports
|
||||
def _display_report_header(
|
||||
self, line_details=[['phone', 'website'], ['vat']],
|
||||
icon=True, line_separator=' - '):
|
||||
self,
|
||||
line_details=[["phone", "website"], ["vat"]],
|
||||
icon=True,
|
||||
line_separator=" - ",
|
||||
):
|
||||
self.ensure_one()
|
||||
res = ''
|
||||
res = ""
|
||||
address = self.partner_id._display_address(without_company=True)
|
||||
address = address.replace('\n', ' - ')
|
||||
address = address.replace("\n", " - ")
|
||||
|
||||
line1 = '%s - %s' % (self._report_company_legal_name(), address)
|
||||
line1 = "%s - %s" % (self._report_company_legal_name(), address)
|
||||
lines = [line1]
|
||||
options = self._prepare_header_options()
|
||||
for details in line_details:
|
||||
line = self.generate_line(
|
||||
details, options, icon=icon, separator=line_separator)
|
||||
details, options, icon=icon, separator=line_separator
|
||||
)
|
||||
lines.append(line)
|
||||
res = '\n'.join(lines)
|
||||
res = "\n".join(lines)
|
||||
return res
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
_inherit = "res.partner"
|
||||
|
||||
# tracking=True is handled in the 'mail' module, and base_usability
|
||||
# doesn't depend on 'mail', so adding tracking on res.partner fields
|
||||
# has been moved to mail_usability
|
||||
ref = fields.Char(copy=False)
|
||||
# For reports
|
||||
name_title = fields.Char(
|
||||
compute='_compute_name_title', string='Name with Title')
|
||||
name_title = fields.Char(compute="_compute_name_title", string="Name with Title")
|
||||
|
||||
@api.depends('name', 'title')
|
||||
@api.depends("name", "title")
|
||||
def _compute_name_title(self):
|
||||
for partner in self:
|
||||
name_title = partner.name
|
||||
@@ -28,21 +27,22 @@ class ResPartner(models.Model):
|
||||
if partner.lang:
|
||||
partner_lg = partner.with_context(lang=partner.lang)
|
||||
title = partner_lg.title.shortcut or partner_lg.title.name
|
||||
name_title = ' '.join([title, name_title])
|
||||
name_title = " ".join([title, name_title])
|
||||
partner.name_title = name_title
|
||||
|
||||
def _display_address(self, without_company=False):
|
||||
'''Remove empty lines'''
|
||||
"""Remove empty lines"""
|
||||
res = super()._display_address(without_company=without_company)
|
||||
while "\n\n" in res:
|
||||
res = res.replace('\n\n', '\n')
|
||||
res = res.replace("\n\n", "\n")
|
||||
return res
|
||||
|
||||
# for reports
|
||||
def _display_full_address(
|
||||
self,
|
||||
details=['company', 'name', 'address', 'phone', 'mobile', 'email'],
|
||||
icon=True):
|
||||
self,
|
||||
details=["company", "name", "address", "phone", "mobile", "email"],
|
||||
icon=True,
|
||||
):
|
||||
self.ensure_one()
|
||||
# To make the icons work with py3o with PDF export, on the py3o server:
|
||||
# 1) sudo apt-get install fonts-symbola
|
||||
@@ -54,85 +54,89 @@ class ResPartner(models.Model):
|
||||
title = False
|
||||
title_short = False
|
||||
else:
|
||||
company = self.parent_id and self.parent_id.is_company and\
|
||||
self.parent_id.name or False
|
||||
company = (
|
||||
self.parent_id
|
||||
and self.parent_id.is_company
|
||||
and self.parent_id.name
|
||||
or False
|
||||
)
|
||||
name = self.name_title
|
||||
name_no_title = self.name
|
||||
title = self.title.name
|
||||
title_short = self.title.shortcut
|
||||
options = {
|
||||
'name': {
|
||||
'value': name,
|
||||
},
|
||||
'company': {
|
||||
'value': company,
|
||||
},
|
||||
'title': {
|
||||
'value': title,
|
||||
},
|
||||
'title_short': {
|
||||
'value': title_short,
|
||||
},
|
||||
'name_no_title': {
|
||||
'value': name_no_title,
|
||||
},
|
||||
'phone': {
|
||||
'value': self.phone,
|
||||
"name": {
|
||||
"value": name,
|
||||
},
|
||||
"company": {
|
||||
"value": company,
|
||||
},
|
||||
"title": {
|
||||
"value": title,
|
||||
},
|
||||
"title_short": {
|
||||
"value": title_short,
|
||||
},
|
||||
"name_no_title": {
|
||||
"value": name_no_title,
|
||||
},
|
||||
"phone": {
|
||||
"value": self.phone,
|
||||
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm
|
||||
'icon': '\U0001F4DE',
|
||||
'label': _('Tel:'),
|
||||
},
|
||||
'mobile': {
|
||||
'value': self.mobile,
|
||||
"icon": "\U0001F4DE",
|
||||
"label": _("Tel:"),
|
||||
},
|
||||
"mobile": {
|
||||
"value": self.mobile,
|
||||
# http://www.fileformat.info/info/unicode/char/1f4f1/index.htm
|
||||
'icon': '\U0001F4F1',
|
||||
'label': _('Mobile:'),
|
||||
},
|
||||
'email': {
|
||||
'value': self.email,
|
||||
"icon": "\U0001F4F1",
|
||||
"label": _("Mobile:"),
|
||||
},
|
||||
"email": {
|
||||
"value": self.email,
|
||||
# http://www.fileformat.info/info/unicode/char/2709/index.htm
|
||||
'icon': '\u2709',
|
||||
'label': _('E-mail:'),
|
||||
},
|
||||
'website': {
|
||||
'value': self.website,
|
||||
"icon": "\u2709",
|
||||
"label": _("E-mail:"),
|
||||
},
|
||||
"website": {
|
||||
"value": self.website,
|
||||
# http://www.fileformat.info/info/unicode/char/1f310/index.htm
|
||||
'icon': '\U0001f310',
|
||||
'label': _('Website:'),
|
||||
},
|
||||
'address': {
|
||||
'value': self._display_address(without_company=True),
|
||||
},
|
||||
'vat': {
|
||||
'value': self.commercial_partner_id.vat,
|
||||
'label': _('VAT Number:'),
|
||||
},
|
||||
'commercial_ref': {
|
||||
'value': self.commercial_partner_id.ref,
|
||||
'label': _('Customer Number:'),
|
||||
},
|
||||
'ref': {
|
||||
'value': self.ref,
|
||||
'label': _('Customer Number:'),
|
||||
},
|
||||
"icon": "\U0001f310",
|
||||
"label": _("Website:"),
|
||||
},
|
||||
"address": {
|
||||
"value": self._display_address(without_company=True),
|
||||
},
|
||||
"vat": {
|
||||
"value": self.commercial_partner_id.vat,
|
||||
"label": _("VAT Number:"),
|
||||
},
|
||||
"commercial_ref": {
|
||||
"value": self.commercial_partner_id.ref,
|
||||
"label": _("Customer Number:"),
|
||||
},
|
||||
"ref": {
|
||||
"value": self.ref,
|
||||
"label": _("Customer Number:"),
|
||||
},
|
||||
# Same with 'supplier_' prefix, to change the label
|
||||
'supplier_commercial_ref': {
|
||||
'value': self.commercial_partner_id.ref,
|
||||
'label': _('Supplier Number:'),
|
||||
},
|
||||
'supplier_ref': {
|
||||
'value': self.ref,
|
||||
'label': _('Supplier Number:'),
|
||||
},
|
||||
}
|
||||
"supplier_commercial_ref": {
|
||||
"value": self.commercial_partner_id.ref,
|
||||
"label": _("Supplier Number:"),
|
||||
},
|
||||
"supplier_ref": {
|
||||
"value": self.ref,
|
||||
"label": _("Supplier Number:"),
|
||||
},
|
||||
}
|
||||
res = []
|
||||
for detail in details:
|
||||
if options.get(detail) and options[detail]['value']:
|
||||
if options.get(detail) and options[detail]["value"]:
|
||||
entry = options[detail]
|
||||
prefix = icon and entry.get('icon') or entry.get('label')
|
||||
prefix = icon and entry.get("icon") or entry.get("label")
|
||||
if prefix:
|
||||
res.append('%s %s' % (prefix, entry['value']))
|
||||
res.append("%s %s" % (prefix, entry["value"]))
|
||||
else:
|
||||
res.append('%s' % entry['value'])
|
||||
res = '\n'.join(res)
|
||||
res.append("%s" % entry["value"])
|
||||
res = "\n".join(res)
|
||||
return res
|
||||
|
||||
@@ -6,9 +6,9 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartnerBank(models.Model):
|
||||
_inherit = 'res.partner.bank'
|
||||
_inherit = "res.partner.bank"
|
||||
|
||||
# In the 'base' module, they didn't put any string, so the bank name is
|
||||
# displayed as 'Name', which the string of the related field it
|
||||
# points to
|
||||
bank_name = fields.Char(string='Bank Name')
|
||||
bank_name = fields.Char(string="Bank Name")
|
||||
|
||||
@@ -6,6 +6,6 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartnerCategory(models.Model):
|
||||
_inherit = 'res.partner.category'
|
||||
_inherit = "res.partner.category"
|
||||
|
||||
name = fields.Char(translate=False)
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models, SUPERUSER_ID
|
||||
import logging
|
||||
|
||||
from odoo import SUPERUSER_ID, api, models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
_inherit = "res.users"
|
||||
|
||||
@api.model
|
||||
def _script_partners_linked_to_users_no_company(self):
|
||||
if self.env.user.id != SUPERUSER_ID:
|
||||
self = self.sudo()
|
||||
logger.info(
|
||||
'START to set company_id=False on partners related to users')
|
||||
logger.info("START to set company_id=False on partners related to users")
|
||||
users = self.with_context(active_test=False).search([])
|
||||
for user in users:
|
||||
if user.partner_id.company_id:
|
||||
user.partner_id.write({'company_id': False})
|
||||
user.partner_id.write({"company_id": False})
|
||||
logger.info(
|
||||
'Wrote company_id=False on user %s ID %d',
|
||||
user.login, user.id)
|
||||
logger.info(
|
||||
'END setting company_id=False on partners related to users')
|
||||
"Wrote company_id=False on user %s ID %d", user.login, user.id
|
||||
)
|
||||
logger.info("END setting company_id=False on partners related to users")
|
||||
|
||||
@@ -11,7 +11,7 @@ This module adds the following functions:
|
||||
* Better search and form view for country and state
|
||||
* Display technical name of modules in kanban view
|
||||
* Change module filter to `installable`
|
||||
* Add widget=handle on sequence of res.partner.bank
|
||||
* Add widget=handle on sequence of res.partner.bank
|
||||
* Add city and country in partner tree view
|
||||
* Add widget="email" on email of contacts
|
||||
* Add script to fix partners related to users in multi-company setup
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2016-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
|
||||
<odoo noupdate="1">
|
||||
|
||||
<!-- This group is used to hide menu entries to everybody,
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
|
||||
<record id="view_module_filter" model="ir.ui.view">
|
||||
<field name="model">ir.module.module</field>
|
||||
<field name="inherit_id" ref="base.view_module_filter"/>
|
||||
<field name="inherit_id" ref="base.view_module_filter" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='extra']" position="after">
|
||||
<filter name="installable" string="Installable" domain="[('state', '!=', 'uninstallable')]"/>
|
||||
<filter
|
||||
name="installable"
|
||||
string="Installable"
|
||||
domain="[('state', '!=', 'uninstallable')]"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_sequence_search" model="ir.ui.view">
|
||||
<field name="model">ir.sequence</field>
|
||||
<field name="inherit_id" ref="base.view_sequence_search"/>
|
||||
<field name="inherit_id" ref="base.view_sequence_search" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="prefix"/>
|
||||
<field name="prefix" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2015-2020 Akretion France (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_country_state_search" model="ir.ui.view">
|
||||
<field name="name">base_usability.res.country.state.search</field>
|
||||
<field name="model">res.country.state</field>
|
||||
<field name="inherit_id" ref="base.view_country_state_search"/>
|
||||
<field name="inherit_id" ref="base.view_country_state_search" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="attributes">
|
||||
<attribute name="filter_domain">['|', ('name', 'ilike', self), ('code', '=', self)]</attribute>
|
||||
<attribute
|
||||
name="filter_domain"
|
||||
>['|', ('name', 'ilike', self), ('code', '=', self)]</attribute>
|
||||
</field>
|
||||
<field name="name" position="after">
|
||||
<field name="code"/>
|
||||
<field name="code" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -26,11 +27,19 @@
|
||||
<field name="model">res.country</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" filter_domain="['|', ('name', 'ilike', self), ('code', '=', self)]" string="Name or Code"/>
|
||||
<field name="code"/>
|
||||
<field name="currency_id"/>
|
||||
<field
|
||||
name="name"
|
||||
filter_domain="['|', ('name', 'ilike', self), ('code', '=', self)]"
|
||||
string="Name or Code"
|
||||
/>
|
||||
<field name="code" />
|
||||
<field name="currency_id" />
|
||||
<group string="Group By" name="groupby">
|
||||
<filter name="currency_groupby" string="Currency" context="{'group_by': 'currency_id'}"/>
|
||||
<filter
|
||||
name="currency_groupby"
|
||||
string="Currency"
|
||||
context="{'group_by': 'currency_id'}"
|
||||
/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -39,10 +48,10 @@
|
||||
<record id="view_country_form" model="ir.ui.view">
|
||||
<field name="name">base_usability.res.country.form</field>
|
||||
<field name="model">res.country</field>
|
||||
<field name="inherit_id" ref="base.view_country_form"/>
|
||||
<field name="inherit_id" ref="base.view_country_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="code" position="after">
|
||||
<field name="country_group_ids" widget="many2many_tags"/>
|
||||
<field name="country_group_ids" widget="many2many_tags" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
© 2014-2016 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_partner_form" model="ir.ui.view">
|
||||
<field name="name">base_usability.title.on.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="inherit_id" ref="base.view_partner_form" />
|
||||
<field name="arch" type="xml">
|
||||
<!-- Show title not only on Contacts -->
|
||||
<xpath expr="//field[@name='child_ids']/form//field[@name='title']" position="attributes">
|
||||
<attribute name="attrs"></attribute>
|
||||
<xpath
|
||||
expr="//field[@name='child_ids']/form//field[@name='title']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="attrs" />
|
||||
</xpath>
|
||||
<!-- Show double VAT partner even when not in editable mode -->
|
||||
<div attrs="{'invisible': [('same_vat_partner_id', '=', False)]}" position="attributes">
|
||||
<div
|
||||
attrs="{'invisible': [('same_vat_partner_id', '=', False)]}"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="class">alert alert-warning</attribute>
|
||||
</div>
|
||||
</field>
|
||||
@@ -26,10 +31,10 @@
|
||||
<record id="view_partner_simple_form" model="ir.ui.view">
|
||||
<field name="name">base_usability.title.on.partner.simplified.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_simple_form"/>
|
||||
<field name="inherit_id" ref="base.view_partner_simple_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="function" position="before">
|
||||
<field name="title"/>
|
||||
<field name="title" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -37,10 +42,13 @@
|
||||
<record id="view_partner_tree" model="ir.ui.view">
|
||||
<field name="name">base_usability.res.partner.tree</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_tree"/>
|
||||
<field name="inherit_id" ref="base.view_partner_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="display_name" position="after">
|
||||
<field name="ref" optional="hide" />
|
||||
</field>
|
||||
<field name="phone" position="after">
|
||||
<field name="mobile" optional="show" widget="phone" class="o_force_ltr"/>
|
||||
<field name="mobile" optional="show" widget="phone" class="o_force_ltr" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -48,12 +56,14 @@
|
||||
<record id="view_res_partner_filter" model="ir.ui.view">
|
||||
<field name="name">base_usability.partner.search.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_res_partner_filter"/>
|
||||
<field name="inherit_id" ref="base.view_res_partner_filter" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="attributes">
|
||||
<attribute name="string">Name or Email or Reference</attribute>
|
||||
<!-- for 'ref', change '=' to 'start with' -->
|
||||
<attribute name="filter_domain">['|','|',('display_name','ilike',self),('ref','=ilike',self + '%'),('email','ilike',self)]</attribute>
|
||||
<attribute
|
||||
name="filter_domain"
|
||||
>['|','|',('display_name','ilike',self),('ref','=ilike',self + '%'),('email','ilike',self)]</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2018-2020 Akretion (http://www.akretion.com/)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
@@ -8,13 +8,13 @@
|
||||
<record id="view_partner_bank_tree" model="ir.ui.view">
|
||||
<field name="name">base_usability.res.partner.bank.tree</field>
|
||||
<field name="model">res.partner.bank</field>
|
||||
<field name="inherit_id" ref="base.view_partner_bank_tree"/>
|
||||
<field name="inherit_id" ref="base.view_partner_bank_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="bank_name" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</field>
|
||||
<field name="bank_name" position="after">
|
||||
<field name="bank_id"/>
|
||||
<field name="bank_id" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{
|
||||
'name': 'Company Code',
|
||||
'summary': 'Add a code field in company',
|
||||
'version': '12.0.0.0.1',
|
||||
'author': 'Akretion',
|
||||
'maintainer': 'Akretion',
|
||||
'license': 'AGPL-3',
|
||||
'category': 'base',
|
||||
'depends': [
|
||||
'base',
|
||||
"name": "Company Code",
|
||||
"summary": "Add a code field in company",
|
||||
"version": "12.0.0.0.1",
|
||||
"author": "Akretion",
|
||||
"maintainer": "Akretion",
|
||||
"license": "AGPL-3",
|
||||
"category": "base",
|
||||
"depends": [
|
||||
"base",
|
||||
],
|
||||
'website': 'http://www.akretion.com/',
|
||||
'data': [
|
||||
'views/company_view.xml',
|
||||
"website": "https://github.com/OCA/odoo-usability",
|
||||
"data": [
|
||||
"views/company_view.xml",
|
||||
],
|
||||
'installable': False,
|
||||
"installable": False,
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Copyright 2019 David BEAL @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import models, fields
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
_inherit = "res.company"
|
||||
|
||||
code = fields.Char(
|
||||
required=True, default='CODE',
|
||||
help="Field used in object name as suffix")
|
||||
required=True, default="CODE", help="Field used in object name as suffix"
|
||||
)
|
||||
|
||||
def _add_company_code(self, super_object):
|
||||
""
|
||||
@@ -19,15 +19,20 @@ class ResCompany(models.Model):
|
||||
return self.env['res.company']._add_company_code(super())
|
||||
"""
|
||||
records = super_object.__self__
|
||||
if records and records[0]._name == 'res.company':
|
||||
if records and records[0]._name == "res.company":
|
||||
codes = {x.id: x.code for x in records}
|
||||
else:
|
||||
codes = {x.id: x['company_id']['code'] for x in records
|
||||
if getattr(x, 'company_id')}
|
||||
codes = {
|
||||
x.id: x["company_id"]["code"]
|
||||
for x in records
|
||||
if getattr(x, "company_id")
|
||||
}
|
||||
if not codes:
|
||||
return super_object.name_get()
|
||||
return [(elm[0], '%s (%s)' % (elm[1], codes[elm[0]] or ''))
|
||||
for elm in super_object.name_get()]
|
||||
return [
|
||||
(elm[0], "%s (%s)" % (elm[1], codes[elm[0]] or ""))
|
||||
for elm in super_object.name_get()
|
||||
]
|
||||
|
||||
def name_get(self):
|
||||
return self.env['res.company']._add_company_code(super())
|
||||
return self.env["res.company"]._add_company_code(super())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
To display your company code with `name_get()` just
|
||||
To display your company code with `name_get()` just
|
||||
write this in your custom code according to your model
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_company_form" model="ir.ui.view">
|
||||
<field name="model">res.company</field>
|
||||
<field name="inherit_id" ref="base.view_company_form"/>
|
||||
<field name="inherit_id" ref="base.view_company_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="before">
|
||||
<field name="code"/>
|
||||
<field name="code" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user