Compare commits
484 Commits
8.0
...
12_sale_pi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfb704cd07 | ||
|
|
982c781edf | ||
|
|
d2f3227f53 | ||
|
|
2bddfa49f6 | ||
|
|
30334e617d | ||
|
|
e220da006f | ||
|
|
c5ffe375a5 | ||
|
|
dd8487bf0f | ||
|
|
a62d1e01f4 | ||
|
|
e397f3908e | ||
|
|
7b51c993ec | ||
|
|
f480332d3a | ||
|
|
2f875867c5 | ||
|
|
e9049570ee | ||
|
|
52eff801b6 | ||
|
|
d84ef2e8c5 | ||
|
|
9298378e62 | ||
|
|
a7f5e10be4 | ||
|
|
ea5f679a59 | ||
|
|
d63706f764 | ||
|
|
d9b8da5799 | ||
|
|
4896075c7d | ||
|
|
78b0e416fa | ||
|
|
beb0a27ad6 | ||
|
|
5b620a6c5f | ||
|
|
8c1089e138 | ||
|
|
765be077cd | ||
|
|
4f6dc99319 | ||
|
|
59b0a5ac10 | ||
|
|
2d755be1d7 | ||
|
|
61768a34e9 | ||
|
|
140f2d54ee | ||
|
|
e4d4bcd7ca | ||
|
|
e193df7def | ||
|
|
cadbd840d9 | ||
|
|
3052d7c905 | ||
|
|
f59f2ad8ec | ||
|
|
fa22c63176 | ||
|
|
4437afb7d5 | ||
|
|
b762af222d | ||
|
|
f258bf6fdb | ||
|
|
43c60dcb30 | ||
|
|
f4f99647d2 | ||
|
|
a75e14dc8d | ||
|
|
dbd600bcd9 | ||
|
|
4b442bd11b | ||
|
|
d2978475b8 | ||
|
|
c57d2a0564 | ||
|
|
7e0c438ae8 | ||
|
|
7d7a42ba8e | ||
|
|
a21ec776c1 | ||
|
|
62d0af15ac | ||
|
|
24c4dd0225 | ||
|
|
16c2833252 | ||
|
|
f9552e7271 | ||
|
|
19ad0eff78 | ||
|
|
9f28bc6703 | ||
|
|
11ae6e1297 | ||
|
|
b2bf902a78 | ||
|
|
790c55e607 | ||
|
|
694a0d4d29 | ||
|
|
c2ed859534 | ||
|
|
a4765c6cb2 | ||
|
|
9cb99db2b3 | ||
|
|
8b0e59b22f | ||
|
|
dccfead879 | ||
|
|
9cae5e00b6 | ||
|
|
620b459415 | ||
|
|
2062a1f307 | ||
|
|
79172fcc45 | ||
|
|
d937d954ab | ||
|
|
3bb11966dd | ||
|
|
0bd3b0412c | ||
|
|
5a72d2b1b9 | ||
|
|
682f85b2d7 | ||
|
|
087e4d6333 | ||
|
|
d5bf1f1a24 | ||
|
|
317d4fdd42 | ||
|
|
9657f1bcfd | ||
|
|
dc95c30f60 | ||
|
|
2fb715905a | ||
|
|
60a5fa7a33 | ||
|
|
06e0617026 | ||
|
|
fa3e483026 | ||
|
|
1957018a14 | ||
|
|
cfebb6c99c | ||
|
|
cd745c74a9 | ||
|
|
e1f9f2ea92 | ||
|
|
ce08af35bf | ||
|
|
475993422e | ||
|
|
ce4fac8a10 | ||
|
|
a848181bee | ||
|
|
13b9109fc0 | ||
|
|
ec0e3c1868 | ||
|
|
b42474a3c2 | ||
|
|
e5323fb968 | ||
|
|
d806fa48f4 | ||
|
|
c38d533557 | ||
|
|
4c145c12af | ||
|
|
0a2f129ed8 | ||
|
|
4d75ef9fb5 | ||
|
|
bebc328a7b | ||
|
|
08fde42217 | ||
|
|
a3f4f94109 | ||
|
|
4e1b285204 | ||
|
|
71e30f2872 | ||
|
|
8e4798dbfc | ||
|
|
0ffa8b19ea | ||
|
|
0422c16c97 | ||
|
|
67f0690659 | ||
|
|
2e2aff229e | ||
|
|
8aa2f60763 | ||
|
|
55b9b1a619 | ||
|
|
7165d113b6 | ||
|
|
ea471c2e01 | ||
|
|
7997f267b7 | ||
|
|
8dc9fdcfa0 | ||
|
|
3bd50e49eb | ||
|
|
4733b6b903 | ||
|
|
c0ca9eeab0 | ||
|
|
8e29f5a396 | ||
|
|
29c41d9dea | ||
|
|
953ca6d01e | ||
|
|
e57122de31 | ||
|
|
82bc47905b | ||
|
|
730a3e99bf | ||
|
|
e5072cbb63 | ||
|
|
5378ad8ee6 | ||
|
|
66174e1cb0 | ||
|
|
2db41cfd6f | ||
|
|
5d440b3c61 | ||
|
|
41882b3529 | ||
|
|
eab4cdf17f | ||
|
|
120a1b8c16 | ||
|
|
ba19c3c76f | ||
|
|
3e26d60503 | ||
|
|
92ef5170e0 | ||
|
|
8eb0f8b6a2 | ||
|
|
b29c938044 | ||
|
|
7f0ff6e36e | ||
|
|
119bb936fa | ||
|
|
31c67cb923 | ||
|
|
e7638c239f | ||
|
|
528438586c | ||
|
|
f07d416034 | ||
|
|
905437e026 | ||
|
|
9f1cf7af1e | ||
|
|
1a193dcbec | ||
|
|
92a175ea97 | ||
|
|
80843719a6 | ||
|
|
ee3b872e66 | ||
|
|
c199853d4e | ||
|
|
1491cbd2d0 | ||
|
|
f8c1ba6c28 | ||
|
|
51b49249f0 | ||
|
|
66532aec75 | ||
|
|
813c123517 | ||
|
|
90271a7ffe | ||
|
|
5ad4c6fb4a | ||
|
|
12d9393279 | ||
|
|
30e62353e6 | ||
|
|
1070c68c55 | ||
|
|
49e9cd0d06 | ||
|
|
441df56422 | ||
|
|
94959f459a | ||
|
|
ec40108896 | ||
|
|
f651126f25 | ||
|
|
edf701d79c | ||
|
|
b31082377d | ||
|
|
6e0907dabe | ||
|
|
ab7c61b796 | ||
|
|
400a633753 | ||
|
|
2a97ffb6d9 | ||
|
|
c5548caee4 | ||
|
|
f880c02038 | ||
|
|
af4c5a20cc | ||
|
|
d722727918 | ||
|
|
51eb9c4a58 | ||
|
|
fbb05fe4c6 | ||
|
|
0767469cd6 | ||
|
|
924f332702 | ||
|
|
02c0fbdf4e | ||
|
|
6100e65a8a | ||
|
|
0dd436a2c8 | ||
|
|
faabfe2659 | ||
|
|
76538eecd5 | ||
|
|
9a48f7d3b8 | ||
|
|
1e5024d02e | ||
|
|
ae632f3cf9 | ||
|
|
55929f4b26 | ||
|
|
fbbd1beb08 | ||
|
|
400a084d9b | ||
|
|
532e2637dc | ||
|
|
dd8bae81be | ||
|
|
30d92b40f2 | ||
|
|
fcf5081531 | ||
|
|
6f75c06889 | ||
|
|
ec9b7ee02d | ||
|
|
d9df99789d | ||
|
|
5fd36697c4 | ||
|
|
224fb7c189 | ||
|
|
2e601e75fd | ||
|
|
19f7f8c74a | ||
|
|
5955d15934 | ||
|
|
5ca272565e | ||
|
|
6f702d8b70 | ||
|
|
4b0d3f9796 | ||
|
|
d30c0f9b37 | ||
|
|
fe727a92bc | ||
|
|
034f01287b | ||
|
|
54bd62d2d9 | ||
|
|
0ca880add4 | ||
|
|
618ced700a | ||
|
|
7b6fae6247 | ||
|
|
760ba7877e | ||
|
|
17d8964db8 | ||
|
|
2107b52fe6 | ||
|
|
ecf4634601 | ||
|
|
e0f627c48b | ||
|
|
d850162da8 | ||
|
|
bda4fccb32 | ||
|
|
29741ebd68 | ||
|
|
6eeaffa8c4 | ||
|
|
7fd2d26336 | ||
|
|
fe21c5f8af | ||
|
|
f3172cd715 | ||
|
|
07d2f238d1 | ||
|
|
90f439e045 | ||
|
|
0a8e3c4452 | ||
|
|
91d4a8c767 | ||
|
|
67bbee61a0 | ||
|
|
1748663a42 | ||
|
|
ba821dbd06 | ||
|
|
a5bd385e45 | ||
|
|
b6accd6edd | ||
|
|
b0be02ea48 | ||
|
|
274bca281a | ||
|
|
9aa723bf06 | ||
|
|
dc90787030 | ||
|
|
a138418518 | ||
|
|
354c8d7b34 | ||
|
|
c831a25c92 | ||
|
|
68f4c183e2 | ||
|
|
414b939623 | ||
|
|
1c523cfdcb | ||
|
|
da93011a07 | ||
|
|
4cb0c3e77c | ||
|
|
b656c9b930 | ||
|
|
37252b1e6a | ||
|
|
6d62bcce06 | ||
|
|
990855baaa | ||
|
|
e0df8d1763 | ||
|
|
e3ed65d296 | ||
|
|
eaa64ce5f2 | ||
|
|
c64f09f830 | ||
|
|
54c762ebaf | ||
|
|
b7b42f12fe | ||
|
|
83acc2822d | ||
|
|
fdce87925b | ||
|
|
0d54c5c91f | ||
|
|
4d892552b0 | ||
|
|
36716f063f | ||
|
|
2eda6328c9 | ||
|
|
582b1c4275 | ||
|
|
27e876e0c8 | ||
|
|
4c12fd0b29 | ||
|
|
91320397e3 | ||
|
|
7cb3b37766 | ||
|
|
21fce89b15 | ||
|
|
6b01a881b0 | ||
|
|
64b340e585 | ||
|
|
a7bcc3eaf5 | ||
|
|
ef5779cedb | ||
|
|
74c399009a | ||
|
|
8c19bd1b25 | ||
|
|
e786204d06 | ||
|
|
0e60089010 | ||
|
|
f69c634c71 | ||
|
|
17948ce2ef | ||
|
|
b90bda2aab | ||
|
|
e98249f016 | ||
|
|
29d84c5e2b | ||
|
|
af4a7798b2 | ||
|
|
4f7abe3354 | ||
|
|
5707feeb42 | ||
|
|
a3475adc83 | ||
|
|
3bc744f143 | ||
|
|
f1ad1dd547 | ||
|
|
7f239bdc87 | ||
|
|
53da0d37a2 | ||
|
|
da85c222e9 | ||
|
|
53a1f50f10 | ||
|
|
d7085dad70 | ||
|
|
8b361d54e1 | ||
|
|
0bf83bab19 | ||
|
|
7d51c25430 | ||
|
|
786f005b65 | ||
|
|
458bb3f5a3 | ||
|
|
5f6a0a1bdf | ||
|
|
324580ec79 | ||
|
|
21ab3fb466 | ||
|
|
9b4566a9ec | ||
|
|
f585405498 | ||
|
|
ca34cd2f09 | ||
|
|
17ba157e67 | ||
|
|
7bb0a8649f | ||
|
|
402b503a4e | ||
|
|
dde9b5b767 | ||
|
|
41c31f9207 | ||
|
|
23c9b085e6 | ||
|
|
631ea737b0 | ||
|
|
ceb8adafc3 | ||
|
|
fa68b2b22a | ||
|
|
b098150580 | ||
|
|
d699052b6a | ||
|
|
ccd7df2f3b | ||
|
|
7a3f9f8a3b | ||
|
|
20c43fbb49 | ||
|
|
c1d3ccc2cb | ||
|
|
db75a2529f | ||
|
|
9035ebc5a3 | ||
|
|
b6fb8bb2e4 | ||
|
|
a34d47a13b | ||
|
|
72b1333bb2 | ||
|
|
5e28829325 | ||
|
|
d20c3bebed | ||
|
|
6bcd620f0e | ||
|
|
02e5a23fec | ||
|
|
d0c50c7319 | ||
|
|
d34adc04ae | ||
|
|
d48af14e10 | ||
|
|
91dad14704 | ||
|
|
3d56951504 | ||
|
|
bfaf7658df | ||
|
|
da36d22946 | ||
|
|
5415322302 | ||
|
|
fa30eefc4e | ||
|
|
6ec6b8ae38 | ||
|
|
288749e585 | ||
|
|
929021b0c0 | ||
|
|
2a3d944078 | ||
|
|
71aa26efcf | ||
|
|
025ed0860e | ||
|
|
56a01d3056 | ||
|
|
92f68df6c9 | ||
|
|
c15be9ebb9 | ||
|
|
1c2865f34c | ||
|
|
c5a0aa82ad | ||
|
|
97ddaeedd8 | ||
|
|
79826ab525 | ||
|
|
e4fb2c8363 | ||
|
|
1dbc339176 | ||
|
|
e4dae53dba | ||
|
|
ae17f5752e | ||
|
|
3cdc1c1dd4 | ||
|
|
c2fe917ad0 | ||
|
|
ac84bf6468 | ||
|
|
fdde407c9d | ||
|
|
72e355e17c | ||
|
|
1e4d119076 | ||
|
|
d71b1fe7b8 | ||
|
|
8abc01fac6 | ||
|
|
c54da74e1f | ||
|
|
54db503a65 | ||
|
|
bb7ecae874 | ||
|
|
241041f863 | ||
|
|
21b6b7c694 | ||
|
|
dfc3457358 | ||
|
|
e3c0050755 | ||
|
|
00160a48d6 | ||
|
|
5e854b1b1f | ||
|
|
6dab81a65b | ||
|
|
155b5c205c | ||
|
|
21a32bf6c6 | ||
|
|
b9e0e55764 | ||
|
|
783a979232 | ||
|
|
74cad7f48e | ||
|
|
9fe71ae92e | ||
|
|
981f2ad858 | ||
|
|
94f2858c68 | ||
|
|
7a1528d93d | ||
|
|
2df224cb0a | ||
|
|
bf6123e10f | ||
|
|
b0086cf8e0 | ||
|
|
f50a10c637 | ||
|
|
79a093e250 | ||
|
|
3cc1104b27 | ||
|
|
b1bb108585 | ||
|
|
7c7ea430cb | ||
|
|
e79dd8323e | ||
|
|
3337b344ce | ||
|
|
4ad2ede42b | ||
|
|
686dca00d8 | ||
|
|
c9cd2f2726 | ||
|
|
e2b86ff11c | ||
|
|
d3c5d59bf0 | ||
|
|
8a34472576 | ||
|
|
f2f3a11e1e | ||
|
|
aa7ce9cd59 | ||
|
|
4c678fd3a6 | ||
|
|
2ee7de451d | ||
|
|
d59c38e42f | ||
|
|
4611a8b215 | ||
|
|
cd0c760086 | ||
|
|
ae9a73488e | ||
|
|
fc29b2b480 | ||
|
|
41bfd4fc6c | ||
|
|
8b8dc00512 | ||
|
|
a2690bb146 | ||
|
|
5919d9b3c3 | ||
|
|
2c53f60000 | ||
|
|
8598673530 | ||
|
|
1bd86ce02a | ||
|
|
8cc2fe66f4 | ||
|
|
4be79484b0 | ||
|
|
ac90fe57ae | ||
|
|
e1c35e0455 | ||
|
|
0869d2d1d9 | ||
|
|
929cce16bf | ||
|
|
d7ddbf7cd7 | ||
|
|
333afdff00 | ||
|
|
405c8508e9 | ||
|
|
0119e40a76 | ||
|
|
7ef94252a7 | ||
|
|
d85a0cfc10 | ||
|
|
683e698db7 | ||
|
|
0ea0110fea | ||
|
|
11d113449a | ||
|
|
b78e1f50dc | ||
|
|
04b91380a4 | ||
|
|
851193eb5a | ||
|
|
da7c38701c | ||
|
|
7532676cf9 | ||
|
|
e34323ec99 | ||
|
|
7d51bdbcd3 | ||
|
|
6b16063692 | ||
|
|
78915ccf95 | ||
|
|
dc287c243b | ||
|
|
bd9979a87d | ||
|
|
5e907bf979 | ||
|
|
9bbb908761 | ||
|
|
3479535356 | ||
|
|
f91738176a | ||
|
|
d0b02c6ae2 | ||
|
|
c1d1260542 | ||
|
|
b50c167b40 | ||
|
|
b5d7f5e5f7 | ||
|
|
d1813a0f42 | ||
|
|
2db13433c3 | ||
|
|
f62f7121db | ||
|
|
b8d98a80ad | ||
|
|
187a531fce | ||
|
|
58b83c2844 | ||
|
|
0bd84171e2 | ||
|
|
88fd24faa9 | ||
|
|
f36904552d | ||
|
|
19d2dba6cb | ||
|
|
54059a3918 | ||
|
|
250b749b23 | ||
|
|
6d887f79cf | ||
|
|
3bde97db3f | ||
|
|
d0204a6a30 | ||
|
|
b559f2258e | ||
|
|
7206f30c57 | ||
|
|
b1f978408e | ||
|
|
3401f972ad | ||
|
|
aeb987eca7 | ||
|
|
c74f82d232 | ||
|
|
8bf7cf0218 | ||
|
|
cb2dc352af | ||
|
|
5416fc1219 | ||
|
|
9a42d93013 | ||
|
|
43a1f7e027 | ||
|
|
70f393822d | ||
|
|
121261630d | ||
|
|
a5420e85c4 | ||
|
|
8d3097de2a | ||
|
|
0c6e8ac3d8 | ||
|
|
fa5c5fc511 | ||
|
|
f8ae3c9bf0 | ||
|
|
c3919e438a | ||
|
|
1607dd6597 | ||
|
|
fe06c37cd5 | ||
|
|
79d8f6edc5 |
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
# Pycharm
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Mr Developer
|
||||||
|
.mr.developer.cfg
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|
||||||
|
# Rope
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
2
account_invoice_margin/__init__.py
Normal file
2
account_invoice_margin/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import account_invoice
|
||||||
|
from . import account_invoice_report
|
||||||
24
account_invoice_margin/__manifest__.py
Normal file
24
account_invoice_margin/__manifest__.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2015-2019 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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'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',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
152
account_invoice_margin/account_invoice.py
Normal file
152
account_invoice_margin/account_invoice.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Copyright 2015-2019 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).
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
import odoo.addons.decimal_precision as dp
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceLine(models.Model):
|
||||||
|
_inherit = 'account.invoice.line'
|
||||||
|
|
||||||
|
standard_price_company_currency = fields.Float(
|
||||||
|
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).")
|
||||||
|
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'),
|
||||||
|
help="Cost price in invoice currency in the unit of measure "
|
||||||
|
"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')
|
||||||
|
margin_company_currency = fields.Monetary(
|
||||||
|
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")
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
'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
|
||||||
|
margin_inv_cur = 0.0
|
||||||
|
margin_comp_cur = 0.0
|
||||||
|
margin_rate = 0.0
|
||||||
|
inv = il.invoice_id
|
||||||
|
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)
|
||||||
|
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 =\
|
||||||
|
il.price_subtotal - il.quantity * standard_price_inv_cur
|
||||||
|
margin_comp_cur = inv.currency_id._convert(
|
||||||
|
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':
|
||||||
|
margin_inv_cur *= -1
|
||||||
|
margin_comp_cur *= -1
|
||||||
|
il.standard_price_invoice_currency = standard_price_inv_cur
|
||||||
|
il.margin_invoice_currency = margin_inv_cur
|
||||||
|
il.margin_company_currency = margin_comp_cur
|
||||||
|
il.margin_rate = margin_rate
|
||||||
|
|
||||||
|
# We want to copy standard_price on invoice line for customer
|
||||||
|
# invoice/refunds. We can't do that via on_change of product_id,
|
||||||
|
# because it is not always played when invoice is created from code
|
||||||
|
# => we inherit write/create
|
||||||
|
# We write standard_price_company_currency even on supplier invoice/refunds
|
||||||
|
# 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'])
|
||||||
|
std_price = pp.standard_price
|
||||||
|
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
|
||||||
|
return super(AccountInvoiceLine, self).create(vals)
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
if not vals:
|
||||||
|
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'])
|
||||||
|
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'])
|
||||||
|
else:
|
||||||
|
inv_uom = False
|
||||||
|
else:
|
||||||
|
inv_uom = il.uom_id or False
|
||||||
|
std_price = 0.0
|
||||||
|
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})
|
||||||
|
return super(AccountInvoiceLine, self).write(vals)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoice(models.Model):
|
||||||
|
_inherit = 'account.invoice'
|
||||||
|
|
||||||
|
margin_invoice_currency = fields.Monetary(
|
||||||
|
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')
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
'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'])
|
||||||
|
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']
|
||||||
60
account_invoice_margin/account_invoice_report.py
Normal file
60
account_invoice_margin/account_invoice_report.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Copyright 2018-2019 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).
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceReport(models.Model):
|
||||||
|
_inherit = 'account.invoice.report'
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
_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.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'],
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# TODO check for refunds
|
||||||
|
def _sub_select(self):
|
||||||
|
select_str = super(AccountInvoiceReport, self)._sub_select()
|
||||||
|
select_str += ", SUM(ail.margin_company_currency) AS margin"
|
||||||
|
return select_str
|
||||||
|
|
||||||
|
def _select(self):
|
||||||
|
select_str = super(AccountInvoiceReport, self)._select()
|
||||||
|
select_str += ", sub.margin AS margin"
|
||||||
|
return select_str
|
||||||
51
account_invoice_margin/account_invoice_view.xml
Normal file
51
account_invoice_margin/account_invoice_view.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?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="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"/>
|
||||||
|
<div name="margin_rate" groups="base.group_no_one">
|
||||||
|
<field name="margin_rate" class="oe_inline"/> %
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="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>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Account Invoice Partner Bank Usability module for OpenERP
|
|
||||||
# Copyright (C) 2013 Akretion (http://www.akretion.com)
|
|
||||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from . import account_invoice
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Account Invoice Partner Bank Usability module for OpenERP
|
|
||||||
# Copyright (C) 2013 Akretion (http://www.akretion.com)
|
|
||||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
'name': 'Account Invoice Partner Bank Usability',
|
|
||||||
'version': '0.1',
|
|
||||||
'category': 'Accounting & Finance',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'summary': 'If the company has a single bank account, get the first one on the customer invoice',
|
|
||||||
'description': """
|
|
||||||
Account Invoice Partner Bank Usability
|
|
||||||
======================================
|
|
||||||
|
|
||||||
If the company has a single bank account, we get set this bank account by default on the customer invoice.
|
|
||||||
|
|
||||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
|
|
||||||
""",
|
|
||||||
'author': 'Akretion',
|
|
||||||
'website': 'http://www.akretion.com',
|
|
||||||
'depends': ['account'],
|
|
||||||
'data': [],
|
|
||||||
'images': [],
|
|
||||||
'installable': True,
|
|
||||||
'active': False,
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Account Invoice Partner Bank Usability module for OpenERP
|
|
||||||
# Copyright (C) 2013 Akretion (http://www.akretion.com)
|
|
||||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from openerp.osv import orm
|
|
||||||
|
|
||||||
|
|
||||||
class account_invoice(orm.Model):
|
|
||||||
_inherit = "account.invoice"
|
|
||||||
|
|
||||||
def invoice_out_get_first_partner_bank(self, cr, uid, context=None):
|
|
||||||
'''Get the first bank account of your company on customer invoice
|
|
||||||
if your company only has one bank account'''
|
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
res_partner_bank_id = False
|
|
||||||
if context.get('type') == 'out_invoice' or \
|
|
||||||
context.get('inv_type') == 'out_invoice':
|
|
||||||
cur_user = self.pool['res.users'].browse(
|
|
||||||
cr, uid, uid, context=context)
|
|
||||||
partner_banks = cur_user.company_id.partner_id.bank_ids
|
|
||||||
if partner_banks and len(partner_banks) == 1:
|
|
||||||
res_partner_bank_id = partner_banks[0].id
|
|
||||||
return res_partner_bank_id
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'partner_bank_id': invoice_out_get_first_partner_bank,
|
|
||||||
}
|
|
||||||
39
account_invoice_update_wizard/README.rst
Normal file
39
account_invoice_update_wizard/README.rst
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
|
||||||
|
Account Invoice Update Wizard
|
||||||
|
=============================
|
||||||
|
|
||||||
|
This module adds a button *Update Invoice* on Customer and Supplier invoices in
|
||||||
|
Open or Paid state. This button starts a wizard which allows the user to update
|
||||||
|
non-legal fields of the invoice:
|
||||||
|
|
||||||
|
* Source Document
|
||||||
|
* Reference/Description
|
||||||
|
* Payment terms (update allowed only to a payment term with same number of terms
|
||||||
|
of the same amount and on invoices without any payment)
|
||||||
|
* Bank Account
|
||||||
|
* Salesman
|
||||||
|
* Notes
|
||||||
|
* Description of invoice lines
|
||||||
|
* Analytic account
|
||||||
|
* Analytic tags
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues
|
||||||
|
<https://github.com/akretion/odoo-usability/issues>`_. In case of trouble, please
|
||||||
|
check there if your issue has already been reported. If you spotted it first,
|
||||||
|
help us smash it by providing detailed and welcomed feedback.
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Alexis de Lattre <alexis.delattre@akretion.com>
|
||||||
|
* Florian da Costa <florian.dacosta@akretion.com>
|
||||||
|
* Matthieu Dietrich <matthieu.dietrich@camptocamp.com>
|
||||||
|
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
|
||||||
|
* Mykhailo Panarin <m.panarin@mobilunity.com>
|
||||||
|
* Artem Kostyuk <a.kostyuk@mobilunity.com>
|
||||||
2
account_invoice_update_wizard/__init__.py
Normal file
2
account_invoice_update_wizard/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import models
|
||||||
|
from . import wizard
|
||||||
21
account_invoice_update_wizard/__manifest__.py
Normal file
21
account_invoice_update_wizard/__manifest__.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
# Copyright 2018-2019 Camptocamp
|
||||||
|
# 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',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'wizard/account_invoice_update_view.xml',
|
||||||
|
'views/account_invoice.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
1
account_invoice_update_wizard/models/__init__.py
Normal file
1
account_invoice_update_wizard/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import account_invoice
|
||||||
22
account_invoice_update_wizard/models/account_invoice.py
Normal file
22
account_invoice_update_wizard/models/account_invoice.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoice(models.Model):
|
||||||
|
_inherit = 'account.invoice'
|
||||||
|
|
||||||
|
def prepare_update_wizard(self):
|
||||||
|
self.ensure_one()
|
||||||
|
wizard = self.env['account.invoice.update']
|
||||||
|
res = wizard._prepare_default_get(self)
|
||||||
|
action = self.env.ref(
|
||||||
|
'account_invoice_update_wizard.account_invoice_update_action'
|
||||||
|
).read()[0]
|
||||||
|
action['name'] = "Update Wizard"
|
||||||
|
action['res_id'] = wizard.create(res).id
|
||||||
|
return action
|
||||||
|
|
||||||
1
account_invoice_update_wizard/tests/__init__.py
Normal file
1
account_invoice_update_wizard/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import test_account_invoice_update_wizard
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
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.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': 'の',
|
||||||
|
})
|
||||||
|
|
||||||
|
def create_wizard(self, invoice):
|
||||||
|
res = self.invoice1.prepare_update_wizard()
|
||||||
|
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
|
||||||
|
after the invoice has been approved.
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- update the move line
|
||||||
|
- create a new analytic line.
|
||||||
|
"""
|
||||||
|
self.invoice1.action_invoice_open()
|
||||||
|
self.create_wizard(self.invoice1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
after the invoice has been approved.
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- update the move line
|
||||||
|
- update the existing analytic line."""
|
||||||
|
self.inv_line1.account_analytic_id = self.aa2
|
||||||
|
|
||||||
|
self.invoice1.action_invoice_open()
|
||||||
|
self.create_wizard(self.invoice1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
after the invoice has been approved where both
|
||||||
|
lines were grouped in the same move line.
|
||||||
|
|
||||||
|
This will raise an error.
|
||||||
|
"""
|
||||||
|
self.invoice1.journal_id.group_invoice_lines = True
|
||||||
|
|
||||||
|
self.inv_line2.product_id = self.product16
|
||||||
|
self.inv_line2.unit_price = 42.0
|
||||||
|
|
||||||
|
self.invoice1.action_invoice_open()
|
||||||
|
self.create_wizard(self.invoice1)
|
||||||
|
|
||||||
|
line1 = self.wiz.line_ids[0]
|
||||||
|
line1.account_analytic_id = self.aa1
|
||||||
|
with self.assertRaises(UserError):
|
||||||
|
self.wiz.run()
|
||||||
|
|
||||||
|
def test_add_analytic_tags_line1(self):
|
||||||
|
""" Add analytic tags on an invoice line
|
||||||
|
after the invoice has been approved.
|
||||||
|
|
||||||
|
This will update move line.
|
||||||
|
"""
|
||||||
|
self.invoice1.action_invoice_open()
|
||||||
|
self.create_wizard(self.invoice1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
after the invoice has been approved.
|
||||||
|
|
||||||
|
It will update move line and analytic line
|
||||||
|
"""
|
||||||
|
self.inv_line1.account_analytic_id = self.aa2
|
||||||
|
self.inv_line1.analytic_tag_ids = self.atag1
|
||||||
|
|
||||||
|
self.invoice1.action_invoice_open()
|
||||||
|
self.create_wizard(self.invoice1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
after the invoice has been approved.
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- update move line
|
||||||
|
- create an analytic line
|
||||||
|
"""
|
||||||
|
self.invoice1.action_invoice_open()
|
||||||
|
self.create_wizard(self.invoice1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
after the invoice has been approved.
|
||||||
|
|
||||||
|
This will raise an error as it is not implemented.
|
||||||
|
"""
|
||||||
|
self.inv_line1.account_analytic_id = self.aa2
|
||||||
|
|
||||||
|
self.invoice1.action_invoice_open()
|
||||||
|
self.create_wizard(self.invoice1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
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)
|
||||||
|
self.assertFalse(related_ml.analytic_account_id)
|
||||||
|
self.assertFalse(related_ml.analytic_line_ids)
|
||||||
29
account_invoice_update_wizard/views/account_invoice.xml
Normal file
29
account_invoice_update_wizard/views/account_invoice.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?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="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>
|
||||||
|
</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="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>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
1
account_invoice_update_wizard/wizard/__init__.py
Normal file
1
account_invoice_update_wizard/wizard/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import account_invoice_update
|
||||||
302
account_invoice_update_wizard/wizard/account_invoice_update.py
Normal file
302
account_invoice_update_wizard/wizard/account_invoice_update.py
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
# Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
# Copyright 2018-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
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceUpdate(models.TransientModel):
|
||||||
|
_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')
|
||||||
|
line_ids = fields.One2many(
|
||||||
|
'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']
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _m2o_fields2update(self):
|
||||||
|
return ['payment_term_id', 'user_id', 'partner_bank_id']
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_default_get(self, invoice):
|
||||||
|
res = {'invoice_id': invoice.id, 'line_ids': []}
|
||||||
|
for sfield in self._simple_fields2update():
|
||||||
|
res[sfield] = invoice[sfield]
|
||||||
|
for m2ofield in self._m2o_fields2update():
|
||||||
|
res[m2ofield] = invoice[m2ofield].id or False
|
||||||
|
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,
|
||||||
|
}])
|
||||||
|
return res
|
||||||
|
|
||||||
|
@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])]"
|
||||||
|
else:
|
||||||
|
res['domain']['partner_bank_id'] =\
|
||||||
|
"[('partner_id', '=', partner_id)]"
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _prepare_invoice(self):
|
||||||
|
vals = {}
|
||||||
|
inv = self.invoice_id
|
||||||
|
for sfield in self._simple_fields2update():
|
||||||
|
if self[sfield] != inv[sfield]:
|
||||||
|
vals[sfield] = self[sfield]
|
||||||
|
for m2ofield in self._m2o_fields2update():
|
||||||
|
if self[m2ofield] != inv[m2ofield]:
|
||||||
|
vals[m2ofield] = self[m2ofield].id or False
|
||||||
|
if 'payment_term_id' in vals:
|
||||||
|
pterm_list = self.payment_term_id.compute(
|
||||||
|
value=1, date_ref=inv.date_invoice)[0]
|
||||||
|
if pterm_list:
|
||||||
|
vals['date_due'] = max(line[0] for line in pterm_list)
|
||||||
|
return vals
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _line_simple_fields2update(self):
|
||||||
|
return ["name",]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _line_m2o_fields2update(self):
|
||||||
|
return ["account_analytic_id",]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _line_m2m_fields2update(self):
|
||||||
|
return ["analytic_tag_ids",]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_invoice_line(self, line):
|
||||||
|
vals = {}
|
||||||
|
for field in self._line_simple_fields2update():
|
||||||
|
if line[field] != line.invoice_line_id[field]:
|
||||||
|
vals[field] = line[field]
|
||||||
|
for field in self._line_m2o_fields2update():
|
||||||
|
if line[field] != line.invoice_line_id[field]:
|
||||||
|
vals[field] = line[field].id
|
||||||
|
for field in self._line_m2m_fields2update():
|
||||||
|
if line[field] != line.invoice_line_id[field]:
|
||||||
|
vals[field] = [(6, 0, line[field].ids)]
|
||||||
|
return vals
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _prepare_move(self):
|
||||||
|
mvals = {}
|
||||||
|
inv = self.invoice_id
|
||||||
|
ini_ref = inv.move_id.ref
|
||||||
|
ref = inv.reference or inv.name
|
||||||
|
if ini_ref != ref:
|
||||||
|
mvals['ref'] = ref
|
||||||
|
return mvals
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _get_matching_inv_line(self, move_line):
|
||||||
|
""" Find matching invoice line by product """
|
||||||
|
# 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)
|
||||||
|
if len(inv_line) != 1:
|
||||||
|
raise UserError(
|
||||||
|
"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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)]
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)]
|
||||||
|
return alvals
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _update_payment_term_move(self):
|
||||||
|
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):
|
||||||
|
# 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]
|
||||||
|
new_pterm = {} # key = int(amount * 100), value = [date1, date2]
|
||||||
|
for entry in term_res:
|
||||||
|
amount = int(entry[1] * 10 * prec)
|
||||||
|
if amount in new_pterm:
|
||||||
|
new_pterm[amount].append(entry[0])
|
||||||
|
else:
|
||||||
|
new_pterm[amount] = [entry[0]]
|
||||||
|
mlines = {} # key = int(amount * 100), value : [line1, line2]
|
||||||
|
for line in inv.move_id.line_ids:
|
||||||
|
if line.account_id == inv.account_id:
|
||||||
|
amount = int(abs(line.credit - line.debit) * 10 * prec)
|
||||||
|
if amount in mlines:
|
||||||
|
mlines[amount].append(line)
|
||||||
|
else:
|
||||||
|
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))
|
||||||
|
for line in lines:
|
||||||
|
line.date_maturity = new_pterm[iamount].pop()
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def run(self):
|
||||||
|
self.ensure_one()
|
||||||
|
inv = self.invoice_id
|
||||||
|
updated = False
|
||||||
|
# re-write date_maturity on move line
|
||||||
|
self._update_payment_term_move()
|
||||||
|
ivals = self._prepare_invoice()
|
||||||
|
if ivals:
|
||||||
|
updated = True
|
||||||
|
inv.write(ivals)
|
||||||
|
if inv.move_id:
|
||||||
|
mvals = self._prepare_move()
|
||||||
|
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)
|
||||||
|
):
|
||||||
|
if ml.credit == 0.0:
|
||||||
|
continue
|
||||||
|
inv_line = self._get_matching_inv_line(ml)
|
||||||
|
mlvals = self._prepare_move_line(inv_line)
|
||||||
|
if mlvals:
|
||||||
|
updated = True
|
||||||
|
ml.write(mlvals)
|
||||||
|
aalines = ml.analytic_line_ids
|
||||||
|
alvals = self._prepare_analytic_line(inv_line)
|
||||||
|
if aalines and alvals:
|
||||||
|
updated = True
|
||||||
|
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)
|
||||||
|
# remove existing analytic line
|
||||||
|
to_remove_aalines.unlink()
|
||||||
|
else:
|
||||||
|
aalines.write(alvals)
|
||||||
|
elif 'account_id' in alvals:
|
||||||
|
# Create analytic lines if analytic account
|
||||||
|
# is added later
|
||||||
|
ml.create_analytic_lines()
|
||||||
|
for line in self.line_ids:
|
||||||
|
ilvals = self._prepare_invoice_line(line)
|
||||||
|
if ilvals:
|
||||||
|
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.'))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceLineUpdate(models.TransientModel):
|
||||||
|
_name = 'account.invoice.line.update'
|
||||||
|
_description = 'Update non-legal fields of invoice lines'
|
||||||
|
|
||||||
|
parent_id = fields.Many2one(
|
||||||
|
'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.")
|
||||||
|
quantity = fields.Float(
|
||||||
|
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'))
|
||||||
|
account_analytic_id = fields.Many2one(
|
||||||
|
'account.analytic.account', string='Analytic Account')
|
||||||
|
analytic_tag_ids = fields.Many2many(
|
||||||
|
'account.analytic.tag', string='Analytic Tags')
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?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">
|
||||||
|
<field name="model">account.invoice.update</field>
|
||||||
|
<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"/>
|
||||||
|
</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>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button name="run" type="object" class="oe_highlight" string="Update"/>
|
||||||
|
<button special="cancel" string="Cancel" class="oe_link"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account_invoice_update_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Invoice Update Wizard</field>
|
||||||
|
<field name="res_model">account.invoice.update</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
4
account_usability/__init__.py
Normal file
4
account_usability/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from . import account
|
||||||
|
#from . import account_invoice_report
|
||||||
|
from . import partner
|
||||||
|
from . import wizard
|
||||||
49
account_usability/__manifest__.py
Normal file
49
account_usability/__manifest__.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Copyright 2015-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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Account Usability',
|
||||||
|
'version': '10.0.1.0.0',
|
||||||
|
'category': 'Accounting & Finance',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Small usability enhancements in account module',
|
||||||
|
'description': """
|
||||||
|
Account Usability
|
||||||
|
=================
|
||||||
|
|
||||||
|
The usability enhancements include:
|
||||||
|
* show the supplier invoice number in the tree view of supplier invoices
|
||||||
|
* 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.
|
||||||
|
* fast search on *Reconcile Ref* for account move line.
|
||||||
|
* disable reconciliation "guessing"
|
||||||
|
* add sale dates to invoice report to be compliant with
|
||||||
|
https://www.service-public.fr/professionnels-entreprises/vosdroits/F31808
|
||||||
|
|
||||||
|
Together with this module, I recommend the use of the following modules:
|
||||||
|
* account_invoice_supplier_ref_unique (OCA project account-invoicing)
|
||||||
|
* account_move_line_no_default_search (OCA project account-financial-tools)
|
||||||
|
* invoice_fiscal_position_update (OCA project account-invoicing)
|
||||||
|
|
||||||
|
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
||||||
|
""",
|
||||||
|
'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': [
|
||||||
|
'account_view.xml',
|
||||||
|
'account_report.xml',
|
||||||
|
'account_invoice_report_view.xml',
|
||||||
|
'partner_view.xml',
|
||||||
|
'wizard/account_invoice_mark_sent_view.xml',
|
||||||
|
'report/invoice_report.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
661
account_usability/account.py
Normal file
661
account_usability/account.py
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
# Copyright 2015-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).
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.tools import float_compare, float_is_zero
|
||||||
|
from odoo.tools.misc import formatLang
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
from odoo import SUPERUSER_ID
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoice(models.Model):
|
||||||
|
_inherit = 'account.invoice'
|
||||||
|
|
||||||
|
origin = fields.Char(track_visibility='onchange')
|
||||||
|
reference = fields.Char(track_visibility='onchange')
|
||||||
|
sent = fields.Boolean(track_visibility='onchange')
|
||||||
|
date_invoice = fields.Date(track_visibility='onchange')
|
||||||
|
date_due = fields.Date(track_visibility='onchange')
|
||||||
|
payment_term_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
account_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
journal_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
partner_bank_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
fiscal_position_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
amount_total = fields.Monetary(track_visibility='onchange')
|
||||||
|
# I want to see the number of cancelled invoice in chatter
|
||||||
|
move_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
# for invoice report
|
||||||
|
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)
|
||||||
|
sale_dates = fields.Char(
|
||||||
|
compute="_compute_sales_dates", readonly=True,
|
||||||
|
help="This information appears on invoice qweb report "
|
||||||
|
"(you may use it for your own report)")
|
||||||
|
|
||||||
|
def _compute_has_discount(self):
|
||||||
|
prec = self.env['decimal.precision'].precision_get('Discount')
|
||||||
|
for inv in self:
|
||||||
|
has_discount = False
|
||||||
|
for line in inv.invoice_line_ids:
|
||||||
|
if 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']
|
||||||
|
for inv in self:
|
||||||
|
if iao.search([
|
||||||
|
('res_model', '=', 'account.invoice'),
|
||||||
|
('res_id', '=', inv.id),
|
||||||
|
('type', '=', 'binary'),
|
||||||
|
('company_id', '=', inv.company_id.id)], limit=1):
|
||||||
|
inv.has_attachment = True
|
||||||
|
else:
|
||||||
|
inv.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.invoice'),
|
||||||
|
('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))]
|
||||||
|
return res
|
||||||
|
|
||||||
|
# when you have an invoice created from a lot of sale orders, the 'name'
|
||||||
|
# field is very large, which makes the name_get() of that invoice very big
|
||||||
|
# which screws-up the form view of that invoice because of the link at the
|
||||||
|
# top of the screen
|
||||||
|
# That's why we have to cut the name_get() when it's too long
|
||||||
|
def name_get(self):
|
||||||
|
old_res = super(AccountInvoice, self).name_get()
|
||||||
|
res = []
|
||||||
|
for old_re in old_res:
|
||||||
|
name = old_re[1]
|
||||||
|
if name and len(name) > 100:
|
||||||
|
# nice cut
|
||||||
|
name = u'%s ...' % ', '.join(name.split(', ')[:3])
|
||||||
|
# if not enough, hard cut
|
||||||
|
if len(name) > 120:
|
||||||
|
name = u'%s ...' % old_re[1][:120]
|
||||||
|
res.append((old_re[0], name))
|
||||||
|
return res
|
||||||
|
|
||||||
|
# I really hate to see a "/" in the 'name' field of the account.move.line
|
||||||
|
# generated from customer invoices linked to the partners' account because:
|
||||||
|
# 1) the label of an account move line is an important field, we can't
|
||||||
|
# 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
|
||||||
|
# @api.multi
|
||||||
|
# def action_move_create(self):
|
||||||
|
# res = super(AccountInvoice, self).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.invoice.line'].search([
|
||||||
|
('invoice_id', 'in', self.ids), ('quantity', '=', 0)])
|
||||||
|
lines.unlink()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def fix_invoice_attachment_filename(self):
|
||||||
|
# This script is designed to fix attachment of invoices
|
||||||
|
# badly generated by Odoo v8. I found this problem in Nov 2018 at
|
||||||
|
# Encres Dubuit when investigating a bug where Odoo would create a
|
||||||
|
# new attachment when printing an old invoice that already had the
|
||||||
|
# PDF of the invoice as attachment
|
||||||
|
logger.info('START fix customer invoice attachment filename')
|
||||||
|
# Run this script as admin to fix problem in all companies
|
||||||
|
self = self.sudo()
|
||||||
|
attachs = self.env['ir.attachment'].search([
|
||||||
|
('res_model', '=', 'account.invoice'),
|
||||||
|
('res_id', '!=', False),
|
||||||
|
('type', '=', 'binary'),
|
||||||
|
('name', '=like', 'INV%.pdf'),
|
||||||
|
('datas_fname', '=like', 'INV%.pdf.pdf')])
|
||||||
|
for attach in attachs:
|
||||||
|
inv = self.browse(attach.res_id)
|
||||||
|
if inv.type in ('out_invoice', 'out_refund'):
|
||||||
|
attach.datas_fname = attach.name
|
||||||
|
logger.info(
|
||||||
|
'Fixed field datas_fname of attachment ID %s name %s',
|
||||||
|
attach.id, attach.name)
|
||||||
|
logger.info('END fix customer invoice attachment filename')
|
||||||
|
|
||||||
|
# for report
|
||||||
|
def py3o_lines_layout(self):
|
||||||
|
self.ensure_one()
|
||||||
|
res = []
|
||||||
|
has_sections = False
|
||||||
|
subtotal = 0.0
|
||||||
|
sign = self.type == 'out_refund' and -1 or 1
|
||||||
|
for line in self.invoice_line_ids:
|
||||||
|
if line.display_type == 'line_section':
|
||||||
|
# insert line
|
||||||
|
if has_sections:
|
||||||
|
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})
|
||||||
|
if has_sections: # insert last subtotal line
|
||||||
|
res.append({'subtotal': subtotal})
|
||||||
|
# res:
|
||||||
|
# [
|
||||||
|
# {'line': account_invoice_line(1) with display_type=='line_section'},
|
||||||
|
# {'line': account_invoice_line(2) without display_type},
|
||||||
|
# {'line': account_invoice_line(3) without display_type},
|
||||||
|
# {'line': account_invoice_line(4) with display_type=='line_note'},
|
||||||
|
# {'subtotal': 8932.23},
|
||||||
|
# ]
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _compute_sales_dates(self):
|
||||||
|
""" 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')
|
||||||
|
lang = inv.partner_id.commercial_partner_id.lang
|
||||||
|
date_format = self.env["res.lang"]._lang_get(
|
||||||
|
lang or "").date_format
|
||||||
|
dates = ["%s%s" % (
|
||||||
|
x.name,
|
||||||
|
x.confirmation_date and " (%s)" %
|
||||||
|
# only when confirmation_date display it
|
||||||
|
x.confirmation_date.strftime(date_format) or "")
|
||||||
|
for x in sales]
|
||||||
|
inv.sale_dates = ", ".join(dates)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceLine(models.Model):
|
||||||
|
_inherit = 'account.invoice.line'
|
||||||
|
|
||||||
|
# In the 'account' module, we have related stored field for:
|
||||||
|
# company_id, partner_id, currency_id
|
||||||
|
invoice_type = fields.Selection(store=True)
|
||||||
|
date_invoice = fields.Date(
|
||||||
|
related='invoice_id.date_invoice', store=True, readonly=True)
|
||||||
|
commercial_partner_id = fields.Many2one(
|
||||||
|
related='invoice_id.partner_id.commercial_partner_id',
|
||||||
|
store=True, readonly=True, compute_sudo=True)
|
||||||
|
state = fields.Selection(
|
||||||
|
related='invoice_id.state', store=True, readonly=True,
|
||||||
|
string='Invoice State')
|
||||||
|
invoice_number = fields.Char(
|
||||||
|
related='invoice_id.move_id.name', store=True, readonly=True,
|
||||||
|
string='Invoice Number')
|
||||||
|
|
||||||
|
|
||||||
|
class AccountJournal(models.Model):
|
||||||
|
_inherit = 'account.journal'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@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'):
|
||||||
|
for journal in self:
|
||||||
|
res.append((journal.id, journal.code))
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
for journal in self:
|
||||||
|
currency = journal.currency_id or\
|
||||||
|
journal.company_id.currency_id
|
||||||
|
name = "[%s] %s (%s)" % (
|
||||||
|
journal.code, journal.name, currency.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')
|
||||||
|
for jrl in self:
|
||||||
|
if jrl.type in ('bank', 'cash'):
|
||||||
|
if (
|
||||||
|
jrl.default_debit_account_id and
|
||||||
|
jrl.default_debit_account_id.user_type_id !=
|
||||||
|
bank_acc_type):
|
||||||
|
raise ValidationError(_(
|
||||||
|
"On journal '%s', the default debit account '%s' "
|
||||||
|
"should be configured with Type = 'Bank and Cash'.")
|
||||||
|
% (jrl.display_name,
|
||||||
|
jrl.default_debit_account_id.display_name))
|
||||||
|
if (
|
||||||
|
jrl.default_credit_account_id and
|
||||||
|
jrl.default_credit_account_id.user_type_id !=
|
||||||
|
bank_acc_type):
|
||||||
|
raise ValidationError(_(
|
||||||
|
"On journal '%s', the default credit account '%s' "
|
||||||
|
"should be configured with Type = 'Bank and Cash'.")
|
||||||
|
% (jrl.display_name,
|
||||||
|
jrl.default_credit_account_id.display_name))
|
||||||
|
|
||||||
|
|
||||||
|
class AccountAccount(models.Model):
|
||||||
|
_inherit = 'account.account'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@api.depends('name', 'code')
|
||||||
|
def name_get(self):
|
||||||
|
if self._context.get('account_account_show_code_only'):
|
||||||
|
res = []
|
||||||
|
for record in self:
|
||||||
|
res.append((record.id, record.code))
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
return super(AccountAccount, self).name_get()
|
||||||
|
|
||||||
|
# https://github.com/odoo/odoo/issues/23040
|
||||||
|
# TODO mig to v12
|
||||||
|
def fix_bank_account_types(self):
|
||||||
|
aao = self.env['account.account']
|
||||||
|
companies = self.env['res.company'].search([])
|
||||||
|
if len(companies) > 1 and self.env.user.id != SUPERUSER_ID:
|
||||||
|
raise UserError(
|
||||||
|
"In multi-company setups, you should run this "
|
||||||
|
"script as admin user")
|
||||||
|
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')
|
||||||
|
journal_accounts_bank_type = aao
|
||||||
|
for journal in journals:
|
||||||
|
for account in [
|
||||||
|
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)
|
||||||
|
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')
|
||||||
|
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)
|
||||||
|
logger.info("END of the script 'fix bank and cash account types'")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# TODO mig to v12
|
||||||
|
@api.model
|
||||||
|
def create_account_groups(self, level=2, name_prefix=u'Comptes '):
|
||||||
|
'''Should be launched by a script. Make sure the account_group module is installed
|
||||||
|
(the account_usability module doesn't depend on it currently'''
|
||||||
|
assert level >= 1
|
||||||
|
assert isinstance(level, int)
|
||||||
|
companies = self.env['res.company'].search([])
|
||||||
|
if len(companies) > 1:
|
||||||
|
logger.info(
|
||||||
|
'Multi-company detected: running script create_account_groups '
|
||||||
|
'as admin')
|
||||||
|
self = self.sudo()
|
||||||
|
ago = self.env['account.group']
|
||||||
|
groups = ago.search([])
|
||||||
|
if groups:
|
||||||
|
raise UserError(_("Some account groups already exists"))
|
||||||
|
accounts = self.search([])
|
||||||
|
struct = {'childs': {}}
|
||||||
|
for account in accounts:
|
||||||
|
assert len(account.code) > level
|
||||||
|
n = 1
|
||||||
|
parent = struct
|
||||||
|
gparent = False
|
||||||
|
while n <= level:
|
||||||
|
group_code = account.code[:n]
|
||||||
|
if group_code not in parent['childs']:
|
||||||
|
new_group = ago.create({
|
||||||
|
'name': u'%s%s' % (name_prefix or '', group_code),
|
||||||
|
'code_prefix': group_code,
|
||||||
|
'parent_id': gparent and gparent.id or False,
|
||||||
|
})
|
||||||
|
parent['childs'][group_code] = {'obj': new_group, 'childs': {}}
|
||||||
|
parent = parent['childs'][group_code]
|
||||||
|
gparent = parent['obj']
|
||||||
|
n += 1
|
||||||
|
account.group_id = gparent.id
|
||||||
|
|
||||||
|
|
||||||
|
class AccountAnalyticAccount(models.Model):
|
||||||
|
_inherit = 'account.analytic.account'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def name_get(self):
|
||||||
|
if self._context.get('analytic_account_show_code_only'):
|
||||||
|
res = []
|
||||||
|
for record in self:
|
||||||
|
res.append((record.id, record.code or record.name))
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
return super(AccountAnalyticAccount, self).name_get()
|
||||||
|
|
||||||
|
_sql_constraints = [(
|
||||||
|
'code_company_unique',
|
||||||
|
'unique(code, company_id)',
|
||||||
|
'An analytic account with the same code already '
|
||||||
|
'exists in the same company!')]
|
||||||
|
|
||||||
|
|
||||||
|
class AccountMove(models.Model):
|
||||||
|
_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)]})
|
||||||
|
date = fields.Date(copy=False)
|
||||||
|
default_account_id = fields.Many2one(
|
||||||
|
related='journal_id.default_debit_account_id', readonly=True)
|
||||||
|
default_credit = fields.Float(
|
||||||
|
compute='_compute_default_credit_debit', readonly=True)
|
||||||
|
default_debit = fields.Float(
|
||||||
|
compute='_compute_default_credit_debit', readonly=True)
|
||||||
|
|
||||||
|
@api.depends('line_ids.credit', 'line_ids.debit')
|
||||||
|
def _compute_default_credit_debit(self):
|
||||||
|
for move in self:
|
||||||
|
total_debit = total_credit = default_debit = default_credit = 0.0
|
||||||
|
for l in move.line_ids:
|
||||||
|
total_debit += l.debit
|
||||||
|
total_credit += l.credit
|
||||||
|
# I could use float_compare, but I don't think it's really needed
|
||||||
|
# in this context
|
||||||
|
if total_debit > total_credit:
|
||||||
|
default_credit = total_debit - total_credit
|
||||||
|
else:
|
||||||
|
default_debit = total_credit - total_debit
|
||||||
|
move.default_credit = default_credit
|
||||||
|
move.default_debit = default_debit
|
||||||
|
|
||||||
|
|
||||||
|
class AccountMoveLine(models.Model):
|
||||||
|
_inherit = 'account.move.line'
|
||||||
|
# Native order:
|
||||||
|
# _order = "date desc, id desc"
|
||||||
|
# Problem: when you manually create a journal entry, the
|
||||||
|
# order of the lines is inverted when you save ! It is quite annoying for
|
||||||
|
# the user...
|
||||||
|
_order = "date desc, id asc"
|
||||||
|
|
||||||
|
# Update field only to add a string (there is no string in account module)
|
||||||
|
invoice_id = fields.Many2one(string='Invoice')
|
||||||
|
account_reconcile = fields.Boolean(
|
||||||
|
related='account_id.reconcile', readonly=True)
|
||||||
|
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)
|
||||||
|
|
||||||
|
@api.onchange('credit')
|
||||||
|
def _credit_onchange(self):
|
||||||
|
prec = self.env['decimal.precision'].precision_get('Account')
|
||||||
|
if (
|
||||||
|
not float_is_zero(self.credit, precision_digits=prec) and
|
||||||
|
not float_is_zero(self.debit, precision_digits=prec)):
|
||||||
|
self.debit = 0
|
||||||
|
|
||||||
|
@api.onchange('debit')
|
||||||
|
def _debit_onchange(self):
|
||||||
|
prec = self.env['decimal.precision'].precision_get('Account')
|
||||||
|
if (
|
||||||
|
not float_is_zero(self.debit, precision_digits=prec) and
|
||||||
|
not float_is_zero(self.credit, precision_digits=prec)):
|
||||||
|
self.credit = 0
|
||||||
|
|
||||||
|
@api.onchange('currency_id', 'amount_currency')
|
||||||
|
def _amount_currency_change(self):
|
||||||
|
prec = self.env['decimal.precision'].precision_get('Account')
|
||||||
|
if (
|
||||||
|
self.currency_id and
|
||||||
|
self.amount_currency and
|
||||||
|
float_is_zero(self.credit, precision_digits=prec) and
|
||||||
|
float_is_zero(self.debit, precision_digits=prec)):
|
||||||
|
date = self.date or None
|
||||||
|
amount_company_currency = self.currency_id.with_context(
|
||||||
|
date=date).compute(
|
||||||
|
self.amount_currency, self.env.user.company_id.currency_id)
|
||||||
|
precision = self.env['decimal.precision'].precision_get('Account')
|
||||||
|
if float_compare(
|
||||||
|
amount_company_currency, 0,
|
||||||
|
precision_digits=precision) == -1:
|
||||||
|
self.debit = amount_company_currency * -1
|
||||||
|
else:
|
||||||
|
self.credit = amount_company_currency
|
||||||
|
|
||||||
|
def show_account_move_form(self):
|
||||||
|
self.ensure_one()
|
||||||
|
action = self.env['ir.actions.act_window'].for_xml_id(
|
||||||
|
'account', 'action_move_line_form')
|
||||||
|
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')
|
||||||
|
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])
|
||||||
|
line.reconcile_string = rec_str
|
||||||
|
|
||||||
|
|
||||||
|
class AccountPartialReconcile(models.Model):
|
||||||
|
_inherit = "account.partial.reconcile"
|
||||||
|
_rec_name = "id"
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for rec in self:
|
||||||
|
# There is no seq for partial rec, so I simulate one with the ID
|
||||||
|
# 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)
|
||||||
|
res.append((rec.id, name))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBankStatement(models.Model):
|
||||||
|
_inherit = 'account.bank.statement'
|
||||||
|
|
||||||
|
start_date = fields.Date(
|
||||||
|
compute='_compute_dates', string='Start Date', readonly=True,
|
||||||
|
store=True)
|
||||||
|
end_date = fields.Date(
|
||||||
|
compute='_compute_dates', string='End Date', readonly=True,
|
||||||
|
store=True)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@api.depends('line_ids.date')
|
||||||
|
def _compute_dates(self):
|
||||||
|
for st in self:
|
||||||
|
dates = [line.date for line in st.line_ids]
|
||||||
|
st.start_date = dates and min(dates) or False
|
||||||
|
st.end_date = dates and max(dates) or False
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@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, statement.end_date)
|
||||||
|
res.append((statement.id, name))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBankStatementLine(models.Model):
|
||||||
|
_inherit = 'account.bank.statement.line'
|
||||||
|
# Native order is:
|
||||||
|
# _order = 'statement_id 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
|
||||||
|
# in the following scenario : move line 'x' has been "guessed" by OpenERP
|
||||||
|
# to be reconciled with a statement line 'Y' at the end of the bank
|
||||||
|
# statement, but it is a mistake because it should be reconciled with
|
||||||
|
# statement line 'B' at the beginning of the bank statement
|
||||||
|
# When the user is on statement line 'B', he tries to select
|
||||||
|
# move line 'x', but it can't find it... because it is already "reserved"
|
||||||
|
# by the guess of OpenERP for statement line 'Y' ! To solve this problem,
|
||||||
|
# the user must go to statement line 'Y' and unselect move line 'x'
|
||||||
|
# and then come back on statement line 'B' and select move line 'A'...
|
||||||
|
# but non super-expert users can't do that because it is impossible to
|
||||||
|
# figure out that the fact that the user can't find move line 'x'
|
||||||
|
# is caused by this.
|
||||||
|
# Set search_reconciliation_proposition to False by default
|
||||||
|
# TODO: re-write in v10
|
||||||
|
# def get_data_for_reconciliations(
|
||||||
|
# self, cr, uid, ids, excluded_ids=None,
|
||||||
|
# search_reconciliation_proposition=False, context=None):
|
||||||
|
# # Make variable name shorted for PEP8 !
|
||||||
|
# search_rec_prop = search_reconciliation_proposition
|
||||||
|
# return super(AccountBankStatementLine, self).\
|
||||||
|
# get_data_for_reconciliations(
|
||||||
|
# cr, uid, ids, excluded_ids=excluded_ids,
|
||||||
|
# search_reconciliation_proposition=search_rec_prop,
|
||||||
|
# context=context)
|
||||||
|
|
||||||
|
def _prepare_reconciliation_move(self, move_ref):
|
||||||
|
vals = super(AccountBankStatementLine, self).\
|
||||||
|
_prepare_reconciliation_move(move_ref)
|
||||||
|
# By default, ref contains the name of the statement + name of the
|
||||||
|
# statement line. It causes 2 problems:
|
||||||
|
# 1) The 'ref' field is too big
|
||||||
|
# 2) The name of the statement line is already written in the name of
|
||||||
|
# the move line -> not useful to have the info 2 times
|
||||||
|
# In the end, I think it's better to just put nothing (we could write
|
||||||
|
# the name of the statement which has the account number, but it
|
||||||
|
# doesn't bring any useful info to the accountant)
|
||||||
|
# The only "good" thing to do would be to have a sequence per
|
||||||
|
# statement line and write it in this 'ref' field
|
||||||
|
# But that would required an additionnal field on statement lines
|
||||||
|
vals['ref'] = False
|
||||||
|
return vals
|
||||||
|
|
||||||
|
def show_account_move(self):
|
||||||
|
self.ensure_one()
|
||||||
|
action = self.env['ir.actions.act_window'].for_xml_id(
|
||||||
|
'account', 'action_move_journal_line')
|
||||||
|
if self.journal_entry_ids:
|
||||||
|
action.update({
|
||||||
|
'views': False,
|
||||||
|
'view_id': False,
|
||||||
|
'view_mode': 'form,tree',
|
||||||
|
'res_id': self.journal_entry_ids[0].id,
|
||||||
|
})
|
||||||
|
return action
|
||||||
|
else:
|
||||||
|
raise UserError(_(
|
||||||
|
'No journal entry linked to this bank statement line.'))
|
||||||
|
|
||||||
|
|
||||||
|
class AccountFiscalPosition(models.Model):
|
||||||
|
_inherit = 'account.fiscal.position'
|
||||||
|
|
||||||
|
# TODO mig to v12 ?
|
||||||
|
@api.model
|
||||||
|
def get_fiscal_position_no_partner(
|
||||||
|
self, company_id=None, vat_subjected=False, country_id=None):
|
||||||
|
'''This method is inspired by the method get_fiscal_position()
|
||||||
|
in odoo/addons/account/partner.py : it uses the same algo
|
||||||
|
but without a real partner.
|
||||||
|
Returns a recordset of fiscal position, or False'''
|
||||||
|
domains = [[
|
||||||
|
('auto_apply', '=', True),
|
||||||
|
('vat_required', '=', vat_subjected),
|
||||||
|
('company_id', '=', company_id)]]
|
||||||
|
if vat_subjected:
|
||||||
|
domains += [[
|
||||||
|
('auto_apply', '=', True),
|
||||||
|
('vat_required', '=', False),
|
||||||
|
('company_id', '=', company_id)]]
|
||||||
|
|
||||||
|
for domain in domains:
|
||||||
|
if country_id:
|
||||||
|
fps = self.search(
|
||||||
|
domain + [('country_id', '=', country_id)], limit=1)
|
||||||
|
if fps:
|
||||||
|
return fps[0]
|
||||||
|
|
||||||
|
fps = self.search(
|
||||||
|
domain +
|
||||||
|
[('country_group_id.country_ids', '=', country_id)],
|
||||||
|
limit=1)
|
||||||
|
if fps:
|
||||||
|
return fps[0]
|
||||||
|
|
||||||
|
fps = self.search(
|
||||||
|
domain +
|
||||||
|
[('country_id', '=', None), ('country_group_id', '=', None)],
|
||||||
|
limit=1)
|
||||||
|
if fps:
|
||||||
|
return fps[0]
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class AccountReconcileModel(models.Model):
|
||||||
|
_inherit = 'account.reconcile.model'
|
||||||
|
|
||||||
|
@api.onchange('name')
|
||||||
|
def onchange_name(self):
|
||||||
|
# Do NOT copy by default name on label
|
||||||
|
# Because it's much better to have the bank statement line label as
|
||||||
|
# label of the counter-part move line, then the label of the button
|
||||||
|
assert True # Stupid line of code just to have something...
|
||||||
|
|
||||||
|
|
||||||
|
class AccountIncoterms(models.Model):
|
||||||
|
_inherit = 'account.incoterms'
|
||||||
|
|
||||||
|
@api.depends('code', 'name')
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for rec in self:
|
||||||
|
res.append((rec.id, '[%s] %s' % (rec.code, rec.name)))
|
||||||
|
return res
|
||||||
26
account_usability/account_invoice_report.py
Normal file
26
account_usability/account_invoice_report.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Copyright 2018-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).
|
||||||
|
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceReport(models.Model):
|
||||||
|
_inherit = 'account.invoice.report'
|
||||||
|
|
||||||
|
number = fields.Char(string="Number", readonly=True)
|
||||||
|
|
||||||
|
def _sub_select(self):
|
||||||
|
select_str = super(AccountInvoiceReport, self)._sub_select()
|
||||||
|
select_str += ", ai.number"
|
||||||
|
return select_str
|
||||||
|
|
||||||
|
def _select(self):
|
||||||
|
select_str = super(AccountInvoiceReport, self)._select()
|
||||||
|
select_str += ", sub.number"
|
||||||
|
return select_str
|
||||||
|
|
||||||
|
def _group_by(self):
|
||||||
|
group_by_str = super(AccountInvoiceReport, self)._group_by()
|
||||||
|
group_by_str += ", ai.number"
|
||||||
|
return group_by_str
|
||||||
51
account_usability/account_invoice_report_view.xml
Normal file
51
account_usability/account_invoice_report_view.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2018 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_invoice_report_tree" model="ir.ui.view">
|
||||||
|
<field name="name">usability.account.invoice.report.tree</field>
|
||||||
|
<field name="model">account.invoice.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Invoices Analysis">
|
||||||
|
<field name="number"/>
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="date_due"/>
|
||||||
|
<field name="type"/>
|
||||||
|
<field name="commercial_partner_id"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="product_qty" sum="1"/>
|
||||||
|
<field name="uom_name" groups="uom.group_uom"/>
|
||||||
|
<field name="price_total" 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, 'search_default_year': 1}</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, 'search_default_year': 1}</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="arch" type="xml">
|
||||||
|
<pivot position="attributes">
|
||||||
|
<attribute name="disable_linking"></attribute>
|
||||||
|
</pivot>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
15
account_usability/account_report.xml
Normal file
15
account_usability/account_report.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2018-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="account.account_invoices" model="ir.actions.report">
|
||||||
|
<!-- Don't attach on supplier invoices/refunds ! -->
|
||||||
|
<field name="attachment">(object.type in ('out_invoice', 'out_refund')) and (object.state in ('open','in_payment','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
53
account_usability/account_several_improvements.diff
Normal file
53
account_usability/account_several_improvements.diff
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
|
||||||
|
index 8ed1e48..615da43 100644
|
||||||
|
--- a/addons/account/models/account_bank_statement.py
|
||||||
|
+++ b/addons/account/models/account_bank_statement.py
|
||||||
|
@@ -563,7 +563,13 @@ class AccountBankStatementLine(models.Model):
|
||||||
|
"""
|
||||||
|
# Blue lines = payment on bank account not assigned to a statement yet
|
||||||
|
reconciliation_aml_accounts = [self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id]
|
||||||
|
- domain_reconciliation = ['&', '&', ('statement_id', '=', False), ('account_id', 'in', reconciliation_aml_accounts), ('payment_id','<>', False)]
|
||||||
|
+ # AKRETION HACK 11/7/2017
|
||||||
|
+ # Remove ('payment_id','<>', False) in order to allow to select move lines
|
||||||
|
+ # generated from payment orders or check deposit
|
||||||
|
+ # but I add ('journal_id', '=', self.journal_id.id) to exclude the
|
||||||
|
+ # opening entry of the first fiscal year
|
||||||
|
+ #domain_reconciliation = ['&', '&', ('statement_id', '=', False), ('account_id', 'in', reconciliation_aml_accounts), ('payment_id','<>', False)]
|
||||||
|
+ domain_reconciliation = ['&', '&', ('statement_id', '=', False), ('account_id', 'in', reconciliation_aml_accounts), ('journal_id', '=', self.journal_id.id)]
|
||||||
|
|
||||||
|
# Black lines = unreconciled & (not linked to a payment or open balance created by statement
|
||||||
|
domain_matching = [('reconciled', '=', False)]
|
||||||
|
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
|
||||||
|
index b60ffbe..6c27c57 100644
|
||||||
|
--- a/addons/account/models/account_move.py
|
||||||
|
+++ b/addons/account/models/account_move.py
|
||||||
|
@@ -599,6 +599,7 @@ class AccountMoveLine(models.Model):
|
||||||
|
domain = expression.AND([domain, [('id', 'not in', excluded_ids)]])
|
||||||
|
if str:
|
||||||
|
str_domain = [
|
||||||
|
+ '|', ('account_id.code', '=ilike', str + '%'),
|
||||||
|
'|', ('move_id.name', 'ilike', str),
|
||||||
|
'|', ('move_id.ref', 'ilike', str),
|
||||||
|
'|', ('date_maturity', 'like', str),
|
||||||
|
diff --git a/addons/account/static/src/js/account_reconciliation_widgets.js b/addons/account/static/src/js/account_reconciliation_widgets.js
|
||||||
|
index 453bd41..48c396e 100644
|
||||||
|
--- a/addons/account/static/src/js/account_reconciliation_widgets.js
|
||||||
|
+++ b/addons/account/static/src/js/account_reconciliation_widgets.js
|
||||||
|
@@ -76,7 +76,7 @@ var abstractReconciliation = Widget.extend(ControlPanelMixin, {
|
||||||
|
this.model_res_users = new Model("res.users");
|
||||||
|
this.model_tax = new Model("account.tax");
|
||||||
|
this.model_presets = new Model("account.reconcile.model");
|
||||||
|
- this.max_move_lines_displayed = 5;
|
||||||
|
+ this.max_move_lines_displayed = 15;
|
||||||
|
// Number of reconciliations loaded initially and by clicking 'show more'
|
||||||
|
this.num_reconciliations_fetched_in_batch = 10;
|
||||||
|
this.animation_speed = 100; // "Blocking" animations
|
||||||
|
@@ -1755,7 +1755,7 @@ var bankStatementReconciliationLine = abstractReconciliationLine.extend({
|
||||||
|
relation: "res.partner",
|
||||||
|
string: _t("Partner"),
|
||||||
|
type: "many2one",
|
||||||
|
- domain: [['parent_id','=',false], '|', ['customer','=',true], ['supplier','=',true]],
|
||||||
|
+ domain: [['parent_id','=',false]], // AKRETION HACK 26/6/2017 allow all parent partners
|
||||||
|
help: "",
|
||||||
|
readonly: false,
|
||||||
|
required: true,
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
|
||||||
|
index 4374528..aea1361 100644
|
||||||
|
--- a/addons/account/models/account_bank_statement.py
|
||||||
|
+++ b/addons/account/models/account_bank_statement.py
|
||||||
|
@@ -1008,7 +1008,7 @@ class AccountBankStatementLine(models.Model):
|
||||||
|
#record the move name on the statement line to be able to retrieve it in case of unreconciliation
|
||||||
|
self.write({'move_name': move.name})
|
||||||
|
payment.write({'payment_reference': move.name})
|
||||||
|
- elif self.move_name:
|
||||||
|
- raise UserError(_('Operation not allowed. Since your statement line already received a number, you cannot reconcile it entirely with existing journal entries otherwise it would make a gap in the numbering. You should book an entry and make a regular revert of it in case you want to cancel it.'))
|
||||||
|
+ #elif self.move_name:
|
||||||
|
+ # raise UserError(_('Operation not allowed. Since your statement line already received a number, you cannot reconcile it entirely with existing journal entries otherwise it would make a gap in the numbering. You should book an entry and make a regular revert of it in case you want to cancel it.'))
|
||||||
|
counterpart_moves.assert_balanced()
|
||||||
|
return counterpart_moves
|
||||||
555
account_usability/account_view.xml
Normal file
555
account_usability/account_view.xml
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015-2019 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>
|
||||||
|
|
||||||
|
<!-- INVOICE -->
|
||||||
|
<record id="invoice_supplier_form" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.supplier.invoice.form</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="fiscal_position_id" position="attributes">
|
||||||
|
<attribute name="widget">selection</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="incoterm_id" position="attributes">
|
||||||
|
<attribute name="widget">selection</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="invoice_line_ids" position="before">
|
||||||
|
<button name="delete_lines_qty_zero" states="draft" string="⇒ Delete lines qty=0" type="object" class="oe_link oe_right" groups="account.group_account_invoice"/>
|
||||||
|
</field>
|
||||||
|
<xpath expr="//field[@name='tax_line_ids']/tree/field[@name='amount']" position="before">
|
||||||
|
<field name="base" readonly="1"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="invoice_form" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.invoice.form</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="fiscal_position_id" position="attributes">
|
||||||
|
<attribute name="widget">selection</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="incoterm_id" position="attributes">
|
||||||
|
<attribute name="widget">selection</attribute>
|
||||||
|
</field>
|
||||||
|
<!-- move sent field and make it visible -->
|
||||||
|
<field name="sent" position="replace"/>
|
||||||
|
<field name="move_id" position="before">
|
||||||
|
<field name="sent"/>
|
||||||
|
</field>
|
||||||
|
<xpath expr="//field[@name='tax_line_ids']/tree/field[@name='amount']" position="before">
|
||||||
|
<field name="base" readonly="1"/>
|
||||||
|
</xpath>
|
||||||
|
<!-- Warning: there are 2 invoice_print buttons in the native view... probably a bug -->
|
||||||
|
<!--
|
||||||
|
<xpath expr="//button[@name='invoice_print']" position="attributes">
|
||||||
|
<attribute name="attrs">{'invisible': [('state', 'not in', ('open', 'paid'))]}</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@name='invoice_print'][2]" position="attributes">
|
||||||
|
<attribute name="attrs">{'invisible': True}</attribute>
|
||||||
|
</xpath> -->
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="invoice_tree" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.invoice_tree</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="reference" position="attributes">
|
||||||
|
<attribute name="invisible">not context.get('type') in ('in_invoice', 'in_refund')</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_account_invoice_filter" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.invoice.search</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<filter name="late" position="after">
|
||||||
|
<separator/>
|
||||||
|
<filter name="to_send" string="To Send" domain="[('sent', '=', False), ('state', 'in', ('open', 'paid'))]"/>
|
||||||
|
<filter name="sent" string="Sent" domain="[('sent', '=', True)]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter name="no_attachment" string="Missing Attachment" domain="[('has_attachment', '=', False)]"/>
|
||||||
|
</filter>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Having a menu entry on invoice lines is often very usefull for odoo user:
|
||||||
|
they can search in their lines, etc...
|
||||||
|
So I enhance the generic views and add actions, but I don't add menu entries here ;
|
||||||
|
the creation of the corresponding menu entry should be done in the customer-specifc
|
||||||
|
module -->
|
||||||
|
<record id="view_invoice_line_tree" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.invoice_line_tree</field>
|
||||||
|
<field name="model">account.invoice.line</field>
|
||||||
|
<field name="inherit_id" ref="account.view_invoice_line_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="name" position="before">
|
||||||
|
<field name="partner_id" invisible="not context.get('show_invoice_fields')"/>
|
||||||
|
<field name="date_invoice" invisible="not context.get('show_invoice_fields')"/>
|
||||||
|
<field name="invoice_number" invisible="not context.get('show_invoice_fields')"/>
|
||||||
|
</field>
|
||||||
|
<field name="currency_id" position="after">
|
||||||
|
<field name="state" invisible="not context.get('show_invoice_fields')"/>
|
||||||
|
<field name="invoice_type" invisible="1"/>
|
||||||
|
</field>
|
||||||
|
<field name="quantity" position="attributes">
|
||||||
|
<attribute name="sum">1</attribute>
|
||||||
|
</field>
|
||||||
|
<xpath expr="/tree" position="attributes">
|
||||||
|
<attribute name="decoration-info">state == 'draft'</attribute>
|
||||||
|
<attribute name="decoration-muted">state == 'cancel'</attribute>
|
||||||
|
<attribute name="edit">0</attribute>
|
||||||
|
<attribute name="create">0</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account_invoice_line_search" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.invoice_line_search</field>
|
||||||
|
<field name="model">account.invoice.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Invoice Lines">
|
||||||
|
<field name="partner_id"/>
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="account_id"/>
|
||||||
|
<field name="invoice_number"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<filter name="out_invoice" string="Customer Invoices"
|
||||||
|
domain="[('invoice_type', '=', 'out_invoice')]"/>
|
||||||
|
<filter name="out_refund" string="Customer Refunds"
|
||||||
|
domain="[('invoice_type', '=', 'out_refund')]"/>
|
||||||
|
<filter name="in_invoice" string="Supplier Invoices"
|
||||||
|
domain="[('invoice_type', '=', 'in_invoice')]"/>
|
||||||
|
<filter name="in_refund" string="Supplier Refunds"
|
||||||
|
domain="[('invoice_type', '=', 'in_refund')]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
|
||||||
|
<filter name="unpaid" string="Not Paid" domain="[('state', '=', 'open')]"/>
|
||||||
|
<filter name="paid" string="Paid" domain="[('state', '=', 'paid')]"/>
|
||||||
|
|
||||||
|
<group string="Group By" name="groupby">
|
||||||
|
<filter name="partner_groupby" string="Partner"
|
||||||
|
context="{'group_by': 'partner_id'}"/>
|
||||||
|
<filter name="date_groupby" string="Invoice Date"
|
||||||
|
context="{'group_by': 'date_invoice'}"/>
|
||||||
|
<filter name="product_groupby" string="Product"
|
||||||
|
context="{'group_by': 'product_id'}"/>
|
||||||
|
<filter name="account_groupby" string="Account"
|
||||||
|
context="{'group_by': 'account_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="out_invoice_line_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Customer Invoice Lines</field>
|
||||||
|
<field name="res_model">account.invoice.line</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="domain">[('invoice_type', '=', 'out_invoice')]</field>
|
||||||
|
<field name="context">{'show_invoice_fields': True}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="out_refund_line_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Customer Refund Lines</field>
|
||||||
|
<field name="res_model">account.invoice.line</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="domain">[('invoice_type', '=', 'out_refund')]</field>
|
||||||
|
<field name="context">{'show_invoice_fields': True}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="out_invoice_refund_line_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Customer Invoice Lines</field>
|
||||||
|
<field name="res_model">account.invoice.line</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="domain">[('invoice_type', 'in', ('out_invoice', 'out_refund'))]</field>
|
||||||
|
<field name="context">{'show_invoice_fields': True}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="in_invoice_line_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Supplier Invoice Lines</field>
|
||||||
|
<field name="res_model">account.invoice.line</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="domain">[('invoice_type', '=', 'in_invoice')]</field>
|
||||||
|
<field name="context">{'show_invoice_fields': True}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="in_refund_line_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Supplier Refund Lines</field>
|
||||||
|
<field name="res_model">account.invoice.line</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="domain">[('invoice_type', '=', 'in_refund')]</field>
|
||||||
|
<field name="context">{'show_invoice_fields': True}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="in_invoice_refund_line_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Supplier Invoice Lines</field>
|
||||||
|
<field name="res_model">account.invoice.line</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="domain">[('invoice_type', 'in', ('in_invoice', 'in_refund'))]</field>
|
||||||
|
<field name="context">{'show_invoice_fields': True}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account_invoice_report_tree" model="ir.ui.view">
|
||||||
|
<field name="name">usability.account.invoice.report.tree</field>
|
||||||
|
<field name="model">account.invoice.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Invoices Analysis">
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="commercial_partner_id"/>
|
||||||
|
<field name="type"/>
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="product_qty" sum="1"/>
|
||||||
|
<field name="price_total" sum="1"/>
|
||||||
|
<field name="state"/>
|
||||||
|
<field name="currency_id" invisible="1"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account.action_account_invoice_report_all_supp" model="ir.actions.act_window">
|
||||||
|
<field name="view_mode">pivot,graph,tree</field>
|
||||||
|
<field name="context">{'search_default_current':1, 'search_default_supplier':1, 'search_default_year': 1}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account.action_account_invoice_report_all" model="ir.actions.act_window">
|
||||||
|
<field name="view_mode">pivot,graph,tree</field>
|
||||||
|
<field name="context">{'search_default_current':1, 'search_default_customer':1, 'search_default_year': 1}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_account_invoice_report_pivot" model="ir.ui.view">
|
||||||
|
<field name="name">usability.account.invoice.report.pivot</field>
|
||||||
|
<field name="model">account.invoice.report</field>
|
||||||
|
<field name="inherit_id" ref="account.view_account_invoice_report_pivot"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="/pivot" position="attributes">
|
||||||
|
<attribute name="disable_linking"></attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_invoice_tax_form" model="ir.ui.view">
|
||||||
|
<field name="name">usability.account.invoice.tax.form</field>
|
||||||
|
<field name="model">account.invoice.tax</field>
|
||||||
|
<field name="inherit_id" ref="account.view_invoice_tax_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="name" position="after">
|
||||||
|
<field name="tax_id"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_account_payment_form" model="ir.ui.view">
|
||||||
|
<field name="name">usability.account.payment.form</field>
|
||||||
|
<field name="model">account.payment</field>
|
||||||
|
<field name="inherit_id" ref="account.view_account_payment_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="communication" position="after">
|
||||||
|
<field name="payment_reference"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- model account.move.line / Journal Items -->
|
||||||
|
<record id="account.action_account_moves_all_a" model="ir.actions.act_window">
|
||||||
|
<field name="limit">200</field>
|
||||||
|
<!-- Win space, because there are already many columns -->
|
||||||
|
<field name="context">{'journal_show_code_only': True}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- replace group_account_manager on Journal Items-->
|
||||||
|
<record id="account.menu_action_account_moves_all" model="ir.ui.menu">
|
||||||
|
<field name="groups_id" eval="[(6, 0, [ref('account.group_account_user')])]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- model account.move / Journal Entries -->
|
||||||
|
<record id="account.action_move_journal_line" model="ir.actions.act_window">
|
||||||
|
<field name="limit">200</field>
|
||||||
|
<field name="context">{'view_no_maturity': True}</field> <!-- Don't filter by default on misc journal -->
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="journal_id" position="after">
|
||||||
|
<field name="default_move_line_name"/>
|
||||||
|
<field name="default_account_id" invisible="1"/>
|
||||||
|
<field name="default_credit" invisible="1"/>
|
||||||
|
<field name="default_debit" invisible="1"/>
|
||||||
|
</field>
|
||||||
|
<xpath expr="//field[@name='line_ids']" position="attributes">
|
||||||
|
<attribute name="context" operation="python_dict" key="default_name">default_move_line_name</attribute>
|
||||||
|
<attribute name="context" operation="python_dict" key="default_account_id">default_account_id</attribute>
|
||||||
|
<attribute name="context" operation="python_dict" key="default_credit">default_credit</attribute>
|
||||||
|
<attribute name="context" operation="python_dict" key="default_debit">default_debit</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='line_ids']/tree/field[@name='credit']" position="after">
|
||||||
|
<field name="reconcile_string"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="partner_id" position="after">
|
||||||
|
<field name="reconcile_string" />
|
||||||
|
<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="partial_reconciled" string="Partially Reconciled" domain="[('reconcile_partial_id', '!=', False)]"/> -->
|
||||||
|
</filter>
|
||||||
|
<filter name="unreconciled" position="attributes">
|
||||||
|
<attribute name="string">Unreconciled or Partially Reconciled</attribute>
|
||||||
|
</filter>
|
||||||
|
<field name="name" position="attributes">
|
||||||
|
<attribute name="string">Name or Reference</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="partner_id" position="attributes">
|
||||||
|
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_move_line_form" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.account_move_line_form</field>
|
||||||
|
<field name="model">account.move.line</field>
|
||||||
|
<field name="inherit_id" ref="account.view_move_line_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="quantity" position="after">
|
||||||
|
<field name="product_id" />
|
||||||
|
</field>
|
||||||
|
<field name="move_id" position="after">
|
||||||
|
<field name="invoice_id"/>
|
||||||
|
<field name="account_reconcile" invisible="1"/>
|
||||||
|
</field>
|
||||||
|
<xpath expr="//field[@name='full_reconcile_id']/.." position="replace">
|
||||||
|
<field name="full_reconcile_id" nolabel="1"/> <!-- label is already in view -->
|
||||||
|
<field name="matched_debit_ids" readonly="1" widget="many2many_tags" attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '=', [])]}"/>
|
||||||
|
<field name="matched_credit_ids" readonly="1" widget="many2many_tags" attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), ('matched_credit_ids', '=', [])]}"/>
|
||||||
|
<field name="reconciled" invisible="1"/>
|
||||||
|
<button name="open_reconcile_view" class="oe_link" type="object"
|
||||||
|
string="-> View partially reconciled entries" colspan="2"
|
||||||
|
attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), '&', ('matched_debit_ids', '=', []), ('matched_credit_ids', '=', [])]}"/>
|
||||||
|
<span colspan="2" attrs="{'invisible': ['|', '|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '!=', []), ('matched_credit_ids', '!=', [])]}" class="o_form_field">No Partial Reconcile</span>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//label[@for='full_reconcile_id']/.." position="attributes">
|
||||||
|
<attribute name="attrs">{'invisible': [('account_reconcile', '=', False)]}</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_move_line_tree" model="ir.ui.view">
|
||||||
|
<field name="name">account_usability.account_move_line_tree</field>
|
||||||
|
<field name="model">account.move.line</field>
|
||||||
|
<field name="inherit_id" ref="account.view_move_line_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<!-- Move reconcile_id to a better position -->
|
||||||
|
<field name="full_reconcile_id" position="replace"/>
|
||||||
|
<field name="credit" position="after">
|
||||||
|
<field name="balance" sum="Total Balance"/>
|
||||||
|
<field name="reconcile_string"/>
|
||||||
|
</field>
|
||||||
|
<field name="date_maturity" position="after">
|
||||||
|
<button name="show_account_move_form" type="object" icon="fa-arrows-h" string="Show Journal Entry"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_account_move_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_move_filter"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="partner_id" position="attributes">
|
||||||
|
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
|
||||||
|
</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="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>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="type" position="after">
|
||||||
|
<field name="include_initial_balance" />
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="name" position="after">
|
||||||
|
<field name="code"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<filter name="dashboard" position="after">
|
||||||
|
<group name="groupby" string="Group By">
|
||||||
|
<filter name="type_groupby" string="Type" context="{'group_by': 'type'}"/>
|
||||||
|
</group>
|
||||||
|
</filter>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='line_ids']/tree/field[@name='bank_account_id']" position="after">
|
||||||
|
<!-- The cancel button is provided by the account_cancel module, but we don't want to depend on it -->
|
||||||
|
<button name="show_account_move" type="object"
|
||||||
|
string="View Account Move" icon="fa fa-arrow-right"
|
||||||
|
attrs="{'invisible': [('journal_entry_ids', '=', [])]}"/>
|
||||||
|
</xpath>
|
||||||
|
<field name="date" position="after">
|
||||||
|
<field name="start_date"/>
|
||||||
|
<field name="end_date"/>
|
||||||
|
</field>
|
||||||
|
<field name="date" position="attributes">
|
||||||
|
<attribute name="invisible">1</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="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>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="date" position="attributes">
|
||||||
|
<attribute name="invisible">1</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="date" position="after">
|
||||||
|
<field name="start_date"/>
|
||||||
|
<field name="end_date"/>
|
||||||
|
</field>
|
||||||
|
<filter name="date" position="attributes">
|
||||||
|
<attribute name="invisible">1</attribute>
|
||||||
|
</filter>
|
||||||
|
<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>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ACCOUNT TAX -->
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="company_id" position="before">
|
||||||
|
<field name="price_include" string="Include"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ACCOUNT TAX GROUP -->
|
||||||
|
<!-- in the account module, there is nothing for account.tax.group : no form/tree view, no menu... -->
|
||||||
|
<record id="account_tax_group_form" model="ir.ui.view">
|
||||||
|
<field name="name">usability.account.tax.group.form</field>
|
||||||
|
<field name="model">account.tax.group</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Tax Group">
|
||||||
|
<group name="main">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sequence" invisible="1"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account_tax_group_tree" model="ir.ui.view">
|
||||||
|
<field name="name">usability.account.tax.group.tree</field>
|
||||||
|
<field name="model">account.tax.group</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Tax Groups">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account_tax_group_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Tax Groups</field>
|
||||||
|
<field name="res_model">account.tax.group</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="account_tax_group_menu" action="account_tax_group_action" parent="account.account_account_menu" sequence="2"/>
|
||||||
|
|
||||||
|
<!-- Remove menu entry "Accounting > Configuration > Accounting > Bank Accounts"
|
||||||
|
(account.journal filtered on type = 'bank' with special tree and form view)
|
||||||
|
because it is useless and confusing -->
|
||||||
|
<record id="account.menu_action_account_bank_journal_form" model="ir.ui.menu">
|
||||||
|
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Duplicate the menu "Sales > Configuration > Contacts > Bank Accounts"
|
||||||
|
under "Accounting > Configuration", because most users will try to find it there -->
|
||||||
|
<menuitem id="bank_account_account_config_menu" name="Bank Accounts" parent="account.menu_finance_configuration" sequence="9"/>
|
||||||
|
|
||||||
|
<menuitem id="res_bank_account_config_menu" action="base.action_res_bank_form" parent="bank_account_account_config_menu" sequence="10"/>
|
||||||
|
|
||||||
|
<menuitem id="res_partner_bank_account_config_menu" action="base.action_res_partner_bank_account_form" parent="bank_account_account_config_menu" sequence="20"/>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
|
||||||
|
index b1d8012329d..b8a8e2a673d 100644
|
||||||
|
--- a/addons/account/models/account_payment.py
|
||||||
|
+++ b/addons/account/models/account_payment.py
|
||||||
|
@@ -210,6 +210,7 @@ class account_payment(models.Model):
|
||||||
|
payment_difference = fields.Monetary(compute='_compute_payment_difference', readonly=True)
|
||||||
|
payment_difference_handling = fields.Selection([('open', 'Keep open'), ('reconcile', 'Mark invoice as fully paid')], default='open', string="Payment Difference", copy=False)
|
||||||
|
writeoff_account_id = fields.Many2one('account.account', string="Difference Account", domain=[('deprecated', '=', False)], copy=False)
|
||||||
|
+ writeoff_analytic_account_id = fields.Many2one('account.analytic.account', string="Difference Analytic Account", copy=False)
|
||||||
|
|
||||||
|
# FIXME: ondelete='restrict' not working (eg. cancel a bank statement reconciliation with a payment)
|
||||||
|
move_line_ids = fields.One2many('account.move.line', 'payment_id', readonly=True, copy=False, ondelete='restrict')
|
||||||
|
@@ -431,6 +432,7 @@ class account_payment(models.Model):
|
||||||
|
amount_currency_wo = -abs(amount_currency_wo)
|
||||||
|
writeoff_line['name'] = _('Counterpart')
|
||||||
|
writeoff_line['account_id'] = self.writeoff_account_id.id
|
||||||
|
+ writeoff_line['analytic_account_id'] = self.writeoff_analytic_account_id.id or False
|
||||||
|
writeoff_line['debit'] = debit_wo
|
||||||
|
writeoff_line['credit'] = credit_wo
|
||||||
|
writeoff_line['amount_currency'] = amount_currency_wo
|
||||||
|
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
|
||||||
|
index 2460458fbaa..4065d8f9952 100644
|
||||||
|
--- a/addons/account/views/account_payment_view.xml
|
||||||
|
+++ b/addons/account/views/account_payment_view.xml
|
||||||
|
@@ -206,6 +206,8 @@
|
||||||
|
</div>
|
||||||
|
<field name="writeoff_account_id" string="Post Difference In"
|
||||||
|
attrs="{'invisible': [('payment_difference_handling','=','open')], 'required': [('payment_difference_handling', '=', 'reconcile')]}"/>
|
||||||
|
+ <field name="writeoff_analytic_account_id" string="Post Difference In Analytic Account"
|
||||||
|
+ attrs="{'invisible': [('payment_difference_handling','=','open')]}"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
13
account_usability/partner.py
Normal file
13
account_usability/partner.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Copyright 2017-2019 Akretion France (https://akretion.com/)
|
||||||
|
# @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
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
invoice_warn = fields.Selection(track_visibility='onchange')
|
||||||
|
property_account_position_id = fields.Many2one(
|
||||||
|
track_visibility='onchange')
|
||||||
23
account_usability/partner_view.xml
Normal file
23
account_usability/partner_view.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?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_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="arch" type="xml">
|
||||||
|
<field name="property_account_position_id" position="attributes">
|
||||||
|
<attribute name="widget">selection</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
10
account_usability/report/invoice_report.xml
Normal file
10
account_usability/report/invoice_report.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?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"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
BIN
account_usability/static/description/icon.png
Normal file
BIN
account_usability/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
2
account_usability/wizard/__init__.py
Normal file
2
account_usability/wizard/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import account_invoice_mark_sent
|
||||||
|
from . import account_move_reversal
|
||||||
23
account_usability/wizard/account_invoice_mark_sent.py
Normal file
23
account_usability/wizard/account_invoice_mark_sent.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2017-2019 Akretion France (https://akretion.com/en)
|
||||||
|
# @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
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceMarkSent(models.TransientModel):
|
||||||
|
_name = 'account.invoice.mark.sent'
|
||||||
|
_description = 'Mark invoices as sent'
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
assert self.env.context.get('active_model') == 'account.invoice',\
|
||||||
|
'Source model must be invoices'
|
||||||
|
assert self.env.context.get('active_ids'), 'No invoices selected'
|
||||||
|
invoices = self.env['account.invoice'].search([
|
||||||
|
('id', 'in', self.env.context.get('active_ids')),
|
||||||
|
('state', 'in', ('open', 'paid'))])
|
||||||
|
invoices.write({'sent': True})
|
||||||
|
logger.info('Marking invoices with ID %s as sent', invoices.ids)
|
||||||
|
return
|
||||||
36
account_usability/wizard/account_invoice_mark_sent_view.xml
Normal file
36
account_usability/wizard/account_invoice_mark_sent_view.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2017-2019 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">
|
||||||
|
<form string="Mark invoices as sent">
|
||||||
|
<p>
|
||||||
|
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"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<act_window id="account_invoice_mark_sent_action"
|
||||||
|
multi="True"
|
||||||
|
key2="client_action_multi"
|
||||||
|
name="Mark as Sent"
|
||||||
|
res_model="account.invoice.mark.sent"
|
||||||
|
src_model="account.invoice"
|
||||||
|
view_mode="form"
|
||||||
|
target="new"
|
||||||
|
groups="account.group_account_invoice" />
|
||||||
|
|
||||||
|
</odoo>
|
||||||
24
account_usability/wizard/account_move_reversal.py
Normal file
24
account_usability/wizard/account_move_reversal.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2018-2019 Akretion France (https://akretion.com/)
|
||||||
|
# @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
|
||||||
|
|
||||||
|
|
||||||
|
class AccountMoveReversal(models.TransientModel):
|
||||||
|
_inherit = 'account.move.reversal'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _default_date(self):
|
||||||
|
date = 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'])
|
||||||
|
date_dt = fields.Date.from_string(move.date) +\
|
||||||
|
relativedelta(days=1)
|
||||||
|
date = fields.Date.to_string(date_dt)
|
||||||
|
return date
|
||||||
|
|
||||||
|
date = fields.Date(default=_default_date)
|
||||||
1
base_company_extension/__init__.py
Normal file
1
base_company_extension/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import company
|
||||||
28
base_company_extension/__manifest__.py
Normal file
28
base_company_extension/__manifest__.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014-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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Base Company Extension',
|
||||||
|
'version': '12.0.1.0.0',
|
||||||
|
'category': 'Partner',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Adds capital and title on company',
|
||||||
|
'description': """
|
||||||
|
Base Company Extension
|
||||||
|
======================
|
||||||
|
|
||||||
|
This module adds 2 fields on the Company :
|
||||||
|
|
||||||
|
* *Capital Amount*
|
||||||
|
|
||||||
|
* *Legal Form*
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
# I depend on base_usability only for _report_company_legal_name()
|
||||||
|
'depends': ['base_usability'],
|
||||||
|
'data': ['company_view.xml'],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
28
base_company_extension/company.py
Normal file
28
base_company_extension/company.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright 2014-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).
|
||||||
|
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = "res.company"
|
||||||
|
|
||||||
|
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, ...")
|
||||||
|
|
||||||
|
def _report_company_legal_name(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.legal_type:
|
||||||
|
name = u'%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."
|
||||||
|
)]
|
||||||
22
base_company_extension/company_view.xml
Normal file
22
base_company_extension/company_view.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2014-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_company_form" model="ir.ui.view">
|
||||||
|
<field name="name">company.extension.form</field>
|
||||||
|
<field name="model">res.company</field>
|
||||||
|
<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>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
1
base_partner_ref/__init__.py
Normal file
1
base_partner_ref/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import partner
|
||||||
26
base_partner_ref/__manifest__.py
Normal file
26
base_partner_ref/__manifest__.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Base Partner Reference',
|
||||||
|
'version': '12.0.1.0.0',
|
||||||
|
'category': 'Partner',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': "Improve usage of partner's Internal Reference",
|
||||||
|
'description': """
|
||||||
|
Base Partner Reference
|
||||||
|
======================
|
||||||
|
|
||||||
|
* Adds Internal Reference in partner tree view
|
||||||
|
|
||||||
|
* Adds Internal Reference in name_get()
|
||||||
|
|
||||||
|
* Adds unicity constraint on Internal Reference
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
'depends': ['base'],
|
||||||
|
'data': ['partner_view.xml'],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
51
base_partner_ref/partner.py
Normal file
51
base_partner_ref/partner.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 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 models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
ref = fields.Char(copy=False) # To avoid blocking duplicate
|
||||||
|
|
||||||
|
_sql_constraints = [(
|
||||||
|
'ref_unique',
|
||||||
|
'unique(ref)',
|
||||||
|
'A partner already exists with this internal reference!'
|
||||||
|
)]
|
||||||
|
|
||||||
|
def _get_name(self):
|
||||||
|
partner = self
|
||||||
|
name = partner.name or ''
|
||||||
|
|
||||||
|
# START modif of native method
|
||||||
|
if partner.ref:
|
||||||
|
name = u"[%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
|
||||||
52
base_partner_ref/partner_view.xml
Normal file
52
base_partner_ref/partner_view.xml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?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="arch" type="xml">
|
||||||
|
<field name="type" position="after">
|
||||||
|
<field name="ref"/>
|
||||||
|
</field>
|
||||||
|
<xpath expr="//page[@name='sales_purchases']//field[@name='ref']" position="replace"/>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="display_name" position="after">
|
||||||
|
<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>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
1
base_usability/__init__.py
Normal file
1
base_usability/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
40
base_usability/__manifest__.py
Normal file
40
base_usability/__manifest__.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# © 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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Base Usability',
|
||||||
|
'version': '12.0.0.1.0',
|
||||||
|
'category': 'Partner',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Better usability in base module',
|
||||||
|
'description': """
|
||||||
|
Base Usability
|
||||||
|
==============
|
||||||
|
|
||||||
|
This module adds *track_visibility='onchange'* on all the important fields of the Partner object.
|
||||||
|
|
||||||
|
By default, Odoo doesn't display the title field on all the partner form views. This module fixes it (it replaces the module base_title_on_partner).
|
||||||
|
|
||||||
|
It also adds a log message at INFO level when sending an email via SMTP.
|
||||||
|
|
||||||
|
It displays the local modules with installable filter.
|
||||||
|
A group by 'State' is added to module search view.
|
||||||
|
|
||||||
|
It provides a _display_report_header method on the res.company object and
|
||||||
|
_display_full_address on res.partner which are useful for reporting.
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
'depends': ['base'],
|
||||||
|
'data': [
|
||||||
|
'security/group.xml',
|
||||||
|
'views/partner_view.xml',
|
||||||
|
'views/partner_bank_view.xml',
|
||||||
|
'views/users_view.xml',
|
||||||
|
'views/country_view.xml',
|
||||||
|
'views/module_view.xml',
|
||||||
|
'views/base_view.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
|
||||||
|
index 083607f9..99ae8857 100644
|
||||||
|
--- a/odoo/addons/base/models/res_users.py
|
||||||
|
+++ b/odoo/addons/base/models/res_users.py
|
||||||
|
@@ -426,7 +426,13 @@ class Users(models.Model):
|
||||||
|
for user in users:
|
||||||
|
user.partner_id.active = user.active
|
||||||
|
if user.partner_id.company_id:
|
||||||
|
- user.partner_id.write({'company_id': user.company_id.id})
|
||||||
|
+ # AKRETION HACK: if you have a multi-company setup where
|
||||||
|
+ # partners are NOT shared between companies, having
|
||||||
|
+ # company_id=False on partners related to users
|
||||||
|
+ # avoids a lot of trouble (you should also disable 'read'
|
||||||
|
+ # on the ir.rule 'user rule' (XMLID base.res_users_rule)
|
||||||
|
+ # user.partner_id.write({'company_id': user.company_id.id})
|
||||||
|
+ user.partner_id.write({'company_id': False})
|
||||||
|
return users
|
||||||
|
|
||||||
|
@api.multi
|
||||||
6
base_usability/models/__init__.py
Normal file
6
base_usability/models/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from . import users
|
||||||
|
from . import partner
|
||||||
|
from . import company
|
||||||
|
from . import mail
|
||||||
|
from . import misc
|
||||||
|
from . import ir_model
|
||||||
81
base_usability/models/company.py
Normal file
81
base_usability/models/company.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# © 2015-2016 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, _
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def generate_line(self, fields, options, icon=True, separator=' - '):
|
||||||
|
assert fields
|
||||||
|
assert options
|
||||||
|
content = []
|
||||||
|
for field in fields:
|
||||||
|
value = False
|
||||||
|
if isinstance(field, tuple) and len(field) == 2:
|
||||||
|
value = field[0]
|
||||||
|
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')
|
||||||
|
if value:
|
||||||
|
prefix = icon and uicon or label
|
||||||
|
if prefix:
|
||||||
|
content.append('%s %s' % (prefix, value))
|
||||||
|
else:
|
||||||
|
content.append(value)
|
||||||
|
line = separator.join(content)
|
||||||
|
return line
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _prepare_header_options(self):
|
||||||
|
self.ensure_one()
|
||||||
|
options = {
|
||||||
|
'phone': {
|
||||||
|
'value': self.phone,
|
||||||
|
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm
|
||||||
|
'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': _('TVA :')}, # TODO translate
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
|
||||||
|
def _report_company_legal_name(self):
|
||||||
|
'''Method inherited in the module base_company_extension'''
|
||||||
|
self.ensure_one()
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
# for reports
|
||||||
|
@api.multi
|
||||||
|
def _display_report_header(
|
||||||
|
self, line_details=[['phone', 'website'], ['vat']],
|
||||||
|
icon=True, line_separator=' - '):
|
||||||
|
self.ensure_one()
|
||||||
|
res = ''
|
||||||
|
address = self.partner_id._display_address(without_company=True)
|
||||||
|
address = address.replace('\n', ' - ')
|
||||||
|
|
||||||
|
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)
|
||||||
|
lines.append(line)
|
||||||
|
res = '\n'.join(lines)
|
||||||
|
return res
|
||||||
16
base_usability/models/ir_model.py
Normal file
16
base_usability/models/ir_model.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright 2019 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).
|
||||||
|
|
||||||
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
|
class IrModel(models.Model):
|
||||||
|
_inherit = 'ir.model'
|
||||||
|
|
||||||
|
@api.depends('name', 'model')
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for rec in self:
|
||||||
|
res.append((rec.id, '%s (%s)' % (rec.name, rec.model)))
|
||||||
|
return res
|
||||||
35
base_usability/models/mail.py
Normal file
35
base_usability/models/mail.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# © 2015-2016 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.addons.base.models.ir_mail_server import extract_rfc2822_addresses
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IrMailServer(models.Model):
|
||||||
|
_inherit = "ir.mail_server"
|
||||||
|
|
||||||
|
@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):
|
||||||
|
# Start copy from native method
|
||||||
|
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'))
|
||||||
|
return super(IrMailServer, self).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)
|
||||||
19
base_usability/models/misc.py
Normal file
19
base_usability/models/misc.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# © 2015-2016 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, api
|
||||||
|
from odoo.tools import misc
|
||||||
|
from odoo.tools import float_compare
|
||||||
|
|
||||||
|
|
||||||
|
class BaseLanguageExport(models.TransientModel):
|
||||||
|
_inherit = 'base.language.export'
|
||||||
|
|
||||||
|
# Default format for language files = format used by OpenERP modules
|
||||||
|
format = fields.Selection(default='po')
|
||||||
|
|
||||||
|
|
||||||
|
class BaseLanguageInstall(models.TransientModel):
|
||||||
|
_inherit = 'base.language.install'
|
||||||
|
|
||||||
|
overwrite = fields.Boolean(default=True)
|
||||||
139
base_usability/models/partner.py
Normal file
139
base_usability/models/partner.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# © 2015-2016 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, api, _
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
# track_visibility is handled in the 'mail' module, and base_usability
|
||||||
|
# doesn't depend on 'mail', but that doesn't hurt, it will just be
|
||||||
|
# ignored if mail is not installed
|
||||||
|
name = fields.Char(track_visibility='onchange')
|
||||||
|
parent_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
ref = fields.Char(track_visibility='onchange', copy=False)
|
||||||
|
lang = fields.Selection(track_visibility='onchange')
|
||||||
|
user_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
vat = fields.Char(track_visibility='onchange')
|
||||||
|
customer = fields.Boolean(track_visibility='onchange')
|
||||||
|
supplier = fields.Boolean(track_visibility='onchange')
|
||||||
|
type = fields.Selection(track_visibility='onchange')
|
||||||
|
street = fields.Char(track_visibility='onchange')
|
||||||
|
street2 = fields.Char(track_visibility='onchange')
|
||||||
|
zip = fields.Char(track_visibility='onchange')
|
||||||
|
city = fields.Char(track_visibility='onchange')
|
||||||
|
state_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
country_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
email = fields.Char(track_visibility='onchange')
|
||||||
|
is_company = fields.Boolean(track_visibility='onchange')
|
||||||
|
active = fields.Boolean(track_visibility='onchange')
|
||||||
|
company_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
# For reports
|
||||||
|
name_title = fields.Char(
|
||||||
|
compute='_compute_name_title', string='Name with Title')
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@api.depends('name', 'title')
|
||||||
|
def _compute_name_title(self):
|
||||||
|
for partner in self:
|
||||||
|
name_title = partner.name
|
||||||
|
if partner.title and not partner.is_company:
|
||||||
|
partner_lg = partner
|
||||||
|
# If prefer to read the lang of the partner than the lang
|
||||||
|
# of the context. That way, an English man will be displayed
|
||||||
|
# with his title in English whatever the environment
|
||||||
|
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])
|
||||||
|
partner.name_title = name_title
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _display_address(self, without_company=False):
|
||||||
|
'''Remove empty lines'''
|
||||||
|
res = super(ResPartner, self)._display_address(
|
||||||
|
without_company=without_company)
|
||||||
|
while "\n\n" in res:
|
||||||
|
res = res.replace('\n\n', '\n')
|
||||||
|
return res
|
||||||
|
|
||||||
|
# for reports
|
||||||
|
@api.multi
|
||||||
|
def _display_full_address(
|
||||||
|
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
|
||||||
|
# 2) start libreoffice in xvfb (don't use --headless) (To confirm)
|
||||||
|
if self.is_company:
|
||||||
|
company = self.name
|
||||||
|
name = False
|
||||||
|
else:
|
||||||
|
name = self.name_title
|
||||||
|
company = self.parent_id and self.parent_id.is_company and\
|
||||||
|
self.parent_id.name or False
|
||||||
|
options = {
|
||||||
|
'name': {
|
||||||
|
'value': name,
|
||||||
|
},
|
||||||
|
'company': {
|
||||||
|
'value': company,
|
||||||
|
},
|
||||||
|
'phone': {
|
||||||
|
'value': self.phone,
|
||||||
|
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm
|
||||||
|
'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,
|
||||||
|
# http://www.fileformat.info/info/unicode/char/2709/index.htm
|
||||||
|
'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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = []
|
||||||
|
for detail in details:
|
||||||
|
if options.get(detail) and options[detail]['value']:
|
||||||
|
entry = options[detail]
|
||||||
|
prefix = icon and entry.get('icon') or entry.get('label')
|
||||||
|
if prefix:
|
||||||
|
res.append('%s %s' % (prefix, entry['value']))
|
||||||
|
else:
|
||||||
|
res.append('%s' % entry['value'])
|
||||||
|
res = '\n'.join(res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartnerCategory(models.Model):
|
||||||
|
_inherit = 'res.partner.category'
|
||||||
|
|
||||||
|
name = fields.Char(translate=False)
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartnerBank(models.Model):
|
||||||
|
_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')
|
||||||
40
base_usability/models/users.py
Normal file
40
base_usability/models/users.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Copyright 2018 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, SUPERUSER_ID, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ResUsers(models.Model):
|
||||||
|
_inherit = 'res.users'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def default_get(self, fields_list):
|
||||||
|
res = super(ResUsers, self).default_get(fields_list)
|
||||||
|
# For a new partner auto-created when you create a new user, we prefer
|
||||||
|
# customer=False and supplier=True by default
|
||||||
|
res.update({
|
||||||
|
'customer': False,
|
||||||
|
'supplier': True,
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _script_partners_linked_to_users_no_company(self):
|
||||||
|
if self.env.user.id != SUPERUSER_ID:
|
||||||
|
raise UserError(_('You must run this script as admin user'))
|
||||||
|
logger.info(
|
||||||
|
'START to set company_id=False on partners related to users')
|
||||||
|
users = self.search(
|
||||||
|
['|', ('active', '=', True), ('active', '=', False)])
|
||||||
|
for user in users:
|
||||||
|
if user.partner_id.company_id:
|
||||||
|
user.partner_id.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')
|
||||||
|
return True
|
||||||
22
base_usability/security/group.xml
Normal file
22
base_usability/security/group.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?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 noupdate="1">
|
||||||
|
|
||||||
|
<!-- This group is used to hide menu entries to everybody,
|
||||||
|
so you should not put any user in this group. It is used
|
||||||
|
by the module account_hide_analytic_line, but it will certainly
|
||||||
|
be used by other modules in the future, that's why I declare
|
||||||
|
this group in the base_usability module
|
||||||
|
I don't want to use the base.group_no_one for this, because a lot
|
||||||
|
of interesing menu entries are attached to this group, so it's
|
||||||
|
common to have several users that belong to this group -->
|
||||||
|
<record id="group_nobody" model="res.groups">
|
||||||
|
<field name="name">Nobody (used to hide native menus)</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
BIN
base_usability/static/description/icon.png
Normal file
BIN
base_usability/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
15
base_usability/views/base_view.xml
Normal file
15
base_usability/views/base_view.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="ir_cron_view_tree" model="ir.ui.view">
|
||||||
|
<field name="model">ir.cron</field>
|
||||||
|
<field name="inherit_id" ref="base.ir_cron_view_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="name" position="after">
|
||||||
|
<field name="model_id"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
51
base_usability/views/country_view.xml
Normal file
51
base_usability/views/country_view.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
© 2015-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="res_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="arch" type="xml">
|
||||||
|
<search string="Search States">
|
||||||
|
<field name="name" filter_domain="['|', ('name', 'ilike', self), ('code', '=', self)]" string="Name or Code"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="country_id"/>
|
||||||
|
<group string="Group By" name="groupby">
|
||||||
|
<filter name="country_groupby" string="Country" context="{'group_by': 'country_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="res_country_search" model="ir.ui.view">
|
||||||
|
<field name="name">base_usability.res.country.search</field>
|
||||||
|
<field name="model">res.country</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Countries">
|
||||||
|
<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'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="code" position="after">
|
||||||
|
<field name="country_group_ids" widget="many2many_tags"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
39
base_usability/views/module_view.xml
Normal file
39
base_usability/views/module_view.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015-2019 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="module_view_kanban" model="ir.ui.view">
|
||||||
|
<field name="name">Better display of module technical name</field>
|
||||||
|
<field name="model">ir.module.module</field>
|
||||||
|
<field name="inherit_id" ref="base.module_view_kanban"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//h4[@class='o_kanban_record_title']/code[@groups='base.group_no_one']" position="before">
|
||||||
|
<br/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<xpath expr="//filter[@name='extra']" position="after">
|
||||||
|
<filter name="installable" string="Installable" domain="[('state', '!=', 'uninstallable')]"/>
|
||||||
|
</xpath>
|
||||||
|
<group expand="0" position="inside">
|
||||||
|
<filter name="state_groupby" string="State" context="{'group_by': 'state'}"/>
|
||||||
|
</group>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="base.open_module_tree" model="ir.actions.act_window">
|
||||||
|
<field name="context">{'search_default_installable': 1}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
22
base_usability/views/partner_bank_view.xml
Normal file
22
base_usability/views/partner_bank_view.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2018 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_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="arch" type="xml">
|
||||||
|
<field name="sequence" position="attributes">
|
||||||
|
<attribute name="invisible">0</attribute>
|
||||||
|
<attribute name="widget">handle</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
67
base_usability/views/partner_view.xml
Normal file
67
base_usability/views/partner_view.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?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="arch" type="xml">
|
||||||
|
<!-- Wider 'name' field -->
|
||||||
|
<xpath expr="//sheet/div[hasclass('oe_title')]" position="attributes">
|
||||||
|
<attribute name="style">width: 650px;</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='child_ids']/form//field[@name='email']" position="attributes">
|
||||||
|
<attribute name="widget">email</attribute>
|
||||||
|
</xpath>
|
||||||
|
<!-- Show title not only on Contacts -->
|
||||||
|
<xpath expr="//field[@name='child_ids']/form//field[@name='title']" position="attributes">
|
||||||
|
<attribute name="attrs"></attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="function" position="before">
|
||||||
|
<field name="title"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="arch" type="xml">
|
||||||
|
<field name="country_id" position="attributes">
|
||||||
|
<attribute name="invisible">0</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="country_id" position="before">
|
||||||
|
<field name="city"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<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="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>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
21
base_usability/views/users_view.xml
Normal file
21
base_usability/views/users_view.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2018 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_users_tree" model="ir.ui.view">
|
||||||
|
<field name="name">base_usability.res.users.tree</field>
|
||||||
|
<field name="model">res.users</field>
|
||||||
|
<field name="inherit_id" ref="base.view_users_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="login_date" position="after">
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
78
company_code/README.rst
Normal file
78
company_code/README.rst
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
============
|
||||||
|
Company Code
|
||||||
|
============
|
||||||
|
|
||||||
|
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
|
!! changes will be overwritten. !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
:target: https://odoo-community.org/page/development-status
|
||||||
|
:alt: Beta
|
||||||
|
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
.. |badge3| image:: https://img.shields.io/badge/github-akretion%2Fodoo--usability-lightgray.png?logo=github
|
||||||
|
:target: https://github.com/akretion/odoo-usability/tree/12.0/company_code
|
||||||
|
:alt: akretion/odoo-usability
|
||||||
|
|
||||||
|
|badge1| |badge2| |badge3|
|
||||||
|
|
||||||
|
- add `code` field to company.
|
||||||
|
- update name_get with this field
|
||||||
|
|
||||||
|
**Table of contents**
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
To display your company code with `name_get()` just
|
||||||
|
write this code in your custom code according your model
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
return self.env['res.company']._add_company_code(super())
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues <https://github.com/akretion/odoo-usability/issues>`_.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||||
|
`feedback <https://github.com/akretion/odoo-usability/issues/new?body=module:%20company_code%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||||
|
|
||||||
|
Do not contact contributors directly about support or help with technical issues.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Authors
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
* Akretion
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
David Beal <david.beal@akretion.com>
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This module is part of the `akretion/odoo-usability <https://github.com/akretion/odoo-usability/tree/12.0/company_code>`_ project on GitHub.
|
||||||
|
|
||||||
|
|
||||||
|
You are welcome to contribute.
|
||||||
1
company_code/__init__.py
Normal file
1
company_code/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
20
company_code/__manifest__.py
Normal file
20
company_code/__manifest__.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Copyright 2019 David BEAL @ Akretion
|
||||||
|
# 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',
|
||||||
|
],
|
||||||
|
'website': 'http://www.akretion.com/',
|
||||||
|
'data': [
|
||||||
|
'views/company_view.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
1
company_code/models/__init__.py
Normal file
1
company_code/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import company
|
||||||
33
company_code/models/company.py
Normal file
33
company_code/models/company.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright 2019 David BEAL @ Akretion
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
code = fields.Char(
|
||||||
|
required=True, default='CODE',
|
||||||
|
help="Field used in object name as suffix")
|
||||||
|
|
||||||
|
def _add_company_code(self, super_object):
|
||||||
|
""
|
||||||
|
""" Add the `code` field to your _rec_name. Use it like that:
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
return self.env['res.company']._add_company_code(super())
|
||||||
|
"""
|
||||||
|
records = super_object.__self__
|
||||||
|
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')}
|
||||||
|
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()]
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
return self.env['res.company']._add_company_code(super())
|
||||||
1
company_code/readme/CONTRIBUTORS.rst
Normal file
1
company_code/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1 @@
|
|||||||
|
David Beal <david.beal@akretion.com>
|
||||||
2
company_code/readme/DESCRIPTION.rst
Normal file
2
company_code/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- add `code` field to company.
|
||||||
|
- update name_get with this field
|
||||||
13
company_code/readme/USAGE.rst
Normal file
13
company_code/readme/USAGE.rst
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
To display your company code with `name_get()` just
|
||||||
|
write this in your custom code according to your model
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
return self.env['res.company']._add_company_code(super())
|
||||||
|
|
||||||
|
```
|
||||||
415
company_code/static/description/index.html
Normal file
415
company_code/static/description/index.html
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
|
||||||
|
<title>Company Code</title>
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
/*
|
||||||
|
:Author: David Goodger (goodger@python.org)
|
||||||
|
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
|
||||||
|
:Copyright: This stylesheet has been placed in the public domain.
|
||||||
|
|
||||||
|
Default cascading style sheet for the HTML output of Docutils.
|
||||||
|
|
||||||
|
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||||
|
customize this style sheet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* used to remove borders from tables and images */
|
||||||
|
.borderless, table.borderless td, table.borderless th {
|
||||||
|
border: 0 }
|
||||||
|
|
||||||
|
table.borderless td, table.borderless th {
|
||||||
|
/* Override padding for "table.docutils td" with "! important".
|
||||||
|
The right padding separates the table cells. */
|
||||||
|
padding: 0 0.5em 0 0 ! important }
|
||||||
|
|
||||||
|
.first {
|
||||||
|
/* Override more specific margin styles with "! important". */
|
||||||
|
margin-top: 0 ! important }
|
||||||
|
|
||||||
|
.last, .with-subtitle {
|
||||||
|
margin-bottom: 0 ! important }
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none }
|
||||||
|
|
||||||
|
.subscript {
|
||||||
|
vertical-align: sub;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
.superscript {
|
||||||
|
vertical-align: super;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
a.toc-backref {
|
||||||
|
text-decoration: none ;
|
||||||
|
color: black }
|
||||||
|
|
||||||
|
blockquote.epigraph {
|
||||||
|
margin: 2em 5em ; }
|
||||||
|
|
||||||
|
dl.docutils dd {
|
||||||
|
margin-bottom: 0.5em }
|
||||||
|
|
||||||
|
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||||
|
dl.docutils dt {
|
||||||
|
font-weight: bold }
|
||||||
|
*/
|
||||||
|
|
||||||
|
div.abstract {
|
||||||
|
margin: 2em 5em }
|
||||||
|
|
||||||
|
div.abstract p.topic-title {
|
||||||
|
font-weight: bold ;
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||||
|
div.hint, div.important, div.note, div.tip, div.warning {
|
||||||
|
margin: 2em ;
|
||||||
|
border: medium outset ;
|
||||||
|
padding: 1em }
|
||||||
|
|
||||||
|
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||||
|
div.important p.admonition-title, div.note p.admonition-title,
|
||||||
|
div.tip p.admonition-title {
|
||||||
|
font-weight: bold ;
|
||||||
|
font-family: sans-serif }
|
||||||
|
|
||||||
|
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||||
|
div.danger p.admonition-title, div.error p.admonition-title,
|
||||||
|
div.warning p.admonition-title, .code .error {
|
||||||
|
color: red ;
|
||||||
|
font-weight: bold ;
|
||||||
|
font-family: sans-serif }
|
||||||
|
|
||||||
|
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||||
|
compound paragraphs.
|
||||||
|
div.compound .compound-first, div.compound .compound-middle {
|
||||||
|
margin-bottom: 0.5em }
|
||||||
|
|
||||||
|
div.compound .compound-last, div.compound .compound-middle {
|
||||||
|
margin-top: 0.5em }
|
||||||
|
*/
|
||||||
|
|
||||||
|
div.dedication {
|
||||||
|
margin: 2em 5em ;
|
||||||
|
text-align: center ;
|
||||||
|
font-style: italic }
|
||||||
|
|
||||||
|
div.dedication p.topic-title {
|
||||||
|
font-weight: bold ;
|
||||||
|
font-style: normal }
|
||||||
|
|
||||||
|
div.figure {
|
||||||
|
margin-left: 2em ;
|
||||||
|
margin-right: 2em }
|
||||||
|
|
||||||
|
div.footer, div.header {
|
||||||
|
clear: both;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
div.line-block {
|
||||||
|
display: block ;
|
||||||
|
margin-top: 1em ;
|
||||||
|
margin-bottom: 1em }
|
||||||
|
|
||||||
|
div.line-block div.line-block {
|
||||||
|
margin-top: 0 ;
|
||||||
|
margin-bottom: 0 ;
|
||||||
|
margin-left: 1.5em }
|
||||||
|
|
||||||
|
div.sidebar {
|
||||||
|
margin: 0 0 0.5em 1em ;
|
||||||
|
border: medium outset ;
|
||||||
|
padding: 1em ;
|
||||||
|
background-color: #ffffee ;
|
||||||
|
width: 40% ;
|
||||||
|
float: right ;
|
||||||
|
clear: right }
|
||||||
|
|
||||||
|
div.sidebar p.rubric {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-size: medium }
|
||||||
|
|
||||||
|
div.system-messages {
|
||||||
|
margin: 5em }
|
||||||
|
|
||||||
|
div.system-messages h1 {
|
||||||
|
color: red }
|
||||||
|
|
||||||
|
div.system-message {
|
||||||
|
border: medium outset ;
|
||||||
|
padding: 1em }
|
||||||
|
|
||||||
|
div.system-message p.system-message-title {
|
||||||
|
color: red ;
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
div.topic {
|
||||||
|
margin: 2em }
|
||||||
|
|
||||||
|
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||||
|
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||||
|
margin-top: 0.4em }
|
||||||
|
|
||||||
|
h1.title {
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
h2.subtitle {
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
hr.docutils {
|
||||||
|
width: 75% }
|
||||||
|
|
||||||
|
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||||
|
clear: left ;
|
||||||
|
float: left ;
|
||||||
|
margin-right: 1em }
|
||||||
|
|
||||||
|
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||||
|
clear: right ;
|
||||||
|
float: right ;
|
||||||
|
margin-left: 1em }
|
||||||
|
|
||||||
|
img.align-center, .figure.align-center, object.align-center {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.align-center {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
text-align: left }
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
clear: both ;
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right }
|
||||||
|
|
||||||
|
/* reset inner alignment in figures */
|
||||||
|
div.align-right {
|
||||||
|
text-align: inherit }
|
||||||
|
|
||||||
|
/* div.align-center * { */
|
||||||
|
/* text-align: left } */
|
||||||
|
|
||||||
|
.align-top {
|
||||||
|
vertical-align: top }
|
||||||
|
|
||||||
|
.align-middle {
|
||||||
|
vertical-align: middle }
|
||||||
|
|
||||||
|
.align-bottom {
|
||||||
|
vertical-align: bottom }
|
||||||
|
|
||||||
|
ol.simple, ul.simple {
|
||||||
|
margin-bottom: 1em }
|
||||||
|
|
||||||
|
ol.arabic {
|
||||||
|
list-style: decimal }
|
||||||
|
|
||||||
|
ol.loweralpha {
|
||||||
|
list-style: lower-alpha }
|
||||||
|
|
||||||
|
ol.upperalpha {
|
||||||
|
list-style: upper-alpha }
|
||||||
|
|
||||||
|
ol.lowerroman {
|
||||||
|
list-style: lower-roman }
|
||||||
|
|
||||||
|
ol.upperroman {
|
||||||
|
list-style: upper-roman }
|
||||||
|
|
||||||
|
p.attribution {
|
||||||
|
text-align: right ;
|
||||||
|
margin-left: 50% }
|
||||||
|
|
||||||
|
p.caption {
|
||||||
|
font-style: italic }
|
||||||
|
|
||||||
|
p.credits {
|
||||||
|
font-style: italic ;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
p.label {
|
||||||
|
white-space: nowrap }
|
||||||
|
|
||||||
|
p.rubric {
|
||||||
|
font-weight: bold ;
|
||||||
|
font-size: larger ;
|
||||||
|
color: maroon ;
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
p.sidebar-title {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-weight: bold ;
|
||||||
|
font-size: larger }
|
||||||
|
|
||||||
|
p.sidebar-subtitle {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
p.topic-title {
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
pre.address {
|
||||||
|
margin-bottom: 0 ;
|
||||||
|
margin-top: 0 ;
|
||||||
|
font: inherit }
|
||||||
|
|
||||||
|
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||||
|
margin-left: 2em ;
|
||||||
|
margin-right: 2em }
|
||||||
|
|
||||||
|
pre.code .ln { color: grey; } /* line numbers */
|
||||||
|
pre.code, code { background-color: #eeeeee }
|
||||||
|
pre.code .comment, code .comment { color: #5C6576 }
|
||||||
|
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||||
|
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||||
|
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||||
|
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||||
|
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||||
|
|
||||||
|
span.classifier {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-style: oblique }
|
||||||
|
|
||||||
|
span.classifier-delimiter {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
span.interpreted {
|
||||||
|
font-family: sans-serif }
|
||||||
|
|
||||||
|
span.option {
|
||||||
|
white-space: nowrap }
|
||||||
|
|
||||||
|
span.pre {
|
||||||
|
white-space: pre }
|
||||||
|
|
||||||
|
span.problematic {
|
||||||
|
color: red }
|
||||||
|
|
||||||
|
span.section-subtitle {
|
||||||
|
/* font-size relative to parent (h1..h6 element) */
|
||||||
|
font-size: 80% }
|
||||||
|
|
||||||
|
table.citation {
|
||||||
|
border-left: solid 1px gray;
|
||||||
|
margin-left: 1px }
|
||||||
|
|
||||||
|
table.docinfo {
|
||||||
|
margin: 2em 4em }
|
||||||
|
|
||||||
|
table.docutils {
|
||||||
|
margin-top: 0.5em ;
|
||||||
|
margin-bottom: 0.5em }
|
||||||
|
|
||||||
|
table.footnote {
|
||||||
|
border-left: solid 1px black;
|
||||||
|
margin-left: 1px }
|
||||||
|
|
||||||
|
table.docutils td, table.docutils th,
|
||||||
|
table.docinfo td, table.docinfo th {
|
||||||
|
padding-left: 0.5em ;
|
||||||
|
padding-right: 0.5em ;
|
||||||
|
vertical-align: top }
|
||||||
|
|
||||||
|
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||||
|
font-weight: bold ;
|
||||||
|
text-align: left ;
|
||||||
|
white-space: nowrap ;
|
||||||
|
padding-left: 0 }
|
||||||
|
|
||||||
|
/* "booktabs" style (no vertical lines) */
|
||||||
|
table.docutils.booktabs {
|
||||||
|
border: 0px;
|
||||||
|
border-top: 2px solid;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table.docutils.booktabs * {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
table.docutils.booktabs th {
|
||||||
|
border-bottom: thin solid;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||||
|
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||||
|
font-size: 100% }
|
||||||
|
|
||||||
|
ul.auto-toc {
|
||||||
|
list-style-type: none }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="document" id="company-code">
|
||||||
|
<h1 class="title">Company Code</h1>
|
||||||
|
|
||||||
|
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
|
!! changes will be overwritten. !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||||
|
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/akretion/odoo-usability/tree/12.0/company_code"><img alt="akretion/odoo-usability" src="https://img.shields.io/badge/github-akretion%2Fodoo--usability-lightgray.png?logo=github" /></a></p>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>add <cite>code</cite> field to company.</li>
|
||||||
|
<li>update name_get with this field</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Table of contents</strong></p>
|
||||||
|
<div class="contents local topic" id="contents">
|
||||||
|
<ul class="simple">
|
||||||
|
<li><a class="reference internal" href="#bug-tracker" id="id1">Bug Tracker</a></li>
|
||||||
|
<li><a class="reference internal" href="#credits" id="id2">Credits</a><ul>
|
||||||
|
<li><a class="reference internal" href="#authors" id="id3">Authors</a></li>
|
||||||
|
<li><a class="reference internal" href="#contributors" id="id4">Contributors</a></li>
|
||||||
|
<li><a class="reference internal" href="#maintainers" id="id5">Maintainers</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="bug-tracker">
|
||||||
|
<h1><a class="toc-backref" href="#id1">Bug Tracker</a></h1>
|
||||||
|
<p>Bugs are tracked on <a class="reference external" href="https://github.com/akretion/odoo-usability/issues">GitHub Issues</a>.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||||
|
<a class="reference external" href="https://github.com/akretion/odoo-usability/issues/new?body=module:%20company_code%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||||
|
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="credits">
|
||||||
|
<h1><a class="toc-backref" href="#id2">Credits</a></h1>
|
||||||
|
<div class="section" id="authors">
|
||||||
|
<h2><a class="toc-backref" href="#id3">Authors</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Akretion</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="contributors">
|
||||||
|
<h2><a class="toc-backref" href="#id4">Contributors</a></h2>
|
||||||
|
<p>David Beal <<a class="reference external" href="mailto:david.beal@akretion.com">david.beal@akretion.com</a>></p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="maintainers">
|
||||||
|
<h2><a class="toc-backref" href="#id5">Maintainers</a></h2>
|
||||||
|
<p>This module is part of the <a class="reference external" href="https://github.com/akretion/odoo-usability/tree/12.0/company_code">akretion/odoo-usability</a> project on GitHub.</p>
|
||||||
|
<p>You are welcome to contribute.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
14
company_code/views/company_view.xml
Normal file
14
company_code/views/company_view.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?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="arch" type="xml">
|
||||||
|
<field name="partner_id" position="before">
|
||||||
|
<field name="code"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
1
delivery_usability/__init__.py
Normal file
1
delivery_usability/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import stock
|
||||||
29
delivery_usability/__manifest__.py
Normal file
29
delivery_usability/__manifest__.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2018-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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Delivery Usability',
|
||||||
|
'version': '12.0.1.0.0',
|
||||||
|
'category': 'Stock',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Several usability enhancements in Delivery',
|
||||||
|
'description': """
|
||||||
|
Delivery Usability
|
||||||
|
===================
|
||||||
|
|
||||||
|
The usability enhancements include:
|
||||||
|
* allow modification of carrier and it's tracking ref. on a done picking
|
||||||
|
* display field 'invoice_shipping_on_delivery' on sale.order form view
|
||||||
|
|
||||||
|
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
'depends': ['delivery'],
|
||||||
|
'data': [
|
||||||
|
'sale_view.xml',
|
||||||
|
'stock_view.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
23
delivery_usability/sale_view.xml
Normal file
23
delivery_usability/sale_view.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2018-2019 Akretion
|
||||||
|
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_order_form_with_carrier" model="ir.ui.view">
|
||||||
|
<field name="name">delivery_usability.sale.order.form</field>
|
||||||
|
<field name="model">sale.order</field>
|
||||||
|
<field name="inherit_id" ref="delivery.view_order_form_with_carrier"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<group name="sale_pay" position="inside">
|
||||||
|
<field name="invoice_shipping_on_delivery"/>
|
||||||
|
</group>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
12
delivery_usability/stock.py
Normal file
12
delivery_usability/stock.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Copyright 2018-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).
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class StockPicking(models.Model):
|
||||||
|
_inherit = 'stock.picking'
|
||||||
|
|
||||||
|
carrier_id = fields.Many2one(track_visibility='onchange')
|
||||||
|
carrier_tracking_ref = fields.Char(track_visibility='onchange')
|
||||||
26
delivery_usability/stock_view.xml
Normal file
26
delivery_usability/stock_view.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2018 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_picking_withcarrier_out_form" model="ir.ui.view">
|
||||||
|
<field name="name">delivery_usability.stock.picking.form</field>
|
||||||
|
<field name="model">stock.picking</field>
|
||||||
|
<field name="inherit_id" ref="delivery.view_picking_withcarrier_out_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="carrier_id" position="attributes">
|
||||||
|
<!-- Sometimes we have to modify carrier_id when state is done
|
||||||
|
so remove readonly when state = done from view and add tracking on_change in
|
||||||
|
field definition -->
|
||||||
|
<attribute name="attrs">{}</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="carrier_tracking_ref" position="attributes">
|
||||||
|
<attribute name="attrs">{}</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
0
developer_menu/__init__.py
Normal file
0
developer_menu/__init__.py
Normal file
31
developer_menu/__manifest__.py
Normal file
31
developer_menu/__manifest__.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Developer Menu',
|
||||||
|
'version': '12.0.0.0.0',
|
||||||
|
'category': 'Tools',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': "Menu Shortcut for developer usage",
|
||||||
|
'description': """
|
||||||
|
Developer menu
|
||||||
|
==============
|
||||||
|
|
||||||
|
Add a menu which gather main technical used menus
|
||||||
|
|
||||||
|
How to use it
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Ensure you're in ERP manager group and go to configuration page
|
||||||
|
near `Technical` menu
|
||||||
|
|
||||||
|
This module has been written by David Béal
|
||||||
|
from Akretion <david.beal@akretion.com>.
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
'depends': ['mail'],
|
||||||
|
'data': [
|
||||||
|
'menu_view.xml'
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
21
developer_menu/menu_view.xml
Normal file
21
developer_menu/menu_view.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<menuitem id="conf_tech" parent="base.menu_administration" name="🧰" groups="base.group_erp_manager" sequence="100"/>
|
||||||
|
<menuitem id="model" name="Model" parent="conf_tech" action="base.action_model_model" sequence="10"/>
|
||||||
|
<menuitem id="view" name="View" parent="conf_tech" action="base.action_ui_view" sequence="20" />
|
||||||
|
<menuitem id="rec_rule" name="Record Rule" parent="conf_tech" action="base.action_rule" sequence="30" />
|
||||||
|
<menuitem id="menu" name="Menu" parent="conf_tech" action="base.grant_menu_access" sequence="100" />
|
||||||
|
<menuitem id="seq" name="Sequence" parent="conf_tech" action="base.ir_sequence_form" sequence="100" />
|
||||||
|
<menuitem id="model_data" name="Model Data" parent="conf_tech" action="base.action_model_data" sequence="100" />
|
||||||
|
<menuitem id="param" name="Param" parent="conf_tech" action="base.ir_config_list_action" sequence="100" />
|
||||||
|
<menuitem id="cron" name="Cron" parent="conf_tech" action="base.ir_cron_act" sequence="100" />
|
||||||
|
<menuitem id="window" name="Act Window" parent="conf_tech" action="base.ir_action_window" sequence="100" />
|
||||||
|
<menuitem id="server" name="Act Server" parent="conf_tech" action="base.action_server_action" sequence="100" />
|
||||||
|
<menuitem id="report" name="Report" parent="conf_tech" action="base.ir_action_report" sequence="100" />
|
||||||
|
<menuitem id="mail_tmpl" name="Mail Tmpl" parent="conf_tech" action="mail.action_email_template_tree_all" sequence="100" />
|
||||||
|
<menuitem id="property" name="Property" parent="conf_tech" action="base.ir_property_form" sequence="100" />
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
1
eradicate_quick_create/__init__.py
Normal file
1
eradicate_quick_create/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .hooks import web_m2x_options_create
|
||||||
25
eradicate_quick_create/__manifest__.py
Normal file
25
eradicate_quick_create/__manifest__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2014-2019 Akretion France (http://www.akretion.com)
|
||||||
|
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Eradicate Quick Create',
|
||||||
|
'version': '12.0.2.0.0',
|
||||||
|
'category': 'Tools',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Disable quick create on all objects',
|
||||||
|
'description': """
|
||||||
|
Eradicate Quick Create
|
||||||
|
======================
|
||||||
|
|
||||||
|
Disable quick create on all objects of Odoo.
|
||||||
|
|
||||||
|
This new version of the module uses the module *web_m2x_options* from the OCA *web* project instead of the module *base_optional_quick_create* from the OCA project *server-ux*.
|
||||||
|
|
||||||
|
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
'depends': ['web_m2x_options'],
|
||||||
|
'post_init_hook': 'web_m2x_options_create',
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
19
eradicate_quick_create/hooks.py
Normal file
19
eradicate_quick_create/hooks.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Copyright 2019 Akretion France
|
||||||
|
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import SUPERUSER_ID
|
||||||
|
from odoo.api import Environment
|
||||||
|
|
||||||
|
|
||||||
|
def web_m2x_options_create(cr, registry):
|
||||||
|
env = Environment(cr, SUPERUSER_ID, {})
|
||||||
|
config_parameter = env['ir.config_parameter'].search(
|
||||||
|
[('key', '=', 'web_m2x_options.create')])
|
||||||
|
if config_parameter and config_parameter.value != 'False':
|
||||||
|
config_parameter.value = 'False'
|
||||||
|
else:
|
||||||
|
env['ir.config_parameter'].create({
|
||||||
|
'key': 'web_m2x_options.create',
|
||||||
|
'value': 'False',
|
||||||
|
})
|
||||||
BIN
eradicate_quick_create/static/description/icon.png
Normal file
BIN
eradicate_quick_create/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
4
intrastat_product_type/__init__.py
Normal file
4
intrastat_product_type/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import intrastat_product_type
|
||||||
|
from .post_install import set_intrastat_type_on_products
|
||||||
28
intrastat_product_type/__manifest__.py
Normal file
28
intrastat_product_type/__manifest__.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright 2016-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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Intrastat Product Type',
|
||||||
|
'version': '12.0.1.0.0',
|
||||||
|
'category': 'Accounting',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Adds a special field Intrastat Type on Products',
|
||||||
|
'description': """
|
||||||
|
Intrastat Product Type
|
||||||
|
======================
|
||||||
|
|
||||||
|
This module is designed for a very special usage scenario. Some companies want to handle the delivery of services the same way as they handle the delivery of goods ; they want to show the services in the delivery note, etc. So, those companies configure the services with Type = *Consumable*. This works well to have the services on the outgoing pickings, but it is a problem for the intrastat declarations.
|
||||||
|
|
||||||
|
This module adds a field *Intrastat Type* on the Product Form with 2 possible options: *Product* or *Service*. The intrastat declaration will use this field instead of the native *Type* field.
|
||||||
|
|
||||||
|
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
# 'depends': ['intrastat_product', 'l10n_fr_intrastat_service'],
|
||||||
|
'depends': ['intrastat_product'],
|
||||||
|
'data': ['product_view.xml'],
|
||||||
|
'post_init_hook': 'set_intrastat_type_on_products',
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
82
intrastat_product_type/intrastat_product_type.py
Normal file
82
intrastat_product_type/intrastat_product_type.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2016-2019 Akretion (http://www.akretion.com)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||||
|
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class ProductTemplate(models.Model):
|
||||||
|
_inherit = 'product.template'
|
||||||
|
|
||||||
|
intrastat_type = fields.Selection([
|
||||||
|
('product', 'Product'),
|
||||||
|
('service', 'Service'),
|
||||||
|
], string='Intrastat Type', default='product', required=True,
|
||||||
|
help="Type of product used for the intrastat declarations. "
|
||||||
|
"For example, you can configure a product with "
|
||||||
|
"'Product Type' = 'Consumable' and 'Intrastat Type' = 'Service'.")
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@api.constrains('type', 'intrastat_type')
|
||||||
|
def check_intrastat_type(self):
|
||||||
|
for pt in self:
|
||||||
|
if pt.intrastat_type == 'product' and pt.type == 'service':
|
||||||
|
raise ValidationError(_(
|
||||||
|
"On the product %s, you cannot set Product Type to "
|
||||||
|
"'Service' and Intrastat Type to 'Product'.") % pt.name)
|
||||||
|
if pt.intrastat_type == 'service' and pt.type == 'product':
|
||||||
|
raise ValidationError(_(
|
||||||
|
"On the product %s, you cannot set Intrastat Type to "
|
||||||
|
"'Service' and Product Type to 'Stockable product' "
|
||||||
|
"(but you can set Product Type to 'Consumable' or "
|
||||||
|
"'Service').") % pt.name)
|
||||||
|
|
||||||
|
@api.onchange('type')
|
||||||
|
def intrastat_type_onchange(self):
|
||||||
|
if self.type in ('product', 'consu'):
|
||||||
|
self.intrastat_type = 'product'
|
||||||
|
elif self.type == 'service':
|
||||||
|
self.intrastat_type = 'service'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if vals.get('type'):
|
||||||
|
if not vals.get('intrastat_type'):
|
||||||
|
if vals['type'] in ('product', 'consu'):
|
||||||
|
vals['intrastat_type'] = 'product'
|
||||||
|
elif vals['type'] == 'service':
|
||||||
|
vals['intrastat_type'] = 'service'
|
||||||
|
elif (
|
||||||
|
vals.get('intrastat_type') == 'product' and
|
||||||
|
vals['type'] == 'service'):
|
||||||
|
# usefull because intrastat_type = 'product' by default and
|
||||||
|
# wizards in other modules that don't depend on this module
|
||||||
|
# (e.g. sale_rental) may create a product with only
|
||||||
|
# {'type': 'service'} and no 'intrastat_type'
|
||||||
|
vals['intrastat_type'] = 'service'
|
||||||
|
return super(ProductTemplate, self).create(vals)
|
||||||
|
|
||||||
|
|
||||||
|
#class L10nFrIntrastatServiceDeclaration(models.Model):
|
||||||
|
# _inherit = "l10n.fr.intrastat.service.declaration"
|
||||||
|
|
||||||
|
# def _is_service(self, invoice_line):
|
||||||
|
# if invoice_line.product_id.intrastat_type == 'service':
|
||||||
|
# return True
|
||||||
|
# else:
|
||||||
|
# return False
|
||||||
|
|
||||||
|
|
||||||
|
class IntrastatProductDeclaration(models.Model):
|
||||||
|
_inherit = 'intrastat.product.declaration'
|
||||||
|
|
||||||
|
def _is_product(self, invoice_line):
|
||||||
|
if (
|
||||||
|
invoice_line.product_id and
|
||||||
|
invoice_line.product_id.intrastat_type == 'product'):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
10
intrastat_product_type/post_install.py
Normal file
10
intrastat_product_type/post_install.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2016-2019 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
|
||||||
|
def set_intrastat_type_on_products(cr, registry):
|
||||||
|
cr.execute(
|
||||||
|
"UPDATE product_template SET intrastat_type='service' "
|
||||||
|
"WHERE type='service'")
|
||||||
|
return
|
||||||
23
intrastat_product_type/product_view.xml
Normal file
23
intrastat_product_type/product_view.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2016-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="product_template_form_view" model="ir.ui.view">
|
||||||
|
<field name="name">intrastat_product_type.product.template.form</field>
|
||||||
|
<field name="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="product.product_template_form_view" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="type" position="after">
|
||||||
|
<field name="intrastat_type"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
3
mrp_average_cost/__init__.py
Normal file
3
mrp_average_cost/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from . import models
|
||||||
33
mrp_average_cost/__manifest__.py
Normal file
33
mrp_average_cost/__manifest__.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (C) 2016-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).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'MRP Average Cost',
|
||||||
|
'version': '12.0.1.0.0',
|
||||||
|
'category': 'Manufactuing',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Update standard_price upon validation of a manufacturing order',
|
||||||
|
'description': """
|
||||||
|
MRP Average Cost
|
||||||
|
================
|
||||||
|
|
||||||
|
By default, the official stock module updates the standard_price of a product that has costing_method = 'average' when validating an incoming picking. But the official 'mrp' module doesn't do that when you validate a manufactuging order.
|
||||||
|
|
||||||
|
This module adds this feature : when you validate a manufacturing order of a product that has costing method = 'average', the standard_price of the product will be updated by taking into account the standard_price of each raw material and also a number of work hours defined on the BOM.
|
||||||
|
|
||||||
|
Together with this module, I recommend the use of my module product_usability, available in the same branch, which contains a backport of the model product.price.history from v8 to v7.
|
||||||
|
|
||||||
|
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
'depends': ['mrp'],
|
||||||
|
'data': [
|
||||||
|
'security/mrp_average_cost_security.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'data/mrp_data.xml',
|
||||||
|
'views/mrp_view.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
23
mrp_average_cost/data/mrp_data.xml
Normal file
23
mrp_average_cost/data/mrp_data.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
|
||||||
|
<record forcecreate="True" id="labour_hours" model="decimal.precision">
|
||||||
|
<field name="name">Labour Hours</field>
|
||||||
|
<field name="digits">3</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="phantom_update_product_standard_price" model="ir.cron">
|
||||||
|
<field name="name">Update Cost Price of products with Phantom BOM</field>
|
||||||
|
<field name="active" eval="False"/>
|
||||||
|
<field name="user_id" ref="base.user_root"/>
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="interval_type">days</field>
|
||||||
|
<field name="numbercall">-1</field> <!-- don't limit the number of calls -->
|
||||||
|
<field name="doall" eval="False"/>
|
||||||
|
<field name="model_id" ref="mrp.model_mrp_bom"/>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">model._phantom_update_product_standard_price()</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
3
mrp_average_cost/models/__init__.py
Normal file
3
mrp_average_cost/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from . import mrp
|
||||||
260
mrp_average_cost/models/mrp.py
Normal file
260
mrp_average_cost/models/mrp.py
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
# Copyright (C) 2016-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).
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
import odoo.addons.decimal_precision as dp
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools import float_compare, float_is_zero
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MrpBomLabourLine(models.Model):
|
||||||
|
_name = 'mrp.bom.labour.line'
|
||||||
|
_description = 'Labour lines on BOM'
|
||||||
|
|
||||||
|
bom_id = fields.Many2one(
|
||||||
|
comodel_name='mrp.bom',
|
||||||
|
string='Labour Lines',
|
||||||
|
ondelete='cascade')
|
||||||
|
|
||||||
|
labour_time = fields.Float(
|
||||||
|
string='Labour Time',
|
||||||
|
required=True,
|
||||||
|
digits=dp.get_precision('Labour Hours'),
|
||||||
|
help="Average labour time for the production of "
|
||||||
|
"items of the BOM, in hours.")
|
||||||
|
|
||||||
|
labour_cost_profile_id = fields.Many2one(
|
||||||
|
comodel_name='labour.cost.profile',
|
||||||
|
string='Labour Cost Profile',
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
note = fields.Text(
|
||||||
|
string='Note')
|
||||||
|
|
||||||
|
_sql_constraints = [(
|
||||||
|
'labour_time_positive',
|
||||||
|
'CHECK (labour_time >= 0)',
|
||||||
|
"The value of the field 'Labour Time' must be positive or 0.")]
|
||||||
|
|
||||||
|
|
||||||
|
class MrpBom(models.Model):
|
||||||
|
_inherit = 'mrp.bom'
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
'labour_line_ids.labour_time',
|
||||||
|
'labour_line_ids.labour_cost_profile_id.hour_cost')
|
||||||
|
def _compute_total_labour_cost(self):
|
||||||
|
for bom in self:
|
||||||
|
cost = 0.0
|
||||||
|
for lline in bom.labour_line_ids:
|
||||||
|
cost += lline.labour_time *\
|
||||||
|
lline.labour_cost_profile_id.hour_cost
|
||||||
|
bom.total_labour_cost = cost
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
'bom_line_ids.product_id.standard_price',
|
||||||
|
'total_labour_cost', 'extra_cost')
|
||||||
|
def _compute_total_cost(self):
|
||||||
|
for bom in self:
|
||||||
|
comp_cost = 0.0
|
||||||
|
for line in bom.bom_line_ids:
|
||||||
|
comp_price = line.product_id.standard_price
|
||||||
|
comp_qty_product_uom = line.product_uom_id._compute_quantity(
|
||||||
|
line.product_qty, line.product_id.uom_id)
|
||||||
|
comp_cost += comp_price * comp_qty_product_uom
|
||||||
|
total_cost = comp_cost + bom.extra_cost + bom.total_labour_cost
|
||||||
|
bom.total_components_cost = comp_cost
|
||||||
|
bom.total_cost = total_cost
|
||||||
|
|
||||||
|
labour_line_ids = fields.One2many(
|
||||||
|
'mrp.bom.labour.line', 'bom_id', string='Labour Lines')
|
||||||
|
total_labour_cost = fields.Float(
|
||||||
|
compute='_compute_total_labour_cost', readonly=True,
|
||||||
|
digits=dp.get_precision('Product Price'),
|
||||||
|
string="Total Labour Cost", store=True)
|
||||||
|
extra_cost = fields.Float(
|
||||||
|
string='Extra Cost', track_visibility='onchange',
|
||||||
|
digits=dp.get_precision('Product Price'),
|
||||||
|
help="Extra cost for the production of the quantity of "
|
||||||
|
"items of the BOM, in company currency. "
|
||||||
|
"You can use this field to enter the cost of the consumables "
|
||||||
|
"that are used to produce the product but are not listed in "
|
||||||
|
"the BOM")
|
||||||
|
total_components_cost = fields.Float(
|
||||||
|
compute='_compute_total_cost', readonly=True,
|
||||||
|
digits=dp.get_precision('Product Price'),
|
||||||
|
string='Total Components Cost')
|
||||||
|
total_cost = fields.Float(
|
||||||
|
compute='_compute_total_cost', readonly=True,
|
||||||
|
string='Total Cost',
|
||||||
|
digits=dp.get_precision('Product Price'),
|
||||||
|
help="Total Cost = Total Components Cost + "
|
||||||
|
"Total Labour Cost + Extra Cost")
|
||||||
|
company_currency_id = fields.Many2one(
|
||||||
|
related='company_id.currency_id', string='Company Currency')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _phantom_update_product_standard_price(self):
|
||||||
|
logger.info('Start to auto-update cost price from phantom bom')
|
||||||
|
boms = self.search([('type', '=', 'phantom')])
|
||||||
|
boms.with_context(
|
||||||
|
product_price_history_origin='Automatic update of Phantom BOMs')\
|
||||||
|
.manual_update_product_standard_price()
|
||||||
|
logger.info('End of the auto-update cost price from phantom bom')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def manual_update_product_standard_price(self):
|
||||||
|
if 'product_price_history_origin' not in self._context:
|
||||||
|
self = self.with_context(
|
||||||
|
product_price_history_origin='Manual update from BOM')
|
||||||
|
precision = self.env['decimal.precision'].precision_get(
|
||||||
|
'Product Price')
|
||||||
|
for bom in self:
|
||||||
|
wproduct = bom.product_id
|
||||||
|
if not wproduct:
|
||||||
|
wproduct = bom.product_tmpl_id
|
||||||
|
if float_compare(
|
||||||
|
wproduct.standard_price, bom.total_cost,
|
||||||
|
precision_digits=precision):
|
||||||
|
wproduct.with_context().write(
|
||||||
|
{'standard_price': bom.total_cost})
|
||||||
|
logger.info(
|
||||||
|
'Cost price updated to %s on product %s',
|
||||||
|
bom.total_cost, wproduct.display_name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MrpBomLine(models.Model):
|
||||||
|
_inherit = 'mrp.bom.line'
|
||||||
|
|
||||||
|
standard_price = fields.Float(
|
||||||
|
related='product_id.standard_price',
|
||||||
|
readonly=True,
|
||||||
|
string='Standard Price')
|
||||||
|
|
||||||
|
|
||||||
|
class LabourCostProfile(models.Model):
|
||||||
|
_name = 'labour.cost.profile'
|
||||||
|
_inherit = ['mail.thread']
|
||||||
|
_description = 'Labour Cost Profile'
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
string='Name',
|
||||||
|
required=True,
|
||||||
|
track_visibility='onchange')
|
||||||
|
|
||||||
|
hour_cost = fields.Float(
|
||||||
|
string='Cost per Hour',
|
||||||
|
required=True,
|
||||||
|
digits=dp.get_precision('Product Price'),
|
||||||
|
track_visibility='onchange',
|
||||||
|
help="Labour cost per hour per person in company currency")
|
||||||
|
|
||||||
|
company_id = fields.Many2one(
|
||||||
|
comodel_name='res.company',
|
||||||
|
string='Company',
|
||||||
|
required=True,
|
||||||
|
default=lambda self: self.env['res.company']._company_default_get())
|
||||||
|
|
||||||
|
company_currency_id = fields.Many2one(
|
||||||
|
related='company_id.currency_id',
|
||||||
|
readonly=True,
|
||||||
|
store=True,
|
||||||
|
string='Company Currency')
|
||||||
|
|
||||||
|
@api.depends('name', 'hour_cost', 'company_currency_id.symbol')
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for record in self:
|
||||||
|
res.append((record.id, u'%s (%s %s)' % (
|
||||||
|
record.name, record.hour_cost,
|
||||||
|
record.company_currency_id.symbol)))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class MrpProduction(models.Model):
|
||||||
|
_inherit = 'mrp.production'
|
||||||
|
|
||||||
|
unit_cost = fields.Float(
|
||||||
|
string='Unit Cost', readonly=True,
|
||||||
|
digits=dp.get_precision('Product Price'),
|
||||||
|
help="This cost per unit in the unit of measure of the product "
|
||||||
|
"in company currency takes into account "
|
||||||
|
"the cost of the raw materials and the labour cost defined on"
|
||||||
|
"the BOM.")
|
||||||
|
|
||||||
|
company_currency_id = fields.Many2one(
|
||||||
|
related='company_id.currency_id', readonly=True,
|
||||||
|
string='Company Currency')
|
||||||
|
|
||||||
|
def compute_order_unit_cost(self):
|
||||||
|
self.ensure_one()
|
||||||
|
mo_total_price = 0.0 # In the UoM of the M0
|
||||||
|
labor_cost_per_unit = 0.0 # In the UoM of the product
|
||||||
|
extra_cost_per_unit = 0.0 # In the UoM of the product
|
||||||
|
# I read the raw materials MO, not on BOM, in order to make
|
||||||
|
# it work with the "dynamic" BOMs (few raw material are auto-added
|
||||||
|
# on the fly on MO)
|
||||||
|
prec = self.env['decimal.precision'].precision_get(
|
||||||
|
'Product Unit of Measure')
|
||||||
|
for raw_smove in self.move_raw_ids:
|
||||||
|
# I don't filter on state, in order to make it work with
|
||||||
|
# partial productions
|
||||||
|
# For partial productions, mo.product_qty is not updated
|
||||||
|
# so we compute with fully qty and we compute with all raw
|
||||||
|
# materials (consumed or not), so it gives a good price
|
||||||
|
# per unit at the end
|
||||||
|
raw_price = raw_smove.product_id.standard_price
|
||||||
|
raw_material_cost = raw_price * raw_smove.product_qty
|
||||||
|
logger.info(
|
||||||
|
'MO %s product %s: raw_material_cost=%s',
|
||||||
|
self.name, raw_smove.product_id.display_name,
|
||||||
|
raw_material_cost)
|
||||||
|
mo_total_price += raw_material_cost
|
||||||
|
if self.bom_id:
|
||||||
|
bom = self.bom_id
|
||||||
|
# if not bom.total_labour_cost:
|
||||||
|
# raise orm.except_orm(
|
||||||
|
# _('Error:'),
|
||||||
|
# _("Total Labor Cost is 0 on bill of material '%s'.")
|
||||||
|
# % bom.name)
|
||||||
|
if float_is_zero(bom.product_qty, precision_digits=prec):
|
||||||
|
raise UserError(_(
|
||||||
|
"Missing Product Quantity on bill of material '%s'.")
|
||||||
|
% bom.display_name)
|
||||||
|
bom_qty_product_uom = bom.product_uom_id._compute_quantity(
|
||||||
|
bom.product_qty, bom.product_tmpl_id.uom_id)
|
||||||
|
assert bom_qty_product_uom > 0, 'BoM qty should be positive'
|
||||||
|
labor_cost_per_unit = bom.total_labour_cost / bom_qty_product_uom
|
||||||
|
extra_cost_per_unit = bom.extra_cost / bom_qty_product_uom
|
||||||
|
# mo_standard_price and labor_cost_per_unit are
|
||||||
|
# in the UoM of the product (not of the MO/BOM)
|
||||||
|
mo_qty_product_uom = self.product_uom_id._compute_quantity(
|
||||||
|
self.product_qty, self.product_id.uom_id)
|
||||||
|
assert mo_qty_product_uom > 0, 'MO qty should be positive'
|
||||||
|
mo_standard_price = mo_total_price / mo_qty_product_uom
|
||||||
|
logger.info(
|
||||||
|
'MO %s: labor_cost_per_unit=%s extra_cost_per_unit=%s',
|
||||||
|
self.name, labor_cost_per_unit, extra_cost_per_unit)
|
||||||
|
mo_standard_price += labor_cost_per_unit
|
||||||
|
mo_standard_price += extra_cost_per_unit
|
||||||
|
return mo_standard_price
|
||||||
|
|
||||||
|
def post_inventory(self):
|
||||||
|
'''This is the method where _action_done() is called on finished move
|
||||||
|
So we write on 'price_unit' of the finished move and THEN we call
|
||||||
|
super() which will call _action_done() which itself calls
|
||||||
|
product_price_update_before_done()'''
|
||||||
|
for order in self:
|
||||||
|
if order.product_id.cost_method == 'average':
|
||||||
|
unit_cost = order.compute_order_unit_cost()
|
||||||
|
order.unit_cost = unit_cost
|
||||||
|
logger.info('MO %s: unit_cost=%s', order.name, unit_cost)
|
||||||
|
for finished_move in order.move_finished_ids.filtered(
|
||||||
|
lambda x: x.product_id == order.product_id):
|
||||||
|
finished_move.price_unit = unit_cost
|
||||||
|
return super(MrpProduction, self).post_inventory()
|
||||||
9
mrp_average_cost/security/ir.model.access.csv
Normal file
9
mrp_average_cost/security/ir.model.access.csv
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_labour_cost_profile_read,Read access on labour.cost.profile to MRP user,model_labour_cost_profile,mrp.group_mrp_user,1,0,0,0
|
||||||
|
access_labour_cost_profile_read_sale,Read access on labour.cost.profile to Sale user,model_labour_cost_profile,sales_team.group_sale_salesman,1,0,0,0
|
||||||
|
access_labour_cost_profile_read_stock,Read access on labour.cost.profile to Stock user,model_labour_cost_profile,stock.group_stock_user,1,0,0,0
|
||||||
|
access_labour_cost_profile_full,Full access on labour.cost.profile to MRP manager,model_labour_cost_profile,mrp.group_mrp_manager,1,1,1,1
|
||||||
|
access_mrp_bom_labour_line_read,Read access on mrp.bom.labour.line to MRP user,model_mrp_bom_labour_line,mrp.group_mrp_user,1,0,0,0
|
||||||
|
access_mrp_bom_labour_line_read_sale,Read access on mrp.bom.labour.line to Sale user,model_mrp_bom_labour_line,sales_team.group_sale_salesman,1,0,0,0
|
||||||
|
access_mrp_bom_labour_line_read_stock,Read access on mrp.bom.labour.line to Stock user,model_mrp_bom_labour_line,stock.group_stock_user,1,0,0,0
|
||||||
|
access_mrp_bom_labour_line_full,Full access on mrp.bom.labour.line to MRP manager,model_mrp_bom_labour_line,mrp.group_mrp_manager,1,1,1,1
|
||||||
|
10
mrp_average_cost/security/mrp_average_cost_security.xml
Normal file
10
mrp_average_cost/security/mrp_average_cost_security.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
|
||||||
|
<record id="labour_cost_profile_rule" model="ir.rule">
|
||||||
|
<field name="name">Labour Cost Profile multi-company</field>
|
||||||
|
<field name="model_id" ref="model_labour_cost_profile"/>
|
||||||
|
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
128
mrp_average_cost/views/mrp_view.xml
Normal file
128
mrp_average_cost/views/mrp_view.xml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016-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="mrp_bom_form_view" model="ir.ui.view">
|
||||||
|
<field name="name">mrp_average_cost.mrp.bom.form</field>
|
||||||
|
<field name="model">mrp.bom</field>
|
||||||
|
<field name="inherit_id" ref="mrp.mrp_bom_form_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="picking_type_id" position="after">
|
||||||
|
<field name="total_components_cost" widget="monetary"
|
||||||
|
options="{'currency_field': 'company_currency_id'}"/>
|
||||||
|
<field name="total_labour_cost" widget="monetary"
|
||||||
|
options="{'currency_field': 'company_currency_id'}"/>
|
||||||
|
<field name="extra_cost" widget="monetary"
|
||||||
|
options="{'currency_field': 'company_currency_id'}"/>
|
||||||
|
<label for="total_cost"/>
|
||||||
|
<div>
|
||||||
|
<field name="total_cost" widget="monetary"
|
||||||
|
options="{'currency_field': 'company_currency_id'}"
|
||||||
|
class="oe_inline"/>
|
||||||
|
<button type="object" name="manual_update_product_standard_price"
|
||||||
|
string="Update Cost Price of Product" class="oe_link"/>
|
||||||
|
</div>
|
||||||
|
<field name="company_currency_id" invisible="1"/>
|
||||||
|
</field>
|
||||||
|
<notebook position="inside">
|
||||||
|
<page string="Labour" name="labour_lines">
|
||||||
|
<group name="labour_lines_grp">
|
||||||
|
<field name="labour_line_ids" nolabel="1"/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
<xpath expr="//field[@name='bom_line_ids']/tree/field[@name='product_uom_id']" position="after">
|
||||||
|
<field name="standard_price"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="mrp_bom_labour_line_tree" model="ir.ui.view">
|
||||||
|
<field name="name">mrp_bom_labour_line.tree</field>
|
||||||
|
<field name="model">mrp.bom.labour.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Labour Lines" editable="bottom">
|
||||||
|
<field name="bom_id" invisible="not context.get('mrp_bom_labour_line_main_view')"/>
|
||||||
|
<field name="labour_time" string="Labour Time (hours)"/>
|
||||||
|
<field name="labour_cost_profile_id"/>
|
||||||
|
<field name="note"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="mrp_bom_labour_line_form" model="ir.ui.view">
|
||||||
|
<field name="name">mrp_bom_labour_line.form</field>
|
||||||
|
<field name="model">mrp.bom.labour.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Labour Line">
|
||||||
|
<group name="main">
|
||||||
|
<field name="bom_id" invisible="not context.get('mrp_bom_labour_line_main_view')"/>
|
||||||
|
<label for="labour_time"/>
|
||||||
|
<div name="labour_time">
|
||||||
|
<field name="labour_time" class="oe_inline"/> hours
|
||||||
|
</div>
|
||||||
|
<field name="labour_cost_profile_id"/>
|
||||||
|
<field name="note"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<record id="labour_cost_profile_form" model="ir.ui.view">
|
||||||
|
<field name="name">labour_cost_profile_form</field>
|
||||||
|
<field name="model">labour.cost.profile</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Labour Cost Profile">
|
||||||
|
<group name="main">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="hour_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
<field name="company_currency_id" invisible="1"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="labour_cost_profile_tree" model="ir.ui.view">
|
||||||
|
<field name="name">labour_cost_profile_tree</field>
|
||||||
|
<field name="model">labour.cost.profile</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Labour Cost Profiles">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="hour_cost"/>
|
||||||
|
<field name="company_currency_id"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="labour_cost_profile_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Labour Cost Profile</field>
|
||||||
|
<field name="res_model">labour.cost.profile</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="labour_cost_profile_menu" action="labour_cost_profile_action"
|
||||||
|
parent="mrp.menu_mrp_configuration" sequence="200"/>
|
||||||
|
|
||||||
|
<record id="mrp_production_form_view" model="ir.ui.view">
|
||||||
|
<field name="name">mrp_average_cost.mrp_production_form</field>
|
||||||
|
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||||
|
<field name="model">mrp.production</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="availability" position="after">
|
||||||
|
<field name="unit_cost" widget="monetary" options="{'currency_field': 'company_currency_id'}" attrs="{'invisible': [('state', '!=', 'done')]}"/>
|
||||||
|
<field name="company_currency_id" invisible="1"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
0
mrp_no_product_template_menu/__init__.py
Normal file
0
mrp_no_product_template_menu/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user