Compare commits
671 Commits
9e33a577e8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ac2e8514a | |||
| 3bb2811e76 | |||
| 2bcbf169cf | |||
| a007ed2895 | |||
| 792733c93b | |||
| 321eae8d91 | |||
| 7d7bd33b97 | |||
| 7fcc90d0a4 | |||
| d81a8750d3 | |||
| 94d0f2f828 | |||
| d76a9a8dd6 | |||
| 705a1706c3 | |||
| 275008c160 | |||
| 1d7a058d68 | |||
| baf1641e1e | |||
| 058bbc7612 | |||
| de1156e5bc | |||
| 3235a1ba57 | |||
| 27d2a81b31 | |||
| e5b986efc5 | |||
| 7dcb68ab69 | |||
| 03c294ad8c | |||
| aaa1490389 | |||
|
|
3994154cad | ||
| 979bbec013 | |||
| d0bafebbbe | |||
| 9b9daf5467 | |||
| 4f9d46cd4a | |||
| 07ba685c0c | |||
| 89d35b5499 | |||
| dfbd8db2b8 | |||
| c5633bdeb8 | |||
| a181e9cfaa | |||
| 1ba95fd636 | |||
| 34dd8eaf54 | |||
| a1bb0e8a04 | |||
| c62cee33af | |||
| f8edbed1eb | |||
| 5596fed28b | |||
| 0ffd43149b | |||
| 9429f7d2e3 | |||
| 33d742ffce | |||
| 2afa454a03 | |||
| 439e9002aa | |||
| ada4c445c8 | |||
| b1f97ade55 | |||
| df4570aff8 | |||
| e284f193fb | |||
| 3f14cd6323 | |||
| fa6198296b | |||
| 94e60fef6c | |||
| b282fe9bc0 | |||
| ffd673f9ef | |||
| 8088fd5254 | |||
| 42d02cc6f8 | |||
| af344b6fc7 | |||
| 264ff534cf | |||
| 64390eaecb | |||
| 99763f10f2 | |||
| 9fa91f1255 | |||
| 6b29088c31 | |||
| 5515bfa15b | |||
| e767f4543e | |||
| f05046b8c9 | |||
| 28a9a334c3 | |||
| da93139bcc | |||
| 12d9b046d2 | |||
| 914fecf3a0 | |||
| 94bbed79f0 | |||
| 0f8e620b0d | |||
| 5a5a0699f2 | |||
| 8118d3190a | |||
| 493dd54606 | |||
| ab80f8d909 | |||
| 2fe13c1e76 | |||
| a70c2c4853 | |||
| d6fa273d11 | |||
| 816d1fcaec | |||
| 8973f8a6d0 | |||
| 572f6ef902 | |||
| bd72f8cef3 | |||
| a94f36e26f | |||
| 669320c976 | |||
| facd627f5e | |||
| b2bd777ec8 | |||
| 71751a96c0 | |||
| c655427be5 | |||
| 0272fc70bc | |||
| 39afd9dc32 | |||
| 0e30f9ac30 | |||
| 8be68db6db | |||
| aba25d62dc | |||
| 4c85f80412 | |||
| a0bd6f0e28 | |||
| cda8b4067b | |||
| 728e2c7d60 | |||
| 4652a1a94a | |||
| b53d7a216d | |||
| 0e004027df | |||
| df8439f3f5 | |||
| cb35de91ba | |||
| 59171ec07b | |||
| 9b0dd0e7c1 | |||
| cf0c0923a6 | |||
| bdf13f4fac | |||
| e184da898f | |||
|
|
b3e8dc9c33 | ||
|
|
fab46dde2f | ||
| 3dfbb25281 | |||
| eb38977dca | |||
| ac3198ee70 | |||
| cf4bd94d03 | |||
| 07ee8b4f99 | |||
| 7fc134f7a6 | |||
| e4590e6177 | |||
| 5a644d430b | |||
|
|
808e75daa6 | ||
|
|
febe29c6b4 | ||
|
|
b312ea9bd3 | ||
| 3321830b5e | |||
| f2fe626d72 | |||
| ddd88cd840 | |||
| 47d0f025b3 | |||
| 8719a63fa5 | |||
| 27b865184c | |||
| c2a523430e | |||
| 8d386e6754 | |||
| 62eed772fc | |||
| b0bcaf293d | |||
| 9cf1bc3604 | |||
| 858b7258d9 | |||
| 3122255bc7 | |||
| 5c3464da5c | |||
| 82143ba87d | |||
| 1b292e4594 | |||
| 0f4edc16ce | |||
| 7ac1e118ae | |||
| 94fd181c11 | |||
| 4ea701d669 | |||
| b3b14039e0 | |||
| ae39ff2f7d | |||
| 2ffd99b03a | |||
| 7d5ac651ab | |||
| e8b3fe5bc4 | |||
| ea85927a17 | |||
| 99d5d2a849 | |||
| c0ac8fac68 | |||
| 2743a7bc14 | |||
| cd49703879 | |||
| cc4a48c38c | |||
| 6fedb92b1a | |||
| cad7f7235e | |||
| b08d2f4dab | |||
| 2a30b03cbe | |||
| eb244b1e3e | |||
| f2947c9f86 | |||
| f9ade37eab | |||
| 5635856711 | |||
| edc20fa546 | |||
| d4f9d02cb4 | |||
| 780f3dcbdb | |||
| d5c6d5c4b5 | |||
| 104d5c616c | |||
| f45cdb0807 | |||
| b7b54c8b7b | |||
| c9dceb9c06 | |||
| cf7aa7f5b5 | |||
| 2998d8d570 | |||
| 83b879d200 | |||
| 50b262dbd2 | |||
| fbf3842e6c | |||
| ef8d914820 | |||
| 423d9476fc | |||
| d08510f084 | |||
| ed818c2287 | |||
| 703868001b | |||
| 6af3de0ffb | |||
| 95dba725a8 | |||
| a880535bc4 | |||
| 8f676c2396 | |||
| dfbc006efb | |||
| 4f331d5c8e | |||
| 227a6c14a8 | |||
| 2b0381c2ae | |||
| 2a1512a436 | |||
| 1c20181d06 | |||
| 6c2061e779 | |||
| 1f24646227 | |||
| 2d737b2628 | |||
| 903b42f730 | |||
| f8d53a27fa | |||
| 4b5e49f264 | |||
| 3b3f9c0988 | |||
| 248e410d88 | |||
| a754e15e32 | |||
| ee183e23c5 | |||
| 8441bf98e7 | |||
| 02e5c43f3d | |||
| 6ba5361269 | |||
| f0714d5670 | |||
| b8bfcc4308 | |||
| c268c3b460 | |||
| 04b3c1d924 | |||
| d8012bd2da | |||
| dd2acc2d09 | |||
| a8102d513f | |||
| ee1cb3e7fb | |||
| d0dd8965cd | |||
| 0833baa2ab | |||
| 8eba0cd066 | |||
| 38af948374 | |||
| 36ee92cbb2 | |||
| fdf855f7db | |||
| 31f617ae1c | |||
| 07e48944b6 | |||
| 3a0edea8cd | |||
| 9b7ccf1feb | |||
| dd8e3e3056 | |||
| d8a0dd43cf | |||
| 7b427813be | |||
| d705d86e80 | |||
| 667b66487d | |||
| 9c92904b3e | |||
| 8a60a8e67f | |||
| e6de614507 | |||
| a7c061f52d | |||
| e4e2087463 | |||
| c15b13cb91 | |||
| e981f96cad | |||
| aac7bad852 | |||
| 1e4d9d6e17 | |||
| a0363fa9c7 | |||
| e19dcafcb7 | |||
| 6a5a267cfd | |||
| 5c9f437945 | |||
| ec90d19cc7 | |||
| 77b1ab333b | |||
| 1f3e32fbe8 | |||
| a435631289 | |||
| 1a5e2e4033 | |||
| 7107625664 | |||
| dbfd6a0d89 | |||
| 6ac19a1334 | |||
| 855dc3a4dd | |||
| 52459a267b | |||
| 9d13cfd587 | |||
| de8310891b | |||
| 925fdfd32b | |||
| 96ad6dd1c6 | |||
| 68bd105e59 | |||
| 90f64cf55f | |||
| f722c6fa1c | |||
| 217344d3d5 | |||
| 5cb4317742 | |||
| cf3ad4bb31 | |||
| fcc5c3866a | |||
| f59150d1a9 | |||
| 67cb5491e2 | |||
| 734c0c8a34 | |||
| fd04810f0c | |||
| c8e8e4f866 | |||
| 3a6f426033 | |||
| 5838d848c2 | |||
| 79c701b711 | |||
| c36c6a411f | |||
| 745325eb20 | |||
| dbd5e5b77f | |||
| c2f3d3995f | |||
| 02a15c93de | |||
| abbcdfd2ec | |||
| dc067fb812 | |||
| 8691ba430f | |||
| e2e0e8cece | |||
| d182b0eceb | |||
| 74d1465470 | |||
| 1abfbbbc3d | |||
| 72501ab625 | |||
| 65e9f4d7d6 | |||
| 0a72076346 | |||
| 3977e138e2 | |||
| 45acff11bc | |||
| 1b15ab4c69 | |||
| 7735306f9c | |||
| 76b34ba280 | |||
| eb82211da7 | |||
| c2495bb6ea | |||
| 9e5cd39629 | |||
| 05a90f64a5 | |||
| 4d3c5e26e2 | |||
| 6ac1904a0c | |||
| 8e82986bd5 | |||
| 6ffc159255 | |||
| 4ebfbe6ddd | |||
| 700adb87f9 | |||
| 126225ea6a | |||
| 1339f9b2f1 | |||
| b3b7803313 | |||
| 24b4e00080 | |||
| 846c2d6a93 | |||
| 801e53f953 | |||
| 06f9a62dbc | |||
| eceeca997e | |||
| f37d21c761 | |||
| 1b97899091 | |||
| afee4243cc | |||
| 743c76c131 | |||
| acf0d4b687 | |||
| d659f14e12 | |||
| b74389f9a6 | |||
| b3c33ed314 | |||
| 73006db98a | |||
| 3599570d5e | |||
| 2ae512e074 | |||
| 90932a290d | |||
| 77922997eb | |||
| 574c1a67f4 | |||
| 1de1d15064 | |||
| 25800aa8a4 | |||
| 5a377fda13 | |||
| 3ab58e63f8 | |||
| a114c14e89 | |||
| a98415fe62 | |||
| c272630a8a | |||
| 9cc0f0715b | |||
| 6860ba9e60 | |||
| e9bd6ff57f | |||
| 0efa60f3b5 | |||
| d3a118db07 | |||
| 48d0b6a3f6 | |||
| 4e1d6895f8 | |||
| 4a0461df44 | |||
| adcce718fd | |||
| 5763ca64a5 | |||
| 0094bd6974 | |||
| c096b39259 | |||
| 654a8418bf | |||
| dd4b8055a7 | |||
| 9556f870ec | |||
|
|
4c08b95b3b | ||
|
|
a6e90f6e8b | ||
| 7df570c860 | |||
| 31d0e00f89 | |||
| 9a1673c6cd | |||
| fb17c13a61 | |||
| 671251d23e | |||
| 4e7911b0ac | |||
| c233e8cb77 | |||
| de0ef9fdf4 | |||
| c95b273291 | |||
| 542246baac | |||
| 0415ced262 | |||
| b767f7648d | |||
| 405c2c2bf0 | |||
| f39675543a | |||
| 2fecf60295 | |||
| 3b9a6ca943 | |||
| fcd24af41b | |||
| b0d69207e4 | |||
| 91bf2a3de4 | |||
| e9a8355587 | |||
| d483e7cf69 | |||
| 2dd8360d62 | |||
| 779e1aa361 | |||
| 2223927d34 | |||
| a26d0516f0 | |||
| 5605b19164 | |||
| f8d7f1e9d9 | |||
| 735c5431af | |||
| 38c614af98 | |||
| fcd809a3e3 | |||
| 83f7e43a6e | |||
| a9b048d4cf | |||
| 7da99b3bf7 | |||
| 242406588c | |||
| 3c4261ccf6 | |||
| 2428978d90 | |||
| c9028f1590 | |||
| 1af7ae99a4 | |||
| 26f43c2438 | |||
| 5b5685ae12 | |||
| 28c4fb3ff8 | |||
| 181c4b3d34 | |||
| 50ac9f297c | |||
| c2d4e34516 | |||
| 5b5f1d77ff | |||
| 0fffb341fe | |||
| 8b4daeac84 | |||
| 4da56e4b8a | |||
| e7a1a83c90 | |||
| c6d7b0da2b | |||
| 12727e53cb | |||
| 436d0b9a0c | |||
| 7a39754a63 | |||
| 55f342f106 | |||
| e78977dfa5 | |||
| ddca542bfc | |||
| 1e5611f726 | |||
| aed68ba6f4 | |||
| 54309a1980 | |||
| 1d398ab4b5 | |||
| 2a5eec0e62 | |||
| 20f51de50f | |||
| a22d185fb0 | |||
| 390207a371 | |||
| 01c23c1ec2 | |||
| c7146dd8a7 | |||
| 7569d06254 | |||
| f0ed5b9bd3 | |||
| 1075f49bd1 | |||
| 1efaad8c38 | |||
| 1dc8803287 | |||
| 50636c1294 | |||
| 6dfb12c125 | |||
| 96653be311 | |||
| 488fb15498 | |||
| f56f394ec3 | |||
| 4b9621a5f0 | |||
| 900d733fcd | |||
| 1adc4248ee | |||
| 3c5c20276d | |||
| f16b334089 | |||
| f29664fd77 | |||
| f7e70e0438 | |||
| 2cf1b25dad | |||
| 27e081228f | |||
| ea3cb65d7d | |||
| 9ad0217a69 | |||
| 1f3616858a | |||
| 072960307a | |||
| 83612eeea3 | |||
| 8e16259ab2 | |||
| 3a1b9f2d90 | |||
| 22a083a853 | |||
| c3d768d5a8 | |||
| 133ca49eda | |||
| f8aded48af | |||
| c6a2d48e4c | |||
| 22861dbc91 | |||
| 8ff380e229 | |||
| 80b2fed34b | |||
| 74e6cd0bf2 | |||
| 70f4bc291c | |||
| a231f32646 | |||
| d1c0f4a738 | |||
| ce883fa950 | |||
| 149306ff4e | |||
| 23fbcfa988 | |||
| 3a31cc354a | |||
| 62941a5c3b | |||
| d2ae65af82 | |||
| b35fa829e2 | |||
| 9d436d6ea0 | |||
| 9a6bb83c19 | |||
| ba79f88b43 | |||
| 4d2f203469 | |||
| c907a87b09 | |||
| ff75f9ab33 | |||
| 3877ff0b6f | |||
| 74b6425e93 | |||
| b20c3d54a4 | |||
| 60f26ddc9d | |||
| 07ddb212e3 | |||
| 4395884754 | |||
| 160ec1544b | |||
| 18c0329a94 | |||
| cdfc117220 | |||
| a613f59a2d | |||
| 4925cc8bfd | |||
| dd2b6952bc | |||
| 30b9da1a78 | |||
| 1a041fdcbf | |||
| 5f159b06dd | |||
| 30e0aa6543 | |||
| 20a4e20e91 | |||
| 82a30f246d | |||
| e683ce7e62 | |||
| 3c788d4764 | |||
| b4d944b10c | |||
| 24826bec50 | |||
| 9677d0dae2 | |||
| 2b3c823743 | |||
| 355831c76d | |||
| e6bbf2f1c1 | |||
| 0d034816f7 | |||
| b0a0d31528 | |||
| a85ab97d46 | |||
| 14ba0361d2 | |||
| 26c955ef53 | |||
| a71ce107f0 | |||
| 343fc8527c | |||
| de78da846d | |||
| 50e25138fd | |||
| f2c7adc2b3 | |||
| c017aac9a7 | |||
| 09c78395f1 | |||
| 4ae64269f4 | |||
| 90593b70c2 | |||
| 4d5bbc5523 | |||
| a23aac4527 | |||
| e37970a43d | |||
| fe3a3ea71e | |||
| 8e890f3c43 | |||
| a684c7b9c1 | |||
| 28d3400651 | |||
| 000cf54fbc | |||
| 65fe45bae2 | |||
| d8ac25dcda | |||
| c27c915700 | |||
| d4da45344c | |||
| bfa874da43 | |||
| 199caa2632 | |||
| 0f218374a7 | |||
| e03dee7def | |||
| 83aa474073 | |||
| 75bd34b673 | |||
| 03d65c253c | |||
| adac3ca630 | |||
| 221e701507 | |||
| 5a7b5845b6 | |||
| 616cf6d73c | |||
| 589ff16401 | |||
| a7d360b584 | |||
| 56b173d225 | |||
| 27450d51f6 | |||
| ea31706ff8 | |||
| 35fe191939 | |||
| 062b42f83f | |||
| debe374f1b | |||
| b5b554bad0 | |||
| 1d6f3158c0 | |||
| ff2b897b7a | |||
| 30d4362c09 | |||
| aa9a18fb93 | |||
| 4faa7fd83c | |||
| ca20d418cc | |||
| 3314d31fcd | |||
| f6db19d1d9 | |||
| 257916bd6c | |||
| 2cdcbe7303 | |||
| 98893647f6 | |||
| 2e276e2b1f | |||
| 384c1285b1 | |||
| 308252215f | |||
| 7965b22954 | |||
| 8ae2c7ca9c | |||
| 16151381cc | |||
| 4db0395a11 | |||
| fbfa73110f | |||
| 9544418897 | |||
| e40a32e796 | |||
| 530f2f9d97 | |||
| 9879926f08 | |||
| 04e9d7dc03 | |||
| 7a123b1af6 | |||
| a2b053c565 | |||
| 6018429369 | |||
| 4d4e7ab625 | |||
| 68ce1f3a62 | |||
| d8a56637c9 | |||
| a1486175f2 | |||
| 228ee07a9c | |||
| 036a2801e5 | |||
| c2838dcfd3 | |||
| 6e26f178ce | |||
| 4458871423 | |||
| 99782b5b0e | |||
| d09d183cdc | |||
| ec45440400 | |||
| edb23b008c | |||
| fca279a038 | |||
| 512862113a | |||
| 1ed79a2363 | |||
| 05a75f000a | |||
| 77a5366622 | |||
| c203e1b300 | |||
| 22ad8547e4 | |||
| 2ea2165653 | |||
| 5e72d4f98d | |||
| b1680cc96c | |||
| cfb719a7da | |||
| 9e4a12605c | |||
| 4013f9d74c | |||
| 280cff5fdb | |||
| 897c6f6824 | |||
| 8906f00d36 | |||
| 29a719c117 | |||
| d71257720e | |||
| aee9c3277e | |||
| b68f475e22 | |||
| 90eab73430 | |||
| 472806ef06 | |||
| 0def187750 | |||
| 229b6037fb | |||
| 8a90216357 | |||
| 4bbd7a5e76 | |||
| 9c8d7f11ae | |||
| b39607d987 | |||
| 65482b4a8b | |||
| 8b9787d4c0 | |||
| a1ab7dec82 | |||
| 5ae8af84fb | |||
| c90b14fcc1 | |||
| e9ff9c76ab | |||
| 63d8266a9c | |||
| add4cdc137 | |||
| 4d94aa78ed | |||
| da01249f66 | |||
|
|
5cbb57c657 | ||
| 50a8c6328f | |||
| eaa5c8b544 | |||
| 78e9e06a8b | |||
| 9f06398b2c | |||
| 51a84f1f2e | |||
| 00330008d1 | |||
| 3480eb8a7a | |||
| 5179d98289 | |||
| 2109d7a3e4 | |||
| 1f350e6207 | |||
| 7722292482 | |||
| ec359f6b8a | |||
| 845b9cf749 | |||
| 48b941b109 | |||
| 18ece66cdb | |||
| acfc2fe88a | |||
| 888b880bd6 | |||
| 1f62ae91dd | |||
| 05e68636ad | |||
| bfb9bb3188 | |||
| b78e64f9f1 | |||
| 199b8aec12 | |||
| 1757075f2b | |||
| 172d38479d | |||
| 4902368b15 | |||
| 7b4f757cb5 | |||
| b37f132cdf | |||
| 15f791bd92 | |||
| 58cd66e543 | |||
| cc6ce82ec1 | |||
| 11526ef3ee | |||
| 6d52317804 | |||
| a99efcfc5b | |||
| 0d5cf7dffc | |||
| 613b679908 | |||
| 51ced23ab8 | |||
| 2958e1fb9e | |||
| 346a34951d | |||
| b644aea007 | |||
| 5dbaba5f32 | |||
| d133665fc7 | |||
| c2cb2a874c | |||
| 408970c339 | |||
| ea2627c9ae | |||
| ac988a714a | |||
| 97eae6e4a6 | |||
| 3976b387d7 | |||
| 9b8e8127a1 | |||
| f53a9bce27 | |||
| a7753b974f | |||
| c687828ba5 | |||
| 5054b64cd0 | |||
| 06922973b7 | |||
| 18ebf7f06c | |||
| 44c4560f24 | |||
| 7643bf21fb | |||
| 97677025d7 | |||
| 02fe5b3e5d | |||
| 6e529deca0 | |||
| efee365fc6 | |||
| 6bf245ac64 | |||
| 238869989a | |||
|
|
806e374ceb |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules)",
|
||||
"Bash(ls -d */)",
|
||||
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade)",
|
||||
"Bash(ls -1d */)",
|
||||
"Bash(for f:*)",
|
||||
"Bash(do echo:*)",
|
||||
"Read(//c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/**)",
|
||||
"Bash(done)",
|
||||
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade/view)",
|
||||
"Bash(ls -1 *.xml)",
|
||||
"Bash(py --version)"
|
||||
]
|
||||
}
|
||||
}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
deployment/vps-TradonDev_Instructions.md
|
||||
deployment/vps/46.202.173.47-credentials.md
|
||||
*.pyc
|
||||
~$*
|
||||
notes/accounting/excel_web_api/.env
|
||||
|
||||
55
AGENTS.md
55
AGENTS.md
@@ -38,15 +38,55 @@ Guide rapide pour les agents qui codent dans ce repository.
|
||||
- Lire `wsgi.py`, `rpc.py`, `protocols/*`, `tests/test_rpc.py`, `tests/test_wsgi.py`.
|
||||
- Si bug metier:
|
||||
- Modifier uniquement `modules/<module>/` + ses tests.
|
||||
- Conventions de champs dates:
|
||||
- Dans ce projet, ne pas introduire de `fields.DateTime`.
|
||||
- Utiliser `fields.Date` pour les dates metier et les champs de suivi UI, sauf demande explicite deja existante dans le module cible.
|
||||
- Si bug template Relatorio (`.fodt`):
|
||||
- Lire d'abord le template standard voisin du meme domaine (`invoice.fodt`, `sale.fodt`, etc.).
|
||||
- Preferer des proprietes Python simples exposees par le modele plutot que des expressions Genshi complexes dans le template.
|
||||
- Dans les placeholders XML, utiliser `"` et `'` plutot que des antislashs type `\'`.
|
||||
- Si un document facture depend fortement d'une vente/achat, ajouter au besoin un petit pont Python pour exposer des `report_*` stables au template.
|
||||
- Pour les templates `stock.shipment.in`, preferer aussi des proprietes `report_*` sur le shipment plutot que des contextes ad hoc (`si_*`) quand le document devient metier ou client-specifique.
|
||||
- Si plusieurs actions de report pointent vers `report_name = 'account.invoice'`, verifier aussi le cache `invoice_report_cache` dans `modules/account_invoice/invoice.py`: un mauvais cache peut faire croire que plusieurs actions utilisent le meme `.fodt`.
|
||||
- Avant de conclure qu'un template ou une action est faux, verifier si le report alternatif doit bypasser le cache standard.
|
||||
- Pour les templates shipment, ne pas supposer qu'une variable locale comme `shipment` sera definie partout dans Genshi, surtout dans les headers/footers; preferer `records[0]....` ou des placeholders alignes sur le scope reel du report.
|
||||
- Dans `purchase_trade`, pour remonter d'une facture vers shipment, pro forma, freight ou autres donnees logistiques, privilegier le lot physique comme pont entre `purchase.line`, `sale.line` et shipment.
|
||||
- Pour `FREIGHT VALUE`, ne pas lire un champ direct sur la facture: retrouver le fee de shipment (`shipment_in`) dont le produit est `Maritime freight`, puis utiliser `fee.get_amount()`.
|
||||
- Rappels session templates (2026-04-08):
|
||||
- `insurance.fodt`: le texte "insured for account of" doit afficher la compagnie courante (shipment.company.party), pas le client.
|
||||
- `insurance.fodt`: exposer des proprietes Python `report_*` sur `stock.shipment.in` pour les montants (incoming moves) et les zones client-specifiques.
|
||||
- `insurance.fodt`: "Amount insured" suit la regle metier 110% du montant incoming (base calculee via lot -> purchase.line.unit_price * quantite courante convertie).
|
||||
- `insurance.fodt`: zone "Contact the following surveyor" alimentee par une propriete dediee, avec champ `surveyor` (party.party) cote shipment.
|
||||
- `packing_list.fodt`: date en haut a droite = date du jour; unites Net/Gross = unite de `purchase.line`.
|
||||
- `bill.fodt` (sale): la 2eme date doit etre une vraie maturity date (depuis `invoice.lines_to_pay.maturity_date`), pas `payment_term.rec_name`.
|
||||
- `bill.fodt` (sale): le montant en lettres doit provenir du montant du bill (facture/total), pas du `unit_price` de ligne.
|
||||
- Quand un template affiche les placeholders en brut, verifier que les champs sont bien des placeholders Relatorio dans le XML (pas du texte litteral).
|
||||
- Eviter les apostrophes echappees style `\'` dans placeholders; preferer `"` et `'`.
|
||||
|
||||
## 4.bis) Memos metier et templates
|
||||
|
||||
- Regles metier transverses:
|
||||
- `notes/business_rules.md`
|
||||
- Regles metier locales `purchase_trade`:
|
||||
- `modules/purchase_trade/docs/business-rules.md`
|
||||
- `modules/purchase_trade/docs_source/business/` pour les sources de verite
|
||||
des pages business publiees dans le wiki
|
||||
- `modules/purchase_trade/docs/business/` pour les pages generees lues par le
|
||||
wiki
|
||||
- Decisions templates / reports:
|
||||
- `notes/template_business_rules.md`
|
||||
- Documentation comptable et reporting:
|
||||
- `notes/accounting/README.md`
|
||||
- `notes/accounting/gap_analysis_workflow.md`
|
||||
- `notes/accounting/reporting.md`
|
||||
- `notes/accounting/business_rules.md`
|
||||
- `notes/accounting/purchase_trade_extensions.md`
|
||||
- Regles sensibles `purchase_trade` a relire avant de toucher lots, quantites
|
||||
ou fees:
|
||||
- `modules/purchase_trade/AGENTS.md`
|
||||
- `modules/purchase_trade/docs_source/business/lots-and-quantities.md`
|
||||
- `modules/purchase_trade/docs/business-rules.md` BR-PT-020 / BR-PT-021
|
||||
(`quantity_theorical`, `lot.qt`, lots physiques, fees et PnL fee).
|
||||
|
||||
## 5) Workflow de modification (obligatoire)
|
||||
|
||||
@@ -105,3 +145,18 @@ Toujours fournir:
|
||||
3. Proposer le patch minimal.
|
||||
4. Implementer + tester cible.
|
||||
5. Rendre avec le contrat de sortie (section 8).
|
||||
- Rappels session 2026-04-09:
|
||||
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: poids et unites depuis `lot.qt.hist` / `lot_unit_line`, priorite lots `physic`, sinon lot `virtual` unique.
|
||||
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: infos shipment depuis les lots reels des lignes facture; ne rien afficher si plusieurs shipments differents.
|
||||
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: `S/I` = `shipment.reference`; `NB BALES: 0` => `Unchanged` sur le final.
|
||||
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: quantites uniformisees a `2` decimales; conversion `LBS` via UoM, jamais via un facteur fixe aveugle.
|
||||
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: si plusieurs lignes reutilisent le meme lot, les lignes detaillees suivent la quantite facturee convertie, mais le `GROSS` global doit rester le vrai delta historique du lot.
|
||||
- `sale_ict.fodt`: meme priorite lots; les mots suivent l'unite reelle; le total convertit vers une unite commune, qui est celle du lot virtuel seulement s'il y a un seul lot virtuel sur tout le report.
|
||||
- `lot.report.r_del_period`: utiliser `sale.line.del_period` pour `lot_s` sans `lot_p`, sinon `purchase.line.del_period`.
|
||||
- `lot.do_weighing`: `lot_qt` editable et ecrasement direct de `lot.lot_qt`.
|
||||
- `account.invoice`: `Validate` cree aussi le `account.move` pour les factures client, attribue aussi le `number` a ce stade pour les factures client comme fournisseur; `Post` ne doit plus forcer une fresh session sur ce flux.
|
||||
- `pricing.pricing`: saisie manuelle autorisee meme sans composant; en manuel, l'utilisateur saisit seulement `Qt` et `Settl. price`; `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` et `eod_price` sont derives automatiquement.
|
||||
- `pricing.pricing`: en manuel, `fixed_qt` = cumul des `quantity`, `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`, `unfixed_qt` = reste a fixer, `unfixed_qt_price` = `settl_price` de la ligne.
|
||||
- `pricing.pricing`: `eod_price` reste non editable et calcule en prix moyen pondere; `last=True` gere par groupe `line + component`, choisi sur la `pricing_date` la plus grande.
|
||||
- `purchase_trade`: `trader` filtre sur `TRADER`, `operator` sur `OPERATOR`; fallback sur `quantity` si `quantity_theorical` est vide dans les quotas/pricings.
|
||||
- `sale.line` / `purchase.line`: en mode `basis`, sans `price_component`, le `Price` et le `Fix. progress` de la ligne doivent remonter depuis la ligne `Summary` sans component.
|
||||
|
||||
1
debug.log
Normal file
1
debug.log
Normal file
@@ -0,0 +1 @@
|
||||
[0407/143111.471:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: Accès refusé. (0x5)
|
||||
36
deployment/vps/46.202.173.47-credentials.md
Normal file
36
deployment/vps/46.202.173.47-credentials.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Fiche VPS - 46.202.173.47
|
||||
|
||||
Date de reference: 2026-04-07
|
||||
|
||||
## Identite serveur
|
||||
|
||||
- IP: `46.202.173.47`
|
||||
- Hostname alias conseille: `vps3`
|
||||
|
||||
## Acces
|
||||
|
||||
- Cle publique Laurent Barontini (vps-deploy):
|
||||
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEm8JMCYsk6I1IoYhIHXNrdyERHdh+eeDCJagOHaRAEK vps-deploy`
|
||||
|
||||
- Cle publique Sylvain Duvernay (s.duvernay@singa-associates.com):
|
||||
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6Xsp/v6q6JO04ETv1880qoSPptUMxlWQvgcBz67o63 s.duvernay@singa-associates.com`
|
||||
- Fichier local: `$env:USERPROFILE\.ssh\id_ed25519`
|
||||
|
||||
- Mot de passe fourni:
|
||||
`!!OpenSquared!!`
|
||||
|
||||
- Utilisateur SSH:
|
||||
'root'
|
||||
- Port SSH:
|
||||
'22'
|
||||
|
||||
## Commande de connexion type
|
||||
|
||||
- Laurent Barontini (cle vps-deploy):
|
||||
`ssh -i $env:USERPROFILE\.ssh\vps_deploy_key <user>@46.202.173.47`
|
||||
|
||||
- Sylvain Duvernay (cle id_ed25519):
|
||||
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47`
|
||||
|
||||
- Avec port custom:
|
||||
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 -p <port> <user>@46.202.173.47`
|
||||
@@ -1,3 +0,0 @@
|
||||
Serveur: 'VPS-62.72.36.116'
|
||||
Alias Name: 'VPS DEV'
|
||||
IP Address:'62.72.36.116'
|
||||
170
ir/model.py
170
ir/model.py
@@ -1538,6 +1538,10 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
|
||||
})
|
||||
action = fields.Function(
|
||||
fields.Char("Action"), 'get_action', searcher='search_action')
|
||||
old_values = fields.Text("Old Values", readonly=True)
|
||||
new_values = fields.Text("New Values", readonly=True)
|
||||
change_summary = fields.Function(
|
||||
fields.Text("Changes"), 'get_change_summary')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
@@ -1583,6 +1587,172 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
|
||||
return f'{field_name} : {state}'
|
||||
return self.target
|
||||
|
||||
@staticmethod
|
||||
def _load_change_values(value):
|
||||
if not value:
|
||||
return {}
|
||||
try:
|
||||
return json.loads(value)
|
||||
except (TypeError, ValueError):
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _format_change_value(value):
|
||||
if isinstance(value, list):
|
||||
if len(value) > 6:
|
||||
return '[%s items]' % len(value)
|
||||
return ', '.join(Log._format_change_value(v) for v in value)
|
||||
if isinstance(value, dict):
|
||||
return json.dumps(value, sort_keys=True, ensure_ascii=False)
|
||||
if value in (None, ''):
|
||||
return ''
|
||||
return str(value)
|
||||
|
||||
@staticmethod
|
||||
def _singular_label(label):
|
||||
if label.endswith('ies'):
|
||||
return label[:-3] + 'y'
|
||||
if label.endswith('s'):
|
||||
return label[:-1]
|
||||
return label
|
||||
|
||||
def _nested_record_label(self, field, field_label, record_id):
|
||||
record_label = self._singular_label(field_label)
|
||||
if not self.resource:
|
||||
return '%s %s' % (record_label, record_id)
|
||||
records = list(getattr(self.resource, field.name, None) or [])
|
||||
line_records = [
|
||||
record for record in records
|
||||
if getattr(record, 'type', 'line') == 'line']
|
||||
ordered_records = line_records or records
|
||||
for index, record in enumerate(ordered_records, 1):
|
||||
if str(getattr(record, 'id', '')) == str(record_id):
|
||||
return '%s %s' % (record_label, index)
|
||||
return '%s %s' % (record_label, record_id)
|
||||
|
||||
def _format_nested_change_summary(
|
||||
self, field, field_label, target_model, old_value, new_value):
|
||||
pool = Pool()
|
||||
Field = pool.get('ir.model.field')
|
||||
lines = []
|
||||
if not isinstance(old_value, dict) or not isinstance(new_value, dict):
|
||||
return lines
|
||||
def record_sort_key(value):
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return value
|
||||
for record_id in sorted(
|
||||
set(old_value) | set(new_value), key=record_sort_key):
|
||||
record_old = old_value.get(record_id, {})
|
||||
record_new = new_value.get(record_id, {})
|
||||
if not isinstance(record_old, dict) or not isinstance(
|
||||
record_new, dict):
|
||||
continue
|
||||
for child_field in sorted(set(record_old) | set(record_new)):
|
||||
child_old = record_old.get(child_field)
|
||||
child_new = record_new.get(child_field)
|
||||
if child_old == child_new:
|
||||
continue
|
||||
child_label = Field.get_name(target_model, child_field)
|
||||
lines.append('%s %s %s => %s' % (
|
||||
self._nested_record_label(
|
||||
field, field_label, record_id),
|
||||
child_label,
|
||||
self._format_change_value(child_old),
|
||||
self._format_change_value(child_new)))
|
||||
return lines
|
||||
|
||||
@staticmethod
|
||||
def _iter_change_commands(value):
|
||||
if not isinstance(value, list):
|
||||
return
|
||||
for item in value:
|
||||
if isinstance(item, list) and item and isinstance(item[0], str):
|
||||
command = item[0]
|
||||
if len(item) >= 3:
|
||||
yield command, item[1], item[2]
|
||||
elif len(item) >= 2:
|
||||
yield command, item[1], None
|
||||
if value and all(isinstance(item, list) for item in value):
|
||||
return
|
||||
index = 0
|
||||
while index < len(value):
|
||||
command = value[index]
|
||||
if not isinstance(command, str):
|
||||
index += 1
|
||||
continue
|
||||
if command == 'write':
|
||||
if index + 2 >= len(value):
|
||||
break
|
||||
yield command, value[index + 1], value[index + 2]
|
||||
index += 3
|
||||
continue
|
||||
if index + 1 >= len(value):
|
||||
break
|
||||
yield command, value[index + 1], None
|
||||
index += 2
|
||||
|
||||
def _format_command_change_summary(
|
||||
self, field, field_label, target_model, new_value):
|
||||
pool = Pool()
|
||||
Field = pool.get('ir.model.field')
|
||||
lines = []
|
||||
for command, record_ids, values in self._iter_change_commands(
|
||||
new_value) or []:
|
||||
if command != 'write' or not isinstance(values, dict):
|
||||
continue
|
||||
for record_id in record_ids:
|
||||
for child_field, child_value in sorted(values.items()):
|
||||
child_label = Field.get_name(target_model, child_field)
|
||||
lines.append('%s %s => %s' % (
|
||||
self._nested_record_label(
|
||||
field, field_label, record_id),
|
||||
child_label,
|
||||
self._format_change_value(child_value)))
|
||||
return lines
|
||||
|
||||
def get_change_summary(self, name):
|
||||
if self.event != 'write':
|
||||
return ''
|
||||
old_values = self._load_change_values(self.old_values)
|
||||
new_values = self._load_change_values(self.new_values)
|
||||
if not old_values and not new_values:
|
||||
return ''
|
||||
pool = Pool()
|
||||
Field = pool.get('ir.model.field')
|
||||
model = self.resource.__name__ if self.resource else None
|
||||
lines = []
|
||||
for field_name in sorted(set(old_values) | set(new_values)):
|
||||
old_value = old_values.get(field_name)
|
||||
new_value = new_values.get(field_name)
|
||||
if old_value == new_value:
|
||||
continue
|
||||
if model:
|
||||
field_label = Field.get_name(model, field_name)
|
||||
else:
|
||||
field_label = field_name
|
||||
field = (
|
||||
getattr(self.resource.__class__, field_name, None)
|
||||
if self.resource else None)
|
||||
if field and hasattr(field, 'get_target'):
|
||||
target_model = field.get_target().__name__
|
||||
nested_lines = self._format_nested_change_summary(
|
||||
field, field_label, target_model, old_value, new_value)
|
||||
if nested_lines:
|
||||
lines.extend(nested_lines)
|
||||
continue
|
||||
command_lines = self._format_command_change_summary(
|
||||
field, field_label, target_model, new_value)
|
||||
if command_lines:
|
||||
lines.extend(command_lines)
|
||||
continue
|
||||
lines.append('%s: %s -> %s' % (
|
||||
field_label,
|
||||
self._format_change_value(old_value),
|
||||
self._format_change_value(new_value)))
|
||||
return '\n'.join(lines)
|
||||
|
||||
@classmethod
|
||||
def search_action(cls, name, clause):
|
||||
return [('target', *clause[1:])]
|
||||
|
||||
@@ -73,6 +73,7 @@ attlist.field &=
|
||||
| "text"
|
||||
| "time"
|
||||
| "timedelta"
|
||||
| "tolerance_gauge"
|
||||
| "url"
|
||||
}?
|
||||
attlist.field &= attribute fill { "0" | "1" }?
|
||||
@@ -122,6 +123,12 @@ attlist.field &= attribute symbol { text }?
|
||||
attlist.field &= [a:defaultValue = "1"] attribute grouping { "0" | "1" }?
|
||||
attlist.field &= [a:defaultValue = "square"] attribute border { "square" | "circle" | "rounded" }?
|
||||
attlist.field &= attribute loading { "lazy" | "eager" }?
|
||||
attlist.field &= attribute min { text }?
|
||||
attlist.field &= attribute max { text }?
|
||||
attlist.field &= attribute min_field { text }?
|
||||
attlist.field &= attribute max_field { text }?
|
||||
attlist.field &= attribute center { text }?
|
||||
attlist.field &= attribute digits { text }?
|
||||
image = element image { attlist.image, empty }
|
||||
attlist.image &= attribute name { text }
|
||||
attlist.image &= [a:defaultValue = "icon"] attribute type { "icon" | "url" }?
|
||||
|
||||
@@ -251,6 +251,7 @@
|
||||
<value>text</value>
|
||||
<value>time</value>
|
||||
<value>timedelta</value>
|
||||
<value>tolerance_gauge</value>
|
||||
<value>url</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
@@ -577,6 +578,54 @@
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">min</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">max</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">min_field</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">max_field</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">center</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">digits</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
|
||||
@@ -42,6 +42,7 @@ attlist.field &=
|
||||
| "text"
|
||||
| "time"
|
||||
| "timedelta"
|
||||
| "tolerance_gauge"
|
||||
| "url"
|
||||
}?
|
||||
attlist.field &=
|
||||
@@ -75,6 +76,12 @@ attlist.field &= attribute help_field { text }?
|
||||
attlist.field &= attribute view_ids { text }?
|
||||
attlist.field &= attribute symbol { text }?
|
||||
attlist.field &= [a:defaultValue = "1"] attribute grouping { "0" | "1" }?
|
||||
attlist.field &= attribute min { text }?
|
||||
attlist.field &= attribute max { text }?
|
||||
attlist.field &= attribute min_field { text }?
|
||||
attlist.field &= attribute max_field { text }?
|
||||
attlist.field &= attribute center { text }?
|
||||
attlist.field &= attribute digits { text }?
|
||||
prefix = element prefix { attlist.affix, empty }
|
||||
suffix = element suffix { attlist.affix, empty }
|
||||
attlist.affix &= attribute string { text }?
|
||||
|
||||
105
ir/ui/tree.rng
105
ir/ui/tree.rng
@@ -117,6 +117,7 @@
|
||||
<name ns="">widget</name>
|
||||
<choice>
|
||||
<value>binary</value>
|
||||
<value>badge</value>
|
||||
<value>boolean</value>
|
||||
<value>callto</value>
|
||||
<value>char</value>
|
||||
@@ -139,11 +140,21 @@
|
||||
<value>text</value>
|
||||
<value>time</value>
|
||||
<value>timedelta</value>
|
||||
<value>tolerance_gauge</value>
|
||||
<value>variation</value>
|
||||
<value>url</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">badge_colors</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute a:defaultValue="0">
|
||||
@@ -198,6 +209,52 @@
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">sum_previous</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute a:defaultValue="0">
|
||||
<name ns="">sum_variation</name>
|
||||
<choice>
|
||||
<value>0</value>
|
||||
<value>1</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">variation_previous</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">variation_group_by</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute a:defaultValue="0">
|
||||
<name ns="">variation_summary</name>
|
||||
<choice>
|
||||
<value>0</value>
|
||||
<value>1</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
@@ -330,6 +387,54 @@
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">min</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">max</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">min_field</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">max_field</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">center</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="attlist.field" combine="interleave">
|
||||
<optional>
|
||||
<attribute>
|
||||
<name ns="">digits</name>
|
||||
<text/>
|
||||
</attribute>
|
||||
</optional>
|
||||
</define>
|
||||
<define name="prefix">
|
||||
<element>
|
||||
<name ns="">prefix</name>
|
||||
|
||||
@@ -13,4 +13,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="event"/>
|
||||
<field name="action"/>
|
||||
</group>
|
||||
<label name="change_summary"/>
|
||||
<field name="change_summary"/>
|
||||
<label name="old_values"/>
|
||||
<field name="old_values"/>
|
||||
<label name="new_values"/>
|
||||
<field name="new_values"/>
|
||||
</form>
|
||||
|
||||
@@ -8,4 +8,5 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="user" expand="1"/>
|
||||
<field name="event"/>
|
||||
<field name="action" expand="1"/>
|
||||
<field name="change_summary" optional="1"/>
|
||||
</tree>
|
||||
|
||||
@@ -5,6 +5,7 @@ import base64
|
||||
import csv
|
||||
import datetime
|
||||
import decimal
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
import warnings
|
||||
@@ -193,6 +194,99 @@ class ModelStorage(Model):
|
||||
resource=record, event=event, target=target, user=user,
|
||||
**extra))
|
||||
|
||||
@staticmethod
|
||||
def _json_log_value(value):
|
||||
if isinstance(value, (datetime.date, datetime.datetime, datetime.time)):
|
||||
return value.isoformat()
|
||||
if isinstance(value, decimal.Decimal):
|
||||
return str(value)
|
||||
if isinstance(value, bytes):
|
||||
return '<binary>'
|
||||
if isinstance(value, Model):
|
||||
return str(value)
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [ModelStorage._json_log_value(v) for v in value]
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
str(k): ModelStorage._json_log_value(v)
|
||||
for k, v in value.items()
|
||||
}
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _log_value_text(cls, values):
|
||||
values = {
|
||||
name: cls._json_log_value(value)
|
||||
for name, value in values.items()
|
||||
}
|
||||
return json.dumps(values, sort_keys=True, ensure_ascii=False)
|
||||
|
||||
@staticmethod
|
||||
def _iter_log_commands(value):
|
||||
if not isinstance(value, (list, tuple)):
|
||||
return
|
||||
for item in value:
|
||||
if (isinstance(item, (list, tuple))
|
||||
and item and isinstance(item[0], str)):
|
||||
command = item[0]
|
||||
if len(item) >= 3:
|
||||
yield command, item[1], item[2]
|
||||
elif len(item) >= 2:
|
||||
yield command, item[1], None
|
||||
if value and all(isinstance(item, (list, tuple)) for item in value):
|
||||
return
|
||||
index = 0
|
||||
while index < len(value):
|
||||
command = value[index]
|
||||
if not isinstance(command, str):
|
||||
index += 1
|
||||
continue
|
||||
if command == 'write':
|
||||
if index + 2 >= len(value):
|
||||
break
|
||||
yield command, value[index + 1], value[index + 2]
|
||||
index += 3
|
||||
continue
|
||||
if index + 1 >= len(value):
|
||||
break
|
||||
yield command, value[index + 1], None
|
||||
index += 2
|
||||
|
||||
@classmethod
|
||||
def _nested_log_values(cls, values):
|
||||
nested_old = {}
|
||||
nested_new = {}
|
||||
for field_name, value in values.items():
|
||||
field = cls._fields.get(field_name)
|
||||
if not field or not hasattr(field, 'get_target'):
|
||||
continue
|
||||
commands = list(cls._iter_log_commands(value) or [])
|
||||
write_commands = [
|
||||
(ids, command_values)
|
||||
for command, ids, command_values in commands
|
||||
if command == 'write' and command_values]
|
||||
if not write_commands:
|
||||
continue
|
||||
Target = field.get_target()
|
||||
field_old = {}
|
||||
field_new = {}
|
||||
for ids, command_values in write_commands:
|
||||
target_ids = [int(i) for i in ids]
|
||||
field_names = list(sorted(command_values.keys()))
|
||||
with without_check_access():
|
||||
rows = Target.read(target_ids, field_names)
|
||||
for row in rows:
|
||||
record_id = str(row['id'])
|
||||
field_old.setdefault(record_id, {}).update({
|
||||
name: row.get(name)
|
||||
for name in field_names
|
||||
})
|
||||
field_new.setdefault(record_id, {}).update(command_values)
|
||||
if field_old or field_new:
|
||||
nested_old[field_name] = field_old
|
||||
nested_new[field_name] = field_new
|
||||
return nested_old, nested_new
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
'''
|
||||
@@ -267,7 +361,25 @@ class ModelStorage(Model):
|
||||
all_records += records
|
||||
all_fields.update(values.keys())
|
||||
if transaction.check_access and values:
|
||||
cls.log(records, 'write', ','.join(sorted(values.keys())))
|
||||
fields_names = list(sorted(values.keys()))
|
||||
with without_check_access():
|
||||
old_values = {
|
||||
row['id']: {
|
||||
name: row.get(name)
|
||||
for name in fields_names
|
||||
}
|
||||
for row in cls.read(
|
||||
[r.id for r in records], fields_names)}
|
||||
nested_old, nested_new = cls._nested_log_values(values)
|
||||
for record in records:
|
||||
record_old_values = old_values.get(record.id, {})
|
||||
record_new_values = values.copy()
|
||||
record_old_values.update(nested_old)
|
||||
record_new_values.update(nested_new)
|
||||
cls.log([record], 'write', ','.join(fields_names),
|
||||
old_values=cls._log_value_text(
|
||||
record_old_values),
|
||||
new_values=cls._log_value_text(record_new_values))
|
||||
|
||||
ModelAccess.check(cls.__name__, 'write')
|
||||
ModelFieldAccess.check(cls.__name__, all_fields, 'write')
|
||||
|
||||
10
modules/AGENTS.md
Normal file
10
modules/AGENTS.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# AGENTS.md - Regles globales des modules
|
||||
|
||||
Ce guide complete le `AGENTS.md` racine et s'applique a tous les modules sous `modules/`.
|
||||
|
||||
## Regles transversales
|
||||
|
||||
- Ne pas introduire de `fields.DateTime` dans les modules metier sauf demande explicite et code cible deja base sur ce type.
|
||||
- Pour afficher une date issue d'un champ technique comme `create_date`, preferer un champ fonctionnel en `fields.Date` plutot qu'un acces direct en vue.
|
||||
- Si une vue doit afficher une information technique non declaree explicitement sur le modele, ajouter un champ fonctionnel dedie cote Python avant de modifier le XML.
|
||||
|
||||
@@ -30,6 +30,8 @@ def register():
|
||||
account.GeneralLedgerAccountParty,
|
||||
account.GeneralLedgerLine,
|
||||
account.GeneralLedgerLineContext,
|
||||
account.GeneralLedgerDetail,
|
||||
account.GeneralLedgerDetailContext,
|
||||
account.BalanceSheetContext,
|
||||
account.BalanceSheetComparisionContext,
|
||||
account.IncomeStatementContext,
|
||||
@@ -118,6 +120,7 @@ def register():
|
||||
Pool.register(
|
||||
account.AccountTypeStatement,
|
||||
account.GeneralLedger,
|
||||
account.GeneralLedgerDetailReport,
|
||||
account.TrialBalance,
|
||||
account.AgedBalanceReport,
|
||||
move.GeneralJournal,
|
||||
|
||||
@@ -7,9 +7,10 @@ from decimal import Decimal
|
||||
from itertools import zip_longest
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from sql import Column, Literal, Null, Window
|
||||
from sql import Column, Literal, Null, Union, Window
|
||||
from sql.aggregate import Count, Max, Min, Sum
|
||||
from sql.conditionals import Case, Coalesce
|
||||
from sql.operators import Concat
|
||||
|
||||
from trytond import backend
|
||||
from trytond.i18n import gettext
|
||||
@@ -2583,6 +2584,562 @@ class GeneralLedgerLineContext(GeneralLedgerAccountContext):
|
||||
return super().get_context(fields_names=fields_names)
|
||||
|
||||
|
||||
class GeneralLedgerDetailContext(GeneralLedgerAccountContext):
|
||||
'General Ledger Detail Context'
|
||||
__name__ = 'account.general_ledger.detail.context'
|
||||
|
||||
account = fields.Many2One(
|
||||
'account.account', "Account",
|
||||
domain=[
|
||||
('company', '=', Eval('company')),
|
||||
('type', '!=', None),
|
||||
('closed', '!=', True),
|
||||
],
|
||||
depends=['company'])
|
||||
account_from = fields.Char("Account From")
|
||||
account_to = fields.Char("Account To")
|
||||
currency = fields.Many2One('currency.currency', "Transaction Currency")
|
||||
party = fields.Many2One(
|
||||
'party.party', "Party",
|
||||
context={
|
||||
'company': Eval('company', -1),
|
||||
},
|
||||
depends=['company'])
|
||||
document_number = fields.Char("Document Number")
|
||||
|
||||
@classmethod
|
||||
def default_account(cls):
|
||||
return Transaction().context.get('account')
|
||||
|
||||
@classmethod
|
||||
def default_account_from(cls):
|
||||
return Transaction().context.get('account_from')
|
||||
|
||||
@classmethod
|
||||
def default_account_to(cls):
|
||||
return Transaction().context.get('account_to')
|
||||
|
||||
@classmethod
|
||||
def default_currency(cls):
|
||||
return Transaction().context.get('currency')
|
||||
|
||||
@classmethod
|
||||
def default_party(cls):
|
||||
return Transaction().context.get('party')
|
||||
|
||||
@classmethod
|
||||
def default_document_number(cls):
|
||||
return Transaction().context.get('document_number')
|
||||
|
||||
@classmethod
|
||||
def get_context(cls, fields_names=None):
|
||||
fields_names = fields_names.copy() if fields_names is not None else []
|
||||
fields_names += [
|
||||
'account', 'account_from', 'account_to', 'currency', 'party',
|
||||
'document_number']
|
||||
return super().get_context(fields_names=fields_names)
|
||||
|
||||
|
||||
class GeneralLedgerDetail(DescriptionOriginMixin, ModelSQL, ModelView):
|
||||
'General Ledger Detail'
|
||||
__name__ = 'account.general_ledger.detail'
|
||||
|
||||
row_type = fields.Selection([
|
||||
('opening', "Opening"),
|
||||
('movement', "Movement"),
|
||||
('closing', "Closing"),
|
||||
], "Row Type", sort=False)
|
||||
row_sequence = fields.Integer("Row Sequence")
|
||||
|
||||
company = fields.Many2One('company.company', "Company")
|
||||
account = fields.Many2One('account.account', "Account")
|
||||
account_code = fields.Char("Account Code")
|
||||
account_name = fields.Char("Account Name")
|
||||
transaction_currency = fields.Many2One(
|
||||
'currency.currency', "Transaction Currency")
|
||||
base_currency = fields.Many2One('currency.currency', "Base Currency")
|
||||
|
||||
posting_date = fields.Date("Posting Date")
|
||||
journal = fields.Many2One('account.journal', "Journal")
|
||||
journal_entry_number = fields.Char("Journal Entry Number")
|
||||
document_number = fields.Function(
|
||||
fields.Char("Document Number"), 'get_document_field')
|
||||
voucher_number = fields.Char("Voucher Number")
|
||||
document_type = fields.Function(
|
||||
fields.Char("Document Type"), 'get_document_field')
|
||||
document_date = fields.Date("Document Date")
|
||||
posting_status = fields.Selection([
|
||||
('draft', "Draft"),
|
||||
('posted', "Posted"),
|
||||
], "Posting Status", sort=False)
|
||||
|
||||
party = fields.Many2One('party.party', "Party")
|
||||
move = fields.Many2One('account.move', "Move")
|
||||
move_line = fields.Many2One('account.move.line', "Move Line")
|
||||
origin = fields.Reference('Origin', selection='get_origin')
|
||||
description = fields.Char("Line Desc.")
|
||||
move_description = fields.Char("Description")
|
||||
reference = fields.Char("Reference")
|
||||
|
||||
debit_base_currency = fields.Numeric(
|
||||
"Debit Base Currency", digits=(16, 2))
|
||||
credit_base_currency = fields.Numeric(
|
||||
"Credit Base Currency", digits=(16, 2))
|
||||
balance_base_currency = fields.Numeric(
|
||||
"Balance Base Currency", digits=(16, 2))
|
||||
running_balance_base_currency = fields.Numeric(
|
||||
"Running Balance Base Currency", digits=(16, 2))
|
||||
|
||||
debit_transaction_currency = fields.Numeric(
|
||||
"Debit Transaction Currency", digits=(16, 2))
|
||||
credit_transaction_currency = fields.Numeric(
|
||||
"Credit Transaction Currency", digits=(16, 2))
|
||||
balance_transaction_currency = fields.Numeric(
|
||||
"Balance Transaction Currency", digits=(16, 2))
|
||||
running_balance_transaction_currency = fields.Numeric(
|
||||
"Running Balance Transaction Currency", digits=(16, 2))
|
||||
|
||||
payable_qty = fields.Numeric("Quantity", digits=(16, 5))
|
||||
uom = fields.Char("UoM")
|
||||
|
||||
entered_by = fields.Many2One('res.user', "Entered By")
|
||||
entered_date = fields.Date("Entered Date")
|
||||
modified_by = fields.Many2One('res.user', "Modified By")
|
||||
modified_date = fields.Date("Modified Date")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order = [
|
||||
('account_code', 'ASC'),
|
||||
('transaction_currency', 'ASC'),
|
||||
('row_sequence', 'ASC'),
|
||||
('posting_date', 'ASC'),
|
||||
('journal_entry_number', 'ASC'),
|
||||
('id', 'ASC'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _get_period_ids(cls, name, context):
|
||||
pool = Pool()
|
||||
Period = pool.get('account.period')
|
||||
|
||||
period = None
|
||||
if name == 'opening':
|
||||
period_ids = []
|
||||
if context.get('start_period'):
|
||||
period = Period(context['start_period'])
|
||||
elif name == 'closing':
|
||||
period_ids = []
|
||||
if context.get('end_period'):
|
||||
period = Period(context['end_period'])
|
||||
else:
|
||||
periods = Period.search([
|
||||
('fiscalyear', '=', context.get('fiscalyear')),
|
||||
('type', '=', 'standard'),
|
||||
],
|
||||
order=[('start_date', 'DESC')], limit=1)
|
||||
if periods:
|
||||
period, = periods
|
||||
else:
|
||||
start_period_ids = set(cls._get_period_ids('opening', context))
|
||||
end_period_ids = set(cls._get_period_ids('closing', context))
|
||||
return list(end_period_ids.difference(start_period_ids))
|
||||
|
||||
if period:
|
||||
if name == 'opening':
|
||||
date_clause = ('end_date', '<=', period.start_date)
|
||||
else:
|
||||
date_clause = [
|
||||
('end_date', '<=', period.end_date),
|
||||
('start_date', '<', period.end_date),
|
||||
]
|
||||
periods = Period.search([
|
||||
('fiscalyear', '=', context.get('fiscalyear')),
|
||||
date_clause,
|
||||
])
|
||||
if period.start_date == period.end_date:
|
||||
periods.append(period)
|
||||
if periods:
|
||||
period_ids = [p.id for p in periods]
|
||||
if name == 'closing':
|
||||
period_ids.append(period.id)
|
||||
return period_ids
|
||||
|
||||
@classmethod
|
||||
def _get_dates(cls, name, context):
|
||||
if name == 'opening':
|
||||
to_date = context.get('from_date') or datetime.date.min
|
||||
if to_date:
|
||||
try:
|
||||
to_date -= datetime.timedelta(days=1)
|
||||
except OverflowError:
|
||||
pass
|
||||
return None, to_date
|
||||
elif name == 'closing':
|
||||
return None, context.get('to_date')
|
||||
return context.get('from_date'), context.get('to_date')
|
||||
|
||||
@classmethod
|
||||
def _query_context(cls, name, context):
|
||||
if context.get('start_period') or context.get('end_period'):
|
||||
return {
|
||||
'periods': cls._get_period_ids(name, context),
|
||||
'from_date': None,
|
||||
'to_date': None,
|
||||
}
|
||||
elif context.get('from_date') or context.get('to_date'):
|
||||
from_date, to_date = cls._get_dates(name, context)
|
||||
return {
|
||||
'periods': None,
|
||||
'from_date': from_date,
|
||||
'to_date': to_date,
|
||||
}
|
||||
else:
|
||||
if name == 'opening':
|
||||
return {
|
||||
'periods': [],
|
||||
'from_date': None,
|
||||
'to_date': None,
|
||||
}
|
||||
return {
|
||||
'periods': None,
|
||||
'from_date': None,
|
||||
'to_date': None,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _extra_where(cls, line, move, account, tx_currency, context):
|
||||
where = (
|
||||
(move.company == context.get('company'))
|
||||
& (account.type != Null)
|
||||
& (account.closed != Literal(True)))
|
||||
if context.get('account'):
|
||||
where &= line.account == context['account']
|
||||
if context.get('account_from'):
|
||||
where &= account.code >= context['account_from']
|
||||
if context.get('account_to'):
|
||||
where &= account.code <= context['account_to']
|
||||
if context.get('currency'):
|
||||
where &= tx_currency == context['currency']
|
||||
if context.get('party'):
|
||||
where &= line.party == context['party']
|
||||
if context.get('document_number'):
|
||||
value = '%%%s%%' % context['document_number']
|
||||
where &= (
|
||||
move.number.ilike(value)
|
||||
| move.post_number.ilike(value)
|
||||
| move.ext_ref.ilike(value))
|
||||
return where
|
||||
|
||||
@classmethod
|
||||
def _tables(cls):
|
||||
pool = Pool()
|
||||
Line = pool.get('account.move.line')
|
||||
Move = pool.get('account.move')
|
||||
Account = pool.get('account.account')
|
||||
Company = pool.get('company.company')
|
||||
line = Line.__table__()
|
||||
move = Move.__table__()
|
||||
account = Account.__table__()
|
||||
company = Company.__table__()
|
||||
return Line, line, move, account, company
|
||||
|
||||
@classmethod
|
||||
def _lot_tables(cls, Line):
|
||||
if 'lot' not in Line._fields:
|
||||
return None, None
|
||||
pool = Pool()
|
||||
try:
|
||||
Lot = pool.get('lot.lot')
|
||||
Uom = pool.get('product.uom')
|
||||
except KeyError:
|
||||
return None, None
|
||||
return Lot.__table__(), Uom.__table__()
|
||||
|
||||
@classmethod
|
||||
def _account_label(cls, account):
|
||||
return Concat(
|
||||
Concat(Coalesce(account.code, ''), ' - '),
|
||||
Coalesce(account.name, ''))
|
||||
|
||||
@classmethod
|
||||
def _opening_balance_query(cls, context):
|
||||
Line, line, move, account, company = cls._tables()
|
||||
tx_currency = Coalesce(line.second_currency, company.currency)
|
||||
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
|
||||
tx_amount = Case(
|
||||
(line.second_currency != Null,
|
||||
Coalesce(line.amount_second_currency, 0)),
|
||||
else_=base_amount)
|
||||
|
||||
with Transaction().set_context(
|
||||
context, **cls._query_context('opening', context)):
|
||||
line_query, _ = Line.query_get(line)
|
||||
where = line_query & cls._extra_where(
|
||||
line, move, account, tx_currency, context)
|
||||
|
||||
return (line.join(move, condition=line.move == move.id)
|
||||
.join(account, condition=line.account == account.id)
|
||||
.join(company, condition=move.company == company.id)
|
||||
.select(
|
||||
line.account.as_('account'),
|
||||
tx_currency.as_('transaction_currency'),
|
||||
Sum(base_amount).as_('base_amount'),
|
||||
Sum(tx_amount).as_('transaction_amount'),
|
||||
where=where,
|
||||
group_by=[line.account, tx_currency],
|
||||
having=Sum(base_amount) != 0))
|
||||
|
||||
@classmethod
|
||||
def _line_query(cls, name, context):
|
||||
Line, line, move, account, company = cls._tables()
|
||||
lot, uom = cls._lot_tables(Line)
|
||||
opening_balance = cls._opening_balance_query(context)
|
||||
account_label = cls._account_label(account)
|
||||
quantity = Literal(None)
|
||||
unit = Literal(None)
|
||||
tx_currency = Coalesce(line.second_currency, company.currency)
|
||||
from_ = (line.join(move, condition=line.move == move.id)
|
||||
.join(account, condition=line.account == account.id)
|
||||
.join(company, condition=move.company == company.id))
|
||||
from_ = from_.join(opening_balance, 'LEFT',
|
||||
condition=(
|
||||
(opening_balance.account == line.account)
|
||||
& (opening_balance.transaction_currency == tx_currency)))
|
||||
if lot is not None:
|
||||
from_ = (from_
|
||||
.join(lot, 'LEFT', condition=Column(line, 'lot') == lot.id)
|
||||
.join(uom, 'LEFT', condition=lot.lot_unit_line == uom.id))
|
||||
quantity = lot.lot_qt
|
||||
unit = uom.symbol
|
||||
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
|
||||
second_amount = Coalesce(line.amount_second_currency, 0)
|
||||
tx_amount = Case(
|
||||
(line.second_currency != Null, second_amount),
|
||||
else_=base_amount)
|
||||
debit_tx = Case(
|
||||
(line.second_currency == Null, Coalesce(line.debit, 0)),
|
||||
(second_amount > 0, second_amount),
|
||||
else_=Literal(0))
|
||||
credit_tx = Case(
|
||||
(line.second_currency == Null, Coalesce(line.credit, 0)),
|
||||
(second_amount < 0, -second_amount),
|
||||
else_=Literal(0))
|
||||
|
||||
with Transaction().set_context(
|
||||
context, **cls._query_context(name, context)):
|
||||
line_query, _ = Line.query_get(line)
|
||||
where = line_query & cls._extra_where(
|
||||
line, move, account, tx_currency, context)
|
||||
|
||||
window = Window(
|
||||
[line.account, tx_currency],
|
||||
order_by=[move.date.asc, move.number.asc, line.id.asc])
|
||||
|
||||
return from_.select(
|
||||
(line.id * 3).as_('id'),
|
||||
line.create_uid.as_('create_uid'),
|
||||
line.create_date.as_('create_date'),
|
||||
line.write_uid.as_('write_uid'),
|
||||
line.write_date.as_('write_date'),
|
||||
Literal('movement').as_('row_type'),
|
||||
Literal(1).as_('row_sequence'),
|
||||
move.company.as_('company'),
|
||||
line.account.as_('account'),
|
||||
account_label.as_('account_code'),
|
||||
account.name.as_('account_name'),
|
||||
tx_currency.as_('transaction_currency'),
|
||||
company.currency.as_('base_currency'),
|
||||
move.date.as_('posting_date'),
|
||||
move.journal.as_('journal'),
|
||||
move.number.as_('journal_entry_number'),
|
||||
move.post_number.as_('voucher_number'),
|
||||
move.date.as_('document_date'),
|
||||
move.state.as_('posting_status'),
|
||||
line.party.as_('party'),
|
||||
line.move.as_('move'),
|
||||
line.id.as_('move_line'),
|
||||
move.origin.as_('origin'),
|
||||
line.description.as_('description'),
|
||||
move.description.as_('move_description'),
|
||||
Coalesce(move.ext_ref, move.description).as_('reference'),
|
||||
line.debit.as_('debit_base_currency'),
|
||||
line.credit.as_('credit_base_currency'),
|
||||
base_amount.as_('balance_base_currency'),
|
||||
(Coalesce(opening_balance.base_amount, 0)
|
||||
+ Sum(base_amount, window=window)).as_(
|
||||
'running_balance_base_currency'),
|
||||
debit_tx.as_('debit_transaction_currency'),
|
||||
credit_tx.as_('credit_transaction_currency'),
|
||||
tx_amount.as_('balance_transaction_currency'),
|
||||
(Coalesce(opening_balance.transaction_amount, 0)
|
||||
+ Sum(tx_amount, window=window)).as_(
|
||||
'running_balance_transaction_currency'),
|
||||
quantity.as_('payable_qty'),
|
||||
unit.as_('uom'),
|
||||
line.create_uid.as_('entered_by'),
|
||||
line.create_date.as_('entered_date'),
|
||||
line.write_uid.as_('modified_by'),
|
||||
line.write_date.as_('modified_date'),
|
||||
where=where)
|
||||
|
||||
@classmethod
|
||||
def _summary_query(cls, name, context):
|
||||
Line, line, move, account, company = cls._tables()
|
||||
account_label = cls._account_label(account)
|
||||
tx_currency = Coalesce(line.second_currency, company.currency)
|
||||
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
|
||||
tx_amount = Case(
|
||||
(line.second_currency != Null,
|
||||
Coalesce(line.amount_second_currency, 0)),
|
||||
else_=base_amount)
|
||||
row_sequence = 0 if name == 'opening' else 2
|
||||
row_id = (
|
||||
(Min(line.id) * 3 - 1) if name == 'opening'
|
||||
else (Min(line.id) * 3 - 2))
|
||||
|
||||
with Transaction().set_context(
|
||||
context, **cls._query_context(name, context)):
|
||||
line_query, _ = Line.query_get(line)
|
||||
where = line_query & cls._extra_where(
|
||||
line, move, account, tx_currency, context)
|
||||
|
||||
return (line.join(move, condition=line.move == move.id)
|
||||
.join(account, condition=line.account == account.id)
|
||||
.join(company, condition=move.company == company.id)
|
||||
.select(
|
||||
row_id.as_('id'),
|
||||
Literal(0).as_('create_uid'),
|
||||
Max(line.create_date).as_('create_date'),
|
||||
Literal(0).as_('write_uid'),
|
||||
Max(line.write_date).as_('write_date'),
|
||||
Literal(name).as_('row_type'),
|
||||
Literal(row_sequence).as_('row_sequence'),
|
||||
move.company.as_('company'),
|
||||
line.account.as_('account'),
|
||||
account_label.as_('account_code'),
|
||||
account.name.as_('account_name'),
|
||||
tx_currency.as_('transaction_currency'),
|
||||
company.currency.as_('base_currency'),
|
||||
Literal(None).as_('posting_date'),
|
||||
Literal(None).as_('journal'),
|
||||
Literal(None).as_('journal_entry_number'),
|
||||
Literal(None).as_('voucher_number'),
|
||||
Literal(None).as_('document_date'),
|
||||
Literal(None).as_('posting_status'),
|
||||
Literal(None).as_('party'),
|
||||
Literal(None).as_('move'),
|
||||
Literal(None).as_('move_line'),
|
||||
Literal(None).as_('origin'),
|
||||
Literal(name.title()).as_('description'),
|
||||
Literal(None).as_('move_description'),
|
||||
Literal(None).as_('reference'),
|
||||
Literal(0).as_('debit_base_currency'),
|
||||
Literal(0).as_('credit_base_currency'),
|
||||
Sum(base_amount).as_('balance_base_currency'),
|
||||
Sum(base_amount).as_('running_balance_base_currency'),
|
||||
Literal(0).as_('debit_transaction_currency'),
|
||||
Literal(0).as_('credit_transaction_currency'),
|
||||
Sum(tx_amount).as_('balance_transaction_currency'),
|
||||
Sum(tx_amount).as_('running_balance_transaction_currency'),
|
||||
Literal(None).as_('payable_qty'),
|
||||
Literal(None).as_('uom'),
|
||||
Literal(None).as_('entered_by'),
|
||||
Literal(None).as_('entered_date'),
|
||||
Literal(None).as_('modified_by'),
|
||||
Literal(None).as_('modified_date'),
|
||||
where=where,
|
||||
group_by=[
|
||||
move.company, line.account, account.code, account.name,
|
||||
tx_currency, company.currency],
|
||||
having=Sum(base_amount) != 0))
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
pool = Pool()
|
||||
DetailContext = pool.get('account.general_ledger.detail.context')
|
||||
context = DetailContext.get_context()
|
||||
return Union(
|
||||
cls._summary_query('opening', context),
|
||||
cls._line_query('movement', context),
|
||||
cls._summary_query('closing', context))
|
||||
|
||||
@classmethod
|
||||
def get_origin(cls):
|
||||
Move = Pool().get('account.move')
|
||||
return Move.get_origin()
|
||||
|
||||
def _get_related_payment(self):
|
||||
if not self.move_line:
|
||||
return None
|
||||
try:
|
||||
Payment = Pool().get('account.payment')
|
||||
except KeyError:
|
||||
return None
|
||||
payments = Payment.search([('line', '=', self.move_line.id)], limit=1)
|
||||
return payments[0] if payments else None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_reference(value):
|
||||
if not value or hasattr(value, '__name__'):
|
||||
return value
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
try:
|
||||
model_name, record_id = value.split(',', 1)
|
||||
record_id = int(record_id)
|
||||
except ValueError:
|
||||
return None
|
||||
try:
|
||||
Model = Pool().get(model_name)
|
||||
except KeyError:
|
||||
return None
|
||||
return Model(record_id)
|
||||
|
||||
def get_document_field(self, name):
|
||||
origin = self._resolve_reference(
|
||||
self.origin or (self.move.origin if self.move else None))
|
||||
if origin:
|
||||
model_name = origin.__name__
|
||||
if model_name == 'account.invoice':
|
||||
if name == 'document_number':
|
||||
return origin.number or origin.reference
|
||||
side = {
|
||||
'out': 'Customer',
|
||||
'in': 'Supplier',
|
||||
}.get(origin.type, '')
|
||||
stage = ''
|
||||
reference = (origin.reference or '').lower()
|
||||
if 'provisional' in reference:
|
||||
stage = 'Provisional'
|
||||
elif 'final' in reference:
|
||||
stage = 'Final'
|
||||
return ' '.join(filter(None, ['Invoice', side, stage]))
|
||||
if model_name == 'account.payment':
|
||||
if name == 'document_number':
|
||||
return origin.number
|
||||
side = {
|
||||
'receivable': 'Customer',
|
||||
'payable': 'Supplier',
|
||||
}.get(origin.kind, '')
|
||||
return ' '.join(filter(None, ['Payment', side]))
|
||||
if name == 'document_number':
|
||||
return getattr(origin, 'number', None) or getattr(
|
||||
origin, 'reference', None)
|
||||
return getattr(origin, 'rec_name', None) or model_name
|
||||
payment = self._get_related_payment()
|
||||
if payment:
|
||||
if name == 'document_number':
|
||||
return payment.number
|
||||
side = {
|
||||
'receivable': 'Customer',
|
||||
'payable': 'Supplier',
|
||||
}.get(payment.kind, '')
|
||||
return ' '.join(filter(None, ['Payment', side]))
|
||||
return None
|
||||
|
||||
|
||||
class GeneralLedger(Report):
|
||||
__name__ = 'account.general_ledger'
|
||||
|
||||
@@ -2611,6 +3168,52 @@ class GeneralLedger(Report):
|
||||
return report_context
|
||||
|
||||
|
||||
class GeneralLedgerDetailReport(Report):
|
||||
__name__ = 'account.general_ledger.detail.report'
|
||||
|
||||
@classmethod
|
||||
def get_context(cls, records, header, data):
|
||||
pool = Pool()
|
||||
Company = pool.get('company.company')
|
||||
Fiscalyear = pool.get('account.fiscalyear')
|
||||
Period = pool.get('account.period')
|
||||
context = Transaction().context
|
||||
|
||||
report_context = super().get_context(records, header, data)
|
||||
report_context['company'] = Company(context['company'])
|
||||
report_context['fiscalyear'] = Fiscalyear(context['fiscalyear'])
|
||||
for period in ['start_period', 'end_period']:
|
||||
if context.get(period):
|
||||
report_context[period] = Period(context[period])
|
||||
else:
|
||||
report_context[period] = None
|
||||
report_context['from_date'] = context.get('from_date')
|
||||
report_context['to_date'] = context.get('to_date')
|
||||
report_context['groups'] = cls._groups(records)
|
||||
return report_context
|
||||
|
||||
@classmethod
|
||||
def _groups(cls, records):
|
||||
groups = []
|
||||
current_key = None
|
||||
current = None
|
||||
for line in records:
|
||||
currency = getattr(line, 'transaction_currency', None)
|
||||
key = (
|
||||
getattr(line, 'account_code', None),
|
||||
getattr(currency, 'id', None))
|
||||
if key != current_key:
|
||||
current = {
|
||||
'account': line.account_code or line.account_name or '',
|
||||
'currency': currency,
|
||||
'lines': [],
|
||||
}
|
||||
groups.append(current)
|
||||
current_key = key
|
||||
current['lines'].append(line)
|
||||
return groups
|
||||
|
||||
|
||||
class TrialBalance(Report):
|
||||
__name__ = 'account.trial_balance'
|
||||
|
||||
|
||||
@@ -806,6 +806,70 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="rule_group" ref="rule_group_general_ledger_line_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="general_ledger_detail_view_list">
|
||||
<field name="model">account.general_ledger.detail</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">general_ledger_detail_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_general_ledger_detail_form">
|
||||
<field name="name">General Ledger Detail</field>
|
||||
<field name="res_model">account.general_ledger.detail</field>
|
||||
<field name="context_model">account.general_ledger.detail.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_general_ledger_detail_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="general_ledger_detail_view_list"/>
|
||||
<field name="act_window" ref="act_general_ledger_detail_form"/>
|
||||
</record>
|
||||
<record model="ir.action.report" id="report_general_ledger_detail">
|
||||
<field name="name">General Ledger Detail</field>
|
||||
<field name="records">listed</field>
|
||||
<field name="model">account.general_ledger.detail</field>
|
||||
<field name="report_name">account.general_ledger.detail.report</field>
|
||||
<field name="report">account/general_ledger_detail.fodt</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="report_general_ledger_detail_keyword">
|
||||
<field name="keyword">form_print</field>
|
||||
<field name="model">account.general_ledger.detail,-1</field>
|
||||
<field name="action" ref="report_general_ledger_detail"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="General Ledger Detail"
|
||||
parent="menu_reporting"
|
||||
action="act_general_ledger_detail_form"
|
||||
sequence="11"
|
||||
id="menu_general_ledger_detail"/>
|
||||
|
||||
<record model="ir.model.access" id="access_general_ledger_detail">
|
||||
<field name="model">account.general_ledger.detail</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_general_ledger_detail_account">
|
||||
<field name="model">account.general_ledger.detail</field>
|
||||
<field name="group" ref="group_account"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_general_ledger_detail_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model">account.general_ledger.detail</field>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_general_ledger_detail_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_general_ledger_detail_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="general_ledger_account_context_view_form">
|
||||
<field name="model">account.general_ledger.account.context</field>
|
||||
<field name="type">form</field>
|
||||
@@ -817,6 +881,11 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="inherit" ref="general_ledger_account_context_view_form"/>
|
||||
<field name="name">general_ledger_line_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="general_ledger_detail_context_view_form">
|
||||
<field name="model">account.general_ledger.detail.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">general_ledger_detail_context_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="account_balance_sheet_view_tree">
|
||||
<field name="model">account.account.type</field>
|
||||
|
||||
825
modules/account/general_ledger_detail.fodt
Normal file
825
modules/account/general_ledger_detail.fodt
Normal file
@@ -0,0 +1,825 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<office:document xmlns:officeooo="http://openoffice.org/2009/office" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
|
||||
<office:meta><meta:generator>LibreOffice/7.3.5.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator><meta:creation-date>2008-06-07T15:26:41</meta:creation-date><dc:date>2008-08-27T17:03:36</dc:date><meta:editing-cycles>1</meta:editing-cycles><meta:editing-duration>PT0S</meta:editing-duration><meta:document-statistic meta:table-count="4" meta:image-count="0" meta:object-count="0" meta:page-count="3" meta:paragraph-count="51" meta:word-count="145" meta:character-count="1808" meta:non-whitespace-character-count="1714"/><meta:user-defined meta:name="Info 1"/><meta:user-defined meta:name="Info 2"/><meta:user-defined meta:name="Info 3"/><meta:user-defined meta:name="Info 4"/></office:meta>
|
||||
<office:settings>
|
||||
<config:config-item-set config:name="ooo:view-settings">
|
||||
<config:config-item config:name="ViewAreaTop" config:type="long">24130</config:config-item>
|
||||
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="ViewAreaWidth" config:type="long">48129</config:config-item>
|
||||
<config:config-item config:name="ViewAreaHeight" config:type="long">22994</config:config-item>
|
||||
<config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
|
||||
<config:config-item-map-indexed config:name="Views">
|
||||
<config:config-item-map-entry>
|
||||
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
|
||||
<config:config-item config:name="ViewLeft" config:type="long">19627</config:config-item>
|
||||
<config:config-item config:name="ViewTop" config:type="long">29242</config:config-item>
|
||||
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="VisibleTop" config:type="long">24130</config:config-item>
|
||||
<config:config-item config:name="VisibleRight" config:type="long">48128</config:config-item>
|
||||
<config:config-item config:name="VisibleBottom" config:type="long">47122</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item>
|
||||
<config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
|
||||
</config:config-item-map-entry>
|
||||
</config:config-item-map-indexed>
|
||||
</config:config-item-set>
|
||||
<config:config-item-set config:name="ooo:configuration-settings">
|
||||
<config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
|
||||
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
|
||||
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
|
||||
<config:config-item config:name="ApplyUserData" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintFaxName" config:type="string"/>
|
||||
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
|
||||
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
|
||||
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="Rsid" config:type="int">4069242</config:config-item>
|
||||
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
|
||||
<config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="PrinterName" config:type="string"/>
|
||||
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
|
||||
<config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="RsidRoot" config:type="int">2070049</config:config-item>
|
||||
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="TabOverflow" config:type="boolean">false</config:config-item>
|
||||
</config:config-item-set>
|
||||
</office:settings>
|
||||
<office:scripts>
|
||||
<office:script script:language="ooo:Basic">
|
||||
<ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
|
||||
</office:script>
|
||||
</office:scripts>
|
||||
<office:font-face-decls>
|
||||
<style:font-face style:name="Andale Sans UI" svg:font-family="'Andale Sans UI'" style:font-family-generic="system" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="DejaVu Sans" svg:font-family="'DejaVu Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Liberation Serif1" svg:font-family="'Liberation Serif'" style:font-adornments="Bold" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Liberation Serif2" svg:font-family="'Liberation Serif'" style:font-adornments="Regular" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>
|
||||
<style:font-face style:name="Thorndale AMT" svg:font-family="'Thorndale AMT'" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
</office:font-face-decls>
|
||||
<office:styles>
|
||||
<style:default-style style:family="graphic">
|
||||
<style:graphic-properties svg:stroke-color="#000000" draw:fill-color="#99ccff" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/>
|
||||
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
|
||||
<style:tab-stops/>
|
||||
</style:paragraph-properties>
|
||||
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none"/>
|
||||
</style:default-style>
|
||||
<style:default-style style:family="paragraph">
|
||||
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false"/>
|
||||
</style:default-style>
|
||||
<style:default-style style:family="table">
|
||||
<style:table-properties table:border-model="collapsing"/>
|
||||
</style:default-style>
|
||||
<style:default-style style:family="table-row">
|
||||
<style:table-row-properties fo:keep-together="auto"/>
|
||||
</style:default-style>
|
||||
<style:style style:name="Standard" style:family="paragraph" style:class="text">
|
||||
<style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
|
||||
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:font-name="Liberation Serif2" fo:font-family="'Liberation Serif'" style:font-style-name="Regular" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="16pt" style:font-name-asian="DejaVu Sans" style:font-family-asian="'DejaVu Sans'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="DejaVu Sans" style:font-family-complex="'DejaVu Sans'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
|
||||
<style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
|
||||
<style:text-properties style:font-size-asian="12pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
|
||||
<style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
|
||||
<style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-size-complex="12pt" style:font-style-complex="italic"/>
|
||||
</style:style>
|
||||
<style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
|
||||
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
|
||||
<style:text-properties style:font-size-asian="12pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
|
||||
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
|
||||
<style:text-properties fo:font-size="11pt" style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
|
||||
<style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" text:number-lines="false" text:line-number="0"/>
|
||||
<style:text-properties style:font-name="Liberation Serif1" fo:font-family="'Liberation Serif'" style:font-style-name="Bold" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="12pt" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="Horizontal_20_Line" style:display-name="Horizontal Line" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="html">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" style:border-line-width-bottom="0.002cm 0.035cm 0.002cm" fo:padding="0cm" fo:border-left="none" fo:border-right="none" fo:border-top="none" fo:border-bottom="1.11pt double #808080" text:number-lines="false" text:line-number="0" style:join-border="false"/>
|
||||
<style:text-properties fo:font-size="6pt" style:font-size-asian="6pt" style:font-size-complex="6pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Header_20_and_20_Footer" style:display-name="Header and Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
|
||||
<style:paragraph-properties text:number-lines="false" text:line-number="0">
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="8.5cm" style:type="center"/>
|
||||
<style:tab-stop style:position="17cm" style:type="right"/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Header" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
|
||||
<style:paragraph-properties text:number-lines="false" text:line-number="0">
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="8.795cm" style:type="center"/>
|
||||
<style:tab-stop style:position="17.59cm" style:type="right"/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
|
||||
<style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
|
||||
<style:paragraph-properties text:number-lines="false" text:line-number="0">
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="8.795cm" style:type="center"/>
|
||||
<style:tab-stop style:position="17.59cm" style:type="right"/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
|
||||
<style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="Text_20_body_20_indent" style:display-name="Text body indent" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="text">
|
||||
<style:paragraph-properties fo:margin-left="0.499cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
|
||||
<style:paragraph-properties fo:margin-left="1cm" fo:margin-right="1cm" fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="Title" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
<style:text-properties fo:font-size="28pt" fo:font-weight="bold" style:font-size-asian="28pt" style:font-weight-asian="bold" style:font-size-complex="28pt" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="Subtitle" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
|
||||
<style:paragraph-properties fo:margin-top="0.106cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:text-align="center" style:justify-single-word="false"/>
|
||||
<style:text-properties fo:font-size="18pt" style:font-size-asian="18pt" style:font-size-complex="18pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Placeholder" style:family="text">
|
||||
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" loext:opacity="100%" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
|
||||
</style:style>
|
||||
<style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
|
||||
<style:text-properties style:font-name="StarSymbol" fo:font-family="StarSymbol" fo:font-size="9pt" style:font-name-asian="StarSymbol" style:font-family-asian="StarSymbol" style:font-size-asian="9pt" style:font-name-complex="StarSymbol" style:font-family-complex="StarSymbol" style:font-size-complex="9pt"/>
|
||||
</style:style>
|
||||
<text:outline-style style:name="Outline">
|
||||
<text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
<text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
|
||||
<style:list-level-properties text:min-label-distance="0.381cm"/>
|
||||
</text:outline-level-style>
|
||||
</text:outline-style>
|
||||
<text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
|
||||
<text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
|
||||
<text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
|
||||
<style:style style:name="Default_20_Style.1" style:display-name="Default Style.1" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.2" style:display-name="Default Style.2" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.3" style:display-name="Default Style.3" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.4" style:display-name="Default Style.4" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.5" style:display-name="Default Style.5" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.6" style:display-name="Default Style.6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.7" style:display-name="Default Style.7" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.8" style:display-name="Default Style.8" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.9" style:display-name="Default Style.9" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.10" style:display-name="Default Style.10" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.11" style:display-name="Default Style.11" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.12" style:display-name="Default Style.12" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.13" style:display-name="Default Style.13" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.14" style:display-name="Default Style.14" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.15" style:display-name="Default Style.15" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Style.16" style:display-name="Default Style.16" style:family="table-cell">
|
||||
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
|
||||
</style:style>
|
||||
<table:table-template table:name="Default Style" table:first-row-end-column="row" table:first-row-start-column="row" table:last-row-end-column="row" table:last-row-start-column="row">
|
||||
<table:first-row table:style-name="Default_20_Style.1"/>
|
||||
<table:last-row table:style-name="Default_20_Style.2"/>
|
||||
<table:first-column table:style-name="Default_20_Style.3"/>
|
||||
<table:last-column table:style-name="Default_20_Style.4"/>
|
||||
<table:body table:style-name="Default_20_Style.9"/>
|
||||
<table:even-rows table:style-name="Default_20_Style.5"/>
|
||||
<table:odd-rows table:style-name="Default_20_Style.6"/>
|
||||
<table:even-columns table:style-name="Default_20_Style.7"/>
|
||||
<table:odd-columns table:style-name="Default_20_Style.8"/>
|
||||
<table:background table:style-name="Default_20_Style.10"/>
|
||||
<loext:first-row-even-column table:style-name="Default_20_Style.15"/>
|
||||
<loext:last-row-even-column table:style-name="Default_20_Style.16"/>
|
||||
<loext:first-row-end-column table:style-name="Default_20_Style.12"/>
|
||||
<loext:first-row-start-column table:style-name="Default_20_Style.11"/>
|
||||
<loext:last-row-end-column table:style-name="Default_20_Style.14"/>
|
||||
<loext:last-row-start-column table:style-name="Default_20_Style.13"/>
|
||||
</table:table-template>
|
||||
</office:styles>
|
||||
<office:automatic-styles>
|
||||
<style:style style:name="Table2" style:family="table">
|
||||
<style:table-properties style:width="23.94cm" table:align="margins"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table2.A" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="7.98cm" style:rel-column-width="21845*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table2.A1" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="none">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table1" style:family="table">
|
||||
<style:table-properties style:width="23.94cm" table:align="margins"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table1.A" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32768*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table1.B" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32767*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table1.A1" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table2" style:family="table">
|
||||
<style:table-properties style:width="23.94cm" table:align="margins"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table2.A" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="7.98cm" style:rel-column-width="21845*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table2.A1" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="none">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table1" style:family="table">
|
||||
<style:table-properties style:width="23.94cm" table:align="margins"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table1.A" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32768*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table1.B" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32767*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table1.A1" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines" style:family="table">
|
||||
<style:table-properties style:width="23.94cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:break-before="auto" fo:break-after="auto" table:align="margins" fo:background-color="transparent" fo:keep-with-next="always" style:may-break-between-rows="true" style:writing-mode="lr-tb">
|
||||
<style:background-image/>
|
||||
</style:table-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="2.499cm" style:rel-column-width="1417*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.B" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="3.358cm" style:rel-column-width="1904*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.C" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="3.36cm" style:rel-column-width="1905*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.F" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="4.002cm" style:rel-column-width="2269*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.1" style:family="table-row">
|
||||
<style:table-row-properties fo:keep-together="always"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A1" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A2" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A3" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.G3" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A4" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.C4" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.G4" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A5" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.B6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.C6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.D6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.E6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.F6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.G6" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A7" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Lines.A8" style:family="table-cell">
|
||||
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table3" style:family="table" style:master-page-name="">
|
||||
<style:table-properties style:width="23.94cm" style:page-number="auto" fo:break-before="auto" fo:break-after="auto" table:align="margins" fo:background-color="transparent" style:shadow="none" fo:keep-with-next="always" style:may-break-between-rows="true" style:writing-mode="lr-tb">
|
||||
<style:background-image/>
|
||||
</style:table-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.A" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="5.851cm" style:rel-column-width="3317*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.B" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="3.362cm" style:rel-column-width="1906*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.E" style:family="table-column">
|
||||
<style:table-column-properties style:column-width="4.001cm" style:rel-column-width="2268*"/>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.1" style:family="table-row">
|
||||
<style:table-row-properties fo:background-color="transparent" fo:keep-together="always">
|
||||
<style:background-image/>
|
||||
</style:table-row-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.A1" style:family="table-cell">
|
||||
<style:table-cell-properties style:vertical-align="" fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border="0.05pt solid #000000" style:writing-mode="lr-tb">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.A2" style:family="table-cell">
|
||||
<style:table-cell-properties style:vertical-align="" fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.A3" style:family="table-cell">
|
||||
<style:table-cell-properties style:vertical-align="" fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="Table3.F3" style:family="table-cell">
|
||||
<style:table-cell-properties style:vertical-align="" fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
|
||||
<style:background-image/>
|
||||
</style:table-cell-properties>
|
||||
</style:style>
|
||||
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Header">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="P2" style:family="paragraph" style:parent-style-name="Header">
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="P3" style:family="paragraph" style:parent-style-name="Footer">
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="P4" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
</style:style>
|
||||
<style:style style:name="P5" style:family="paragraph" style:parent-style-name="Table_20_Heading">
|
||||
<style:paragraph-properties fo:keep-together="always" style:shadow="none" fo:keep-with-next="always"/>
|
||||
</style:style>
|
||||
<style:style style:name="P6" style:family="paragraph" style:parent-style-name="Table_20_Heading">
|
||||
<style:paragraph-properties fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
</style:style>
|
||||
<style:style style:name="P7" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" style:shadow="none" fo:keep-with-next="always"/>
|
||||
<style:text-properties fo:font-size="12pt" fo:font-weight="bold" style:font-size-asian="12pt" style:font-weight-asian="bold" style:font-size-complex="12pt" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="P8" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
|
||||
<style:text-properties fo:font-size="12pt" style:text-underline-style="none" fo:font-weight="normal" style:font-size-asian="10.5pt" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-weight-complex="normal"/>
|
||||
</style:style>
|
||||
<style:style style:name="P9" style:family="paragraph" style:parent-style-name="Table_20_Heading">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P10" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P11" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="002aca3c" style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P12" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
</style:style>
|
||||
<style:style style:name="P13" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties fo:font-size="11pt" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P14" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties fo:font-size="11pt" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P15" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties fo:font-size="11pt" style:text-underline-style="none" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P16" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:text-underline-style="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="P17" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="00251ac8"/>
|
||||
</style:style>
|
||||
<style:style style:name="P18" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
|
||||
<style:text-properties style:text-underline-style="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="P19" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="00251ac8" fo:background-color="transparent"/>
|
||||
</style:style>
|
||||
<style:style style:name="P20" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties style:text-underline-style="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties style:text-underline-style="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:paragraph-rsid="0037c304"/>
|
||||
</style:style>
|
||||
<style:style style:name="P23" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:paragraph-rsid="003a045d"/>
|
||||
</style:style>
|
||||
<style:style style:name="P24" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="003955ec" officeooo:paragraph-rsid="003a045d"/>
|
||||
</style:style>
|
||||
<style:style style:name="P25" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
|
||||
</style:style>
|
||||
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
<style:text-properties style:font-name="Liberation Serif1" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Header">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P28" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
</style:style>
|
||||
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Serif" fo:font-size="12pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:font-size-asian="12pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-overline-style="none" style:text-overline-color="font-color"/>
|
||||
</style:style>
|
||||
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="002aca3c" style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P31" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:font-size-asian="10.5pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="P34" style:family="paragraph" style:parent-style-name="Table_20_Heading">
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="P35" style:family="paragraph" style:parent-style-name="Table_20_Heading">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:break-before="page" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P38" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
|
||||
<style:text-properties fo:font-size="12pt" style:text-underline-style="none" fo:font-weight="normal" officeooo:paragraph-rsid="003e177a" fo:background-color="transparent" style:font-size-asian="10.5pt" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-weight-complex="normal"/>
|
||||
</style:style>
|
||||
<style:style style:name="T1" style:family="text">
|
||||
<style:text-properties officeooo:rsid="0037c304"/>
|
||||
</style:style>
|
||||
<style:style style:name="T2" style:family="text">
|
||||
<style:text-properties officeooo:rsid="0039b016"/>
|
||||
</style:style>
|
||||
<style:style style:name="T3" style:family="text">
|
||||
<style:text-properties officeooo:rsid="003e177a"/>
|
||||
</style:style>
|
||||
<style:style style:name="T4" style:family="text">
|
||||
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
|
||||
</style:style>
|
||||
<style:style style:name="T5" style:family="text">
|
||||
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" officeooo:rsid="003e177a"/>
|
||||
</style:style>
|
||||
<style:style style:name="T6" style:family="text">
|
||||
<style:text-properties style:text-underline-style="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="T7" style:family="text">
|
||||
<style:text-properties style:text-underline-style="none" officeooo:rsid="003e177a"/>
|
||||
</style:style>
|
||||
<style:page-layout style:name="pm1">
|
||||
<style:page-layout-properties fo:page-width="27.94cm" fo:page-height="21.59cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="44" style:layout-grid-base-height="0.55cm" style:layout-grid-ruby-height="0cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="true" style:layout-grid-display="true" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
|
||||
<style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="none" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
|
||||
</style:page-layout-properties>
|
||||
<style:header-style>
|
||||
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-bottom="0.499cm"/>
|
||||
</style:header-style>
|
||||
<style:footer-style>
|
||||
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0.499cm"/>
|
||||
</style:footer-style>
|
||||
</style:page-layout>
|
||||
<style:style style:name="dp1" style:family="drawing-page">
|
||||
<style:drawing-page-properties draw:background-size="full"/>
|
||||
</style:style>
|
||||
</office:automatic-styles>
|
||||
<office:master-styles>
|
||||
<style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1">
|
||||
<style:header>
|
||||
<table:table table:name="Table2" table:style-name="Table2">
|
||||
<table:table-column table:style-name="Table2.A" table:number-columns-repeated="3"/>
|
||||
<table:table-row table:style-name="TableLine94368948362784">
|
||||
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
|
||||
<text:p text:style-name="Header">Company: <text:placeholder text:placeholder-type="text"><company.rec_name></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
|
||||
<text:p text:style-name="P27">General Ledger Detail<text:placeholder text:placeholder-type="text"><if test="not context.get('posted')"></text:placeholder> <text:span text:style-name="T3">Draft</text:span><text:span text:style-name="T3"><text:placeholder text:placeholder-type="text"></if></text:placeholder></text:span></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
|
||||
<text:p text:style-name="P2">Print Date: <text:placeholder text:placeholder-type="text"><format_date(datetime.date.today(), user.language)></text:placeholder><text:s/>at <text:placeholder text:placeholder-type="text"><datetime.datetime.now().strftime('%H:%M:%S')></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
</style:header>
|
||||
<style:footer>
|
||||
<table:table table:name="Table1" table:style-name="Table1">
|
||||
<table:table-column table:style-name="Table1.A"/>
|
||||
<table:table-column table:style-name="Table1.B"/>
|
||||
<table:table-row table:style-name="TableLine94368949141280">
|
||||
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
|
||||
<text:p text:style-name="Footer">User: <text:placeholder text:placeholder-type="text"><user.rec_name></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
|
||||
<text:p text:style-name="P3"><text:page-number text:select-page="current">1</text:page-number>/<text:page-count>3</text:page-count></text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
</style:footer>
|
||||
</style:master-page>
|
||||
</office:master-styles>
|
||||
<office:body>
|
||||
<office:text text:use-soft-page-breaks="true">
|
||||
<office:forms form:automatic-focus="false" form:apply-design-mode="false"/>
|
||||
<text:sequence-decls>
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
|
||||
</text:sequence-decls>
|
||||
<text:p text:style-name="P25">General Ledger Detail<text:placeholder text:placeholder-type="text"><if test="not context.get('posted')"></text:placeholder> <text:span text:style-name="T3">Draft</text:span><text:span text:style-name="T3"><text:placeholder text:placeholder-type="text"></if></text:placeholder></text:span></text:p>
|
||||
<text:p text:style-name="P23"><text:span text:style-name="T1">Fiscal Year: </text:span><text:placeholder text:placeholder-type="text"><fiscalyear.rec_name></text:placeholder></text:p>
|
||||
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text"><if test="start_period or end_period"></text:placeholder></text:p>
|
||||
<text:p text:style-name="P23"><text:span text:style-name="T1">From Period</text:span>: <text:placeholder text:placeholder-type="text"><start_period.name if start_period else ''></text:placeholder><text:s/><text:span text:style-name="T2">To</text:span> <text:placeholder text:placeholder-type="text"><end_period.name if end_period else ''></text:placeholder></text:p>
|
||||
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text"></if></text:placeholder></text:p>
|
||||
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text"><if test="from_date or to_date"></text:placeholder></text:p>
|
||||
<text:p text:style-name="P24">From Date: <text:placeholder text:placeholder-type="text"><format_date(from_date, user.language) if from_date else ''></text:placeholder><text:s/>To <text:placeholder text:placeholder-type="text"><format_date(to_date, user.language) if to_date else ''></text:placeholder></text:p>
|
||||
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text"></if></text:placeholder></text:p>
|
||||
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text"><for each="group in groups"></text:placeholder></text:p>
|
||||
<table:table table:name="Lines" table:style-name="Lines">
|
||||
<table:table-column table:style-name="Lines.A"/>
|
||||
<table:table-column table:style-name="Lines.B"/>
|
||||
<table:table-column table:style-name="Lines.C"/>
|
||||
<table:table-column table:style-name="Lines.B" table:number-columns-repeated="2"/>
|
||||
<table:table-column table:style-name="Lines.F" table:number-columns-repeated="2"/>
|
||||
<table:table-header-rows>
|
||||
<table:table-row table:style-name="Lines.1">
|
||||
<table:table-cell table:style-name="Lines.A1" table:number-columns-spanned="7" office:value-type="string">
|
||||
<text:p text:style-name="P5">Account / Currency</text:p>
|
||||
</table:table-cell>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Lines.1">
|
||||
<table:table-cell table:style-name="Lines.A2" table:number-columns-spanned="7" office:value-type="string">
|
||||
<text:p text:style-name="P7"><text:placeholder text:placeholder-type="text"><group['account']></text:placeholder><text:s/>/<text:s/><text:placeholder text:placeholder-type="text"><group['currency'].rec_name if group['currency'] else ''></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
<table:covered-table-cell/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Lines.1">
|
||||
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Date</text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Journal</text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Entry / Doc.</text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="Table_20_Heading">Debit</text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Credit</text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Running</text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.G3" office:value-type="string"><text:p text:style-name="P6">Description / Ref.</text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table-header-rows>
|
||||
<table:table-row table:style-name="Lines.1">
|
||||
<table:table-cell table:style-name="Lines.A5" table:number-columns-spanned="7" office:value-type="string">
|
||||
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text"><for each="line in group['lines']"></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
<table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Lines.1">
|
||||
<table:table-cell table:style-name="Lines.A6" office:value-type="string"><text:p text:style-name="P33"><text:placeholder text:placeholder-type="text"><format_date(line.posting_date, user.language) if line.posting_date else ''></text:placeholder></text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.B6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text"><line.journal.rec_name if line.journal else ''></text:placeholder></text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.C6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text"><line.journal_entry_number or ''></text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text"><line.document_number or line.voucher_number or ''></text:placeholder></text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.D6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text"><format_currency(line.debit_base_currency or 0, user.language, line.base_currency or company.currency)></text:placeholder></text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.E6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text"><format_currency(line.credit_base_currency or 0, user.language, line.base_currency or company.currency)></text:placeholder></text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.F6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text"><format_currency(line.running_balance_base_currency or 0, user.language, line.base_currency or company.currency)></text:placeholder></text:p></table:table-cell>
|
||||
<table:table-cell table:style-name="Lines.G6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text"><line.description or line.move_description or ''></text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text"><line.party.rec_name if line.party else ''></text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text"><line.reference or ''></text:placeholder></text:p></table:table-cell>
|
||||
</table:table-row>
|
||||
<text:soft-page-break/>
|
||||
<table:table-row table:style-name="Lines.1">
|
||||
<table:table-cell table:style-name="Lines.A7" table:number-columns-spanned="7" office:value-type="string">
|
||||
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text"></for></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
<table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
<text:p text:style-name="P8"><text:placeholder text:placeholder-type="text"></for></text:placeholder></text:p>
|
||||
</office:text>
|
||||
</office:body>
|
||||
</office:document>
|
||||
@@ -1013,7 +1013,9 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
|
||||
|
||||
journal = fields.Function(fields.Many2One('account.journal',"Journal"),'get_journal')
|
||||
|
||||
rate = fields.Function(fields.Numeric("Rate",digits=(1,6)),'get_rate')
|
||||
rate = fields.Numeric("Rate", digits=(10, 6), states={
|
||||
'readonly': _states['readonly'],
|
||||
})
|
||||
|
||||
del _states
|
||||
|
||||
@@ -1117,46 +1119,84 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
|
||||
return line.move.id
|
||||
|
||||
def get_rate(self,name=None):
|
||||
if self.amount_second_currency:
|
||||
return round((self.credit if self.credit else self.debit) / abs(self.amount_second_currency),6)
|
||||
|
||||
@fields.depends(
|
||||
'debit', 'credit',
|
||||
'date','second_currency','amount_second_currency')
|
||||
def on_change_amount_second_currency(self):
|
||||
Currency = Pool().get('currency.currency')
|
||||
Date = Pool().get('ir.date')
|
||||
if self.second_currency != None and self.amount_second_currency != None and (not self.credit and not self.debit):
|
||||
tdate = Date.today()
|
||||
if self.date:
|
||||
tdate = self.date
|
||||
rate = Currency._get_rate([self.second_currency],tdate)
|
||||
if rate:
|
||||
rate = rate[self.second_currency.id]
|
||||
if self.amount_second_currency > 0:
|
||||
self.debit = round(rate * self.amount_second_currency,2)
|
||||
elif self.amount_second_currency > 0:
|
||||
self.credit = round(rate * abs(self.amount_second_currency),2)
|
||||
self.rate = self.get_rate()
|
||||
amount = self.credit if self.credit else self.debit
|
||||
amount_second_currency = getattr(self, 'amount_second_currency', None)
|
||||
if amount_second_currency and amount:
|
||||
return round(abs(amount_second_currency) / amount, 6)
|
||||
|
||||
def _set_rate_from_amounts(self):
|
||||
if (getattr(self, 'amount_second_currency', None)
|
||||
and not getattr(self, 'rate', None)):
|
||||
self.rate = self.get_rate()
|
||||
|
||||
@fields.depends(
|
||||
'debit', 'credit',
|
||||
'date','second_currency','amount_second_currency')
|
||||
'date','second_currency','amount_second_currency', 'rate',
|
||||
'origin', 'move_origin', 'move', '_parent_move.origin',
|
||||
'company', 'account')
|
||||
def on_change_second_currency(self):
|
||||
if self._manual_rate_mode():
|
||||
self.rate = self._get_second_currency_rate()
|
||||
self._compute_amount_from_second_currency()
|
||||
|
||||
@fields.depends(
|
||||
'debit', 'credit',
|
||||
'date','second_currency','amount_second_currency', 'rate',
|
||||
'origin', 'move_origin', 'move', '_parent_move.origin',
|
||||
'company', 'account')
|
||||
def on_change_rate(self):
|
||||
if self._manual_rate_mode() and not self.debit and not self.credit:
|
||||
self._compute_amount_from_second_currency()
|
||||
|
||||
def _manual_rate_mode(self):
|
||||
if getattr(self, 'origin', None) or getattr(self, 'move_origin', None):
|
||||
return False
|
||||
move = getattr(self, 'move', None)
|
||||
if move and getattr(move, 'origin', None):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_second_currency_rate(self):
|
||||
Currency = Pool().get('currency.currency')
|
||||
Date = Pool().get('ir.date')
|
||||
if self.second_currency != None and self.amount_second_currency != None and (not self.credit and not self.debit):
|
||||
tdate = Date.today()
|
||||
if self.date:
|
||||
tdate = self.date
|
||||
rate = Currency._get_rate([self.second_currency],tdate)
|
||||
if rate:
|
||||
rate = rate[self.second_currency.id]
|
||||
if self.second_currency is None:
|
||||
return None
|
||||
tdate = Date.today()
|
||||
if self.date:
|
||||
tdate = self.date
|
||||
rates = Currency._get_rate([self.second_currency], tdate)
|
||||
return rates.get(self.second_currency.id) if rates else None
|
||||
|
||||
def _get_company_currency(self):
|
||||
Company = Pool().get('company.company')
|
||||
for record in (self, getattr(self, 'move', None),
|
||||
getattr(self, 'account', None)):
|
||||
company = getattr(record, 'company', None)
|
||||
if company:
|
||||
return company.currency
|
||||
company_id = Transaction().context.get('company')
|
||||
if company_id:
|
||||
return Company(company_id).currency
|
||||
|
||||
def _compute_amount_from_second_currency(self):
|
||||
if self.second_currency is not None and self.amount_second_currency is not None:
|
||||
if not self.rate:
|
||||
self.rate = self._get_second_currency_rate()
|
||||
if self.rate:
|
||||
company_currency = self._get_company_currency()
|
||||
if not company_currency:
|
||||
return
|
||||
amount = company_currency.round(
|
||||
abs(self.amount_second_currency) / self.rate)
|
||||
if self.amount_second_currency > 0:
|
||||
self.debit = round(rate * self.amount_second_currency,2)
|
||||
elif self.amount_second_currency > 0:
|
||||
self.credit = round(rate * abs(self.amount_second_currency),2)
|
||||
self.rate = self.get_rate()
|
||||
self.debit = amount
|
||||
self.credit = Decimal(0)
|
||||
elif self.amount_second_currency < 0:
|
||||
self.debit = Decimal(0)
|
||||
self.credit = amount
|
||||
else:
|
||||
self.debit = Decimal(0)
|
||||
self.credit = Decimal(0)
|
||||
|
||||
@fields.depends(
|
||||
'move', 'debit', 'credit',
|
||||
@@ -1223,15 +1263,26 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
|
||||
if self.debit:
|
||||
self.credit = Decimal(0)
|
||||
self._amount_second_currency_sign()
|
||||
self.rate = self.get_rate()
|
||||
|
||||
@fields.depends('debit', 'credit', 'amount_second_currency')
|
||||
def on_change_credit(self):
|
||||
if self.credit:
|
||||
self.debit = Decimal(0)
|
||||
self._amount_second_currency_sign()
|
||||
self.rate = self.get_rate()
|
||||
|
||||
@fields.depends('amount_second_currency', 'debit', 'credit')
|
||||
@fields.depends(
|
||||
'amount_second_currency', 'debit', 'credit', 'date', 'second_currency',
|
||||
'rate', 'origin', 'move_origin', 'move', '_parent_move.origin',
|
||||
'company', 'account')
|
||||
def on_change_amount_second_currency(self):
|
||||
if self._manual_rate_mode():
|
||||
if not self.rate:
|
||||
self.rate = self._get_second_currency_rate()
|
||||
self._compute_amount_from_second_currency()
|
||||
elif not self.rate:
|
||||
self.rate = self.get_rate()
|
||||
self._amount_second_currency_sign()
|
||||
|
||||
def _amount_second_currency_sign(self):
|
||||
@@ -1241,6 +1292,7 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
|
||||
self.amount_second_currency = \
|
||||
self.amount_second_currency.copy_sign(
|
||||
(self.debit or 0) - (self.credit or 0))
|
||||
self._set_rate_from_amounts()
|
||||
|
||||
@fields.depends('account')
|
||||
def on_change_account(self):
|
||||
@@ -1545,6 +1597,14 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
|
||||
for fname in move_fields(move_name=False):
|
||||
vals.setdefault(fname, None)
|
||||
lines = super(Line, cls).create(vlist)
|
||||
to_save = []
|
||||
for line in lines:
|
||||
if (getattr(line, 'amount_second_currency', None)
|
||||
and not getattr(line, 'rate', None)):
|
||||
line._set_rate_from_amounts()
|
||||
to_save.append(line)
|
||||
if to_save:
|
||||
cls.save(to_save)
|
||||
period_and_journals = set((line.period, line.journal)
|
||||
for line in lines)
|
||||
for period, journal in period_and_journals:
|
||||
|
||||
@@ -12,7 +12,7 @@ from trytond.modules.account.exceptions import (
|
||||
from trytond.modules.account.tax import TaxableMixin
|
||||
from trytond.modules.company.tests import (
|
||||
CompanyTestMixin, PartyCompanyCheckEraseMixin, create_company, set_company)
|
||||
from trytond.modules.currency.tests import create_currency
|
||||
from trytond.modules.currency.tests import add_currency_rate, create_currency
|
||||
from trytond.pool import Pool
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.transaction import Transaction, inactive_records
|
||||
@@ -576,6 +576,54 @@ class AccountTestCase(
|
||||
self.assertEqual(
|
||||
cash_cur.amount_second_currency, Decimal(50))
|
||||
|
||||
@with_transaction()
|
||||
def test_move_line_second_currency_amount_on_change(self):
|
||||
'Test account move line computes amount from second currency'
|
||||
pool = Pool()
|
||||
Account = pool.get('account.account')
|
||||
Line = pool.get('account.move.line')
|
||||
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
create_chart(company)
|
||||
second_currency = create_currency('sec')
|
||||
add_currency_rate(second_currency, Decimal('0.939506'))
|
||||
|
||||
expense, = Account.search([
|
||||
('type.expense', '=', True),
|
||||
])
|
||||
|
||||
line = Line(
|
||||
account=expense,
|
||||
second_currency=second_currency,
|
||||
amount_second_currency=Decimal('100.00'))
|
||||
line.on_change_amount_second_currency()
|
||||
|
||||
self.assertEqual(line.debit, Decimal('106.44'))
|
||||
self.assertEqual(line.credit, Decimal(0))
|
||||
self.assertEqual(line.rate, Decimal('0.939506'))
|
||||
self.assertEqual(line.get_rate(), Decimal('0.939496'))
|
||||
|
||||
line = Line(
|
||||
account=expense,
|
||||
credit=Decimal('7.61'),
|
||||
second_currency=second_currency,
|
||||
amount_second_currency=Decimal('-108.10'))
|
||||
line.on_change_amount_second_currency()
|
||||
|
||||
self.assertEqual(line.debit, Decimal(0))
|
||||
self.assertEqual(line.credit, Decimal('115.06'))
|
||||
self.assertEqual(line.rate, Decimal('0.939506'))
|
||||
self.assertEqual(line.get_rate(), Decimal('0.939510'))
|
||||
|
||||
line.debit = line.credit = Decimal(0)
|
||||
line.rate = Decimal('1.100000')
|
||||
line.on_change_rate()
|
||||
|
||||
self.assertEqual(line.debit, Decimal(0))
|
||||
self.assertEqual(line.credit, Decimal('98.27'))
|
||||
self.assertEqual(line.rate, Decimal('1.100000'))
|
||||
|
||||
@with_transaction()
|
||||
def test_account_type_amount(self):
|
||||
"Test account type amount"
|
||||
@@ -2049,5 +2097,121 @@ class AccountTestCase(
|
||||
|
||||
self.assertEqual(account.parent, new_account)
|
||||
|
||||
@with_transaction()
|
||||
def test_general_ledger_detail_models_are_registered(self):
|
||||
"Test General Ledger Detail models are registered with expected fields"
|
||||
pool = Pool()
|
||||
Detail = pool.get('account.general_ledger.detail')
|
||||
DetailContext = pool.get('account.general_ledger.detail.context')
|
||||
DetailReport = pool.get(
|
||||
'account.general_ledger.detail.report', type='report')
|
||||
|
||||
for name in [
|
||||
'row_type', 'account_code', 'transaction_currency',
|
||||
'journal_entry_number', 'document_number',
|
||||
'debit_base_currency', 'credit_base_currency',
|
||||
'running_balance_base_currency',
|
||||
'debit_transaction_currency',
|
||||
'credit_transaction_currency',
|
||||
'running_balance_transaction_currency', 'payable_qty']:
|
||||
self.assertIn(name, Detail._fields)
|
||||
for name in [
|
||||
'debit_base_currency', 'credit_base_currency',
|
||||
'balance_base_currency', 'running_balance_base_currency',
|
||||
'debit_transaction_currency',
|
||||
'credit_transaction_currency',
|
||||
'balance_transaction_currency',
|
||||
'running_balance_transaction_currency']:
|
||||
self.assertEqual(Detail._fields[name].digits, (16, 2))
|
||||
self.assertEqual(Detail._fields['payable_qty'].digits, (16, 5))
|
||||
|
||||
for name in [
|
||||
'account', 'account_from', 'account_to', 'currency', 'party',
|
||||
'document_number']:
|
||||
self.assertIn(name, DetailContext._fields)
|
||||
|
||||
account = pool.get('account.account').__table__()
|
||||
account_label = Detail._account_label(account)
|
||||
self.assertIn('||', str(account_label))
|
||||
self.assertNotIn(' + ', str(account_label))
|
||||
self.assertEqual(account_label.params, ('', ' - ', ''))
|
||||
self.assertEqual(
|
||||
DetailReport.__name__, 'account.general_ledger.detail.report')
|
||||
|
||||
@with_transaction()
|
||||
def test_general_ledger_detail_report_groups_by_account_currency(self):
|
||||
"Test General Ledger Detail report groups lines by account and currency"
|
||||
DetailReport = Pool().get(
|
||||
'account.general_ledger.detail.report', type='report')
|
||||
currency = type('Currency', (), {'id': 1})()
|
||||
other_currency = type('Currency', (), {'id': 2})()
|
||||
first = type('Line', (), {
|
||||
'account_code': '1000 - Cash',
|
||||
'account_name': 'Cash',
|
||||
'transaction_currency': currency,
|
||||
})()
|
||||
second = type('Line', (), {
|
||||
'account_code': '1000 - Cash',
|
||||
'account_name': 'Cash',
|
||||
'transaction_currency': currency,
|
||||
})()
|
||||
third = type('Line', (), {
|
||||
'account_code': '2000 - Revenue',
|
||||
'account_name': 'Revenue',
|
||||
'transaction_currency': other_currency,
|
||||
})()
|
||||
|
||||
groups = DetailReport._groups([first, second, third])
|
||||
|
||||
self.assertEqual(len(groups), 2)
|
||||
self.assertEqual(groups[0]['account'], '1000 - Cash')
|
||||
self.assertEqual(groups[0]['currency'], currency)
|
||||
self.assertEqual(groups[0]['lines'], [first, second])
|
||||
self.assertEqual(groups[1]['account'], '2000 - Revenue')
|
||||
self.assertEqual(groups[1]['currency'], other_currency)
|
||||
self.assertEqual(groups[1]['lines'], [third])
|
||||
|
||||
@with_transaction()
|
||||
def test_general_ledger_detail_date_contexts(self):
|
||||
"Test General Ledger Detail opening and movement date contexts"
|
||||
Detail = Pool().get('account.general_ledger.detail')
|
||||
context = {
|
||||
'from_date': datetime.date(2026, 1, 10),
|
||||
'to_date': datetime.date(2026, 1, 31),
|
||||
}
|
||||
|
||||
self.assertEqual(Detail._query_context('opening', context), {
|
||||
'periods': None,
|
||||
'from_date': None,
|
||||
'to_date': datetime.date(2026, 1, 9),
|
||||
})
|
||||
self.assertEqual(Detail._query_context('movement', context), {
|
||||
'periods': None,
|
||||
'from_date': datetime.date(2026, 1, 10),
|
||||
'to_date': datetime.date(2026, 1, 31),
|
||||
})
|
||||
self.assertEqual(Detail._query_context('closing', context), {
|
||||
'periods': None,
|
||||
'from_date': None,
|
||||
'to_date': datetime.date(2026, 1, 31),
|
||||
})
|
||||
|
||||
@with_transaction()
|
||||
def test_general_ledger_detail_document_field_reference_string(self):
|
||||
"Test General Ledger Detail document fields resolve reference strings"
|
||||
pool = Pool()
|
||||
Detail = pool.get('account.general_ledger.detail')
|
||||
Party = pool.get('party.party')
|
||||
|
||||
party = Party(name='Customer')
|
||||
party.save()
|
||||
|
||||
detail = Detail()
|
||||
detail.origin = '%s,%s' % (Party.__name__, party.id)
|
||||
|
||||
self.assertIsNone(detail.get_document_field('document_number'))
|
||||
self.assertEqual(detail.get_document_field('document_type'),
|
||||
party.rec_name)
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
|
||||
41
modules/account/view/general_ledger_detail_context_form.xml
Normal file
41
modules/account/view/general_ledger_detail_context_form.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form>
|
||||
<label name="fiscalyear"/>
|
||||
<field name="fiscalyear"/>
|
||||
<label name="company"/>
|
||||
<field name="company"/>
|
||||
|
||||
<label name="journal"/>
|
||||
<field name="journal" widget="selection"/>
|
||||
<group col="-1" colspan="2" id="checkboxes">
|
||||
<label name="posted"/>
|
||||
<field name="posted"/>
|
||||
</group>
|
||||
|
||||
<label name="start_period"/>
|
||||
<field name="start_period"/>
|
||||
<label name="end_period"/>
|
||||
<field name="end_period"/>
|
||||
|
||||
<label name="from_date"/>
|
||||
<field name="from_date"/>
|
||||
<label name="to_date"/>
|
||||
<field name="to_date"/>
|
||||
|
||||
<label name="account"/>
|
||||
<field name="account"/>
|
||||
<label name="currency"/>
|
||||
<field name="currency"/>
|
||||
|
||||
<label name="account_from"/>
|
||||
<field name="account_from"/>
|
||||
<label name="account_to"/>
|
||||
<field name="account_to"/>
|
||||
|
||||
<label name="party"/>
|
||||
<field name="party"/>
|
||||
<label name="document_number"/>
|
||||
<field name="document_number"/>
|
||||
</form>
|
||||
39
modules/account/view/general_ledger_detail_list.xml
Normal file
39
modules/account/view/general_ledger_detail_list.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="row_type"/>
|
||||
<field name="account_code" expand="1"/>
|
||||
<field name="transaction_currency"/>
|
||||
<field name="base_currency" optional="1"/>
|
||||
<field name="posting_date"/>
|
||||
<field name="journal"/>
|
||||
<field name="journal_entry_number"/>
|
||||
<field name="document_number"/>
|
||||
<field name="voucher_number" optional="1"/>
|
||||
<field name="document_type" optional="1"/>
|
||||
<field name="document_date" optional="1"/>
|
||||
<field name="posting_status" optional="1"/>
|
||||
<field name="party" optional="1"/>
|
||||
<field name="description" expand="1" optional="1"/>
|
||||
<field name="move_description" expand="1" optional="1"/>
|
||||
<field name="reference" optional="1"/>
|
||||
<field name="origin" optional="1"/>
|
||||
<field name="debit_base_currency" sum="1"/>
|
||||
<field name="credit_base_currency" sum="1"/>
|
||||
<field name="balance_base_currency" sum="1"/>
|
||||
<field name="running_balance_base_currency" optional="1"/>
|
||||
<field name="debit_transaction_currency" sum="1" optional="1"/>
|
||||
<field name="credit_transaction_currency" sum="1" optional="1"/>
|
||||
<field name="balance_transaction_currency" sum="1" optional="1"/>
|
||||
<field name="running_balance_transaction_currency" optional="1"/>
|
||||
<field name="payable_qty" sum="1" optional="1"/>
|
||||
<field name="uom" optional="1"/>
|
||||
<field name="entered_by" optional="1"/>
|
||||
<field name="entered_date" optional="1"/>
|
||||
<field name="modified_by" optional="1"/>
|
||||
<field name="modified_date" optional="1"/>
|
||||
<field name="move" optional="1"/>
|
||||
<field name="move_line" optional="1"/>
|
||||
<field name="company" optional="1"/>
|
||||
</tree>
|
||||
@@ -26,6 +26,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="amount_second_currency" symbol=""/>
|
||||
<label name="second_currency"/>
|
||||
<field name="second_currency"/>
|
||||
<label name="rate"/>
|
||||
<field name="rate"/>
|
||||
</page>
|
||||
<page name="tax_lines">
|
||||
<field name="tax_lines" colspan="4"/>
|
||||
|
||||
15
modules/account_ch_os/__init__.py
Normal file
15
modules/account_ch_os/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import Pool
|
||||
|
||||
from . import account
|
||||
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
account.AccountTemplate,
|
||||
module='account_ch_os', type_='model')
|
||||
Pool.register(
|
||||
account.CreateChart,
|
||||
module='account_ch_os', type_='wizard')
|
||||
27
modules/account_ch_os/account.py
Normal file
27
modules/account_ch_os/account.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
|
||||
|
||||
class AccountTemplate(metaclass=PoolMeta):
|
||||
__name__ = 'account.account.template'
|
||||
|
||||
|
||||
class CreateChart(metaclass=PoolMeta):
|
||||
__name__ = 'account.create_chart'
|
||||
|
||||
def default_properties(self, fields):
|
||||
pool = Pool()
|
||||
ModelData = pool.get('ir.model.data')
|
||||
defaults = super().default_properties(fields)
|
||||
try:
|
||||
template_id = ModelData.get_id('account_ch_os', 'root')
|
||||
except KeyError:
|
||||
return defaults
|
||||
if self.account.account_template.id == template_id:
|
||||
defaults['account_receivable'] = self.get_account(
|
||||
'account_ch_os.acct_110000')
|
||||
defaults['account_payable'] = self.get_account(
|
||||
'account_ch_os.acct_200000')
|
||||
return defaults
|
||||
2395
modules/account_ch_os/account_ch_os.xml
Normal file
2395
modules/account_ch_os/account_ch_os.xml
Normal file
File diff suppressed because it is too large
Load Diff
2
modules/account_ch_os/tests/__init__.py
Normal file
2
modules/account_ch_os/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
12
modules/account_ch_os/tests/test_module.py
Normal file
12
modules/account_ch_os/tests/test_module.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.tests.test_tryton import ModuleTestCase
|
||||
|
||||
|
||||
class AccountCHOSTestCase(ModuleTestCase):
|
||||
'Test Account CH OS module'
|
||||
module = 'account_ch_os'
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
6
modules/account_ch_os/tryton.cfg
Normal file
6
modules/account_ch_os/tryton.cfg
Normal file
@@ -0,0 +1,6 @@
|
||||
[tryton]
|
||||
version=7.2.3
|
||||
depends:
|
||||
account
|
||||
xml:
|
||||
account_ch_os.xml
|
||||
3886
modules/account_invoice/commission__ict_final.fodt
Normal file
3886
modules/account_invoice/commission__ict_final.fodt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -485,7 +485,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
})
|
||||
cls.__rpc__.update({
|
||||
'post': RPC(
|
||||
readonly=False, instantiate=0, fresh_session=True),
|
||||
readonly=False, instantiate=0, fresh_session=False),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@@ -515,7 +515,9 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
~((table.state == 'cancelled') & (table.number == Null)),
|
||||
CharLength(table.number), table.number]
|
||||
|
||||
@fields.depends('selection_rate','rate_date')
|
||||
@fields.depends(
|
||||
'selection_rate', 'rate_date', 'currency', 'company', 'invoice_date',
|
||||
'lines')
|
||||
def on_change_with_rate(self, name=None):
|
||||
return self.get_selected_rate()
|
||||
|
||||
@@ -528,8 +530,8 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
def get_selected_rate(self,name=None):
|
||||
Currency = Pool().get('currency.currency')
|
||||
Date = Pool().get('ir.date')
|
||||
company = self.company
|
||||
currency = self.currency
|
||||
company = getattr(self, 'company', None)
|
||||
currency = getattr(self, 'currency', None)
|
||||
|
||||
if not currency or not company:
|
||||
return None
|
||||
@@ -563,6 +565,16 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
if rate:
|
||||
return round(1/rate,6) or 1
|
||||
|
||||
def _compute_company_currency_amount(self, amount, date=None):
|
||||
Currency = Pool().get('currency.currency')
|
||||
if self.currency == self.company.currency:
|
||||
return amount
|
||||
if self.rate:
|
||||
return self.company.currency.round(amount / self.rate)
|
||||
with Transaction().set_context(date=date or self.currency_date):
|
||||
return Currency.compute(
|
||||
self.currency, amount, self.company.currency)
|
||||
|
||||
@staticmethod
|
||||
def default_type():
|
||||
return Transaction().context.get('type', 'out')
|
||||
@@ -1218,6 +1230,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
line.amount_second_currency = (
|
||||
line.amount_second_currency.copy_sign(
|
||||
line.debit - line.credit))
|
||||
line.rate = line.get_rate()
|
||||
line.account = self.account
|
||||
logger.info("_GET_MOVE_LINE_ACCOUNT:%s",line.account)
|
||||
if self.account.party_required:
|
||||
@@ -1286,14 +1299,16 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
remainder = sum(l.debit - l.credit for l in move_lines)
|
||||
if self.payment_term:
|
||||
payment_date = self.payment_term_date or self.invoice_date or today
|
||||
model = str(self.lines[0].origin).split(",")[0] if self.lines[0].origin else None
|
||||
origin = self.lines[0].origin if self.lines and self.lines[0].origin else None
|
||||
model = str(origin).split(",")[0] if origin else None
|
||||
logger.info("MODEL:%s",model)
|
||||
line = None
|
||||
if model:
|
||||
Line = Pool().get(model)
|
||||
line = Line(int(str(self.lines[0].origin).split(",")[1]))
|
||||
line = Line(int(str(origin).split(",")[1]))
|
||||
logger.info("LINE:%s",line)
|
||||
term_lines = self.payment_term.compute(
|
||||
self.total_amount, self.currency, payment_date, line)
|
||||
term_lines = self.payment_term.compute(
|
||||
self.total_amount, self.currency, payment_date, line)
|
||||
else:
|
||||
term_lines = [(self.payment_term_date or today, self.total_amount)]
|
||||
past_payment_term_dates = []
|
||||
@@ -1890,17 +1905,17 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
cls._check_taxes(invoices)
|
||||
# cls._check_similar(invoices)
|
||||
|
||||
invoices_in = cls.browse([i for i in invoices if i.type == 'in'])
|
||||
cls.set_number(invoices_in)
|
||||
cls.set_number(invoices)
|
||||
cls._store_cache(invoices)
|
||||
|
||||
moves = []
|
||||
for invoice in invoices:
|
||||
if invoice.type == 'in':
|
||||
move = invoice.get_move()
|
||||
if move != invoice.move:
|
||||
invoice.move = move
|
||||
moves.append(move)
|
||||
if invoice.currency != invoice.company.currency and not invoice.rate:
|
||||
invoice.rate = invoice.get_selected_rate()
|
||||
move = invoice.get_move()
|
||||
if move != invoice.move:
|
||||
invoice.move = move
|
||||
moves.append(move)
|
||||
invoice.do_lot_invoicing()
|
||||
if moves:
|
||||
Move.save(moves)
|
||||
@@ -1963,23 +1978,35 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
move_line_.amount_second_currency = -original_amount
|
||||
|
||||
if amount < 0:
|
||||
lot_has_sale_invoice = (
|
||||
move_line.lot
|
||||
and (move_line.lot.sale_invoice_line
|
||||
or move_line.lot.sale_invoice_line_prov))
|
||||
move_line.debit = Decimal(0)
|
||||
move_line.credit = -amount
|
||||
move_line.account = gl.product.account_stock_used if not (move_line.lot.sale_invoice_line or move_line.lot.sale_invoice_line_prov) else gl.product.account_stock_out_used
|
||||
move_line.account = gl.product.account_stock_used if not lot_has_sale_invoice else gl.product.account_stock_out_used
|
||||
move_line.account = gl.product.account_cogs_used if gl.fee else move_line.account
|
||||
move_line_.credit = Decimal(0)
|
||||
move_line_.debit = -amount
|
||||
move_line_.account = gl.product.account_stock_in_used
|
||||
else:
|
||||
lot_has_sale_invoice = (
|
||||
move_line.lot
|
||||
and (move_line.lot.sale_invoice_line
|
||||
or move_line.lot.sale_invoice_line_prov))
|
||||
move_line.debit = amount
|
||||
move_line.credit = Decimal(0)
|
||||
move_line.account = gl.product.account_stock_used if not (move_line.lot.sale_invoice_line or move_line.lot.sale_invoice_line_prov) else gl.product.account_stock_out_used
|
||||
move_line.account = gl.product.account_stock_used if not lot_has_sale_invoice else gl.product.account_stock_out_used
|
||||
move_line.account = gl.product.account_cogs_used if gl.fee else move_line.account
|
||||
move_line_.debit = Decimal(0)
|
||||
move_line_.credit = amount
|
||||
move_line_.account = gl.product.account_stock_in_used
|
||||
|
||||
move_lines = [move_line,move_line_]
|
||||
if getattr(move_line, 'amount_second_currency', None):
|
||||
move_line.rate = move_line.get_rate()
|
||||
if getattr(move_line_, 'amount_second_currency', None):
|
||||
move_line_.rate = move_line_.get_rate()
|
||||
|
||||
if drop:
|
||||
drop_line = AccountMoveLine()
|
||||
@@ -2017,6 +2044,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
drop_line_.second_currency = self.currency
|
||||
drop_line_.amount_second_currency = -original_amount
|
||||
move_lines = [drop_line,drop_line_]
|
||||
if getattr(drop_line, 'amount_second_currency', None):
|
||||
drop_line.rate = drop_line.get_rate()
|
||||
if getattr(drop_line_, 'amount_second_currency', None):
|
||||
drop_line_.rate = drop_line_.get_rate()
|
||||
|
||||
return move_lines
|
||||
|
||||
@@ -2038,10 +2069,16 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
var_qt = sum([i.quantity for i in gl])
|
||||
logger.info("LOT_TO_PROCESS:%s",lot)
|
||||
logger.info("FEE_TO_PROCESS:%s",gl[0].fee)
|
||||
if (gl[0].fee and not gl[0].product.landed_cost):
|
||||
fee_correction = (
|
||||
gl[0].fee
|
||||
and any(Decimal(str(i.quantity or 0)) < 0 for i in gl)
|
||||
and any(Decimal(str(i.quantity or 0)) > 0 for i in gl))
|
||||
if (gl[0].fee and not gl[0].product.landed_cost
|
||||
and not fee_correction):
|
||||
diff = gl[0].fee.amount - gl[0].fee.get_non_cog(lot)
|
||||
account_move = gl[0].fee._get_account_move_fee(lot,'in',diff)
|
||||
Move.save([account_move])
|
||||
if account_move:
|
||||
Move.save([account_move])
|
||||
if (lot and not gl[0].fee) or (gl[0].fee and gl[0].product.landed_cost):
|
||||
adjust_move_lines = []
|
||||
mov = None
|
||||
@@ -3174,7 +3211,6 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
|
||||
|
||||
def _compute_taxes(self):
|
||||
pool = Pool()
|
||||
Currency = pool.get('currency.currency')
|
||||
TaxLine = pool.get('account.tax.line')
|
||||
|
||||
tax_lines = []
|
||||
@@ -3183,11 +3219,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
|
||||
taxes = self._get_taxes().values()
|
||||
for tax in taxes:
|
||||
amount = tax['base']
|
||||
with Transaction().set_context(
|
||||
date=self.invoice.currency_date):
|
||||
amount = Currency.compute(
|
||||
self.invoice.currency, amount,
|
||||
self.invoice.company.currency)
|
||||
amount = self.invoice._compute_company_currency_amount(amount)
|
||||
tax_line = TaxLine()
|
||||
tax_line.amount = amount
|
||||
tax_line.type = 'base'
|
||||
@@ -3252,6 +3284,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
|
||||
line.amount_second_currency = (
|
||||
line.amount_second_currency.copy_sign(
|
||||
line.debit - line.credit))
|
||||
line.rate = line.get_rate()
|
||||
line.account = self.account
|
||||
logger.info("GET_MOVE_LINES_ACCOUNT:%s",line.account)
|
||||
if self.account.party_required:
|
||||
@@ -3511,9 +3544,7 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
|
||||
'''
|
||||
Return a list of move lines instances for invoice tax
|
||||
'''
|
||||
Currency = Pool().get('currency.currency')
|
||||
pool = Pool()
|
||||
Currency = pool.get('currency.currency')
|
||||
MoveLine = pool.get('account.move.line')
|
||||
TaxLine = pool.get('account.tax.line')
|
||||
line = MoveLine()
|
||||
@@ -3521,11 +3552,8 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
|
||||
return []
|
||||
line.description = self.description
|
||||
if self.invoice.currency != self.invoice.company.currency:
|
||||
with Transaction().set_context(date=self.invoice.currency_date):
|
||||
amount = Currency.compute(self.invoice.currency, self.amount,
|
||||
self.invoice.company.currency)
|
||||
base = Currency.compute(self.invoice.currency, self.base,
|
||||
self.invoice.company.currency)
|
||||
amount = self.invoice._compute_company_currency_amount(self.amount)
|
||||
base = self.invoice._compute_company_currency_amount(self.base)
|
||||
line.amount_second_currency = self.amount
|
||||
line.second_currency = self.invoice.currency
|
||||
else:
|
||||
@@ -3547,6 +3575,7 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
|
||||
line.amount_second_currency = (
|
||||
line.amount_second_currency.copy_sign(
|
||||
line.debit - line.credit))
|
||||
line.rate = line.get_rate()
|
||||
line.account = self.account
|
||||
if self.account.party_required:
|
||||
line.party = self.invoice.party
|
||||
|
||||
@@ -288,7 +288,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_invoice">
|
||||
<field name="name">Provisional Invoice</field>
|
||||
<field name="name">Invoice</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="report_name">account.invoice</field>
|
||||
<field name="report">account_invoice/invoice.fodt</field>
|
||||
@@ -314,7 +314,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_invoice_ict_final">
|
||||
<field name="name">Final Invoice</field>
|
||||
<field name="name">CN/DN</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="report_name">account.invoice</field>
|
||||
<field name="report">account_invoice/invoice_ict_final.fodt</field>
|
||||
@@ -326,6 +326,45 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="action" ref="report_invoice_ict_final"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_invoice_ict_commission_final">
|
||||
<field name="name">CN/DN Commission</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="report_name">account.invoice</field>
|
||||
<field name="report">account_invoice/commission__ict_final.fodt</field>
|
||||
<field name="single" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="report_invoice_ict_commission_final_keyword">
|
||||
<field name="keyword">form_print</field>
|
||||
<field name="model">account.invoice,-1</field>
|
||||
<field name="action" ref="report_invoice_ict_commission_final"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_sale_commission_ict">
|
||||
<field name="name">Commission invoice Sale</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="report_name">account.invoice</field>
|
||||
<field name="report">account_invoice/sale_commission_ict.fodt</field>
|
||||
<field name="single" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="report_sale_commission_ict_keyword">
|
||||
<field name="keyword">form_print</field>
|
||||
<field name="model">account.invoice,-1</field>
|
||||
<field name="action" ref="report_sale_commission_ict"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_purchase_commission_ict">
|
||||
<field name="name">Commission invoice Purchase</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="report_name">account.invoice</field>
|
||||
<field name="report">account_invoice/purchase_commission_ict.fodt</field>
|
||||
<field name="single" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="report_purchase_commission_ict_keyword">
|
||||
<field name="keyword">form_print</field>
|
||||
<field name="model">account.invoice,-1</field>
|
||||
<field name="action" ref="report_purchase_commission_ict"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.sequence.type" id="sequence_type_account_invoice">
|
||||
<field name="name">Invoice</field>
|
||||
</record>
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:officeooo="http://openoffice.org/2009/office" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
|
||||
<office:meta>
|
||||
<dc:title>Provisional Invoice</dc:title>
|
||||
<dc:title>Invoice</dc:title>
|
||||
<meta:initial-creator>willen</meta:initial-creator>
|
||||
|
||||
|
||||
|
||||
<meta:generator>LibreOffice/7.6.0.3$Windows_X86_64 LibreOffice_project/69edd8b8ebc41d00b4de3915dc82f8f0fc3b6265</meta:generator>
|
||||
<meta:editing-duration>PT3H16M29S</meta:editing-duration>
|
||||
<meta:editing-cycles>9</meta:editing-cycles>
|
||||
<meta:editing-duration>PT3H20M59S</meta:editing-duration>
|
||||
<meta:editing-cycles>12</meta:editing-cycles>
|
||||
|
||||
<dc:date>2026-03-27T07:35:45.157000000</dc:date><meta:document-statistic meta:table-count="10" meta:image-count="2" meta:object-count="0" meta:page-count="2" meta:paragraph-count="61" meta:word-count="210" meta:character-count="2997" meta:non-whitespace-character-count="2429"/></office:meta>
|
||||
<dc:date>2026-04-10T10:30:16.567000000</dc:date><meta:document-statistic meta:table-count="10" meta:image-count="2" meta:object-count="0" meta:page-count="2" meta:paragraph-count="66" meta:word-count="168" meta:character-count="2416" meta:non-whitespace-character-count="1896"/></office:meta>
|
||||
<office:settings>
|
||||
<config:config-item-set config:name="ooo:view-settings">
|
||||
<config:config-item config:name="ViewAreaTop" config:type="long">28575</config:config-item>
|
||||
<config:config-item config:name="ViewAreaTop" config:type="long">6879</config:config-item>
|
||||
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="ViewAreaWidth" config:type="long">27264</config:config-item>
|
||||
<config:config-item config:name="ViewAreaHeight" config:type="long">13187</config:config-item>
|
||||
@@ -23,12 +23,12 @@
|
||||
<config:config-item-map-indexed config:name="Views">
|
||||
<config:config-item-map-entry>
|
||||
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
|
||||
<config:config-item config:name="ViewLeft" config:type="long">7583</config:config-item>
|
||||
<config:config-item config:name="ViewTop" config:type="long">33203</config:config-item>
|
||||
<config:config-item config:name="ViewLeft" config:type="long">13275</config:config-item>
|
||||
<config:config-item config:name="ViewTop" config:type="long">15563</config:config-item>
|
||||
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="VisibleTop" config:type="long">28575</config:config-item>
|
||||
<config:config-item config:name="VisibleTop" config:type="long">6879</config:config-item>
|
||||
<config:config-item config:name="VisibleRight" config:type="long">27263</config:config-item>
|
||||
<config:config-item config:name="VisibleBottom" config:type="long">41760</config:config-item>
|
||||
<config:config-item config:name="VisibleBottom" config:type="long">20064</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
|
||||
@@ -101,7 +101,7 @@
|
||||
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="Rsid" config:type="int">297701</config:config-item>
|
||||
<config:config-item config:name="Rsid" config:type="int">523469</config:config-item>
|
||||
<config:config-item config:name="RsidRoot" config:type="int">84510</config:config-item>
|
||||
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
|
||||
@@ -168,19 +168,20 @@
|
||||
<style:font-face style:name="Lucida Sans1" svg:font-family="'Lucida Sans'" style:font-family-generic="system" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Microsoft YaHei" svg:font-family="'Microsoft YaHei'" style:font-family-generic="system" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Tahoma" svg:font-family="Tahoma, arial" style:font-family-generic="swiss" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Times New Roman" svg:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Times New Roman" svg:font-family="'Times New Roman'" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
<style:font-face style:name="Times New Roman1" svg:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
</office:font-face-decls>
|
||||
<office:styles>
|
||||
<style:default-style style:family="graphic">
|
||||
<style:graphic-properties svg:stroke-color="#808080" draw:fill-color="#cfe7f5" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="true"/>
|
||||
<style:paragraph-properties style:text-autospace="none" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
|
||||
<style:paragraph-properties style:text-autospace="none" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
|
||||
<style:tab-stops/>
|
||||
</style:paragraph-properties>
|
||||
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH"/>
|
||||
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman1" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman1" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman1" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH"/>
|
||||
</style:default-style>
|
||||
<style:default-style style:family="paragraph">
|
||||
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="none" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.249cm" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
|
||||
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman1" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman1" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman1" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
|
||||
</style:default-style>
|
||||
<style:default-style style:family="table">
|
||||
<style:table-properties table:border-model="collapsing"/>
|
||||
@@ -245,7 +246,7 @@
|
||||
</style:style>
|
||||
<style:style style:name="No_20_Spacing" style:display-name="No Spacing" style:family="paragraph">
|
||||
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-align="start" style:justify-single-word="false" fo:text-indent="0cm" style:auto-text-indent="false" style:text-autospace="ideograph-alpha" style:vertical-align="auto"/>
|
||||
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, 'Century Gothic'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="11pt" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="11pt" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, 'Century Gothic'" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="11pt" style:language-complex="ar" style:country-complex="SA"/>
|
||||
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, 'Century Gothic'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="11pt" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman1" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="11pt" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, 'Century Gothic'" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="11pt" style:language-complex="ar" style:country-complex="SA"/>
|
||||
</style:style>
|
||||
<style:style style:name="Header_20_and_20_Footer" style:display-name="Header and Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
|
||||
<style:paragraph-properties text:number-lines="false" text:line-number="0">
|
||||
@@ -295,30 +296,35 @@
|
||||
</style:paragraph-properties>
|
||||
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Normal" style:family="paragraph">
|
||||
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit"/>
|
||||
<style:text-properties fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
|
||||
</style:style>
|
||||
<style:style style:name="Default_20_Paragraph_20_Font" style:display-name="Default Paragraph Font" style:family="text"/>
|
||||
<style:style style:name="Titolo_20_1_20_Carattere" style:display-name="Titolo 1 Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
|
||||
<style:text-properties style:font-name="Arial1" fo:font-family="Arial, Arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="12pt" fo:language="en" fo:country="US" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" fo:font-weight="bold" style:font-name-asian="Arial1" style:font-family-asian="Arial, Arial" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:font-size-asian="12pt" style:language-asian="en" style:country-asian="US" style:font-weight-asian="bold" style:font-name-complex="Arial1" style:font-family-complex="Arial, Arial" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="12pt" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="Intestazione_20_Carattere" style:display-name="Intestazione Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
|
||||
<style:text-properties style:font-name="Times New Roman" fo:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Times New Roman" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-family-complex="'Times New Roman', Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable" style:font-size-complex="10pt"/>
|
||||
<style:text-properties style:font-name="Times New Roman1" fo:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Times New Roman1" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-family-complex="'Times New Roman', Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="Placeholder_20_Text" style:display-name="Placeholder Text" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
|
||||
<style:text-properties fo:color="#808080" loext:opacity="100%" style:font-name="Times New Roman" fo:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman" style:font-family-complex="'Times New Roman', Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
|
||||
<style:text-properties fo:color="#808080" loext:opacity="100%" style:font-name="Times New Roman1" fo:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman1" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman1" style:font-family-complex="'Times New Roman', Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
|
||||
</style:style>
|
||||
<style:style style:name="Testo_20_fumetto_20_Carattere" style:display-name="Testo fumetto Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
|
||||
<style:text-properties style:font-name="Tahoma" fo:font-family="Tahoma, arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="8pt" style:font-name-asian="Tahoma" style:font-family-asian="Tahoma, arial" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:font-size-asian="8pt" style:font-name-complex="Tahoma" style:font-family-complex="Tahoma, arial" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="8pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="PiÃ_a8__20_di_20_pagina_20_Carattere" style:display-name="Piè di pagina Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
|
||||
<style:text-properties style:font-name="Times New Roman" fo:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman" style:font-family-complex="'Times New Roman', Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
|
||||
<style:text-properties style:font-name="Times New Roman1" fo:font-family="'Times New Roman', Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman1" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman1" style:font-family-complex="'Times New Roman', Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
|
||||
</style:style>
|
||||
<style:style style:name="Nessuna_20_spaziatura_20_Carattere" style:display-name="Nessuna spaziatura Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
|
||||
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, 'Century Gothic'" style:font-family-generic="swiss" style:font-pitch="variable" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, 'Century Gothic'" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable"/>
|
||||
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, 'Century Gothic'" style:font-family-generic="swiss" style:font-pitch="variable" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman1" style:font-family-asian="'Times New Roman', Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, 'Century Gothic'" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable"/>
|
||||
</style:style>
|
||||
<style:style style:name="Placeholder" style:family="text">
|
||||
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" loext:opacity="100%" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
|
||||
</style:style>
|
||||
<style:style style:name="Police_20_par_20_dÃ_a9_faut" style:display-name="Police par défaut" style:family="text"/>
|
||||
<style:style style:name="Default_20_Paragraph_20_Font_20__28_WW_29_" style:display-name="Default Paragraph Font (WW)" style:family="text"/>
|
||||
<style:style style:name="Police_20_par_20_défaut" style:display-name="Police par défaut" style:family="text"/>
|
||||
<style:style style:name="Frame" style:family="graphic">
|
||||
<style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph-content" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" fo:background-color="transparent" draw:fill="none" draw:fill-color="#cfe7f5"/>
|
||||
</style:style>
|
||||
@@ -664,7 +670,7 @@
|
||||
</style:style>
|
||||
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
|
||||
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
@@ -684,7 +690,7 @@
|
||||
</style:style>
|
||||
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="US" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
|
||||
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="US" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
@@ -696,7 +702,7 @@
|
||||
</style:style>
|
||||
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
|
||||
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" fo:text-align="center" style:justify-single-word="false" style:writing-mode="lr-tb"/>
|
||||
@@ -708,7 +714,7 @@
|
||||
</style:style>
|
||||
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
|
||||
<style:text-properties fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" fo:text-align="end" style:justify-single-word="false" style:writing-mode="lr-tb"/>
|
||||
@@ -724,7 +730,7 @@
|
||||
</style:style>
|
||||
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties fo:font-size="1pt" fo:language="en" fo:country="US" style:font-size-asian="1pt" style:font-name-complex="Times New Roman" style:font-size-complex="1pt" text:display="none"/>
|
||||
<style:text-properties fo:font-size="1pt" fo:language="en" fo:country="US" style:font-size-asian="1pt" style:font-name-complex="Times New Roman1" style:font-size-complex="1pt" text:display="none"/>
|
||||
</style:style>
|
||||
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
@@ -759,14 +765,34 @@
|
||||
</style:paragraph-properties>
|
||||
<style:text-properties officeooo:paragraph-rsid="00022035"/>
|
||||
</style:style>
|
||||
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" officeooo:paragraph-rsid="000391f3" style:font-size-asian="10pt" style:font-name-complex="Arial1" style:font-size-complex="10pt"/>
|
||||
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Normal">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="10pt" style:font-name-complex="Arial" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="footer">
|
||||
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="Normal">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Calibri" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="10pt" style:font-name-complex="Arial" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="Normal">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="00056da2"/>
|
||||
</style:style>
|
||||
<style:style style:name="P47" style:family="paragraph" style:parent-style-name="Normal">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
|
||||
<style:text-properties style:font-name="Calibri" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P48" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties style:font-name="Calibri" fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Arial1" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P49" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
|
||||
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P50" style:family="paragraph" style:parent-style-name="footer">
|
||||
<style:paragraph-properties style:writing-mode="lr-tb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="header">
|
||||
<style:style style:name="P51" style:family="paragraph" style:parent-style-name="header">
|
||||
<style:paragraph-properties style:writing-mode="lr-tb"/>
|
||||
</style:style>
|
||||
<style:style style:name="T1" style:family="text">
|
||||
@@ -790,6 +816,24 @@
|
||||
<style:style style:name="T7" style:family="text">
|
||||
<style:text-properties fo:language="fr" fo:country="CH"/>
|
||||
</style:style>
|
||||
<style:style style:name="T8" style:family="text">
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="9pt" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="T9" style:family="text">
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="9pt" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="T10" style:family="text">
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" fo:font-size="9pt" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="T11" style:family="text">
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" fo:font-size="9pt" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="T12" style:family="text">
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-name-complex="Arial"/>
|
||||
</style:style>
|
||||
<style:style style:name="T13" style:family="text">
|
||||
<style:text-properties fo:color="#000000" loext:opacity="100%" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-name-complex="Arial"/>
|
||||
</style:style>
|
||||
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
|
||||
<style:graphic-properties style:run-through="background" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="top" style:vertical-rel="baseline" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
|
||||
</style:style>
|
||||
@@ -3866,7 +3910,7 @@
|
||||
<table:table-row table:style-name="Tableau3.1">
|
||||
<table:table-cell table:style-name="Tableau3.A1" office:value-type="string">
|
||||
<text:p text:style-name="P22"/>
|
||||
<text:p text:style-name="P22">Provisional Invoice</text:p>
|
||||
<text:p text:style-name="P22">Invoice</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau3.A1" office:value-type="string">
|
||||
<text:p text:style-name="P22"/>
|
||||
@@ -3930,15 +3974,19 @@
|
||||
<text:p text:style-name="P25">Goods description</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau5.A1" office:value-type="string">
|
||||
<text:p text:style-name="P26">QUANTITY: <text:placeholder text:placeholder-type="text"><format_number(invoice.report_lbs, invoice.party.lang) if invoice.report_lbs != '' else ''></text:placeholder><text:s/>LBS (<text:placeholder text:placeholder-type="text"><format_number(invoice.report_net, invoice.party.lang) if invoice.report_net != '' else ''></text:placeholder> MTS)</text:p>
|
||||
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text"><invoice.report_description_upper or invoice.report_product_description></text:placeholder><text:s/>CROP <text:placeholder text:placeholder-type="text"><invoice.report_crop_name></text:placeholder></text:p>
|
||||
<text:p text:style-name="P26">QUANTITY: <text:placeholder text:placeholder-type="text"><invoice.report_lbs_display></text:placeholder><text:s/>LBS (<text:placeholder text:placeholder-type="text"><invoice.report_net_display></text:placeholder> <text:placeholder text:placeholder-type="text"><invoice.report_weight_unit_upper></text:placeholder>)</text:p>
|
||||
<text:p text:style-name="P26"/>
|
||||
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text"><invoice.report_description_upper or invoice.report_product_description></text:placeholder><text:placeholder text:placeholder-type="text"><' CROP ' + invoice.report_crop_name if invoice.report_crop_name else ''></text:placeholder></text:p>
|
||||
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text"><invoice.report_attributes_name></text:placeholder></text:p>
|
||||
<text:p text:style-name="P18">At <text:placeholder text:placeholder-type="text"><invoice.report_rate_currency_upper></text:placeholder><text:s/><text:placeholder text:placeholder-type="text"><invoice.report_rate_value></text:placeholder><text:s/>PER <text:placeholder text:placeholder-type="text"><invoice.report_rate_unit_upper></text:placeholder><text:s/>(<text:placeholder text:placeholder-type="text"><invoice.report_rate_price_words></text:placeholder>) <text:placeholder text:placeholder-type="text"><invoice.report_rate_pricing_text></text:placeholder></text:p>
|
||||
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text"><for each="block in invoice.report_trade_blocks"></text:placeholder></text:p>
|
||||
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text"><block[0]></text:placeholder></text:p>
|
||||
<text:p text:style-name="P18">At <text:placeholder text:placeholder-type="text"><block[1]></text:placeholder></text:p>
|
||||
<text:p text:style-name="P18"/>
|
||||
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text"></for></text:placeholder></text:p>
|
||||
<text:p text:style-name="P18"/>
|
||||
<text:p text:style-name="P18"/>
|
||||
<text:p text:style-name="P32"><text:placeholder text:placeholder-type="text"><invoice.report_incoterm></text:placeholder></text:p>
|
||||
<text:p text:style-name="P29"><text:span text:style-name="T1">ALL DETAILS AND SPECIFICATIONS AS PER</text:span> <text:span text:style-name="T3">BENEFICIARY </text:span></text:p>
|
||||
<text:p text:style-name="P26">PROFORMA INVOICE NO. <text:placeholder text:placeholder-type="text"><invoice.report_proforma_invoice_number></text:placeholder><text:s/>DATED <text:placeholder text:placeholder-type="text"><format_date(invoice.report_proforma_invoice_date, invoice.party.lang) if invoice.report_proforma_invoice_date else ''></text:placeholder>.</text:p>
|
||||
<text:p text:style-name="P29"><text:span text:style-name="T7"><text:placeholder text:placeholder-type="text"><invoice.report_incoterm></text:placeholder></text:span><text:span text:style-name="T3"><text:s/></text:span></text:p>
|
||||
<text:p text:style-name="P26"/>
|
||||
<text:p text:style-name="P26"/>
|
||||
<text:p text:style-name="P12"/>
|
||||
</table:table-cell>
|
||||
@@ -3952,10 +4000,10 @@
|
||||
<text:p text:style-name="P15"><text:s text:c="19"/>BALES</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
|
||||
<text:p text:style-name="P41"><text:s text:c="13"/>Gross KGS</text:p>
|
||||
<text:p text:style-name="P41"><text:s text:c="13"/>Gross <text:placeholder text:placeholder-type="text"><invoice.report_weight_unit_upper></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
|
||||
<text:p text:style-name="P15"><text:s text:c="13"/>NET KGS</text:p>
|
||||
<text:p text:style-name="P15"><text:s text:c="13"/>NET <text:placeholder text:placeholder-type="text"><invoice.report_weight_unit_upper></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
|
||||
<text:p text:style-name="P41"><text:s text:c="10"/></text:p>
|
||||
@@ -3969,10 +4017,10 @@
|
||||
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text"><invoice.report_nb_bale></text:placeholder><text:s/></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
|
||||
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text"><format_number(invoice.report_gross, invoice.party.lang) if invoice.report_gross != '' else ''></text:placeholder><text:s/></text:p>
|
||||
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text"><invoice.report_gross_display></text:placeholder><text:s/></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
|
||||
<text:p text:style-name="P30"><text:placeholder text:placeholder-type="text"><format_number(invoice.report_net, invoice.party.lang) if invoice.report_net != '' else ''></text:placeholder><text:s/></text:p>
|
||||
<text:p text:style-name="P30"><text:placeholder text:placeholder-type="text"><invoice.report_net_display></text:placeholder><text:s/></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
|
||||
<text:p text:style-name="P16"/>
|
||||
@@ -3996,7 +4044,7 @@
|
||||
<text:p text:style-name="P16">Equivalent to LBS</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau7.A1" office:value-type="string">
|
||||
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text"><format_number(invoice.report_lbs, invoice.party.lang) if invoice.report_lbs != '' else ''></text:placeholder><text:s/></text:p>
|
||||
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text"><invoice.report_lbs_display></text:placeholder><text:s/></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau7.A1" office:value-type="string">
|
||||
<text:p text:style-name="P20"/>
|
||||
@@ -4009,13 +4057,18 @@
|
||||
<table:table-column table:style-name="Tableau8.B"/>
|
||||
<table:table-row table:style-name="Tableau8.1">
|
||||
<table:table-cell table:style-name="Tableau8.A1" office:value-type="string">
|
||||
<text:p text:style-name="P14">At <text:placeholder text:placeholder-type="text"><invoice.report_rate_currency_upper></text:placeholder><text:s/><text:placeholder text:placeholder-type="text"><invoice.report_rate_value></text:placeholder><text:s/>PER <text:placeholder text:placeholder-type="text"><invoice.report_rate_unit_upper></text:placeholder><text:s/>(<text:placeholder text:placeholder-type="text"><invoice.report_rate_price_words></text:placeholder>) <text:placeholder text:placeholder-type="text"><invoice.report_rate_pricing_text></text:placeholder></text:p>
|
||||
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text"><for each="line in invoice.report_rate_lines.splitlines()"></text:placeholder></text:p>
|
||||
<text:p text:style-name="P14">At <text:placeholder text:placeholder-type="text"><line></text:placeholder></text:p>
|
||||
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text"></for></text:placeholder></text:p>
|
||||
<text:p text:style-name="P14"/>
|
||||
<text:p text:style-name="P14">FREIGHT VALUE: <text:placeholder text:placeholder-type="text"><invoice.report_freight_currency_symbol></text:placeholder><text:s/><text:placeholder text:placeholder-type="text"><format_number(invoice.report_freight_amount, invoice.party.lang) if invoice.report_freight_amount != '' else ''></text:placeholder></text:p>
|
||||
<text:p text:style-name="P14"/>
|
||||
<text:p text:style-name="P17">WE CERTIFY THAT THE MERCHANDISE IS OF <text:span text:style-name="T7"><text:placeholder text:placeholder-type="text"><invoice.report_origin or ''></text:placeholder></text:span><text:s/>ORIGIN</text:p>
|
||||
<text:p text:style-name="P31"><text:soft-page-break/>L/C NUMBER </text:p>
|
||||
<text:p text:style-name="P31"/>
|
||||
<text:p text:style-name="P17"/>
|
||||
<text:p text:style-name="P47"><text:span text:style-name="T12">B</text:span><text:span text:style-name="T13">ANK</text:span><text:span text:style-name="T12">: EFG BANK SA</text:span></text:p>
|
||||
<text:p text:style-name="P45">IBAN : CH8808667007168111027</text:p>
|
||||
<text:p text:style-name="P45">SwifT Code: EFGBCHZZ</text:p>
|
||||
<text:p text:style-name="P48"/>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau8.A1" office:value-type="string">
|
||||
<text:p text:style-name="P34"><text:s text:c="3"/><text:placeholder text:placeholder-type="text"><format_currency(invoice.total_amount, invoice.party.lang, invoice.currency)></text:placeholder><text:s/></text:p>
|
||||
@@ -4027,6 +4080,7 @@
|
||||
<text:p text:style-name="P11"/>
|
||||
<table:table table:name="Tableau9" table:style-name="Tableau9">
|
||||
<table:table-column table:style-name="Tableau9.A"/>
|
||||
<text:soft-page-break/>
|
||||
<table:table-row table:style-name="Tableau9.1">
|
||||
<table:table-cell table:style-name="Tableau9.A1" office:value-type="string">
|
||||
<text:p text:style-name="P21">NET LANDED WEIGHTS, ACTUAL TARE, NO FRANCHISE</text:p>
|
||||
@@ -4052,7 +4106,7 @@
|
||||
<text:p text:style-name="P13">Controller Name</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau10.A1" office:value-type="string">
|
||||
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_si_number></text:placeholder></text:p>
|
||||
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_si_reference></text:placeholder></text:p>
|
||||
<text:p text:style-name="P25"/>
|
||||
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_controller_name></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
@@ -4100,4 +4154,4 @@
|
||||
<text:p text:style-name="P38"/>
|
||||
</office:text>
|
||||
</office:body>
|
||||
</office:document>
|
||||
</office:document>
|
||||
File diff suppressed because it is too large
Load Diff
4180
modules/account_invoice/invoice_ict_final.fodt.bak
Normal file
4180
modules/account_invoice/invoice_ict_final.fodt.bak
Normal file
File diff suppressed because it is too large
Load Diff
1835
modules/account_invoice/invoice_melya.fodt
Normal file
1835
modules/account_invoice/invoice_melya.fodt
Normal file
File diff suppressed because it is too large
Load Diff
1498
modules/account_invoice/packing_list.fodt
Normal file
1498
modules/account_invoice/packing_list.fodt
Normal file
File diff suppressed because it is too large
Load Diff
1872
modules/account_invoice/payment_order_melya.fodt
Normal file
1872
modules/account_invoice/payment_order_melya.fodt
Normal file
File diff suppressed because it is too large
Load Diff
3892
modules/account_invoice/purchase_commission_ict.fodt
Normal file
3892
modules/account_invoice/purchase_commission_ict.fodt
Normal file
File diff suppressed because it is too large
Load Diff
3730
modules/account_invoice/sale_commission_ict.fodt
Normal file
3730
modules/account_invoice/sale_commission_ict.fodt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from trytond.modules.account_invoice.exceptions import (
|
||||
PaymentTermValidationError)
|
||||
@@ -251,5 +252,70 @@ class AccountInvoiceTestCase(
|
||||
(datetime.date(2012, 1, 14), Decimal('-1.0')),
|
||||
])
|
||||
|
||||
def test_post_rpc_does_not_require_fresh_session(self):
|
||||
'posting invoices does not force a fresh session'
|
||||
Invoice = Pool().get('account.invoice')
|
||||
|
||||
self.assertFalse(Invoice.__rpc__['post'].fresh_session)
|
||||
|
||||
@with_transaction()
|
||||
def test_validate_invoice_creates_move_for_customer_invoice(self):
|
||||
'validating customer invoices now creates the account move'
|
||||
Invoice = Pool().get('account.invoice')
|
||||
|
||||
move = Mock()
|
||||
invoice = Invoice()
|
||||
invoice.type = 'out'
|
||||
invoice.move = None
|
||||
invoice.get_move = Mock(return_value=move)
|
||||
invoice.do_lot_invoicing = Mock()
|
||||
|
||||
move_model = Mock()
|
||||
|
||||
with patch.object(Invoice, '_check_taxes'), patch.object(
|
||||
Invoice, '_store_cache'), patch.object(
|
||||
Invoice, 'browse', return_value=[]), patch.object(
|
||||
Invoice, 'cleanMoves') as clean_moves, patch.object(
|
||||
Invoice, 'save') as save_invoices, patch(
|
||||
'trytond.modules.account_invoice.invoice.Pool'
|
||||
) as PoolMock:
|
||||
PoolMock.return_value.get.return_value = move_model
|
||||
|
||||
Invoice.validate_invoice([invoice])
|
||||
|
||||
self.assertIs(invoice.move, move)
|
||||
invoice.get_move.assert_called_once_with()
|
||||
invoice.do_lot_invoicing.assert_called_once_with()
|
||||
move_model.save.assert_called_once_with([move])
|
||||
clean_moves.assert_called_once_with([move])
|
||||
save_invoices.assert_called()
|
||||
|
||||
@with_transaction()
|
||||
def test_validate_invoice_sets_number_for_customer_invoice(self):
|
||||
'validating customer invoices now assigns the invoice number'
|
||||
Invoice = Pool().get('account.invoice')
|
||||
|
||||
move = Mock()
|
||||
invoice = Invoice()
|
||||
invoice.type = 'out'
|
||||
invoice.move = None
|
||||
invoice.get_move = Mock(return_value=move)
|
||||
invoice.do_lot_invoicing = Mock()
|
||||
|
||||
move_model = Mock()
|
||||
|
||||
with patch.object(Invoice, '_check_taxes'), patch.object(
|
||||
Invoice, '_store_cache'), patch.object(
|
||||
Invoice, 'set_number') as set_number, patch.object(
|
||||
Invoice, 'cleanMoves'), patch.object(
|
||||
Invoice, 'save'), patch(
|
||||
'trytond.modules.account_invoice.invoice.Pool'
|
||||
) as PoolMock:
|
||||
PoolMock.return_value.get.return_value = move_model
|
||||
|
||||
Invoice.validate_invoice([invoice])
|
||||
|
||||
set_number.assert_called_once_with([invoice])
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
|
||||
@@ -181,7 +181,11 @@ class InvoiceLine(metaclass=PoolMeta):
|
||||
company=self.invoice.company.id, date=accounting_date):
|
||||
anglo_saxon_move_lines = self._get_anglo_saxon_move_lines(
|
||||
cost, type_)
|
||||
if type_ == 'in_supplier' and (self.lot.sale_invoice_line_prov or self.lot.sale_invoice_line) and not self.fee:
|
||||
lot_has_sale_invoice = (
|
||||
self.lot
|
||||
and (self.lot.sale_invoice_line_prov
|
||||
or self.lot.sale_invoice_line))
|
||||
if type_ == 'in_supplier' and lot_has_sale_invoice and not self.fee:
|
||||
anglo_saxon_move_lines_ = self._get_anglo_saxon_move_lines(cost, 'out_customer')
|
||||
result.extend(anglo_saxon_move_lines)
|
||||
result.extend(anglo_saxon_move_lines_)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/form/notebook" position="inside">
|
||||
<page string="Analytic" id="analytic_accounts">
|
||||
<xpath expr="/form/notebook/page[@id='invoice']" position="before">
|
||||
<page string="Analytics" id="analytic_accounts">
|
||||
<field name="analytic_accounts" colspan="4"/>
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
from trytond.pool import Pool
|
||||
from . import automation,rules,freight_booking,cron #, document
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
automation.AutomationDocument,
|
||||
rules.AutomationRuleSet,
|
||||
freight_booking.FreightBookingInfo,
|
||||
cron.Cron,
|
||||
cron.AutomationCron,
|
||||
module='automation', type_='model')
|
||||
from trytond.pool import Pool
|
||||
from . import automation,rules,freight_booking,booking_report,cron #, document
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
automation.AutomationDocument,
|
||||
rules.AutomationRuleSet,
|
||||
freight_booking.FreightBookingInfo,
|
||||
booking_report.FreightBookingShipmentReport,
|
||||
booking_report.FreightBookingShipmentReportLinkStart,
|
||||
cron.Cron,
|
||||
cron.AutomationCron,
|
||||
module='automation', type_='model')
|
||||
Pool.register(
|
||||
booking_report.FreightBookingShipmentReportLink,
|
||||
module='automation', type_='wizard')
|
||||
|
||||
@@ -10,6 +10,7 @@ import io
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
import traceback
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -62,25 +63,57 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
|
||||
@ModelView.button
|
||||
def run_ocr(cls, docs):
|
||||
for doc in docs:
|
||||
logger.info(
|
||||
"RUN_OCR_START doc=%s type=%s state=%s incoming=%s",
|
||||
getattr(doc, 'id', None), getattr(doc, 'type', None),
|
||||
getattr(doc, 'state', None),
|
||||
getattr(getattr(doc, 'document', None), 'id', None))
|
||||
try:
|
||||
if doc.type == 'weight_report':
|
||||
# Décoder le fichier depuis le champ Binary
|
||||
file_data = doc.document.data or b""
|
||||
logger.info(f"File size: {len(file_data)} bytes")
|
||||
logger.info(f"First 20 bytes: {file_data[:20]}")
|
||||
logger.info(f"Last 20 bytes: {file_data[-20:]}")
|
||||
if not doc.document:
|
||||
raise ValueError("No incoming document linked")
|
||||
|
||||
file_data = doc.document.data or b""
|
||||
file_name = doc.document.name or "document"
|
||||
url = "http://automation-service:8006/ocr"
|
||||
logger.info(
|
||||
"RUN_OCR_FILE doc=%s incoming=%s name=%s size=%s "
|
||||
"first_20=%r last_20=%r",
|
||||
getattr(doc, 'id', None), doc.document.id,
|
||||
file_name, len(file_data), file_data[:20],
|
||||
file_data[-20:])
|
||||
|
||||
if not file_data:
|
||||
raise ValueError("Incoming document has no binary data")
|
||||
|
||||
# Envoyer le fichier au service OCR
|
||||
logger.info(
|
||||
"RUN_OCR_HTTP_REQUEST doc=%s url=%s file_name=%s "
|
||||
"size=%s",
|
||||
getattr(doc, 'id', None), url, file_name,
|
||||
len(file_data))
|
||||
response = requests.post(
|
||||
"http://automation-service:8006/ocr",
|
||||
files={"file": (file_name, io.BytesIO(file_data))}
|
||||
url,
|
||||
files={"file": (file_name, io.BytesIO(file_data))},
|
||||
timeout=120,
|
||||
)
|
||||
logger.info(
|
||||
"RUN_OCR_HTTP_RESPONSE doc=%s status=%s reason=%s "
|
||||
"elapsed=%s content_type=%s body_start=%r",
|
||||
getattr(doc, 'id', None), response.status_code,
|
||||
response.reason, response.elapsed,
|
||||
response.headers.get('content-type'),
|
||||
response.text[:1000])
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
logger.info("RUN_OCR_RESPONSE:%s",data)
|
||||
doc.ocr_text = data.get("ocr_text", "")
|
||||
ocr_text = data.get("ocr_text", "") or ""
|
||||
logger.info(
|
||||
"RUN_OCR_JSON doc=%s keys=%s ocr_text_len=%s "
|
||||
"ocr_text_start=%r",
|
||||
getattr(doc, 'id', None), list(data.keys()),
|
||||
len(ocr_text), ocr_text[:500])
|
||||
doc.ocr_text = ocr_text
|
||||
doc.state = "ocr_done"
|
||||
doc.notes = (doc.notes or "") + "OCR done\n"
|
||||
else:
|
||||
@@ -112,9 +145,17 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
|
||||
doc.notes = (doc.notes or "") + " SO updated"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"RUN_OCR_FAILED doc=%s type=%s error=%s traceback=%s",
|
||||
getattr(doc, 'id', None), getattr(doc, 'type', None),
|
||||
e, traceback.format_exc())
|
||||
doc.state = "error"
|
||||
doc.notes = (doc.notes or "") + f"OCR error: {e}\n"
|
||||
doc.save()
|
||||
logger.info(
|
||||
"RUN_OCR_END doc=%s state=%s ocr_text_len=%s",
|
||||
getattr(doc, 'id', None), getattr(doc, 'state', None),
|
||||
len(getattr(doc, 'ocr_text', None) or ""))
|
||||
# -------------------------------------------------------
|
||||
# STRUCTURE (doctr)
|
||||
# -------------------------------------------------------
|
||||
@@ -248,29 +289,10 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
|
||||
ShipmentWR.save([swr])
|
||||
doc.notes = (doc.notes or "") + f"Shipment found: {sh[0].number}\n"
|
||||
logger.info("BL_NUMBER:%s",sh[0].bl_number)
|
||||
if sh[0].incoming_moves:
|
||||
factor_net = wr.net_landed_kg / wr.bales if wr.bales else 1
|
||||
factor_gross = wr.gross_landed_kg / wr.bales if wr.bales else 1
|
||||
for move in sh[0].incoming_moves:
|
||||
lot = move.lot
|
||||
if lot.lot_type == 'physic':
|
||||
wr_payload = {
|
||||
"chunk_key": lot.lot_chunk_key,
|
||||
"gross_weight": float(round(Decimal(lot.lot_qt) * factor_gross,5)),
|
||||
"net_weight": float(round(Decimal(lot.lot_qt) * factor_net,5)),
|
||||
"tare_total": float(round(wr.tare_kg * (Decimal(lot.lot_qt) / wr.bales),5)) ,
|
||||
"bags": int(lot.lot_qt),
|
||||
"surveyor_code": sh[0].controller.get_alf(),
|
||||
"place_key": sh[0].to_location.get_places(),
|
||||
"report_date": int(wr.report_date.strftime("%Y%m%d")),#wr.report_date.isoformat() if wr.report_date else None,
|
||||
"weight_date": int(wr.weight_date.strftime("%Y%m%d")),#wr.weight_date.isoformat() if wr.weight_date else None,
|
||||
"agent": sh[0].agent.get_alf(),
|
||||
"forwarder_ref": sh[0].returned_id
|
||||
}
|
||||
logger.info("PAYLOAD:%s",wr_payload)
|
||||
data = doc.create_weight_report(wr_payload)
|
||||
doc.notes = (doc.notes or "") + f"WR created in Fintrade: {data.get('success')}\n"
|
||||
doc.notes = (doc.notes or "") + f"WR key: {data.get('weight_report_key')}\n"
|
||||
doc.notes = (
|
||||
(doc.notes or "")
|
||||
+ "Global WR linked to shipment. "
|
||||
+ "Create remote lot WRs from the weight report form.\n")
|
||||
|
||||
# if cls.rule_set.ocr_required:[]
|
||||
# cls.run_ocr([doc])
|
||||
@@ -293,4 +315,4 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
|
||||
# except Exception as e:
|
||||
# doc.state = "error"
|
||||
# doc.notes = (doc.notes or "") + f"Pipeline error: {e}\n"
|
||||
doc.save()
|
||||
doc.save()
|
||||
|
||||
193
modules/automation/booking_report.py
Normal file
193
modules/automation/booking_report.py
Normal file
@@ -0,0 +1,193 @@
|
||||
import json
|
||||
|
||||
from sql import Literal, Null
|
||||
from sql.functions import CurrentTimestamp
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Button, StateTransition, StateView, Wizard
|
||||
from trytond.exceptions import UserError
|
||||
|
||||
|
||||
class FreightBookingShipmentReport(ModelSQL, ModelView):
|
||||
"Freight Booking Shipment Report"
|
||||
__name__ = 'freight.booking.shipment.report'
|
||||
|
||||
automation_document = fields.Many2One(
|
||||
'automation.document', "Automation Document")
|
||||
bl_number = fields.Function(fields.Char("BL Number"), 'get_bl_number')
|
||||
shipment = fields.Function(fields.Many2One(
|
||||
'stock.shipment.in', "Shipment"), 'get_shipment')
|
||||
status = fields.Function(fields.Selection([
|
||||
('found', "Found"),
|
||||
('error', "Error"),
|
||||
], "Status"), 'get_status')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order = [
|
||||
('id', 'DESC'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
AutomationDocument = Pool().get('automation.document')
|
||||
document = AutomationDocument.__table__()
|
||||
|
||||
return document.select(
|
||||
Literal(None).as_('create_uid'),
|
||||
CurrentTimestamp().as_('create_date'),
|
||||
Literal(None).as_('write_uid'),
|
||||
Literal(None).as_('write_date'),
|
||||
document.id.as_('id'),
|
||||
document.id.as_('automation_document'),
|
||||
where=(
|
||||
(document.type == 'weight_report')
|
||||
& (document.metadata_json != Null)))
|
||||
|
||||
@classmethod
|
||||
def _extract_bl_number(cls, metadata_json):
|
||||
if not metadata_json:
|
||||
return None
|
||||
try:
|
||||
metadata = json.loads(str(metadata_json))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def find_value(value):
|
||||
if isinstance(value, dict):
|
||||
for key in (
|
||||
'bl_no', 'bl_number', 'bl', 'BL_Number',
|
||||
'BL number', 'B/L No'):
|
||||
bl_number = value.get(key)
|
||||
if bl_number:
|
||||
return str(bl_number).strip()
|
||||
for item in value.values():
|
||||
bl_number = find_value(item)
|
||||
if bl_number:
|
||||
return bl_number
|
||||
elif isinstance(value, list):
|
||||
for item in value:
|
||||
bl_number = find_value(item)
|
||||
if bl_number:
|
||||
return bl_number
|
||||
return None
|
||||
|
||||
return find_value(metadata)
|
||||
|
||||
@classmethod
|
||||
def _get_report_data(cls, reports):
|
||||
ShipmentIn = Pool().get('stock.shipment.in')
|
||||
bl_numbers = {}
|
||||
|
||||
for report in reports:
|
||||
document = report.automation_document
|
||||
bl_number = cls._extract_bl_number(
|
||||
document.metadata_json if document else None)
|
||||
bl_numbers[report.id] = bl_number
|
||||
|
||||
shipments_by_bl = {}
|
||||
for bl_number in {b for b in bl_numbers.values() if b}:
|
||||
shipments = ShipmentIn.search([
|
||||
('bl_number', '=', bl_number),
|
||||
], limit=1)
|
||||
if not shipments:
|
||||
shipments = ShipmentIn.search([
|
||||
('bl_number', 'ilike', bl_number),
|
||||
], limit=1)
|
||||
shipments_by_bl[bl_number] = shipments[0] if shipments else None
|
||||
|
||||
return bl_numbers, shipments_by_bl
|
||||
|
||||
@classmethod
|
||||
def get_bl_number(cls, reports, name):
|
||||
bl_numbers, _ = cls._get_report_data(reports)
|
||||
return bl_numbers
|
||||
|
||||
@classmethod
|
||||
def get_shipment(cls, reports, name):
|
||||
bl_numbers, shipments_by_bl = cls._get_report_data(reports)
|
||||
return {
|
||||
report.id: (
|
||||
shipments_by_bl[bl_numbers[report.id]].id
|
||||
if bl_numbers[report.id]
|
||||
and shipments_by_bl.get(bl_numbers[report.id])
|
||||
else None)
|
||||
for report in reports
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_status(cls, reports, name):
|
||||
bl_numbers, shipments_by_bl = cls._get_report_data(reports)
|
||||
return {
|
||||
report.id: (
|
||||
'found'
|
||||
if bl_numbers[report.id]
|
||||
and shipments_by_bl.get(bl_numbers[report.id])
|
||||
else 'error')
|
||||
for report in reports
|
||||
}
|
||||
|
||||
|
||||
class FreightBookingShipmentReportLinkStart(ModelView):
|
||||
"Link Freight Booking Shipment Report"
|
||||
__name__ = 'freight.booking.shipment.report.link.start'
|
||||
|
||||
bl_number = fields.Char("BL Number", readonly=True)
|
||||
shipment = fields.Many2One(
|
||||
'stock.shipment.in', "Shipment", required=True)
|
||||
|
||||
|
||||
class FreightBookingShipmentReportLink(Wizard):
|
||||
"Link Freight Booking Shipment Report"
|
||||
__name__ = 'freight.booking.shipment.report.link'
|
||||
|
||||
start = StateView(
|
||||
'freight.booking.shipment.report.link.start',
|
||||
'automation.freight_booking_shipment_report_link_start_form',
|
||||
[
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
Button('Link', 'link', 'tryton-ok', default=True),
|
||||
])
|
||||
link = StateTransition()
|
||||
|
||||
def default_start(self, fields):
|
||||
Report = Pool().get('freight.booking.shipment.report')
|
||||
active_ids = Transaction().context.get('active_ids') or []
|
||||
reports = Report.browse(active_ids)
|
||||
error_reports = [
|
||||
report for report in reports
|
||||
if report.status == 'error' and report.bl_number]
|
||||
if len(error_reports) > 1:
|
||||
raise UserError(
|
||||
"Link one error line at a time to avoid overwriting the "
|
||||
"shipment BL number.")
|
||||
return {
|
||||
'bl_number': error_reports[0].bl_number if error_reports else None,
|
||||
}
|
||||
|
||||
def transition_link(self):
|
||||
Report = Pool().get('freight.booking.shipment.report')
|
||||
ShipmentIn = Pool().get('stock.shipment.in')
|
||||
active_ids = Transaction().context.get('active_ids') or []
|
||||
reports = Report.browse(active_ids)
|
||||
error_reports = [
|
||||
report for report in reports
|
||||
if report.status == 'error' and report.bl_number]
|
||||
if not error_reports:
|
||||
raise UserError(
|
||||
"Select at least one error line with a BL number.")
|
||||
if len(error_reports) > 1:
|
||||
raise UserError(
|
||||
"Link one error line at a time to avoid overwriting the "
|
||||
"shipment BL number.")
|
||||
|
||||
shipment = self.start.shipment
|
||||
shipment.bl_number = error_reports[0].bl_number
|
||||
ShipmentIn.save([shipment])
|
||||
return 'end'
|
||||
|
||||
def end(self):
|
||||
return 'reload'
|
||||
73
modules/automation/booking_report.xml
Normal file
73
modules/automation/booking_report.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="freight_booking_shipment_report_tree">
|
||||
<field name="model">freight.booking.shipment.report</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">freight_booking_shipment_report_tree</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_freight_booking_shipment_report">
|
||||
<field name="name">Freight Booking Shipment Report</field>
|
||||
<field name="res_model">freight.booking.shipment.report</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_freight_booking_shipment_report_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="freight_booking_shipment_report_tree"/>
|
||||
<field name="act_window" ref="act_freight_booking_shipment_report"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="freight_booking_shipment_report_link_start_form">
|
||||
<field name="model">freight.booking.shipment.report.link.start</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">freight_booking_shipment_report_link_start_form</field>
|
||||
</record>
|
||||
<record model="ir.action.wizard" id="act_freight_booking_shipment_report_link">
|
||||
<field name="name">Link to Shipment</field>
|
||||
<field name="wiz_name">freight.booking.shipment.report.link</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="keyword_freight_booking_shipment_report_link">
|
||||
<field name="keyword">form_action</field>
|
||||
<field name="model">freight.booking.shipment.report,-1</field>
|
||||
<field name="action" ref="act_freight_booking_shipment_report_link"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_freight_booking_shipment_report">
|
||||
<field name="model">freight.booking.shipment.report</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_freight_booking_shipment_report_link_start">
|
||||
<field name="model">freight.booking.shipment.report.link.start</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_freight_booking_shipment_report_link_start_automation">
|
||||
<field name="model">freight.booking.shipment.report.link.start</field>
|
||||
<field name="group" ref="group_automation"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_freight_booking_shipment_report_automation">
|
||||
<field name="model">freight.booking.shipment.report</field>
|
||||
<field name="group" ref="group_automation"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
name="Freight Booking Shipment Report"
|
||||
action="act_freight_booking_shipment_report"
|
||||
parent="menu_automation"
|
||||
sequence="11"
|
||||
id="menu_freight_booking_shipment_report" />
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -11,6 +11,7 @@ import traceback
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cron(metaclass=PoolMeta):
|
||||
__name__ = 'ir.cron'
|
||||
|
||||
@@ -18,9 +19,11 @@ class Cron(metaclass=PoolMeta):
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.method.selection.append(
|
||||
('automation.cron|update_shipment', "Update Shipment from freight booking info")
|
||||
('automation.cron|update_shipment',
|
||||
"Update Shipment from freight booking info")
|
||||
)
|
||||
|
||||
|
||||
class AutomationCron(ModelSQL, ModelView):
|
||||
"Automation Cron"
|
||||
__name__ = 'automation.cron'
|
||||
@@ -30,7 +33,7 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
('weekly', "Weekly"),
|
||||
('monthly', "Monthly"),
|
||||
], "Frequency", required=True,
|
||||
help="How frequently rates must be updated.")
|
||||
help="How frequently rates must be updated.")
|
||||
|
||||
last_update = fields.Date("Last Update", required=True)
|
||||
|
||||
@@ -70,22 +73,23 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
))
|
||||
|
||||
rows = cursor.fetchall()
|
||||
logger.info(f"Nombre total de lignes à traiter : {len(rows)}")
|
||||
logger.info(f"Nombre total de lignes a traiter : {len(rows)}")
|
||||
|
||||
# ---- PREMIÈRE TRANSACTION : Création des objets de référence ----
|
||||
# Premiere transaction : creation des objets de reference
|
||||
with Transaction().new_transaction() as trans1:
|
||||
try:
|
||||
logger.info("Début de la création des objets de référence...")
|
||||
|
||||
logger.info(
|
||||
"Debut de la creation des objets de reference...")
|
||||
|
||||
parties_to_save = []
|
||||
vessels_to_save = []
|
||||
locations_to_save = []
|
||||
|
||||
|
||||
parties_cache = {}
|
||||
vessels_cache = {}
|
||||
locations_cache = {}
|
||||
|
||||
# Collecter les données des objets de référence
|
||||
|
||||
# Collecter les donnees des objets de reference
|
||||
for row in rows:
|
||||
(
|
||||
si_number, si_date, si_quantity, si_unit,
|
||||
@@ -97,47 +101,44 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
comments, fintrade_booking_key
|
||||
) = row
|
||||
|
||||
# Fonction pour obtenir ou créer un Party
|
||||
def get_or_create_party(name):
|
||||
if not name:
|
||||
return None
|
||||
name_upper = str(name).strip().upper()
|
||||
if name_upper in parties_cache:
|
||||
return parties_cache[name_upper]
|
||||
|
||||
# Chercher d'abord dans la base
|
||||
existing = Party.search([('name', '=', name_upper)], limit=1)
|
||||
|
||||
existing = Party.search(
|
||||
[('name', '=', name_upper)], limit=1)
|
||||
if existing:
|
||||
parties_cache[name_upper] = existing[0]
|
||||
return existing[0]
|
||||
|
||||
# Créer un nouveau
|
||||
|
||||
new_p = Party()
|
||||
new_p.name = name_upper
|
||||
parties_cache[name_upper] = new_p
|
||||
parties_to_save.append(new_p)
|
||||
return new_p
|
||||
|
||||
# Fonction pour obtenir ou créer un Vessel
|
||||
def get_or_create_vessel(name):
|
||||
if not name:
|
||||
return None
|
||||
name_upper = str(name).strip().upper()
|
||||
if name_upper in vessels_cache:
|
||||
return vessels_cache[name_upper]
|
||||
|
||||
existing = Vessel.search([('vessel_name', '=', name_upper)], limit=1)
|
||||
|
||||
existing = Vessel.search(
|
||||
[('vessel_name', '=', name_upper)], limit=1)
|
||||
if existing:
|
||||
vessels_cache[name_upper] = existing[0]
|
||||
return existing[0]
|
||||
|
||||
|
||||
new_v = Vessel()
|
||||
new_v.vessel_name = name_upper
|
||||
vessels_cache[name_upper] = new_v
|
||||
vessels_to_save.append(new_v)
|
||||
return new_v
|
||||
|
||||
# Fonction pour obtenir ou créer une Location
|
||||
def get_or_create_location(name, type_):
|
||||
if not name:
|
||||
return None
|
||||
@@ -145,16 +146,15 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
key = f"{name_upper}_{type_}"
|
||||
if key in locations_cache:
|
||||
return locations_cache[key]
|
||||
|
||||
|
||||
existing = Location.search([
|
||||
('name', '=', name_upper),
|
||||
('type', '=', type_)
|
||||
('type', '=', type_),
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
locations_cache[key] = existing[0]
|
||||
return existing[0]
|
||||
|
||||
|
||||
new_loc = Location()
|
||||
new_loc.name = name_upper
|
||||
new_loc.type = type_
|
||||
@@ -162,40 +162,40 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
locations_to_save.append(new_loc)
|
||||
return new_loc
|
||||
|
||||
# Collecter les objets à créer
|
||||
_ = get_or_create_party(carrier_name)
|
||||
_ = get_or_create_party(agent_name)
|
||||
_ = get_or_create_vessel(vessel_name)
|
||||
_ = get_or_create_location(loading_name, 'supplier')
|
||||
_ = get_or_create_location(destination_name, 'customer')
|
||||
|
||||
# Sauvegarder tous les objets de référence
|
||||
if parties_to_save:
|
||||
logger.info(f"Création de {len(parties_to_save)} parties...")
|
||||
logger.info(f"Creation de {len(parties_to_save)} parties...")
|
||||
Party.save(parties_to_save)
|
||||
|
||||
|
||||
if vessels_to_save:
|
||||
logger.info(f"Création de {len(vessels_to_save)} vessels...")
|
||||
logger.info(f"Creation de {len(vessels_to_save)} vessels...")
|
||||
Vessel.save(vessels_to_save)
|
||||
|
||||
|
||||
if locations_to_save:
|
||||
logger.info(f"Création de {len(locations_to_save)} locations...")
|
||||
logger.info(
|
||||
f"Creation de {len(locations_to_save)} locations...")
|
||||
Location.save(locations_to_save)
|
||||
|
||||
|
||||
trans1.commit()
|
||||
logger.info("Première transaction commitée : objets de référence créés")
|
||||
|
||||
logger.info(
|
||||
"Premiere transaction commitee : objets de reference crees")
|
||||
|
||||
except Exception as e:
|
||||
trans1.rollback()
|
||||
logger.error(f"Erreur dans la création des objets de référence : {e}")
|
||||
logger.error(
|
||||
f"Erreur dans la creation des objets de reference : {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
raise
|
||||
|
||||
# ---- TRANSACTIONS INDIVIDUELLES pour chaque shipment ----
|
||||
# Transactions individuelles pour chaque shipment
|
||||
successful_shipments = 0
|
||||
failed_shipments = []
|
||||
|
||||
# Recréer le curseur après la nouvelle transaction
|
||||
|
||||
cursor2 = Transaction().connection.cursor()
|
||||
cursor2.execute(*t.select(
|
||||
t.ShippingInstructionNumber,
|
||||
@@ -218,7 +218,76 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
))
|
||||
|
||||
rows2 = cursor2.fetchall()
|
||||
|
||||
|
||||
def normalize_bl_number(value):
|
||||
value = str(value or '').strip()
|
||||
if value.lower() in {'none', 'null'}:
|
||||
return ''
|
||||
if value.upper() in {
|
||||
'MULTIPLE BL NUMBERS FOUND',
|
||||
'MULTIPLE B/L NUMBERS FOUND',
|
||||
'NO BL NUMBER FOUND',
|
||||
'NO B/L NUMBER FOUND',
|
||||
}:
|
||||
return ''
|
||||
return value
|
||||
|
||||
def normalize_booking_number(value):
|
||||
value = str(value or '').strip()
|
||||
if value.lower() in {'none', 'null'}:
|
||||
return ''
|
||||
return value
|
||||
|
||||
def sync_shipment_from_booking(shipment, bl_number, bl_date, etd_date,
|
||||
controller):
|
||||
changed = False
|
||||
columns = []
|
||||
values = []
|
||||
booking_bl_number = normalize_bl_number(bl_number)
|
||||
shipment_bl_number = normalize_bl_number(shipment.bl_number)
|
||||
if booking_bl_number and booking_bl_number != shipment_bl_number:
|
||||
logger.info(
|
||||
"FREIGHT_BOOKING_SYNC_BL shipment=%s reference=%s "
|
||||
"old_bl=%s new_bl=%s",
|
||||
shipment.id, shipment.reference,
|
||||
shipment.bl_number, booking_bl_number)
|
||||
shipment.bl_number = booking_bl_number
|
||||
columns.append('bl_number')
|
||||
values.append(booking_bl_number)
|
||||
changed = True
|
||||
if bl_date and shipment.bl_date != bl_date:
|
||||
logger.info(
|
||||
"FREIGHT_BOOKING_SYNC_BL_DATE shipment=%s reference=%s "
|
||||
"old_bl_date=%s new_bl_date=%s",
|
||||
shipment.id, shipment.reference,
|
||||
shipment.bl_date, bl_date)
|
||||
shipment.bl_date = bl_date
|
||||
columns.append('bl_date')
|
||||
values.append(bl_date)
|
||||
changed = True
|
||||
if etd_date and shipment.etd != etd_date:
|
||||
logger.info(
|
||||
"FREIGHT_BOOKING_SYNC_ETD shipment=%s reference=%s "
|
||||
"old_etd=%s new_etd=%s",
|
||||
shipment.id, shipment.reference,
|
||||
shipment.etd, etd_date)
|
||||
shipment.etd = etd_date
|
||||
columns.append('etd')
|
||||
values.append(etd_date)
|
||||
changed = True
|
||||
if controller and shipment.controller_target != controller:
|
||||
shipment.controller_target = controller
|
||||
columns.append('controller_target')
|
||||
values.append(controller)
|
||||
changed = True
|
||||
if changed:
|
||||
table = ShipmentIn.__table__()
|
||||
table_columns = [getattr(table, column) for column in columns]
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute(*table.update(
|
||||
table_columns, values, where=table.id == shipment.id))
|
||||
return changed
|
||||
|
||||
for i, row in enumerate(rows2, 1):
|
||||
(
|
||||
si_number, si_date, si_quantity, si_unit,
|
||||
@@ -229,46 +298,98 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
etd_date, bl_date, controller,
|
||||
comments, fintrade_booking_key
|
||||
) = row
|
||||
|
||||
logger.info(f"Traitement shipment {i}/{len(rows2)} : SI {si_number}")
|
||||
|
||||
# ---- TRANSACTION INDIVIDUELLE pour ce shipment ----
|
||||
si_reference = normalize_booking_number(si_number)
|
||||
|
||||
logger.info(
|
||||
"Traitement shipment %s/%s : SI %s reference=%s",
|
||||
i, len(rows2), si_number, si_reference)
|
||||
|
||||
try:
|
||||
with Transaction().new_transaction() as trans_shipment:
|
||||
logger.info(f"Début transaction pour SI {si_number}")
|
||||
|
||||
# Vérifier si le shipment existe déjà
|
||||
existing_shipment = ShipmentIn.search([
|
||||
('reference', '=', si_number)
|
||||
], limit=1)
|
||||
|
||||
if existing_shipment:
|
||||
logger.info(f"Shipment {si_number} existe déjà, ignoré")
|
||||
logger.info(
|
||||
"Debut transaction pour SI %s reference=%s",
|
||||
si_number, si_reference)
|
||||
|
||||
existing_shipments = ShipmentIn.search([
|
||||
('reference', '=', si_reference)
|
||||
])
|
||||
|
||||
if existing_shipments:
|
||||
if len(existing_shipments) > 1:
|
||||
logger.info(
|
||||
"FREIGHT_BOOKING_DUPLICATE_SHIPMENT_REFERENCE "
|
||||
"reference=%s shipment_ids=%s",
|
||||
si_reference,
|
||||
[shipment.id for shipment in existing_shipments])
|
||||
for existing in existing_shipments:
|
||||
sync_shipment_from_booking(
|
||||
existing, bl_number, bl_date, etd_date,
|
||||
controller)
|
||||
shipment = existing_shipments[0]
|
||||
if shipment.incoming_moves:
|
||||
logger.info(
|
||||
"Shipment %s existe deja avec lots, "
|
||||
"booking data synchronized",
|
||||
si_reference)
|
||||
trans_shipment.commit()
|
||||
continue
|
||||
|
||||
logger.info(
|
||||
"Shipment %s existe deja sans lots, verification freight_booking_lots",
|
||||
si_reference)
|
||||
with Transaction().set_context(
|
||||
_purchase_trade_skip_physical_lot_tolerance_warning=True):
|
||||
inv_date, inv_nb = (
|
||||
shipment._create_lots_from_fintrade())
|
||||
shipment = ShipmentIn(shipment.id)
|
||||
if shipment.incoming_moves:
|
||||
shipment.controller = shipment.get_controller()
|
||||
shipment.controller_target = controller
|
||||
if not shipment.fees:
|
||||
shipment.create_fee(shipment.controller)
|
||||
shipment.instructions = shipment.get_instructions_html(
|
||||
inv_date, inv_nb)
|
||||
ShipmentIn.save([shipment])
|
||||
logger.info(
|
||||
"Shipment %s mis a jour avec %s incoming move(s)",
|
||||
si_reference, len(shipment.incoming_moves))
|
||||
else:
|
||||
logger.info(
|
||||
"Shipment %s existe sans lots et aucun lot disponible pour l'instant",
|
||||
si_reference)
|
||||
trans_shipment.commit()
|
||||
continue
|
||||
|
||||
# Récupérer les objets (maintenant ils existent dans la base)
|
||||
carrier = None
|
||||
if carrier_name:
|
||||
carrier_list = Party.search([('name', '=', str(carrier_name).strip().upper())], limit=1)
|
||||
carrier_list = Party.search([
|
||||
('name', '=',
|
||||
str(carrier_name).strip().upper())
|
||||
], limit=1)
|
||||
if carrier_list:
|
||||
carrier = carrier_list[0]
|
||||
logger.info(f"Carrier trouvé pour {si_number}: {carrier.name}")
|
||||
logger.info(
|
||||
f"Carrier trouve pour {si_number}: {carrier.name}")
|
||||
else:
|
||||
logger.warning(f"Carrier NON TROUVÉ pour {si_number}: '{carrier_name}'")
|
||||
|
||||
logger.warning(
|
||||
f"Carrier NON TROUVE pour {si_number}: '{carrier_name}'")
|
||||
|
||||
agent = None
|
||||
|
||||
agent_list = Party.search([('name', '=', str(agent_name or 'TBN').strip().upper())], limit=1)
|
||||
agent_list = Party.search([
|
||||
('name', '=', str(agent_name or 'TBN').strip().upper())
|
||||
], limit=1)
|
||||
if agent_list:
|
||||
agent = agent_list[0]
|
||||
|
||||
|
||||
vessel = None
|
||||
if vessel_name:
|
||||
vessel_list = Vessel.search([('vessel_name', '=', str(vessel_name).strip().upper())], limit=1)
|
||||
vessel_list = Vessel.search([
|
||||
('vessel_name', '=',
|
||||
str(vessel_name).strip().upper())
|
||||
], limit=1)
|
||||
if vessel_list:
|
||||
vessel = vessel_list[0]
|
||||
|
||||
|
||||
loc_from = None
|
||||
if loading_name:
|
||||
loc_from_list = Location.search([
|
||||
@@ -277,7 +398,7 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
], limit=1)
|
||||
if loc_from_list:
|
||||
loc_from = loc_from_list[0]
|
||||
|
||||
|
||||
loc_to = None
|
||||
if destination_name:
|
||||
loc_to_list = Location.search([
|
||||
@@ -286,19 +407,19 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
], limit=1)
|
||||
if loc_to_list:
|
||||
loc_to = loc_to_list[0]
|
||||
|
||||
# Vérification critique du carrier
|
||||
|
||||
if not carrier:
|
||||
error_msg = f"ERREUR CRITIQUE: Carrier manquant pour SI {si_number} (valeur: '{carrier_name}')"
|
||||
error_msg = (
|
||||
f"ERREUR CRITIQUE: Carrier manquant pour SI {si_number} "
|
||||
f"(valeur: '{carrier_name}')")
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
# Créer le shipment
|
||||
|
||||
shipment = ShipmentIn()
|
||||
shipment.reference = si_number
|
||||
shipment.reference = si_reference
|
||||
shipment.from_location = loc_from
|
||||
shipment.to_location = loc_to
|
||||
shipment.carrier = None #carrier
|
||||
shipment.carrier = None # carrier
|
||||
shipment.supplier = agent
|
||||
shipment.agent = agent
|
||||
shipment.vessel = vessel
|
||||
@@ -307,21 +428,30 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
shipment.bl_date = bl_date
|
||||
shipment.etd = etd_date
|
||||
shipment.etad = shipment.bl_date + timedelta(days=20)
|
||||
|
||||
# Sauvegarder ce shipment uniquement
|
||||
ShipmentIn.save([shipment])
|
||||
inv_date,inv_nb = shipment._create_lots_from_fintrade()
|
||||
shipment.controller = shipment.get_controller()
|
||||
shipment.controller_target = controller
|
||||
shipment.create_fee(shipment.controller)
|
||||
shipment.instructions = shipment.get_instructions_html(inv_date,inv_nb)
|
||||
|
||||
ShipmentIn.save([shipment])
|
||||
with Transaction().set_context(
|
||||
_purchase_trade_skip_physical_lot_tolerance_warning=True):
|
||||
inv_date, inv_nb = shipment._create_lots_from_fintrade()
|
||||
shipment = ShipmentIn(shipment.id)
|
||||
if shipment.incoming_moves:
|
||||
shipment.controller = shipment.get_controller()
|
||||
shipment.controller_target = controller
|
||||
shipment.create_fee(shipment.controller)
|
||||
shipment.instructions = shipment.get_instructions_html(
|
||||
inv_date, inv_nb)
|
||||
ShipmentIn.save([shipment])
|
||||
else:
|
||||
logger.info(
|
||||
"Shipment %s cree sans lots : fees/instructions "
|
||||
"non crees, voir FINTRADE_CLEANUP_NEEDED",
|
||||
si_number)
|
||||
trans_shipment.commit()
|
||||
successful_shipments += 1
|
||||
logger.info(f"✓ Shipment {si_number} créé avec succès")
|
||||
|
||||
if shipment.incoming_moves:
|
||||
successful_shipments += 1
|
||||
logger.info(f"Shipment {si_number} cree avec succes")
|
||||
|
||||
except Exception as e:
|
||||
# Cette transaction échoue mais les autres continuent
|
||||
error_details = {
|
||||
'si_number': si_number,
|
||||
'carrier_name': carrier_name,
|
||||
@@ -329,8 +459,8 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
'traceback': traceback.format_exc()
|
||||
}
|
||||
failed_shipments.append(error_details)
|
||||
|
||||
logger.error(f"✗ ERREUR pour shipment {si_number}: {e}")
|
||||
|
||||
logger.error(f"ERREUR pour shipment {si_number}: {e}")
|
||||
logger.error(f" Carrier: '{carrier_name}'")
|
||||
logger.error(f" Agent: '{agent_name}'")
|
||||
logger.error(f" Vessel: '{vessel_name}'")
|
||||
@@ -338,24 +468,22 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
for line in traceback.format_exc().split('\n'):
|
||||
if line.strip():
|
||||
logger.error(f" {line}")
|
||||
|
||||
# ---- RÉSUMÉ FINAL ----
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("RÉSUMÉ DE L'EXÉCUTION")
|
||||
logger.info("RESUME DE L'EXECUTION")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Total de shipments à traiter : {len(rows2)}")
|
||||
logger.info(f"Shipments créés avec succès : {successful_shipments}")
|
||||
logger.info(f"Shipments en échec : {len(failed_shipments)}")
|
||||
|
||||
logger.info(f"Total de shipments a traiter : {len(rows2)}")
|
||||
logger.info(f"Shipments crees avec succes : {successful_shipments}")
|
||||
logger.info(f"Shipments en echec : {len(failed_shipments)}")
|
||||
|
||||
if failed_shipments:
|
||||
logger.info("\nDétail des échecs :")
|
||||
logger.info("\nDetail des echecs :")
|
||||
for i, error in enumerate(failed_shipments, 1):
|
||||
logger.info(f" {i}. SI {error['si_number']}:")
|
||||
logger.info(f" Carrier: '{error['carrier_name']}'")
|
||||
logger.info(f" Erreur: {error['error']}")
|
||||
|
||||
# Log supplémentaire pour debug
|
||||
logger.info("\nAnalyse des carriers problématiques :")
|
||||
|
||||
logger.info("\nAnalyse des carriers problematiques :")
|
||||
problematic_carriers = {}
|
||||
for error in failed_shipments:
|
||||
carrier = error['carrier_name']
|
||||
@@ -363,15 +491,16 @@ class AutomationCron(ModelSQL, ModelView):
|
||||
problematic_carriers[carrier] += 1
|
||||
else:
|
||||
problematic_carriers[carrier] = 1
|
||||
|
||||
|
||||
for carrier, count in problematic_carriers.items():
|
||||
logger.info(f" Carrier '{carrier}' : {count} échec(s)")
|
||||
|
||||
# Vérifier si ce carrier existe dans la base
|
||||
existing = Party.search([('name', '=', str(carrier).strip().upper())], limit=1)
|
||||
logger.info(f" Carrier '{carrier}' : {count} echec(s)")
|
||||
existing = Party.search([
|
||||
('name', '=', str(carrier).strip().upper())
|
||||
], limit=1)
|
||||
if existing:
|
||||
logger.info(f" → EXISTE DANS LA BASE (ID: {existing[0].id})")
|
||||
logger.info(
|
||||
f" -> EXISTE DANS LA BASE (ID: {existing[0].id})")
|
||||
else:
|
||||
logger.info(f" → N'EXISTE PAS DANS LA BASE")
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info(" -> N'EXISTE PAS DANS LA BASE")
|
||||
|
||||
logger.info("=" * 60)
|
||||
|
||||
@@ -7,4 +7,5 @@ depends:
|
||||
xml:
|
||||
automation.xml
|
||||
freight_booking.xml
|
||||
cron.xml
|
||||
booking_report.xml
|
||||
cron.xml
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<form>
|
||||
<label name="bl_number"/>
|
||||
<field name="bl_number"/>
|
||||
<label name="shipment"/>
|
||||
<field name="shipment"/>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="bl_number"/>
|
||||
<field name="status" widget="badge"
|
||||
badge_colors="found:#16a34a,Found:#16a34a,error:#ef4444,Error:#ef4444"/>
|
||||
<field name="shipment"/>
|
||||
<field name="automation_document"/>
|
||||
</tree>
|
||||
@@ -133,6 +133,19 @@ class Account(DeactivableMixin, ModelSQL, ModelView):
|
||||
if number.type == 'iban':
|
||||
return number.number
|
||||
|
||||
@staticmethod
|
||||
def _bic_compact(bic):
|
||||
return getattr(bic, 'compact', bic)
|
||||
|
||||
@classmethod
|
||||
def _bic_matches(cls, first, second):
|
||||
def normalize(value):
|
||||
value = cls._bic_compact(value)
|
||||
if len(value) == 11 and value.endswith('XXX'):
|
||||
value = value[:8]
|
||||
return value
|
||||
return normalize(first) == normalize(second)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, accounts):
|
||||
super().validate(accounts)
|
||||
@@ -146,7 +159,7 @@ class Account(DeactivableMixin, ModelSQL, ModelView):
|
||||
iban = IBAN(self.iban)
|
||||
bic = BIC(self.bank.bic)
|
||||
if (iban.bic
|
||||
and iban.bic != bic
|
||||
and not self._bic_matches(iban.bic, bic)
|
||||
and (
|
||||
iban.country_code != bic.country_code
|
||||
or (iban.bank_code or iban.branch_code)
|
||||
|
||||
@@ -41,6 +41,16 @@ class BankTestCase(ModuleTestCase):
|
||||
bank.bic = 'foo'
|
||||
bank.save()
|
||||
|
||||
@with_transaction()
|
||||
def test_bic_matches_primary_branch(self):
|
||||
"Test BIC matches main branch"
|
||||
pool = Pool()
|
||||
Account = pool.get('bank.account')
|
||||
|
||||
self.assertTrue(Account._bic_matches('CIALCHBBXXX', 'CIALCHBB'))
|
||||
self.assertTrue(Account._bic_matches('CIALCHBB', 'CIALCHBBXXX'))
|
||||
self.assertFalse(Account._bic_matches('CIALCHBB123', 'CIALCHBB'))
|
||||
|
||||
@with_transaction()
|
||||
def test_iban_format(self):
|
||||
'Test IBAN format'
|
||||
|
||||
@@ -2,9 +2,4 @@
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="//page[@id='costs']" position="inside">
|
||||
<field name="before_carriages" colspan="4"/>
|
||||
|
||||
<field name="after_carriages" colspan="4"/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
||||
@@ -295,23 +295,60 @@ class Incoming(DeactivableMixin, Workflow, ModelSQL, ModelView):
|
||||
@ModelView.button
|
||||
@Workflow.transition('processing')
|
||||
def process(cls, documents, with_children=False):
|
||||
logger.info(
|
||||
"DOCUMENT_INCOMING_PROCESS_CLICK documents=%s with_children=%s",
|
||||
[
|
||||
{
|
||||
'id': document.id,
|
||||
'name': document.name,
|
||||
'type': document.type,
|
||||
'state': document.state,
|
||||
}
|
||||
for document in documents
|
||||
],
|
||||
with_children)
|
||||
transaction = Transaction()
|
||||
context = transaction.context
|
||||
with transaction.set_context(
|
||||
queue_batch=context.get('queue_batch', True)):
|
||||
logger.info(
|
||||
"DOCUMENT_INCOMING_PROCESS_QUEUE documents=%s queue_batch=%s",
|
||||
[document.id for document in documents],
|
||||
transaction.context.get('queue_batch'))
|
||||
cls.__queue__.proceed(documents, with_children=with_children)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('done')
|
||||
def proceed(cls, documents, with_children=False):
|
||||
logger.info(
|
||||
"DOCUMENT_INCOMING_PROCEED_START documents=%s with_children=%s",
|
||||
[
|
||||
{
|
||||
'id': document.id,
|
||||
'name': document.name,
|
||||
'type': document.type,
|
||||
'state': document.state,
|
||||
'active': document.active,
|
||||
'result': str(document.result) if document.result else None,
|
||||
}
|
||||
for document in documents
|
||||
],
|
||||
with_children)
|
||||
pool = Pool()
|
||||
Attachment = pool.get('ir.attachment')
|
||||
results = defaultdict(list)
|
||||
attachments = []
|
||||
for document in documents:
|
||||
if document.result or not document.active:
|
||||
logger.info(
|
||||
"DOCUMENT_INCOMING_PROCEED_SKIP document=%s "
|
||||
"result=%s active=%s",
|
||||
document.id, document.result, document.active)
|
||||
continue
|
||||
logger.info(
|
||||
"DOCUMENT_INCOMING_PROCEED_DISPATCH document=%s method=%s",
|
||||
document.id, f'_process_{document.type}')
|
||||
document.result = getattr(document, f'_process_{document.type}')()
|
||||
results[document.result.__class__].append(document.result)
|
||||
attachment = Attachment(
|
||||
|
||||
@@ -7,6 +7,9 @@ from trytond.modules.document_incoming.exceptions import (
|
||||
DocumentIncomingProcessError)
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IncomingConfiguration(metaclass=PoolMeta):
|
||||
@@ -18,6 +21,13 @@ class IncomingConfiguration(metaclass=PoolMeta):
|
||||
class Incoming(metaclass=PoolMeta):
|
||||
__name__ = 'document.incoming'
|
||||
|
||||
created_at = fields.Function(
|
||||
fields.Date("Created At"),
|
||||
'get_created_at')
|
||||
result_notes = fields.Function(
|
||||
fields.Text("Result Notes"),
|
||||
'get_result_notes')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
@@ -25,21 +35,32 @@ class Incoming(metaclass=PoolMeta):
|
||||
('weight_report', "Weight Report"))
|
||||
cls.type.selection.append(
|
||||
('controller', "Controller"))
|
||||
cls._order = [('create_date', 'DESC')]
|
||||
|
||||
@classmethod
|
||||
def _get_results(cls):
|
||||
return super()._get_results() | {'automation.document'}
|
||||
|
||||
def _process_weight_report(self):
|
||||
logger.info(
|
||||
"DOCUMENT_INCOMING_WR_START incoming=%s name=%s type=%s "
|
||||
"data_size=%s",
|
||||
getattr(self, 'id', None), getattr(self, 'name', None),
|
||||
getattr(self, 'type', None),
|
||||
len(getattr(self, 'data', None) or b""))
|
||||
WR = Pool().get('automation.document')
|
||||
wr = WR()
|
||||
wr.document = self.id
|
||||
wr.type = 'weight_report'
|
||||
wr.state = 'draft'
|
||||
WR.save([wr])
|
||||
logger.info(
|
||||
"DOCUMENT_INCOMING_WR_AUTOMATION_CREATED incoming=%s "
|
||||
"automation_document=%s",
|
||||
getattr(self, 'id', None), getattr(wr, 'id', None))
|
||||
WR.run_ocr([wr])
|
||||
WR.run_metadata([wr])
|
||||
|
||||
WR.run_pipeline([wr])
|
||||
return wr
|
||||
|
||||
def _process_controller(self):
|
||||
@@ -54,6 +75,25 @@ class Incoming(metaclass=PoolMeta):
|
||||
|
||||
return wr
|
||||
|
||||
def get_created_at(self, name=None):
|
||||
create_date = getattr(self, 'create_date', None)
|
||||
if not create_date:
|
||||
return None
|
||||
return create_date.date()
|
||||
|
||||
@classmethod
|
||||
def order_created_at(cls, tables):
|
||||
table, _ = tables[None]
|
||||
return [table.create_date]
|
||||
|
||||
def get_result_notes(self, name=None):
|
||||
result = getattr(self, 'result', None)
|
||||
if not result:
|
||||
return ''
|
||||
if getattr(result, '__name__', None) == 'automation.document':
|
||||
return getattr(result, 'notes', '') or ''
|
||||
return ''
|
||||
|
||||
# @property
|
||||
# def supplier_invoice_company(self):
|
||||
# pass
|
||||
|
||||
@@ -8,5 +8,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="inherit" ref="document_incoming.document_incoming_configuration_view_form"/>
|
||||
<field name="name">document_incoming_configuration_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="document_incoming_view_list_wr">
|
||||
<field name="model">document.incoming</field>
|
||||
<field name="inherit" ref="document_incoming.document_incoming_view_list"/>
|
||||
<field name="name">document_incoming_list</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
version=7.2.0
|
||||
depends:
|
||||
document_incoming
|
||||
automation
|
||||
ir
|
||||
party
|
||||
xml:
|
||||
document.xml
|
||||
view/document_incoming_list.xml
|
||||
|
||||
11
modules/document_incoming_wr/view/document_incoming_list.xml
Normal file
11
modules/document_incoming_wr/view/document_incoming_list.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/tree/field[@name='name']" position="before">
|
||||
<field name="created_at" expand="2"/>
|
||||
</xpath>
|
||||
<xpath expr="/tree/field[@name='result']" position="after">
|
||||
<field name="result_notes" expand="2"/>
|
||||
</xpath>
|
||||
</data>
|
||||
@@ -24,8 +24,14 @@ class Lot(ModelSQL, ModelView):
|
||||
|
||||
lot_name = fields.Char("Lot")
|
||||
number = fields.Char("Number", readonly=True)
|
||||
lot_qt = fields.Float("Quantity",required=False)
|
||||
lot_unit = fields.Many2One('product.uom', "Unit",required=False)
|
||||
lot_qt = fields.Float("Quantity", required=False,
|
||||
states={
|
||||
'readonly': Eval('lot_type') == 'virtual',
|
||||
})
|
||||
lot_unit = fields.Many2One('product.uom', "Unit", required=False,
|
||||
states={
|
||||
'readonly': Eval('lot_type') == 'virtual',
|
||||
})
|
||||
lot_product = fields.Many2One('product.product', "Product")
|
||||
lot_type = fields.Selection([
|
||||
('virtual', 'Open'),
|
||||
@@ -486,8 +492,7 @@ class Lot(ModelSQL, ModelView):
|
||||
st = state_id
|
||||
else:
|
||||
st = self.lot_state.id
|
||||
logger.info("GET_HIST_QT:%s",st)
|
||||
lot = [e for e in self.lot_hist if e.quantity_type.id == st][0]
|
||||
lot = [e for e in self.lot_hist if e.quantity_type.id == st][0]
|
||||
qt = round(lot.quantity,5)
|
||||
gross_qt = round(lot.gross_quantity,5)
|
||||
return qt, gross_qt
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<notebook colspan="4">
|
||||
<page string="General" id="general">
|
||||
<field name="lot_childs" colspan="4" mode="tree,form" view_ids="lot.lot_view_tree_sequence2,lot.lot_view_form"/>
|
||||
<field name="lot_hist" colspan="4"/>
|
||||
<field name="lot_hist" colspan="4" readonly="1"/>
|
||||
</page>
|
||||
<page string="Accounting" id="accounting">
|
||||
<field name="pivot" widget="html_viewer" height="600" colspan="4"/>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<form col="4">
|
||||
<label name="lot"/>
|
||||
<field name="lot"/>
|
||||
<field name="lot" readonly="1"/>
|
||||
<label name="quantity_type"/>
|
||||
<field name="quantity_type"/>
|
||||
<field name="quantity_type" readonly="1"/>
|
||||
<label name="quantity"/>
|
||||
<field name="quantity"/>
|
||||
<field name="quantity" readonly="1"/>
|
||||
<label name="gross_quantity"/>
|
||||
<field name="gross_quantity"/>
|
||||
<field name="gross_quantity" readonly="1"/>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<tree editable="1">
|
||||
<tree>
|
||||
<field name="lot"/>
|
||||
<field name="quantity_type"/>
|
||||
<field name="quantity"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<tree editable="1">
|
||||
<tree>
|
||||
<field name="lot"/>
|
||||
<field name="quantity_type"/>
|
||||
<field name="quantity"/>
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data grouped="1">
|
||||
<record model="ir.message" id="msg_price_value_unique">
|
||||
<field name="text">Only one price value is allowed for each price index and price date.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_party_code_unique">
|
||||
<field name="text">The code on party must be unique.</field>
|
||||
</record>
|
||||
|
||||
@@ -36,30 +36,51 @@ class Price(
|
||||
('composite', 'Composite'),
|
||||
], 'Index type')
|
||||
price_type = fields.Many2One('price.fixtype', "Fixation type")
|
||||
price_unit = fields.Many2One('product.uom', "Unit")
|
||||
price_currency = fields.Many2One('currency.currency', "Currency")
|
||||
price_area = fields.Many2One('price.area',"Market area")
|
||||
price_calendar = fields.Many2One('price.calendar',"Calendar")
|
||||
price_values = fields.One2Many('price.price_value', 'price', "Prices Values")
|
||||
price_composite = fields.One2Many('price.composite','price',"Composites")
|
||||
price_product = fields.One2Many('price.product', 'price', "Product")
|
||||
price_unit = fields.Many2One('product.uom', "Unit")
|
||||
price_currency = fields.Many2One('currency.currency', "Currency")
|
||||
enable_linked_currency = fields.Boolean("Linked currency")
|
||||
linked_currency = fields.Many2One('currency.linked', "Linked currency",
|
||||
domain=[
|
||||
('currency', '=', Eval('price_currency')),
|
||||
],
|
||||
states={
|
||||
'invisible': ~Eval('enable_linked_currency'),
|
||||
'required': Eval('enable_linked_currency'),
|
||||
}, depends=['enable_linked_currency', 'price_currency'])
|
||||
price_area = fields.Many2One('price.area',"Market area")
|
||||
price_calendar = fields.Many2One('price.calendar',"Calendar")
|
||||
price_values = fields.One2Many('price.price_value', 'price', "Prices Values")
|
||||
price_composite = fields.One2Many('price.composite','price',"Composites")
|
||||
price_product = fields.One2Many('price.product', 'price', "Product")
|
||||
price_ct_size = fields.Numeric("Ct size")
|
||||
|
||||
def get_qt(self,nb_ct,unit):
|
||||
Uom = Pool().get('product.uom')
|
||||
return round(Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit)),4)
|
||||
|
||||
def get_price_per_qt(self,price,unit,currency):
|
||||
price_qt = float(0)
|
||||
Uom = Pool().get('product.uom')
|
||||
Currency = Pool().get('currency.currency')
|
||||
if currency != self.price_currency:
|
||||
rates = Currency._get_rate([self.price_currency])
|
||||
if rates[self.price_currency.id]:
|
||||
price_qt = float(price) * Uom.compute_qty(unit, float(1), self.price_unit) * float(rates[self.price_currency.id])
|
||||
else:
|
||||
price_qt = float(price) * Uom.compute_qty(unit, float(1), self.price_unit)
|
||||
return round(price_qt,4)
|
||||
def get_qt(self,nb_ct,unit):
|
||||
Uom = Pool().get('product.uom')
|
||||
return round(Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit)),4)
|
||||
|
||||
def _price_in_main_currency(self, price):
|
||||
price = Decimal(str(price or 0))
|
||||
if self.enable_linked_currency and self.linked_currency:
|
||||
factor = Decimal(self.linked_currency.factor or 0)
|
||||
if factor:
|
||||
price *= factor
|
||||
return price
|
||||
|
||||
def get_price_per_qt(self,price,unit,currency):
|
||||
price_qt = Decimal(0)
|
||||
Uom = Pool().get('product.uom')
|
||||
Currency = Pool().get('currency.currency')
|
||||
price = self._price_in_main_currency(price)
|
||||
unit_factor = Decimal(str(
|
||||
Uom.compute_qty(unit, float(1), self.price_unit) or 0))
|
||||
if currency != self.price_currency:
|
||||
rates = Currency._get_rate([self.price_currency])
|
||||
if rates[self.price_currency.id]:
|
||||
price_qt = price * unit_factor * Decimal(str(
|
||||
rates[self.price_currency.id]))
|
||||
else:
|
||||
price_qt = price * unit_factor
|
||||
return round(price_qt,4)
|
||||
|
||||
def get_amount_nb_ct(self,price,nb_ct,unit,currency):
|
||||
amount = Decimal(0)
|
||||
@@ -68,17 +89,22 @@ class Price(
|
||||
amount = Decimal(self.get_price_per_qt(price,unit,currency)) * Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit))
|
||||
return round(amount,4)
|
||||
|
||||
def get_price(self,dt,unit,currency,last=False):
|
||||
price = float(0)
|
||||
PV = Pool().get('price.price_value')
|
||||
if self.price_values:
|
||||
dt = dt.strftime("%Y-%m-%d")
|
||||
pv = PV.search([('price','=',self.id),('price_date','=',dt)])
|
||||
if not pv and last:
|
||||
pv = PV.search([('price','=',self.id)],order=[('price_date', 'DESC')])
|
||||
if pv:
|
||||
price = self.get_price_per_qt(pv[0].price_value,unit,currency)
|
||||
return round(price,4)
|
||||
def get_price(self,dt,unit,currency,last=False, relative_last=False):
|
||||
price = float(0)
|
||||
PV = Pool().get('price.price_value')
|
||||
if self.price_values:
|
||||
dt = dt.strftime("%Y-%m-%d")
|
||||
pv = PV.search([('price','=',self.id),('price_date','=',dt)])
|
||||
if not pv and last:
|
||||
pv = PV.search([('price','=',self.id)],order=[('price_date', 'DESC')])
|
||||
elif not pv and relative_last:
|
||||
pv = PV.search([
|
||||
('price','=',self.id),
|
||||
('price_date','<=',dt),
|
||||
], order=[('price_date', 'DESC')])
|
||||
if pv:
|
||||
price = self.get_price_per_qt(pv[0].price_value,unit,currency)
|
||||
return round(price,4)
|
||||
|
||||
class FixType(ModelSQL,ModelView):
|
||||
"Fixation type"
|
||||
|
||||
@@ -9,7 +9,7 @@ from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
DeactivableMixin, Index, ModelSQL, ModelView, MultiValueMixin, Unique,
|
||||
ValueMixin, convert_from, fields, sequence_ordered)
|
||||
from trytond.model.exceptions import AccessError, ValidationError
|
||||
from trytond.model.exceptions import AccessError
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Bool, Eval
|
||||
from trytond.tools import is_full_text, lstrip_wildcard
|
||||
@@ -35,37 +35,16 @@ class PriceValue(
|
||||
}), 'get_price_index')
|
||||
|
||||
price_date = fields.Date("Price date")
|
||||
price_value = fields.Float("Price value")
|
||||
open_price = fields.Float("Open price")
|
||||
low_price = fields.Float("Low price")
|
||||
price_value = fields.Float("Price value")
|
||||
open_price = fields.Float("Open price")
|
||||
low_price = fields.Float("Low price")
|
||||
mid_price = fields.Float("Mid price")
|
||||
high_price = fields.Float("High price")
|
||||
|
||||
@classmethod
|
||||
def validate(cls, price_values):
|
||||
super().validate(price_values)
|
||||
cls.check_unique_price_date(price_values)
|
||||
|
||||
@classmethod
|
||||
def check_unique_price_date(cls, price_values):
|
||||
domains = []
|
||||
for price_value in price_values:
|
||||
if not price_value.price or not price_value.price_date:
|
||||
continue
|
||||
domain = [
|
||||
('price', '=', price_value.price.id),
|
||||
('price_date', '=', price_value.price_date),
|
||||
]
|
||||
if price_value.id:
|
||||
domain.append(('id', '!=', price_value.id))
|
||||
domains.append(['AND'] + domain)
|
||||
if domains and cls.search(['OR'] + domains, limit=1):
|
||||
raise ValidationError(
|
||||
gettext('price.msg_price_value_unique'))
|
||||
|
||||
def get_price_index(self, name):
|
||||
if self.price:
|
||||
return self.price.price_index
|
||||
return None
|
||||
|
||||
def get_price_index(self, name):
|
||||
if self.price:
|
||||
return self.price.price_index
|
||||
return None
|
||||
|
||||
class PriceValueReport(
|
||||
ModelSQL, ModelView):
|
||||
@@ -77,9 +56,10 @@ class PriceValueReport(
|
||||
r_price_desc = fields.Char("Description")
|
||||
r_price_date = fields.Date("Price date")
|
||||
r_price_value = fields.Float("Price")
|
||||
r_open_price = fields.Float("Open price")
|
||||
r_low_price = fields.Float("Low price")
|
||||
r_high_price = fields.Float("High price")
|
||||
r_open_price = fields.Float("Open price")
|
||||
r_low_price = fields.Float("Low price")
|
||||
r_mid_price = fields.Float("Mid price")
|
||||
r_high_price = fields.Float("High price")
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
@@ -108,10 +88,11 @@ class PriceValueReport(
|
||||
pp.price_desc.as_('r_price_desc'),
|
||||
pv.price_date.as_('r_price_date'),
|
||||
pv.price_value.as_('r_price_value'),
|
||||
pv.open_price.as_('r_open_price'),
|
||||
pv.low_price.as_('r_low_price'),
|
||||
pv.high_price.as_('r_high_price'),
|
||||
where=wh)
|
||||
pv.open_price.as_('r_open_price'),
|
||||
pv.low_price.as_('r_low_price'),
|
||||
pv.mid_price.as_('r_mid_price'),
|
||||
pv.high_price.as_('r_high_price'),
|
||||
where=wh)
|
||||
|
||||
return query
|
||||
|
||||
@@ -138,9 +119,10 @@ class PriceValueReport2(
|
||||
r_price_desc = fields.Char("Description")
|
||||
r_price_date = fields.Date("Price date")
|
||||
r_price_value = fields.Float("Price")
|
||||
r_open_price = fields.Float("Open price")
|
||||
r_low_price = fields.Float("Low price")
|
||||
r_high_price = fields.Float("High price")
|
||||
r_open_price = fields.Float("Open price")
|
||||
r_low_price = fields.Float("Low price")
|
||||
r_mid_price = fields.Float("Mid price")
|
||||
r_high_price = fields.Float("High price")
|
||||
r_naphta_fob_low = fields.Float("Naph F Med l", digits=(1,2), help="Naphta Fob PAAAI00 low")
|
||||
r_naphta_fob_high = fields.Float("Naph F Med h", digits=(1,2), help="Naphta Fob PAAAI00 high")
|
||||
r_naphta_fob_mid = fields.Float("Naph F Med m", digits=(1,2), help="Naphta Fob PAAAI00 mid")
|
||||
@@ -193,9 +175,10 @@ class PriceValueReport2(
|
||||
Max(pp.price_desc).as_('r_price_desc'),
|
||||
pv.price_date.as_('r_price_date'),
|
||||
Max(pv.price_value).as_('r_price_value'),
|
||||
Max(pv.open_price).as_('r_open_price'),
|
||||
Max(pv.low_price).as_('r_low_price'),
|
||||
Max(pv.high_price).as_('r_high_price'),
|
||||
Max(pv.open_price).as_('r_open_price'),
|
||||
Max(pv.low_price).as_('r_low_price'),
|
||||
Max(pv.mid_price).as_('r_mid_price'),
|
||||
Max(pv.high_price).as_('r_high_price'),
|
||||
Max(Case((pv.price==15, pv.low_price),else_=0)).as_('r_naphta_fob_low'),
|
||||
Max(Case((pv.price==15, pv.high_price),else_=0)).as_('r_naphta_fob_high'),
|
||||
Max(Case((pv.price==15, (pv.high_price-pv.low_price)/2 + pv.low_price),else_=0)).as_('r_naphta_fob_mid'),
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
<field name="price_type"/>
|
||||
<label name="price_unit"/>
|
||||
<field name="price_unit"/>
|
||||
<label name="price_currency"/>
|
||||
<field name="price_currency"/>
|
||||
<label name="price_area"/>
|
||||
<field name="price_area"/>
|
||||
<label name="price_currency"/>
|
||||
<field name="price_currency"/>
|
||||
<label name="enable_linked_currency"/>
|
||||
<field name="enable_linked_currency"/>
|
||||
<label name="linked_currency"/>
|
||||
<field name="linked_currency"/>
|
||||
<label name="price_area"/>
|
||||
<field name="price_area"/>
|
||||
<label name="price_calendar"/>
|
||||
<field name="price_calendar"/>
|
||||
<label name="price_ct_size"/>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
<tree>
|
||||
<field name="r_price_index"/>
|
||||
<field name="r_price_desc"/>
|
||||
<field name="r_price_date"/>
|
||||
<field name="r_price_value"/>
|
||||
<field name="r_low_price"/>
|
||||
<field name="r_high_price"/>
|
||||
</tree>
|
||||
<field name="r_price_date"/>
|
||||
<field name="r_price_value"/>
|
||||
<field name="r_low_price"/>
|
||||
<field name="r_mid_price"/>
|
||||
<field name="r_high_price"/>
|
||||
</tree>
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
<label name="price_value"/>
|
||||
<field name="price_value"/>
|
||||
<label name="open_price"/>
|
||||
<field name="open_price"/>
|
||||
<label name="low_price"/>
|
||||
<field name="low_price"/>
|
||||
<label name="high_price"/>
|
||||
<field name="high_price"/>
|
||||
<field name="open_price"/>
|
||||
<label name="low_price"/>
|
||||
<field name="low_price"/>
|
||||
<label name="mid_price"/>
|
||||
<field name="mid_price"/>
|
||||
<label name="high_price"/>
|
||||
<field name="high_price"/>
|
||||
</group>
|
||||
</form>
|
||||
|
||||
@@ -9,6 +9,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="price_value" fill="1" empty="0"/>
|
||||
<field name="open_price" fill="1" empty="0"/>
|
||||
<field name="low_price" fill="1" empty="0"/>
|
||||
<field name="mid_price" fill="1" empty="0"/>
|
||||
<field name="high_price" fill="1" empty="0"/>
|
||||
</y>
|
||||
</graph>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
<tree>
|
||||
<field name="price_index"/>
|
||||
<field name="price_date"/>
|
||||
<field name="price_value"/>
|
||||
<field name="open_price"/>
|
||||
<field name="low_price"/>
|
||||
<field name="high_price"/>
|
||||
</tree>
|
||||
<field name="price_value"/>
|
||||
<field name="open_price"/>
|
||||
<field name="low_price"/>
|
||||
<field name="mid_price"/>
|
||||
<field name="high_price"/>
|
||||
</tree>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
<tree editable="1">
|
||||
<field name="price_index"/>
|
||||
<field name="price_date"/>
|
||||
<field name="price_value"/>
|
||||
<field name="open_price"/>
|
||||
<field name="low_price"/>
|
||||
<field name="high_price"/>
|
||||
</tree>
|
||||
<field name="price_value"/>
|
||||
<field name="open_price"/>
|
||||
<field name="low_price"/>
|
||||
<field name="mid_price"/>
|
||||
<field name="high_price"/>
|
||||
</tree>
|
||||
|
||||
@@ -519,6 +519,8 @@ class Purchase(
|
||||
|
||||
@property
|
||||
def taxable_lines(self):
|
||||
Date = Pool().get('ir.date')
|
||||
tax_date = getattr(self, 'purchase_date', None) or Date.today()
|
||||
taxable_lines = []
|
||||
# In case we're called from an on_change we have to use some sensible
|
||||
# defaults
|
||||
@@ -529,7 +531,7 @@ class Purchase(
|
||||
getattr(line, 'taxes', None) or [],
|
||||
getattr(line, 'unit_price', None) or Decimal(0),
|
||||
getattr(line, 'quantity', None) or 0,
|
||||
None,
|
||||
tax_date,
|
||||
))
|
||||
return taxable_lines
|
||||
|
||||
@@ -953,19 +955,22 @@ class Purchase(
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('confirmed')
|
||||
@set_employee('confirmed_by')
|
||||
# @Workflow.transition('confirmed')
|
||||
# @set_employee('confirmed_by')
|
||||
def confirm(cls, purchases):
|
||||
pool = Pool()
|
||||
Line = pool.get('purchase.line')
|
||||
transaction = Transaction()
|
||||
context = transaction.context
|
||||
cls.set_purchase_date(purchases)
|
||||
|
||||
cls.write(purchases, {'state': 'confirmed'})
|
||||
lines = list(sum((p.lines for p in purchases), ()))
|
||||
Line._validate(lines, ['unit_price'])
|
||||
|
||||
# Confirm is intentionally disabled: clicking the button must not move
|
||||
# the purchase to Confirmed or trigger downstream processing.
|
||||
pass
|
||||
# pool = Pool()
|
||||
# Line = pool.get('purchase.line')
|
||||
# cls.set_purchase_date(purchases)
|
||||
#
|
||||
# cls.write(purchases, {'state': 'confirmed'})
|
||||
# lines = list(sum((p.lines for p in purchases), ()))
|
||||
# Line._validate(lines, ['unit_price'])
|
||||
#
|
||||
# transaction = Transaction()
|
||||
# context = transaction.context
|
||||
# cls.store_cache(purchases)
|
||||
# for process_after, sub_purchases in groupby(
|
||||
# purchases, lambda p: p.process_after):
|
||||
@@ -1013,16 +1018,22 @@ class Purchase(
|
||||
for purchase in purchases:
|
||||
logger.info("PROCESS_INVOICE:%s",action)
|
||||
invoice = purchase.create_invoice(lots,action)
|
||||
if not invoice:
|
||||
logger.info(
|
||||
"PROCESS_INVOICE_NO_INVOICE: purchase=%s action=%s",
|
||||
getattr(purchase, 'id', None), action)
|
||||
continue
|
||||
if action == 'prov':
|
||||
invoice.reference = 'Provisional'
|
||||
elif action == 'service':
|
||||
invoice.reference = 'Service'
|
||||
else:
|
||||
invoice.reference = 'Final'
|
||||
if invoice:
|
||||
invoices[purchase] = invoice
|
||||
invoices[purchase] = invoice
|
||||
|
||||
cls._save_invoice(invoices,prepayment)
|
||||
if invoices:
|
||||
cls._save_invoice(invoices,prepayment)
|
||||
return invoices
|
||||
|
||||
@classmethod
|
||||
def _save_invoice(cls, invoices,prepayment=None):
|
||||
@@ -1851,6 +1862,83 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
|
||||
def on_change_with_currency(self, name=None):
|
||||
return self.purchase.currency if self.purchase else None
|
||||
|
||||
@staticmethod
|
||||
def _record_id(record):
|
||||
return getattr(record, 'id', record)
|
||||
|
||||
@classmethod
|
||||
def _get_last_fee_invoice_line(cls, fee, lot):
|
||||
fee_id = cls._record_id(fee)
|
||||
lot_id = cls._record_id(lot)
|
||||
if not fee_id or not lot_id:
|
||||
return
|
||||
InvoiceLine = Pool().get('account.invoice.line')
|
||||
lines = InvoiceLine.search([
|
||||
('fee', '=', fee_id),
|
||||
('lot', '=', lot_id),
|
||||
('quantity', '>', 0),
|
||||
('invoice.state', '!=', 'cancelled'),
|
||||
], order=[
|
||||
('invoice.invoice_date', 'DESC'),
|
||||
('invoice.id', 'DESC'),
|
||||
('id', 'DESC'),
|
||||
], limit=1)
|
||||
return lines[0] if lines else None
|
||||
|
||||
@staticmethod
|
||||
def _fee_invoice_line_changed(previous_line, invoice_line):
|
||||
if not previous_line:
|
||||
return True
|
||||
previous_quantity = Decimal(str(previous_line.quantity or 0))
|
||||
quantity = Decimal(str(invoice_line.quantity or 0))
|
||||
previous_price = Decimal(str(previous_line.unit_price or 0))
|
||||
price = Decimal(str(invoice_line.unit_price or 0))
|
||||
return previous_quantity != quantity or previous_price != price
|
||||
|
||||
@classmethod
|
||||
def _get_fee_reversal_invoice_line(cls, previous_line, origin):
|
||||
if not previous_line:
|
||||
return
|
||||
InvoiceLine = Pool().get('account.invoice.line')
|
||||
reversal_line, = InvoiceLine.copy([previous_line], default={
|
||||
'invoice': None,
|
||||
'quantity': -previous_line.quantity,
|
||||
'unit_price': previous_line.unit_price,
|
||||
'party': previous_line.invoice.party,
|
||||
'origin': str(origin),
|
||||
})
|
||||
return reversal_line
|
||||
|
||||
def _get_service_fee_invoice_lines(self, invoice_line, lot):
|
||||
Fee = Pool().get('fee.fee')
|
||||
fee = Fee.search(['purchase','=',self.purchase.id])
|
||||
if not fee:
|
||||
return [invoice_line]
|
||||
|
||||
fee = fee[0]
|
||||
invoice_line.fee = fee
|
||||
fee.warn_percent_price_partial_lots()
|
||||
if fee.mode == 'lumpsum':
|
||||
invoice_line.quantity = 1
|
||||
elif fee.mode == 'pprice':
|
||||
invoice_line.quantity = 1
|
||||
elif fee.mode == 'ppack':
|
||||
invoice_line.quantity = fee.quantity
|
||||
else:
|
||||
invoice_line.quantity = fee.get_fee_lots_qt()
|
||||
|
||||
if getattr(fee, 'state', None) != 'invoiced':
|
||||
return [invoice_line]
|
||||
|
||||
previous_line = self._get_last_fee_invoice_line(fee, lot)
|
||||
if not self._fee_invoice_line_changed(previous_line, invoice_line):
|
||||
return []
|
||||
reversal_line = self._get_fee_reversal_invoice_line(
|
||||
previous_line, self)
|
||||
if reversal_line:
|
||||
return [reversal_line, invoice_line]
|
||||
return [invoice_line]
|
||||
|
||||
def get_invoice_line(self,lots=None,action=None):
|
||||
'Return a list of invoice line for purchase line'
|
||||
pool = Pool()
|
||||
@@ -1930,21 +2018,9 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
|
||||
invoice_line.unit_price = self.unit_price
|
||||
invoice_line.product = self.product
|
||||
invoice_line.stock_moves = []
|
||||
Fee = Pool().get('fee.fee')
|
||||
fee = Fee.search(['purchase','=',self.purchase.id])
|
||||
if fee:
|
||||
invoice_line.fee = fee[0]
|
||||
if fee[0].mode == 'lumpsum':
|
||||
invoice_line.quantity = 1
|
||||
elif fee[0].mode == 'ppack':
|
||||
invoice_line.quantity = fee[0].quantity
|
||||
else:
|
||||
state_id = 0
|
||||
LotQtType = Pool().get('lot.qt.type')
|
||||
lqt = LotQtType.search([('name','=','BL')])
|
||||
if lqt:
|
||||
state_id = lqt[0].id
|
||||
invoice_line.quantity = fee[0].get_fee_lots_qt(state_id)
|
||||
lines.extend(
|
||||
self._get_service_fee_invoice_lines(invoice_line, l))
|
||||
continue
|
||||
|
||||
lines.append(invoice_line)
|
||||
logger.info("GETINVLINE:%s",self.product.type)
|
||||
|
||||
@@ -91,26 +91,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="view" ref="purchase_view_form"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_service">
|
||||
<field name="name">Services</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain" eval="[('line_type', '=', 'service')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_draft">
|
||||
<field name="name">Draft</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain" eval="['AND',('state', '=', 'draft'),('line_type', '=', 'goods')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_quotation">
|
||||
<field name="name">Validation</field>
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="name">Validated</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain" eval="['AND',('state', '=', 'quotation'),('line_type', '=', 'goods')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
@@ -118,7 +102,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_confirmed">
|
||||
<field name="name">Confirmed</field>
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="domain" eval="['AND',('state', '=', 'confirmed'),('line_type', '=', 'goods')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
@@ -126,15 +110,31 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_processing">
|
||||
<field name="name">Processing</field>
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="domain" eval="['AND',('state', '=', 'processing'),('line_type', '=', 'goods')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_draft">
|
||||
<field name="name">Draft</field>
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="domain" eval="['AND',('state', '=', 'draft'),('line_type', '=', 'goods')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_service">
|
||||
<field name="name">Services</field>
|
||||
<field name="sequence" eval="50"/>
|
||||
<field name="domain" eval="[('line_type', '=', 'service')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_purchase_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_purchase_form_domain_exception">
|
||||
<field name="name">Exception</field>
|
||||
<field name="sequence" eval="50"/>
|
||||
<field name="sequence" eval="60"/>
|
||||
<field name="domain"
|
||||
eval="['OR', ('invoice_state', '=', 'exception'), ('shipment_state', '=', 'exception')]"
|
||||
pyson="1"/>
|
||||
@@ -153,7 +153,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
action="act_purchase_form"
|
||||
sequence="10"
|
||||
id="menu_purchase_form"/>
|
||||
|
||||
<record model="ir.action.act_window" id="act_purchase_invoice_relate">
|
||||
<field name="name">Purchases</field>
|
||||
<field name="res_model">purchase.purchase</field>
|
||||
|
||||
@@ -85,7 +85,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<button name="handle_invoice_exception" icon="tryton-forward"/>
|
||||
<button name="handle_shipment_exception" icon="tryton-forward"/>
|
||||
<button name="confirm" icon="tryton-ok"/>
|
||||
<button name="process"/>
|
||||
<button name="process" invisible="1"/>
|
||||
<button name="manual_invoice" icon="tryton-forward"/>
|
||||
</group>
|
||||
<field name="party_lang" invisible="1" colspan="6"/>
|
||||
|
||||
@@ -2,18 +2,4 @@
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="//field[@name='warehouse']" position="after">
|
||||
<label name="carrier"/>
|
||||
<field name="carrier"/>
|
||||
</xpath>
|
||||
<xpath expr="//page[@id='other']" position="before">
|
||||
<page id="costs" string="Costs">
|
||||
<label name="cost_used"/>
|
||||
<group id="cost_used" col="-1" colspan="3">
|
||||
<field name="cost_used" xexpand="0"/>
|
||||
<field name="cost_currency_used"/>
|
||||
<field name="cost_edit" xexpand="0" xalign="0"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
|
||||
254
modules/purchase_trade/AGENTS.md
Normal file
254
modules/purchase_trade/AGENTS.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# AGENTS.md - Module `purchase_trade`
|
||||
|
||||
Ce guide complete le `AGENTS.md` racine.
|
||||
Pour ce module, les regles locales ci-dessous priment.
|
||||
|
||||
## 1) Perimetre metier
|
||||
|
||||
Le module `purchase_trade` etend les flux achat/vente Tryton avec une logique
|
||||
de negoce physique:
|
||||
|
||||
- contrats d'achat (`purchase.purchase`, `purchase.line`)
|
||||
- contrats de vente (`sale.sale`, `sale.line`)
|
||||
- lots physiques et virtuels
|
||||
- matching achat/vente
|
||||
- shipments et execution logistique
|
||||
- frais (`fee.fee`)
|
||||
- templates de documents metier et facture
|
||||
|
||||
## 2) Fichiers pivots
|
||||
|
||||
- Contrats achat:
|
||||
- `modules/purchase_trade/purchase.py`
|
||||
- Contrats vente:
|
||||
- `modules/purchase_trade/sale.py`
|
||||
- Lots / matching / invoicing:
|
||||
- `modules/purchase_trade/lot.py`
|
||||
- Shipments / lien facture-lot:
|
||||
- `modules/purchase_trade/stock.py`
|
||||
- Fees:
|
||||
- `modules/purchase_trade/fee.py`
|
||||
- Bridge facture / templates:
|
||||
- `modules/purchase_trade/invoice.py`
|
||||
- Vues:
|
||||
- `modules/purchase_trade/view/*.xml`
|
||||
- Actions module:
|
||||
- `modules/purchase_trade/*.xml`
|
||||
- Manifest:
|
||||
- `modules/purchase_trade/tryton.cfg`
|
||||
|
||||
## 3) Documentation locale a lire en priorite
|
||||
|
||||
- Regles metier:
|
||||
- `modules/purchase_trade/docs/business-rules.md`
|
||||
- Documentation business publiee dans le wiki:
|
||||
- `modules/purchase_trade/docs/business/*.md`
|
||||
- Sources de verite de la documentation business generee:
|
||||
- `modules/purchase_trade/docs_source/business/*.md`
|
||||
- Regles templates:
|
||||
- `modules/purchase_trade/docs/template-rules.md`
|
||||
- Catalogue des proprietes templates:
|
||||
- `modules/purchase_trade/docs/template-properties.md`
|
||||
- Padding facture provisoire vente / validation comptable:
|
||||
- `modules/purchase_trade/docs/padding-invoice-accounting.md`
|
||||
|
||||
## 4) Invariants metier a preserver
|
||||
|
||||
- Un lot `virtual` est la reference d'ouverture de quantite pour une `purchase.line`.
|
||||
- Une `sale.line` doit aussi avoir au minimum un lot `virtual`; une valuation
|
||||
cote sale ne doit donc pas disparaitre juste parce que le lot est `open`.
|
||||
- Le lot physique est le pont principal entre:
|
||||
- `purchase.line`
|
||||
- `sale.line`
|
||||
- shipment
|
||||
- facture
|
||||
- Pour remonter d'une facture vers shipment / BL / controller / fret:
|
||||
- privilegier le lot physique
|
||||
- ne pas multiplier des chemins d'acces concurrents
|
||||
- Pour les champs de colis (`NB BALES`) dans les templates facture:
|
||||
- la source de verite est `line.lot.lot_qt`
|
||||
- sur une facture, sommer les `lot_qt` des lignes de facture
|
||||
- tenir compte du signe de la ligne de facture pour les notes finales
|
||||
- ne pas proratiser depuis le poids (`net` / `gross`)
|
||||
- Le `FREIGHT VALUE` d'un template facture vient du `fee.fee` du shipment
|
||||
dont le produit est `Maritime freight`.
|
||||
- Pour `stock/insurance.fodt`, le `Amount insured` doit venir en priorite de
|
||||
`110%` du total des `incoming_moves` (fallback fee `Insurance` si aucun
|
||||
montant incoming calculable).
|
||||
- Pour le surveyor du certificat d'assurance shipment, la priorite est:
|
||||
`shipment.surveyor` -> `shipment.controller` -> fournisseur du fee
|
||||
`Insurance`.
|
||||
- Pour `payment_order.fodt`, utiliser des proprietes
|
||||
`invoice.report_payment_order_*` plutot que des tokens legacy `<...>`.
|
||||
- Ajouter un champ de template dans `Document Templates` ne rend pas le report
|
||||
visible dans la fiche: il faut aussi l'action `ir.action.report` +
|
||||
`ir.action.keyword` (`form_print`) cote `account.invoice`.
|
||||
- Le wizard `Create contracts` en mode `matched` peut maintenant partir de
|
||||
plusieurs `lot.qt`, mais doit conserver un matching par lot source et laisser
|
||||
`created_by_code = True` sur les lignes creees pour ne pas declencher les
|
||||
creations automatiques de lots dans les validations.
|
||||
- En valuation / PnL:
|
||||
- la valeur stockee dans `type` est la cle technique (`pur. priced`,
|
||||
`sale priced`, `pur. fee`, etc.), pas le label affiche dans l'UI
|
||||
- les references doivent rester coherentes avec le type de lot:
|
||||
`Purchase/Open`, `Purchase/Physic`, `Sale/Open`, `Sale/Physic`
|
||||
- pour une sale matchee, les lignes de valuation purchase generees sur un lot
|
||||
physique doivent aussi renseigner `sale` et `sale_line` afin de remonter
|
||||
dans l'onglet PnL de la sale
|
||||
- une sale non matchee doit etre valorisable "sale-first" et alimenter
|
||||
`valuation.valuation` / `valuation.valuation.line`
|
||||
- si une `sale.line` `basis` n'a ni `price_summary` ni `lot_price_sale`,
|
||||
creer quand meme une ligne `sale priced` avec `price = 0` et `amount = 0`
|
||||
plutot que de ne rien generer
|
||||
- le MTM ne doit etre renseigne que pour `pur. priced`, `sale priced` et
|
||||
`derivative`; jamais pour les fees
|
||||
- `mtm_price` doit afficher le prix brut de valorisation (sans ratio), alors
|
||||
que `mtm` reste le montant calcule selon la logique de strategie
|
||||
- En pricing:
|
||||
- le `unit_price` doit rester un prix de base, hors `premium`
|
||||
- le `premium` doit impacter le prix total economique et donc le `amount`,
|
||||
aussi bien en `priced` qu'en `basis`
|
||||
- dans `pricing.pricing` en saisie manuelle, l'utilisateur renseigne
|
||||
seulement `quantity` et `settl_price`
|
||||
- `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` et
|
||||
`eod_price` sont des valeurs derivees et ne doivent pas etre saisies a la
|
||||
main
|
||||
- en manuel, `fixed_qt` = cumul des `quantity` du groupe trie par
|
||||
`pricing_date`
|
||||
- en manuel, `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`
|
||||
- en manuel, `unfixed_qt_price` = `settl_price` de la ligne
|
||||
- pour les documents commerciaux / facture, une ligne `basis` affiche le
|
||||
`premium` comme prix visible, pas le prix economique total
|
||||
- si `linked currency` est active, le `premium` est saisi dans la devise /
|
||||
unite liee (ex: `USC/LB`) puis converti vers le repere de la ligne pour le
|
||||
calcul du `amount`
|
||||
- en `basis + linked currency`, le `linked_price` doit representer le prix
|
||||
basis brut (hors premium) dans la devise liee; le `unit_price` reste ce
|
||||
prix brut converti, et le `premium` converti est ajoute seulement dans
|
||||
l'`amount`
|
||||
- si `linked currency` est cochee, `linked_price`, `linked_currency` et
|
||||
`linked_unit` sont requis
|
||||
- dans les forms, presenter le bloc prix dans l'ordre:
|
||||
`price_type` -> linked fields -> `premium` -> `unit_price` -> `amount`
|
||||
- en valuation `basis`, le premium s'applique a chaque composant, pas
|
||||
uniquement a une ligne de resume
|
||||
- pour une ligne `basis` sans `price_summary`, la valuation fallback doit
|
||||
utiliser `unit_price + premium` (et pas `unit_price` seul)
|
||||
- a la validation d'une `sale.line`, si un lot virtuel est cree et qu'aucun
|
||||
matching purchase n'existe, il faut lancer `generate_from_sale_line()` pour
|
||||
alimenter le PnL sale-first
|
||||
- En padding de facture provisoire vente:
|
||||
- le padding est saisi globalement dans `lot.invoice`
|
||||
- il est uniquement applique aux factures provisoires cote vente
|
||||
- il est reparti par lot et stocke dans `lot.sale_invoice_padding`
|
||||
- la ligne facture affiche `Inc. padding` depuis le lot
|
||||
- le padding augmente `account.invoice.line.quantity`, donc le move principal
|
||||
de facture inclut deja le montant padding
|
||||
- les comptes de padding viennent de `account.configuration`:
|
||||
`Default Sale Padding` et `Default Accrual Padding`
|
||||
- la section `Padding` doit rester avant la section `Invoice` dans
|
||||
`Financial / Configuration`
|
||||
- la provisoire cree un `additional_move` debit `Default Sale Padding` /
|
||||
credit `Default Accrual Padding`
|
||||
- la finale cree l'inverse en reprenant le montant depuis
|
||||
`lot.sale_invoice_line_prov`, donc avec le prix, la devise, la date et le
|
||||
taux de la provisoire
|
||||
- le wizard final doit calculer son delta depuis la quantite provisoire hors
|
||||
padding: `sale_invoice_line_prov.quantity - sale_invoice_padding`
|
||||
- En modification de quantite contractuelle:
|
||||
- `quantity_theorical` est la source contractuelle; `quantity` peut refleter
|
||||
l'execute physique.
|
||||
- le `lot.qt` libre doit etre resynchronise sur le solde ouvert reel:
|
||||
`quantity_theorical - lots physiques - lot.qt deja matches/shippes`.
|
||||
- ne jamais ajuster le `lot.qt` libre uniquement par delta si des lots
|
||||
physiques existent deja.
|
||||
- les fees suivent les lots effectifs: physiques s'il en existe dans
|
||||
`fee.lots`, sinon virtuel.
|
||||
- conserver le lien `fee.lots` vers le virtuel comme fallback; ne pas le
|
||||
supprimer lors de la creation d'un physique.
|
||||
- le PnL fee applique la meme regle: full open tant qu'il n'y a que le
|
||||
virtuel, puis uniquement les physiques.
|
||||
- detail durable:
|
||||
`modules/purchase_trade/docs/business-rules.md` BR-PT-020 / BR-PT-021.
|
||||
- En execution controller / SLA:
|
||||
- les objectifs de repartition controller utilisent `party.execution.area`
|
||||
(`country.region`).
|
||||
- les couts SLA utilisent `party.execution.place` et peuvent matcher par
|
||||
`country`, par `location`, ou par couple `country + location`.
|
||||
- la creation du fee controller depuis shipment part de
|
||||
`shipment.to_location`; le pays de matching est
|
||||
`shipment.to_location.country`.
|
||||
- priorite cout SLA: couple pays+location, puis location seule, puis pays
|
||||
seul.
|
||||
|
||||
## 5) Conventions de modification
|
||||
|
||||
### Documentation business
|
||||
|
||||
- Ne pas modifier directement une page generee sous
|
||||
`modules/purchase_trade/docs/business/` si elle contient le commentaire
|
||||
`Generated from docs_source/business`.
|
||||
- Toute regle business nouvelle ou modifiee doit etre editee dans
|
||||
`modules/purchase_trade/docs_source/business/`.
|
||||
- Apres modification des sources business, regenerer le wiki avec:
|
||||
`python modules/purchase_trade/docs/tools/render_business_docs.py`
|
||||
- Avant de rendre une modification documentaire, verifier que le rendu publie
|
||||
est synchronise avec:
|
||||
`python modules/purchase_trade/docs/tools/render_business_docs.py --check`
|
||||
- Les pages francaises et anglaises miroir doivent rester synchronisees.
|
||||
- Le rendu publie doit rester lisible dans MkDocs meme sans extensions
|
||||
optionnelles: eviter les marqueurs bruts `!!!` et `:material-...:` dans les
|
||||
fichiers publies.
|
||||
|
||||
1. Modifier la logique metier dans le fichier pivot le plus proche.
|
||||
2. Si un template `.fodt` devient complexe, deplacer la logique dans une
|
||||
propriete Python `report_*`.
|
||||
3. Pour une facture trade, preferer enrichir `modules/purchase_trade/invoice.py`
|
||||
plutot que surcharger lourdement le `.fodt`.
|
||||
4. Si une regle metier durable change, mettre a jour
|
||||
`docs/business-rules.md`.
|
||||
5. Si une convention de template change, mettre a jour
|
||||
`docs/template-rules.md`.
|
||||
6. Pour les vues XML Tryton de ce module, utiliser `editable="1"` sur les
|
||||
`<tree>` editables; ne pas utiliser `editable="bottom"`.
|
||||
7. Ne pas ajouter `keyword_open` / `open_keyword` sur les vues `<tree>`:
|
||||
ces attributs ne sont pas supportes par Tryton.
|
||||
8. Si une regle de texte par defaut durable est demandee sur achat/vente,
|
||||
preferer un singleton de configuration expose dans un menu fonctionnel
|
||||
existant plutot qu'un menu technique `purchase_trade`.
|
||||
|
||||
## 6) Pieges connus
|
||||
|
||||
- Plusieurs actions de report `account.invoice` peuvent sembler rendre le meme
|
||||
document a cause du cache `invoice_report_cache`.
|
||||
- Les reports alternatifs (`Final Invoice`, `Prepayment`, etc.) ne doivent pas
|
||||
reutiliser le cache du report standard sans verification.
|
||||
- Pour les donnees achat/vente partagees, ne pas supposer qu'une facture de
|
||||
vente doit lire directement sur la `sale.line`: souvent, la verite metier
|
||||
passe par le lot physique et/ou la `account.invoice.line`.
|
||||
- Les templates `invoice_ict*` peuvent partager les memes proprietes Python;
|
||||
si une regle doit valoir pour provisional et final, la mettre dans
|
||||
`modules/purchase_trade/invoice.py` plutot que dupliquer dans les `.fodt`.
|
||||
- Dans les ecrans PnL, le label `Sale price` correspond au type stocke
|
||||
`sale priced`; idem pour `Pur. price` / `pur. priced`.
|
||||
- Une ligne `basis` sans resume de pricing peut sinon disparaitre de la
|
||||
valuation si aucun fallback explicite a `0` n'est prevu.
|
||||
- Le calcul du prix peut diverger entre `unit_price`, `linked_price`,
|
||||
`lot_price` et valuation si le premium n'est pas traite explicitement dans
|
||||
chaque maillon.
|
||||
- Sur `account.invoice`, le workflow `Validate` doit maintenant aligner
|
||||
fournisseur et client pour:
|
||||
- creation du `account.move`
|
||||
- attribution du `number`
|
||||
- `Post` ne doit pas reintroduire une difference de session/fresh login cote
|
||||
client
|
||||
- Pour le padding provisoire vente, ne pas changer la quantite physique du lot:
|
||||
l'ecart doit rester porte par `lot.sale_invoice_padding` et visible sur la
|
||||
facture via `Inc. padding`.
|
||||
|
||||
## 7) Definition of done (module `purchase_trade`)
|
||||
|
||||
- Le flux achat/vente/lot cible reste coherent.
|
||||
- Les impacts templates/facture ont ete verifies conceptuellement.
|
||||
- Les docs locales ont ete mises a jour si une nouvelle regle durable a emerge.
|
||||
- Le patch reste minimal et local au domaine demande.
|
||||
@@ -5,9 +5,11 @@ from trytond.pool import Pool
|
||||
|
||||
from . import (
|
||||
account,
|
||||
configuration,
|
||||
purchase,
|
||||
sale,
|
||||
global_reporting,
|
||||
ctrm_reporting,
|
||||
stock,
|
||||
derivative,
|
||||
lot,
|
||||
@@ -32,9 +34,11 @@ from . import (
|
||||
dimension,
|
||||
weight_report,
|
||||
backtoback,
|
||||
service,
|
||||
invoice,
|
||||
)
|
||||
service,
|
||||
invoice,
|
||||
duplicate,
|
||||
coffee,
|
||||
)
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
@@ -55,16 +59,76 @@ def register():
|
||||
lc.LCMT700,
|
||||
lc.LCMessage,
|
||||
lc.CreateLCStart,
|
||||
global_reporting.GRConfiguration,
|
||||
global_reporting.GRConfiguration,
|
||||
ctrm_reporting.CTRMPhysicalPosition,
|
||||
ctrm_reporting.CTRMPhysicalPositionContext,
|
||||
ctrm_reporting.CTRMFinancialPosition,
|
||||
ctrm_reporting.CTRMFinancialPositionContext,
|
||||
ctrm_reporting.CTRMNetPosition,
|
||||
ctrm_reporting.CTRMNetPositionContext,
|
||||
ctrm_reporting.CTRMOpenPosition,
|
||||
ctrm_reporting.CTRMOpenPositionContext,
|
||||
ctrm_reporting.CTRMLongShortProductStrategy,
|
||||
ctrm_reporting.CTRMLongShortCommodityDirection,
|
||||
ctrm_reporting.CTRMLongShortPeriodProduct,
|
||||
ctrm_reporting.CTRMLongShortPricingStatus,
|
||||
ctrm_reporting.CTRMLongShortDetail,
|
||||
ctrm_reporting.CTRMPositionExposure,
|
||||
ctrm_reporting.CTRMPositionExposureContext,
|
||||
ctrm_reporting.CTRMRealizedPnl,
|
||||
ctrm_reporting.CTRMRealizedPnlContext,
|
||||
ctrm_reporting.CTRMMtmPnl,
|
||||
ctrm_reporting.CTRMMtmPnlContext,
|
||||
ctrm_reporting.CTRMPnlExplain,
|
||||
ctrm_reporting.CTRMPnlExplainContext,
|
||||
ctrm_reporting.CTRMPnlDimension,
|
||||
ctrm_reporting.CTRMPnlDimensionContext,
|
||||
ctrm_reporting.CTRMCreditRisk,
|
||||
ctrm_reporting.CTRMCreditRiskContext,
|
||||
ctrm_reporting.CTRMShippingLogistics,
|
||||
ctrm_reporting.CTRMShippingLogisticsContext,
|
||||
ctrm_reporting.CTRMInventory,
|
||||
ctrm_reporting.CTRMInventoryContext,
|
||||
ctrm_reporting.CTRMContractPerformance,
|
||||
ctrm_reporting.CTRMContractPerformanceContext,
|
||||
ctrm_reporting.CTRMScheduling,
|
||||
ctrm_reporting.CTRMSchedulingContext,
|
||||
ctrm_reporting.CTRMAccruals,
|
||||
ctrm_reporting.CTRMAccrualsContext,
|
||||
ctrm_reporting.CTRMSettlements,
|
||||
ctrm_reporting.CTRMSettlementsContext,
|
||||
ctrm_reporting.CTRMCashFlow,
|
||||
ctrm_reporting.CTRMCashFlowContext,
|
||||
ctrm_reporting.CTRMGLReconciliation,
|
||||
ctrm_reporting.CTRMGLReconciliationContext,
|
||||
configuration.Configuration,
|
||||
pricing.ImportPricesStart,
|
||||
pricing.ImportPricesResult,
|
||||
coffee.CoffeeSample,
|
||||
coffee.CoffeeCuppingCriterion,
|
||||
coffee.CoffeeCuppingSession,
|
||||
coffee.CoffeeCuppingSessionSample,
|
||||
coffee.CoffeeCuppingCup,
|
||||
coffee.CoffeeCuppingResult,
|
||||
coffee.CoffeeCuppingResultLine,
|
||||
coffee.CoffeeLabAnalysis,
|
||||
coffee.PurchaseLine,
|
||||
coffee.SaleLine,
|
||||
duplicate.TradeCustomDuplicateStart,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
configuration.PurchaseConfiguration,
|
||||
module='purchase', type_='model')
|
||||
Pool.register(
|
||||
configuration.SaleConfiguration,
|
||||
module='sale', type_='model')
|
||||
Pool.register(
|
||||
incoming.ImportSwift,
|
||||
incoming.PrepareDocuments,
|
||||
incoming.AnalyzeConditions,
|
||||
lc.CreateLCWizard,
|
||||
pricing.ImportPrices,
|
||||
duplicate.TradeCustomDuplicate,
|
||||
module='purchase_trade', type_='wizard'
|
||||
)
|
||||
Pool.register(
|
||||
@@ -86,6 +150,15 @@ def register():
|
||||
party.PartyExecutionPlace,
|
||||
payment_term.PaymentTerm,
|
||||
payment_term.PaymentTermLine,
|
||||
stock.CharterRateType,
|
||||
stock.CharterConditionRate,
|
||||
stock.CharterConditionLaytimeStartRule,
|
||||
stock.CharterCondition,
|
||||
stock.CharterParty,
|
||||
purchase.ContractClause,
|
||||
purchase.ContractTemplate,
|
||||
purchase.ContractTemplateLine,
|
||||
purchase.ContractClauseSelection,
|
||||
purchase.Purchase,
|
||||
purchase.Line,
|
||||
purchase.Estimated,
|
||||
@@ -104,6 +177,7 @@ def register():
|
||||
open_position.OpenPositionContext,
|
||||
optional.OptionalScenario,
|
||||
fee.Fee,
|
||||
fee.FeeRule,
|
||||
fee.FeeLots,
|
||||
purchase.FeeLots,
|
||||
valuation.Valuation,
|
||||
@@ -111,6 +185,9 @@ def register():
|
||||
valuation.ValuationDyn,
|
||||
valuation.ValuationReport,
|
||||
valuation.ValuationReportContext,
|
||||
valuation.ValuationProcessDimension,
|
||||
valuation.ValuationProcessStart,
|
||||
valuation.ValuationProcessResult,
|
||||
derivative.Derivative,
|
||||
derivative.DerivativeMatch,
|
||||
derivative.MatchWizardStart,
|
||||
@@ -118,16 +195,18 @@ def register():
|
||||
derivative.DerivativeReportContext,
|
||||
fee.FeeReport,
|
||||
fee.FeeContext,
|
||||
forex.ForexCoverPhysicalContract,
|
||||
forex.PForex,
|
||||
forex.ForexBI,
|
||||
purchase.PnlBI,
|
||||
purchase.PositionBI,
|
||||
stock.Move,
|
||||
forex.ForexCoverPhysicalContract,
|
||||
forex.PForex,
|
||||
forex.ForexBI,
|
||||
purchase.PurchasePnlGraph,
|
||||
purchase.PnlBI,
|
||||
purchase.PositionBI,
|
||||
stock.Move,
|
||||
stock.Location,
|
||||
stock.InvoiceLine,
|
||||
stock.ShipmentIn,
|
||||
stock.ShipmentWR,
|
||||
stock.ShipmentIn,
|
||||
stock.ShipmentControllerEmailStart,
|
||||
stock.ShipmentWR,
|
||||
stock.ShipmentInternal,
|
||||
stock.ShipmentOut,
|
||||
stock.StatementOfFacts,
|
||||
@@ -135,16 +214,19 @@ def register():
|
||||
stock.ImportSoFStart,
|
||||
stock.RevaluateStart,
|
||||
stock.Account,
|
||||
stock.AccountMoveLine,
|
||||
stock.AccountMove,
|
||||
stock.ContainerType,
|
||||
stock.ShipmentContainer,
|
||||
lot.Lot,
|
||||
lot.LotQt,
|
||||
lot.LotReport,
|
||||
lot.LotContext,
|
||||
lot.LotShippingStart,
|
||||
lot.LotMatchingStart,
|
||||
stock.AccountMoveLine,
|
||||
stock.AccountMove,
|
||||
stock.ContainerType,
|
||||
stock.ShipmentContainer,
|
||||
lot.Lot,
|
||||
lot.LotQt,
|
||||
lot.LotReport,
|
||||
lot.LotContext,
|
||||
lot.LotShippingStart,
|
||||
lot.LotPlanTransportStart,
|
||||
lot.LotPlanTransportLine,
|
||||
lot.LotMarkFinishedStart,
|
||||
lot.LotMatchingStart,
|
||||
lot.LotWeighingStart,
|
||||
lot.LotAddLot,
|
||||
lot.LotInvoicingLot,
|
||||
@@ -154,10 +236,12 @@ def register():
|
||||
lot.LotImportLot,
|
||||
lot.LotInvoiceStart,
|
||||
lot.LotMatchingLot,
|
||||
lot.LotWeighingLot,
|
||||
lot.ContractsStart,
|
||||
lot.ContractDetail,
|
||||
lot.LotFCR,
|
||||
lot.LotWeighingLot,
|
||||
lot.ContractsStart,
|
||||
lot.ContractDetail,
|
||||
lot.MirrorStart,
|
||||
lot.MirrorLine,
|
||||
lot.LotFCR,
|
||||
lot.LotMove,
|
||||
lot.LotAccountingGraph,
|
||||
workflow.ExecutionPlan,
|
||||
@@ -173,17 +257,18 @@ def register():
|
||||
purchase.DocTypeTemplate,
|
||||
purchase.PurchaseStrategy,
|
||||
purchase.PriceComposition,
|
||||
purchase.PremiumComposition,
|
||||
purchase.QualityAnalysis,
|
||||
purchase.Assay,
|
||||
purchase.AssayLine,
|
||||
purchase.AssayElement,
|
||||
purchase.AssayUnit,
|
||||
purchase.PayableRule,
|
||||
purchase.PenaltyRule,
|
||||
purchase.PenaltyRuleTier,
|
||||
purchase.ConcentrateTerm,
|
||||
backtoback.Backtoback,
|
||||
dimension.AnalyticDimension,
|
||||
purchase.PenaltyRule,
|
||||
purchase.PenaltyRuleTier,
|
||||
purchase.ConcentrateTerm,
|
||||
backtoback.Backtoback,
|
||||
dimension.AnalyticDimension,
|
||||
dimension.AnalyticDimensionValue,
|
||||
dimension.AnalyticDimensionAssignment,
|
||||
weight_report.WeightReport,
|
||||
@@ -191,6 +276,13 @@ def register():
|
||||
Pool.register(
|
||||
account.PhysicalTradeIFRS,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
configuration.AccountConfiguration,
|
||||
configuration.AccountConfigurationDefaultAccount,
|
||||
invoice.InvoiceLineLotWeight,
|
||||
invoice.InvoicePaddingReport,
|
||||
invoice.InvoicePaddingContext,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
invoice.Invoice,
|
||||
invoice.InvoiceLine,
|
||||
@@ -216,6 +308,8 @@ def register():
|
||||
purchase.PurchaseCrop,
|
||||
module='lot', type_='model')
|
||||
Pool.register(
|
||||
sale.ContractClauseSelection,
|
||||
sale.CharterCondition,
|
||||
sale.Sale,
|
||||
sale.SaleLine,
|
||||
sale.SaleCreatePurchaseInput,
|
||||
@@ -238,37 +332,56 @@ def register():
|
||||
sale.OpenPosition,
|
||||
sale.Backtoback,
|
||||
sale.AnalyticDimensionAssignment,
|
||||
sale.PriceComposition,
|
||||
module='sale', type_='model')
|
||||
sale.PriceComposition,
|
||||
sale.PremiumComposition,
|
||||
module='sale', type_='model')
|
||||
Pool.register(
|
||||
lot.LotShipping,
|
||||
lot.LotMatching,
|
||||
lot.LotShipping,
|
||||
lot.LotPlanTransport,
|
||||
lot.LotMatching,
|
||||
lot.LotGoMatching,
|
||||
#lot.LotMatchingUnit,
|
||||
lot.LotWeighing,
|
||||
lot.CreateContracts,
|
||||
lot.LotUnmatch,
|
||||
lot.LotUnship,
|
||||
lot.LotWeighing,
|
||||
lot.CreateContracts,
|
||||
lot.CreateMirror,
|
||||
lot.LotUnmatch,
|
||||
lot.LotMarkFinished,
|
||||
lot.LotUnship,
|
||||
lot.LotRemove,
|
||||
lot.LotInvoice,
|
||||
lot.LotAdding,
|
||||
lot.LotImporting,
|
||||
stock.FindVessel,
|
||||
lot.LotAdding,
|
||||
lot.LotImporting,
|
||||
stock.ShipmentControllerEmail,
|
||||
stock.FindVessel,
|
||||
stock.SofUpdate,
|
||||
stock.Revaluate,
|
||||
purchase.GoToBi,
|
||||
purchase_prepayment.CreatePrepaymentWizard,
|
||||
purchase.PurchaseAllocationsWizard,
|
||||
purchase.InvoicePayment,
|
||||
stock.ImportSoFWizard,
|
||||
dashboard.BotWizard,
|
||||
dashboard.DashboardLoader,
|
||||
forex.ForexReport,
|
||||
purchase.PnlReport,
|
||||
stock.ImportSoFWizard,
|
||||
dashboard.BotWizard,
|
||||
dashboard.DashboardLoader,
|
||||
forex.ForexReport,
|
||||
purchase.PnlReport,
|
||||
purchase.PositionReport,
|
||||
valuation.ValuationProcess,
|
||||
derivative.DerivativeMatchWizard,
|
||||
module='purchase', type_='wizard')
|
||||
Pool.register(
|
||||
sale.SaleCreatePurchase,
|
||||
sale.SaleAllocationsWizard,
|
||||
module='sale', type_='wizard')
|
||||
Pool.register(
|
||||
invoice.InvoiceReport,
|
||||
invoice.SaleReport,
|
||||
invoice.PurchaseReport,
|
||||
stock.ShipmentShippingReport,
|
||||
stock.ShipmentInsuranceReport,
|
||||
stock.ShipmentCOOReport,
|
||||
stock.ShipmentPackingListReport,
|
||||
stock.ShipmentLinkageReport,
|
||||
stock.LotReportLinkageReport,
|
||||
stock.LotReportLinkageFinalReport,
|
||||
module='purchase_trade', type_='report')
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# account.py
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
|
||||
__all__ = ['PhysicalTradeIFRS']
|
||||
__all__ = [
|
||||
'PhysicalTradeIFRS',
|
||||
]
|
||||
__metaclass__ = PoolMeta
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="account_configuration_view_form">
|
||||
<field name="model">account.configuration</field>
|
||||
<field name="inherit" ref="account.configuration_view_form"/>
|
||||
<field name="name">account_configuration_form</field>
|
||||
</record>
|
||||
|
||||
<record model="res.group" id="group_physical_trade_ifrs">
|
||||
<field name="name">Physical Trade IFRS</field>
|
||||
</record>
|
||||
|
||||
1206
modules/purchase_trade/coffee.py
Normal file
1206
modules/purchase_trade/coffee.py
Normal file
File diff suppressed because it is too large
Load Diff
395
modules/purchase_trade/coffee.xml
Normal file
395
modules/purchase_trade/coffee.xml
Normal file
@@ -0,0 +1,395 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="coffee_sample_view_tree">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_sample_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_sample_view_form">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_sample_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_criterion_view_tree">
|
||||
<field name="model">coffee.cupping.criterion</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_cupping_criterion_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_criterion_view_form">
|
||||
<field name="model">coffee.cupping.criterion</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_cupping_criterion_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_session_view_tree">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_cupping_session_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_session_view_form">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_cupping_session_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_session_sample_view_tree">
|
||||
<field name="model">coffee.cupping.session.sample</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_cupping_session_sample_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_session_sample_view_form">
|
||||
<field name="model">coffee.cupping.session.sample</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_cupping_session_sample_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_cup_view_tree">
|
||||
<field name="model">coffee.cupping.cup</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_cupping_cup_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_cup_view_form">
|
||||
<field name="model">coffee.cupping.cup</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_cupping_cup_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_result_view_tree">
|
||||
<field name="model">coffee.cupping.result</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_cupping_result_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_result_view_form">
|
||||
<field name="model">coffee.cupping.result</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_cupping_result_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_result_line_view_tree">
|
||||
<field name="model">coffee.cupping.result.line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_cupping_result_line_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_cupping_result_line_view_form">
|
||||
<field name="model">coffee.cupping.result.line</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_cupping_result_line_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_lab_analysis_view_tree">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">coffee_lab_analysis_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="coffee_lab_analysis_view_form">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">coffee_lab_analysis_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_coffee_sample_form">
|
||||
<field name="name">Coffee Samples</field>
|
||||
<field name="res_model">coffee.sample</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_sample_form_view_tree">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="coffee_sample_view_tree"/>
|
||||
<field name="act_window" ref="act_coffee_sample_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_sample_form_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="coffee_sample_view_form"/>
|
||||
<field name="act_window" ref="act_coffee_sample_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_pending">
|
||||
<field name="name">Pending</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain" eval="[('state', 'in', ['requested', 'received', 'sent_to_lab', 'under_review'])]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_coffee_sample_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_expiring">
|
||||
<field name="name">Expiring soon</field>
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="domain" eval="[('expires_soon', '=', True)]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_coffee_sample_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_expired">
|
||||
<field name="name">Expired</field>
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="domain" eval="[('is_expired', '=', True)]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_coffee_sample_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_approved">
|
||||
<field name="name">Approved</field>
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="domain" eval="[('state', '=', 'approved')]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_coffee_sample_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_all">
|
||||
<field name="name">All</field>
|
||||
<field name="sequence" eval="9999"/>
|
||||
<field name="domain"></field>
|
||||
<field name="act_window" ref="act_coffee_sample_form"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_coffee_cupping_session_form">
|
||||
<field name="name">Cupping Sessions</field>
|
||||
<field name="res_model">coffee.cupping.session</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_cupping_session_form_view_tree">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="coffee_cupping_session_view_tree"/>
|
||||
<field name="act_window" ref="act_coffee_cupping_session_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_cupping_session_form_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="coffee_cupping_session_view_form"/>
|
||||
<field name="act_window" ref="act_coffee_cupping_session_form"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_coffee_cupping_criterion_form">
|
||||
<field name="name">Cupping Criteria</field>
|
||||
<field name="res_model">coffee.cupping.criterion</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_cupping_criterion_form_view_tree">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="coffee_cupping_criterion_view_tree"/>
|
||||
<field name="act_window" ref="act_coffee_cupping_criterion_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_cupping_criterion_form_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="coffee_cupping_criterion_view_form"/>
|
||||
<field name="act_window" ref="act_coffee_cupping_criterion_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_coffee_lab_analysis_form">
|
||||
<field name="name">Lab Analyses</field>
|
||||
<field name="res_model">coffee.lab.analysis</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_lab_analysis_form_view_tree">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="coffee_lab_analysis_view_tree"/>
|
||||
<field name="act_window" ref="act_coffee_lab_analysis_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_coffee_lab_analysis_form_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="coffee_lab_analysis_view_form"/>
|
||||
<field name="act_window" ref="act_coffee_lab_analysis_form"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Coffee" parent="purchase.menu_purchase" sequence="18"
|
||||
id="menu_coffee_purchase"/>
|
||||
<menuitem name="Samples" parent="menu_coffee_purchase" sequence="10"
|
||||
action="act_coffee_sample_form" id="menu_coffee_sample_purchase"/>
|
||||
<menuitem name="Lab Analyses" parent="menu_coffee_purchase"
|
||||
sequence="15" action="act_coffee_lab_analysis_form"
|
||||
id="menu_coffee_lab_analysis_purchase"/>
|
||||
<menuitem name="Cupping Sessions" parent="menu_coffee_purchase"
|
||||
sequence="20" action="act_coffee_cupping_session_form"
|
||||
id="menu_coffee_cupping_session_purchase"/>
|
||||
<menuitem name="Cupping Criteria" parent="menu_coffee_purchase"
|
||||
sequence="30" action="act_coffee_cupping_criterion_form"
|
||||
id="menu_coffee_cupping_criterion_purchase"/>
|
||||
<menuitem name="Coffee" parent="sale.menu_sale" sequence="18"
|
||||
id="menu_coffee_sale"/>
|
||||
<menuitem name="Samples" parent="menu_coffee_sale" sequence="10"
|
||||
action="act_coffee_sample_form" id="menu_coffee_sample_sale"/>
|
||||
<menuitem name="Lab Analyses" parent="menu_coffee_sale"
|
||||
sequence="15" action="act_coffee_lab_analysis_form"
|
||||
id="menu_coffee_lab_analysis_sale"/>
|
||||
<menuitem name="Cupping Sessions" parent="menu_coffee_sale"
|
||||
sequence="20" action="act_coffee_cupping_session_form"
|
||||
id="menu_coffee_cupping_session_sale"/>
|
||||
<menuitem name="Cupping Criteria" parent="menu_coffee_sale"
|
||||
sequence="30" action="act_coffee_cupping_criterion_form"
|
||||
id="menu_coffee_cupping_criterion_sale"/>
|
||||
<record model="ir.ui.menu" id="menu_coffee_purchase">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_sample_purchase">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_lab_analysis_purchase">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_cupping_session_purchase">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_cupping_criterion_purchase">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_sale">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_sample_sale">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_lab_analysis_sale">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_cupping_session_sale">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="menu_coffee_cupping_criterion_sale">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="coffee_sample_receive_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">receive</field>
|
||||
<field name="string">Receive</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_send_to_lab_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">send_to_lab</field>
|
||||
<field name="string">Send to lab</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_review_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">review</field>
|
||||
<field name="string">Review</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_approve_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">approve</field>
|
||||
<field name="string">Approve</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_reject_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">reject</field>
|
||||
<field name="string">Reject</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_evaluate_quality_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">evaluate_quality</field>
|
||||
<field name="string">Evaluate quality</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_expire_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">expire</field>
|
||||
<field name="string">Expire</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_archive_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">archive</field>
|
||||
<field name="string">Archive</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_sample_reset_to_requested_button">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="name">reset_to_requested</field>
|
||||
<field name="string">Reset</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_cupping_session_prepare_button">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="name">prepare</field>
|
||||
<field name="string">Prepare</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_cupping_session_start_button">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="name">start</field>
|
||||
<field name="string">Start</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_cupping_session_done_button">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="name">done</field>
|
||||
<field name="string">Done</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_cupping_session_cancel_button">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="name">cancel</field>
|
||||
<field name="string">Cancel</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_cupping_session_draft_button">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="name">draft</field>
|
||||
<field name="string">Draft</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_lab_analysis_send_button">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="name">send</field>
|
||||
<field name="string">Send</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_lab_analysis_receive_button">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="name">receive</field>
|
||||
<field name="string">Receive</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_lab_analysis_accept_button">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="name">accept</field>
|
||||
<field name="string">Accept</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_lab_analysis_reject_button">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="name">reject</field>
|
||||
<field name="string">Reject</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_lab_analysis_cancel_button">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="name">cancel</field>
|
||||
<field name="string">Cancel</field>
|
||||
</record>
|
||||
<record model="ir.model.button" id="coffee_lab_analysis_draft_button">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="name">draft</field>
|
||||
<field name="string">Draft</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_coffee_sample">
|
||||
<field name="model">coffee.sample</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_coffee_cupping_criterion">
|
||||
<field name="model">coffee.cupping.criterion</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_coffee_cupping_session">
|
||||
<field name="model">coffee.cupping.session</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_coffee_cupping_session_sample">
|
||||
<field name="model">coffee.cupping.session.sample</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_coffee_cupping_cup">
|
||||
<field name="model">coffee.cupping.cup</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_coffee_cupping_result">
|
||||
<field name="model">coffee.cupping.result</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_coffee_cupping_result_line">
|
||||
<field name="model">coffee.cupping.result.line</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_coffee_lab_analysis">
|
||||
<field name="model">coffee.lab.analysis</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
154
modules/purchase_trade/company_defaults.py
Normal file
154
modules/purchase_trade/company_defaults.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
import datetime
|
||||
|
||||
|
||||
ITSA_COMPANY_NAME = 'ITSA'
|
||||
|
||||
|
||||
def record_id(record):
|
||||
return getattr(record, 'id', record)
|
||||
|
||||
|
||||
def is_itsa_company(company=None):
|
||||
if company is None:
|
||||
company_id = Transaction().context.get('company')
|
||||
if not company_id:
|
||||
return False
|
||||
Company = Pool().get('company.company')
|
||||
company = Company(company_id)
|
||||
party = getattr(company, 'party', None)
|
||||
return bool(party and party.name == ITSA_COMPANY_NAME)
|
||||
|
||||
|
||||
def _search_first(model_name, domains):
|
||||
Model = Pool().get(model_name)
|
||||
for domain in domains:
|
||||
try:
|
||||
records = Model.search(domain, limit=1)
|
||||
except Exception:
|
||||
continue
|
||||
if records:
|
||||
return records[0]
|
||||
|
||||
|
||||
def default_itsa_currency():
|
||||
currency = _search_first('currency.currency', [
|
||||
[('code', '=', 'USD')],
|
||||
[('name', '=', 'USD')],
|
||||
[('symbol', '=', 'USD')],
|
||||
])
|
||||
return record_id(currency) if currency else None
|
||||
|
||||
|
||||
def default_itsa_price_fix_type():
|
||||
fix_type = _search_first('price.fixtype', [
|
||||
[('name', '=', 'Market Price')],
|
||||
[('name', '=', 'Market price')],
|
||||
[('name', 'ilike', 'Market Price')],
|
||||
])
|
||||
return record_id(fix_type) if fix_type else None
|
||||
|
||||
|
||||
def default_itsa_price_calendar():
|
||||
calendar = _search_first('price.calendar', [
|
||||
[('name', '=', 'Argus Sulphuric Acid')],
|
||||
[('name', 'ilike', 'Argus Sulphuric Acid')],
|
||||
])
|
||||
return record_id(calendar) if calendar else None
|
||||
|
||||
|
||||
def default_itsa_unit():
|
||||
unit = _search_first('product.uom', [
|
||||
[('symbol', '=', 'Mt')],
|
||||
[('name', '=', 'Mt')],
|
||||
[('symbol', '=', 'MT')],
|
||||
[('name', '=', 'MT')],
|
||||
])
|
||||
return record_id(unit) if unit else None
|
||||
|
||||
|
||||
def default_itsa_bulk_unit():
|
||||
unit = _search_first('product.uom', [
|
||||
[('symbol', '=', 'Bulk')],
|
||||
[('name', '=', 'Bulk')],
|
||||
[('symbol', '=', 'bulk')],
|
||||
[('name', '=', 'bulk')],
|
||||
])
|
||||
return record_id(unit) if unit else None
|
||||
|
||||
|
||||
def _company_from_values(values):
|
||||
company = values.get('company')
|
||||
if company and hasattr(company, 'party'):
|
||||
return company
|
||||
company_id = record_id(company) or Transaction().context.get('company')
|
||||
if not company_id:
|
||||
return None
|
||||
return Pool().get('company.company')(company_id)
|
||||
|
||||
|
||||
def _dimension(name):
|
||||
return _search_first('analytic.dimension', [
|
||||
[('code', '=', name)],
|
||||
[('name', '=', name)],
|
||||
[('code', 'ilike', '%%%s%%' % name)],
|
||||
[('name', 'ilike', '%%%s%%' % name)],
|
||||
])
|
||||
|
||||
|
||||
def _book_year_suffix(today=None):
|
||||
if today is None:
|
||||
today = Pool().get('ir.date').today()
|
||||
year = today.year
|
||||
if today < datetime.date(year, 4, 1):
|
||||
year -= 1
|
||||
return '%02d' % (year % 100)
|
||||
|
||||
|
||||
def _book_value(dimension, today=None):
|
||||
if not dimension:
|
||||
return None
|
||||
suffix = _book_year_suffix(today)
|
||||
return _search_first('analytic.dimension.value', [
|
||||
[
|
||||
('dimension', '=', record_id(dimension)),
|
||||
('code', 'ilike', '%%%s%%' % suffix),
|
||||
],
|
||||
[
|
||||
('dimension', '=', record_id(dimension)),
|
||||
('name', 'ilike', '%%%s%%' % suffix),
|
||||
],
|
||||
])
|
||||
|
||||
|
||||
def default_itsa_analytic_dimension_assignments(company=None, today=None):
|
||||
if not is_itsa_company(company):
|
||||
return []
|
||||
|
||||
assignments = []
|
||||
book = _dimension('Book')
|
||||
if book:
|
||||
value = _book_value(book, today=today)
|
||||
assignments.append({
|
||||
'dimension': record_id(book),
|
||||
'value': record_id(value) if value else None,
|
||||
})
|
||||
|
||||
strategy = _dimension('Strategy')
|
||||
if strategy:
|
||||
assignments.append({
|
||||
'dimension': record_id(strategy),
|
||||
'value': None,
|
||||
})
|
||||
|
||||
return assignments
|
||||
|
||||
|
||||
def add_itsa_analytic_dimension_defaults(values):
|
||||
if values.get('analytic_dimensions'):
|
||||
return
|
||||
company = _company_from_values(values)
|
||||
assignments = default_itsa_analytic_dimension_assignments(company=company)
|
||||
if assignments:
|
||||
values['analytic_dimensions'] = [('create', assignments)]
|
||||
292
modules/purchase_trade/configuration.py
Normal file
292
modules/purchase_trade/configuration.py
Normal file
@@ -0,0 +1,292 @@
|
||||
from sql import Table
|
||||
from sql.operators import Exists
|
||||
|
||||
from trytond.model import ModelSingleton, ModelSQL, ModelView, fields
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
class PurchaseConfiguration(metaclass=PoolMeta):
|
||||
__name__ = 'purchase.configuration'
|
||||
|
||||
allow_modification_after_validation = fields.Boolean(
|
||||
"Autorise modification after validation")
|
||||
auto_hedging = fields.Boolean("Auto hedge")
|
||||
auto_hedging_over = fields.Boolean("Over hedge")
|
||||
|
||||
@classmethod
|
||||
def default_allow_modification_after_validation(cls):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def default_auto_hedging(cls):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def default_auto_hedging_over(cls):
|
||||
return False
|
||||
|
||||
|
||||
class SaleConfiguration(metaclass=PoolMeta):
|
||||
__name__ = 'sale.configuration'
|
||||
|
||||
allow_modification_after_validation = fields.Boolean(
|
||||
"Autorise modification after validation")
|
||||
auto_hedging = fields.Boolean("Auto hedge")
|
||||
auto_hedging_over = fields.Boolean("Over hedge")
|
||||
|
||||
@classmethod
|
||||
def default_allow_modification_after_validation(cls):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def default_auto_hedging(cls):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def default_auto_hedging_over(cls):
|
||||
return False
|
||||
|
||||
|
||||
class AccountConfiguration(metaclass=PoolMeta):
|
||||
__name__ = 'account.configuration'
|
||||
|
||||
default_sale_padding_account = fields.MultiValue(fields.Many2One(
|
||||
'account.account', "Default Sale Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.revenue', '=', True),
|
||||
('company', '=', Eval('context', {}).get('company', -1)),
|
||||
]))
|
||||
default_accrual_padding_account = fields.MultiValue(fields.Many2One(
|
||||
'account.account', "Default Accrual Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.statement', '=', 'balance'),
|
||||
('company', '=', Eval('context', {}).get('company', -1)),
|
||||
]))
|
||||
|
||||
@classmethod
|
||||
def multivalue_model(cls, field):
|
||||
pool = Pool()
|
||||
if field in {
|
||||
'default_sale_padding_account',
|
||||
'default_accrual_padding_account',
|
||||
}:
|
||||
return pool.get('account.configuration.default_account')
|
||||
return super().multivalue_model(field)
|
||||
|
||||
|
||||
class AccountConfigurationDefaultAccount(metaclass=PoolMeta):
|
||||
__name__ = 'account.configuration.default_account'
|
||||
|
||||
default_sale_padding_account = fields.Many2One(
|
||||
'account.account', "Default Sale Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.revenue', '=', True),
|
||||
('company', '=', Eval('company', -1)),
|
||||
])
|
||||
default_accrual_padding_account = fields.Many2One(
|
||||
'account.account', "Default Accrual Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.statement', '=', 'balance'),
|
||||
('company', '=', Eval('company', -1)),
|
||||
])
|
||||
|
||||
|
||||
class Configuration(ModelSingleton, ModelSQL, ModelView):
|
||||
"Purchase Trade Configuration"
|
||||
__name__ = 'purchase_trade.configuration'
|
||||
|
||||
_REPORT_LABELS = (
|
||||
('sale_report_label', 'sale', 'report_sale', 'Proforma'),
|
||||
('sale_commission_report_label', 'account_invoice',
|
||||
'report_sale_commission_ict', 'Commission invoice Sale'),
|
||||
('sale_bill_report_label', 'sale', 'report_bill', 'Draft'),
|
||||
('invoice_report_label', 'account_invoice', 'report_invoice',
|
||||
'Invoice'),
|
||||
('invoice_cndn_report_label', 'account_invoice',
|
||||
'report_invoice_ict_final', 'CN/DN'),
|
||||
('invoice_commission_cndn_report_label', 'account_invoice',
|
||||
'report_invoice_ict_commission_final', 'CN/DN Commission'),
|
||||
('invoice_prepayment_report_label', 'account_invoice',
|
||||
'report_prepayment', 'Prepayment'),
|
||||
('invoice_packing_list_report_label', 'purchase_trade',
|
||||
'report_invoice_packing_list', 'Packing List'),
|
||||
('invoice_payment_order_report_label', 'purchase_trade',
|
||||
'report_payment_order', 'Payment Order'),
|
||||
('purchase_report_label', 'purchase', 'report_purchase', 'Purchase'),
|
||||
('purchase_commission_report_label', 'account_invoice',
|
||||
'report_purchase_commission_ict', 'Commission invoice Purchase'),
|
||||
('shipment_shipping_report_label', 'stock',
|
||||
'report_shipment_in_shipping', 'Shipping instructions'),
|
||||
('shipment_insurance_report_label', 'purchase_trade',
|
||||
'report_shipment_in_insurance', 'Insurance'),
|
||||
('shipment_coo_report_label', 'purchase_trade',
|
||||
'report_shipment_in_coo', 'COO'),
|
||||
('shipment_packing_list_report_label', 'purchase_trade',
|
||||
'report_shipment_in_packing_list', 'Packing List'),
|
||||
('shipment_linkage_report_label', 'purchase_trade',
|
||||
'report_shipment_in_linkage', 'Linkage'),
|
||||
)
|
||||
_COFFEE_MENU_XML_IDS = (
|
||||
'menu_coffee_purchase',
|
||||
'menu_coffee_sample_purchase',
|
||||
'menu_coffee_lab_analysis_purchase',
|
||||
'menu_coffee_cupping_session_purchase',
|
||||
'menu_coffee_cupping_criterion_purchase',
|
||||
'menu_coffee_sale',
|
||||
'menu_coffee_sample_sale',
|
||||
'menu_coffee_lab_analysis_sale',
|
||||
'menu_coffee_cupping_session_sale',
|
||||
'menu_coffee_cupping_criterion_sale',
|
||||
)
|
||||
_ORPHAN_ACT_WINDOW_XML_IDS = (
|
||||
'act_itsa_operations_workflow',
|
||||
'act_tradon_processes',
|
||||
)
|
||||
|
||||
pricing_rule = fields.Text("Pricing Rule")
|
||||
active_coffee_compatibility = fields.Boolean(
|
||||
"Active coffee compatibility")
|
||||
sale_report_template = fields.Char("Sale Template")
|
||||
sale_report_label = fields.Char("Sale Menu Label")
|
||||
sale_commission_report_template = fields.Char(
|
||||
"Sale Commission Template")
|
||||
sale_commission_report_label = fields.Char(
|
||||
"Sale Commission Menu Label")
|
||||
sale_bill_report_template = fields.Char("Sale Bill Template")
|
||||
sale_bill_report_label = fields.Char("Sale Bill Menu Label")
|
||||
sale_final_report_template = fields.Char("Sale Final Template")
|
||||
invoice_report_template = fields.Char("Invoice Template")
|
||||
invoice_report_label = fields.Char("Invoice Menu Label")
|
||||
invoice_cndn_report_template = fields.Char("CN/DN Template")
|
||||
invoice_cndn_report_label = fields.Char("CN/DN Menu Label")
|
||||
invoice_commission_cndn_report_template = fields.Char(
|
||||
"CN/DN Commission Template")
|
||||
invoice_commission_cndn_report_label = fields.Char(
|
||||
"CN/DN Commission Menu Label")
|
||||
invoice_prepayment_report_template = fields.Char("Prepayment Template")
|
||||
invoice_prepayment_report_label = fields.Char("Prepayment Menu Label")
|
||||
invoice_packing_list_report_template = fields.Char("Packing List Template")
|
||||
invoice_packing_list_report_label = fields.Char("Packing List Menu Label")
|
||||
invoice_payment_order_report_template = fields.Char("Payment Order Template")
|
||||
invoice_payment_order_report_label = fields.Char(
|
||||
"Payment Order Menu Label")
|
||||
purchase_report_template = fields.Char("Purchase Template")
|
||||
purchase_report_label = fields.Char("Purchase Menu Label")
|
||||
purchase_commission_report_template = fields.Char(
|
||||
"Purchase Commission Template")
|
||||
purchase_commission_report_label = fields.Char(
|
||||
"Purchase Commission Menu Label")
|
||||
shipment_shipping_report_template = fields.Char("Shipping Template")
|
||||
shipment_shipping_report_label = fields.Char("Shipping Menu Label")
|
||||
shipment_insurance_report_template = fields.Char("Insurance Template")
|
||||
shipment_insurance_report_label = fields.Char("Insurance Menu Label")
|
||||
shipment_coo_report_template = fields.Char("COO Template")
|
||||
shipment_coo_report_label = fields.Char("COO Menu Label")
|
||||
shipment_packing_list_report_template = fields.Char("Packing List Template")
|
||||
shipment_packing_list_report_label = fields.Char(
|
||||
"Packing List Menu Label")
|
||||
shipment_linkage_report_template = fields.Char("Linkage Template")
|
||||
shipment_linkage_report_label = fields.Char("Linkage Menu Label")
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
super().__register__(module_name)
|
||||
|
||||
if module_name == 'purchase_trade':
|
||||
cls._cleanup_orphan_act_window_xml_ids()
|
||||
|
||||
@classmethod
|
||||
def _cleanup_orphan_act_window_xml_ids(cls):
|
||||
cursor = Transaction().connection.cursor()
|
||||
model_data = Table('ir_model_data')
|
||||
act_window = Table('ir_action_act_window')
|
||||
|
||||
cursor.execute(*model_data.delete(
|
||||
where=(model_data.module == 'purchase_trade')
|
||||
& (model_data.model == 'ir.action.act_window')
|
||||
& model_data.fs_id.in_(cls._ORPHAN_ACT_WINDOW_XML_IDS)
|
||||
& ~Exists(act_window.select(
|
||||
act_window.id,
|
||||
where=act_window.id == model_data.db_id))))
|
||||
|
||||
@classmethod
|
||||
def default_active_coffee_compatibility(cls):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
records = super().create(vlist)
|
||||
cls._sync_report_labels(records)
|
||||
active = any(
|
||||
bool(values.get(
|
||||
'active_coffee_compatibility',
|
||||
cls.default_active_coffee_compatibility()))
|
||||
for values in vlist)
|
||||
cls._sync_coffee_menu_visibility(records, active=active)
|
||||
cls._clear_coffee_view_cache()
|
||||
return records
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
actions = list(zip(args[::2], args[1::2]))
|
||||
active = None
|
||||
for records, values in actions:
|
||||
if 'active_coffee_compatibility' in values:
|
||||
active = bool(values['active_coffee_compatibility'])
|
||||
super().write(*args)
|
||||
records = sum(args[::2], [])
|
||||
cls._sync_report_labels(records)
|
||||
cls._sync_coffee_menu_visibility(records, active=active)
|
||||
if active is not None:
|
||||
cls._clear_coffee_view_cache()
|
||||
|
||||
@classmethod
|
||||
def _sync_report_labels(cls, records):
|
||||
if not records:
|
||||
return
|
||||
pool = Pool()
|
||||
ModelData = pool.get('ir.model.data')
|
||||
ActionReport = pool.get('ir.action.report')
|
||||
to_write = []
|
||||
for record in records:
|
||||
for field_name, module, xml_id, default_label in cls._REPORT_LABELS:
|
||||
label = (getattr(record, field_name, '') or '').strip()
|
||||
action_id = ModelData.get_id(module, xml_id)
|
||||
action = ActionReport(action_id)
|
||||
target_label = label or default_label
|
||||
if getattr(action, 'name', '') != target_label:
|
||||
to_write.extend(([action], {'name': target_label}))
|
||||
if to_write:
|
||||
with Transaction().set_user(0):
|
||||
ActionReport.write(*to_write)
|
||||
|
||||
@classmethod
|
||||
def _sync_coffee_menu_visibility(cls, records=None, active=None):
|
||||
if active is None:
|
||||
configurations = cls.search([], limit=1)
|
||||
active = bool(
|
||||
configurations
|
||||
and configurations[0].active_coffee_compatibility)
|
||||
pool = Pool()
|
||||
ModelData = pool.get('ir.model.data')
|
||||
Menu = pool.get('ir.ui.menu')
|
||||
menus = []
|
||||
for xml_id in cls._COFFEE_MENU_XML_IDS:
|
||||
try:
|
||||
menu_id = ModelData.get_id('purchase_trade', xml_id)
|
||||
except Exception:
|
||||
continue
|
||||
menus.append(Menu(menu_id))
|
||||
if menus:
|
||||
with Transaction().set_user(0):
|
||||
Menu.write(menus, {'active': active})
|
||||
|
||||
@classmethod
|
||||
def _clear_coffee_view_cache(cls):
|
||||
ModelView._fields_view_get_cache.clear()
|
||||
72
modules/purchase_trade/configuration.xml
Normal file
72
modules/purchase_trade/configuration.xml
Normal file
@@ -0,0 +1,72 @@
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="purchase_trade_configuration_view_form">
|
||||
<field name="model">purchase_trade.configuration</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">configuration_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="purchase_trade_template_configuration_view_form">
|
||||
<field name="model">purchase_trade.configuration</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_configuration_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="purchase_configuration_view_form">
|
||||
<field name="model">purchase.configuration</field>
|
||||
<field name="inherit" ref="purchase.purchase_configuration_view_form"/>
|
||||
<field name="name">purchase_configuration_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_configuration_view_form">
|
||||
<field name="model">sale.configuration</field>
|
||||
<field name="inherit" ref="sale.sale_configuration_view_form"/>
|
||||
<field name="name">sale_configuration_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_purchase_trade_configuration_form">
|
||||
<field name="name">Trade Configuration</field>
|
||||
<field name="res_model">purchase_trade.configuration</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_purchase_trade_configuration_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="purchase_trade_configuration_view_form"/>
|
||||
<field name="act_window" ref="act_purchase_trade_configuration_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_purchase_trade_template_configuration_form">
|
||||
<field name="name">Document Templates</field>
|
||||
<field name="res_model">purchase_trade.configuration</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_purchase_trade_template_configuration_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="purchase_trade_template_configuration_view_form"/>
|
||||
<field name="act_window" ref="act_purchase_trade_template_configuration_form"/>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
name="Trade Configuration"
|
||||
parent="price.menu_price"
|
||||
action="act_purchase_trade_configuration_form"
|
||||
sequence="10"
|
||||
id="menu_purchase_trade_configuration"
|
||||
icon="tryton-settings"/>
|
||||
<menuitem
|
||||
name="Trade Configuration"
|
||||
parent="purchase.menu_configuration"
|
||||
action="act_purchase_trade_configuration_form"
|
||||
sequence="90"
|
||||
id="menu_purchase_trade_configuration_purchase"
|
||||
icon="tryton-settings"/>
|
||||
<menuitem
|
||||
name="Trade Configuration"
|
||||
parent="sale.menu_configuration"
|
||||
action="act_purchase_trade_configuration_form"
|
||||
sequence="90"
|
||||
id="menu_purchase_trade_configuration_sale"
|
||||
icon="tryton-settings"/>
|
||||
<menuitem
|
||||
name="Document Templates"
|
||||
parent="document_incoming.menu_configuration"
|
||||
action="act_purchase_trade_template_configuration_form"
|
||||
sequence="20"
|
||||
id="menu_purchase_trade_template_configuration"
|
||||
icon="tryton-settings"/>
|
||||
</data>
|
||||
</tryton>
|
||||
2469
modules/purchase_trade/contract_clause_reference.xml
Normal file
2469
modules/purchase_trade/contract_clause_reference.xml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,15 @@ class Cron(metaclass=PoolMeta):
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.method.selection.append(
|
||||
('forex.cron|update_forex', "Update Forex Prices"))
|
||||
super().__setup__()
|
||||
cls.method.selection.append(
|
||||
('forex.cron|update_forex', "Update Forex Prices"))
|
||||
cls.method.selection.append(
|
||||
('valuation.valuation|update_daily_snapshot',
|
||||
"Update Valuation Snapshot"))
|
||||
cls.method.selection.append(
|
||||
('pricing.pricing|update_daily_pricing',
|
||||
"Update Pricing"))
|
||||
|
||||
class PriceCron(ModelSQL, ModelView):
|
||||
"Price Cron"
|
||||
|
||||
@@ -36,10 +36,22 @@
|
||||
<field name="model" search="[('model', '=', 'forex.cron')]"/>
|
||||
</record> -->
|
||||
|
||||
<record model="ir.cron" id="cron_cron">
|
||||
<field name="method">forex.cron|update_forex</field>
|
||||
<field name="interval_number" eval="1"/>
|
||||
<field name="interval_type">days</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
<record model="ir.cron" id="cron_cron">
|
||||
<field name="method">forex.cron|update_forex</field>
|
||||
<field name="interval_number" eval="1"/>
|
||||
<field name="interval_type">days</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.cron" id="valuation_daily_snapshot_cron">
|
||||
<field name="method">valuation.valuation|update_daily_snapshot</field>
|
||||
<field name="interval_number" eval="1"/>
|
||||
<field name="interval_type">days</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.cron" id="pricing_daily_update_cron">
|
||||
<field name="method">pricing.pricing|update_daily_pricing</field>
|
||||
<field name="interval_number" eval="1"/>
|
||||
<field name="interval_type">days</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
3470
modules/purchase_trade/ctrm_reporting.py
Normal file
3470
modules/purchase_trade/ctrm_reporting.py
Normal file
File diff suppressed because it is too large
Load Diff
486
modules/purchase_trade/ctrm_reporting.xml
Normal file
486
modules/purchase_trade/ctrm_reporting.xml
Normal file
@@ -0,0 +1,486 @@
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="ctrm_position_physical_context_view_form">
|
||||
<field name="model">ctrm.reporting.position.physical.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_position_physical_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_position_physical_view_list">
|
||||
<field name="model">ctrm.reporting.position.physical</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_position_physical_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_position_physical">
|
||||
<field name="name">Physical Position</field>
|
||||
<field name="res_model">ctrm.reporting.position.physical</field>
|
||||
<field name="context_model">ctrm.reporting.position.physical.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_position_physical_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_position_physical_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_position_physical"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_position_financial_context_view_form">
|
||||
<field name="model">ctrm.reporting.position.financial.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_position_financial_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_position_financial_view_list">
|
||||
<field name="model">ctrm.reporting.position.financial</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_position_financial_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_position_financial">
|
||||
<field name="name">Financial Position</field>
|
||||
<field name="res_model">ctrm.reporting.position.financial</field>
|
||||
<field name="context_model">ctrm.reporting.position.financial.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_position_financial_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_position_financial_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_position_financial"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_position_net_context_view_form">
|
||||
<field name="model">ctrm.reporting.position.net.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_position_net_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_position_net_view_list">
|
||||
<field name="model">ctrm.reporting.position.net</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_position_net_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_position_net">
|
||||
<field name="name">Net Consolidated Position</field>
|
||||
<field name="res_model">ctrm.reporting.position.net</field>
|
||||
<field name="context_model">ctrm.reporting.position.net.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_position_net_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_position_net_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_position_net"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_realized_context_view_form">
|
||||
<field name="model">ctrm.reporting.pnl.realized.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_pnl_realized_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_realized_view_list">
|
||||
<field name="model">ctrm.reporting.pnl.realized</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_pnl_realized_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_pnl_realized">
|
||||
<field name="name">Realized P&L</field>
|
||||
<field name="res_model">ctrm.reporting.pnl.realized</field>
|
||||
<field name="context_model">ctrm.reporting.pnl.realized.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_pnl_realized_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_pnl_realized_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_pnl_realized"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_mtm_context_view_form">
|
||||
<field name="model">ctrm.reporting.pnl.mtm.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_pnl_mtm_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_mtm_view_list">
|
||||
<field name="model">ctrm.reporting.pnl.mtm</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_pnl_mtm_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_pnl_mtm">
|
||||
<field name="name">Mark-to-Market</field>
|
||||
<field name="res_model">ctrm.reporting.pnl.mtm</field>
|
||||
<field name="context_model">ctrm.reporting.pnl.mtm.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_pnl_mtm_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_pnl_mtm_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_pnl_mtm"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_explain_context_view_form">
|
||||
<field name="model">ctrm.reporting.pnl.explain.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_pnl_explain_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_explain_view_list">
|
||||
<field name="model">ctrm.reporting.pnl.explain</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_pnl_explain_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_pnl_explain">
|
||||
<field name="name">P&L Explain</field>
|
||||
<field name="res_model">ctrm.reporting.pnl.explain</field>
|
||||
<field name="context_model">ctrm.reporting.pnl.explain.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_pnl_explain_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_pnl_explain_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_pnl_explain"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_dimension_context_view_form">
|
||||
<field name="model">ctrm.reporting.pnl.dimension.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_pnl_dimension_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_pnl_dimension_view_list">
|
||||
<field name="model">ctrm.reporting.pnl.dimension</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_pnl_dimension_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_pnl_dimension">
|
||||
<field name="name">P&L by Dimension</field>
|
||||
<field name="res_model">ctrm.reporting.pnl.dimension</field>
|
||||
<field name="context_model">ctrm.reporting.pnl.dimension.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_pnl_dimension_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_pnl_dimension_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_pnl_dimension"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_credit_risk_context_view_form">
|
||||
<field name="model">ctrm.reporting.risk.credit.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_credit_risk_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_credit_risk_view_list">
|
||||
<field name="model">ctrm.reporting.risk.credit</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_credit_risk_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_credit_risk">
|
||||
<field name="name">Credit Risk</field>
|
||||
<field name="res_model">ctrm.reporting.risk.credit</field>
|
||||
<field name="context_model">ctrm.reporting.risk.credit.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_credit_risk_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_credit_risk_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_credit_risk"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_shipping_logistics_context_view_form">
|
||||
<field name="model">ctrm.reporting.operations.shipping.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_shipping_logistics_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_shipping_logistics_view_list">
|
||||
<field name="model">ctrm.reporting.operations.shipping</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_shipping_logistics_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_shipping_logistics">
|
||||
<field name="name">Shipping Logistics</field>
|
||||
<field name="res_model">ctrm.reporting.operations.shipping</field>
|
||||
<field name="context_model">ctrm.reporting.operations.shipping.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_shipping_logistics_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_shipping_logistics_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_shipping_logistics"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_inventory_context_view_form">
|
||||
<field name="model">ctrm.reporting.operations.inventory.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_inventory_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_inventory_view_list">
|
||||
<field name="model">ctrm.reporting.operations.inventory</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_inventory_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_inventory">
|
||||
<field name="name">Inventory</field>
|
||||
<field name="res_model">ctrm.reporting.operations.inventory</field>
|
||||
<field name="context_model">ctrm.reporting.operations.inventory.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_inventory_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_inventory_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_inventory"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_contract_performance_context_view_form">
|
||||
<field name="model">ctrm.reporting.operations.contract.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_contract_performance_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_contract_performance_view_list">
|
||||
<field name="model">ctrm.reporting.operations.contract</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_contract_performance_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_contract_performance">
|
||||
<field name="name">Contract Performance</field>
|
||||
<field name="res_model">ctrm.reporting.operations.contract</field>
|
||||
<field name="context_model">ctrm.reporting.operations.contract.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_contract_performance_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_contract_performance_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_contract_performance"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_scheduling_context_view_form">
|
||||
<field name="model">ctrm.reporting.operations.scheduling.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_scheduling_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_scheduling_view_list">
|
||||
<field name="model">ctrm.reporting.operations.scheduling</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_scheduling_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_scheduling">
|
||||
<field name="name">Scheduling</field>
|
||||
<field name="res_model">ctrm.reporting.operations.scheduling</field>
|
||||
<field name="context_model">ctrm.reporting.operations.scheduling.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_scheduling_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_scheduling_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_scheduling"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_accruals_context_view_form">
|
||||
<field name="model">ctrm.reporting.finance.accruals.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_accruals_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_accruals_view_list">
|
||||
<field name="model">ctrm.reporting.finance.accruals</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_accruals_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_accruals">
|
||||
<field name="name">Accruals</field>
|
||||
<field name="res_model">ctrm.reporting.finance.accruals</field>
|
||||
<field name="context_model">ctrm.reporting.finance.accruals.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_accruals_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_accruals_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_accruals"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_settlements_context_view_form">
|
||||
<field name="model">ctrm.reporting.finance.settlements.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_settlements_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_settlements_view_list">
|
||||
<field name="model">ctrm.reporting.finance.settlements</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_settlements_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_settlements">
|
||||
<field name="name">Settlements and Invoicing</field>
|
||||
<field name="res_model">ctrm.reporting.finance.settlements</field>
|
||||
<field name="context_model">ctrm.reporting.finance.settlements.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_settlements_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_settlements_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_settlements"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_cashflow_context_view_form">
|
||||
<field name="model">ctrm.reporting.finance.cashflow.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_cashflow_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_cashflow_view_list">
|
||||
<field name="model">ctrm.reporting.finance.cashflow</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_cashflow_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_cashflow">
|
||||
<field name="name">Cash Flow Forecast</field>
|
||||
<field name="res_model">ctrm.reporting.finance.cashflow</field>
|
||||
<field name="context_model">ctrm.reporting.finance.cashflow.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_cashflow_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_cashflow_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_cashflow"/>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_gl_reconciliation_context_view_form">
|
||||
<field name="model">ctrm.reporting.finance.gl.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">ctrm_gl_reconciliation_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="ctrm_gl_reconciliation_view_list">
|
||||
<field name="model">ctrm.reporting.finance.gl</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">ctrm_gl_reconciliation_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_ctrm_gl_reconciliation">
|
||||
<field name="name">GL Reconciliation</field>
|
||||
<field name="res_model">ctrm.reporting.finance.gl</field>
|
||||
<field name="context_model">ctrm.reporting.finance.gl.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_ctrm_gl_reconciliation_view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="ctrm_gl_reconciliation_view_list"/>
|
||||
<field name="act_window" ref="act_ctrm_gl_reconciliation"/>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
name="Positions"
|
||||
parent="purchase_trade.menu_global_reporting"
|
||||
sequence="10"
|
||||
id="menu_ctrm_positions"
|
||||
icon="tradon-reporting-positions"/>
|
||||
<menuitem
|
||||
name="Physical Position"
|
||||
parent="menu_ctrm_positions_physical_group"
|
||||
sequence="10"
|
||||
action="act_ctrm_position_physical"
|
||||
id="menu_ctrm_position_physical"/>
|
||||
<menuitem
|
||||
name="Financial Position"
|
||||
parent="menu_ctrm_positions_financial_group"
|
||||
sequence="10"
|
||||
action="act_ctrm_position_financial"
|
||||
id="menu_ctrm_position_financial"/>
|
||||
<menuitem
|
||||
name="Net Consolidated Position"
|
||||
parent="menu_ctrm_positions"
|
||||
sequence="30"
|
||||
action="act_ctrm_position_net"
|
||||
id="menu_ctrm_position_net"/>
|
||||
<menuitem
|
||||
name="Hedge Coverage"
|
||||
parent="menu_ctrm_positions"
|
||||
sequence="40"
|
||||
id="menu_ctrm_position_hedge"/>
|
||||
|
||||
<menuitem
|
||||
name="P&L"
|
||||
parent="purchase_trade.menu_global_reporting"
|
||||
sequence="20"
|
||||
id="menu_ctrm_pnl"
|
||||
icon="tradon-reporting-pnl"/>
|
||||
<menuitem
|
||||
name="Realized P&L"
|
||||
parent="menu_ctrm_pnl"
|
||||
sequence="10"
|
||||
action="act_ctrm_pnl_realized"
|
||||
id="menu_ctrm_pnl_realized"/>
|
||||
<menuitem
|
||||
name="Mark-to-Market"
|
||||
parent="menu_ctrm_pnl"
|
||||
sequence="20"
|
||||
action="act_ctrm_pnl_mtm"
|
||||
id="menu_ctrm_pnl_mtm"/>
|
||||
<menuitem
|
||||
name="P&L Explain"
|
||||
parent="menu_ctrm_pnl"
|
||||
sequence="30"
|
||||
action="act_ctrm_pnl_explain"
|
||||
id="menu_ctrm_pnl_explain"/>
|
||||
<menuitem
|
||||
name="P&L by Dimension"
|
||||
parent="menu_ctrm_pnl"
|
||||
sequence="40"
|
||||
action="act_ctrm_pnl_dimension"
|
||||
id="menu_ctrm_pnl_dimension"/>
|
||||
|
||||
<menuitem
|
||||
name="Risk"
|
||||
parent="purchase_trade.menu_global_reporting"
|
||||
sequence="30"
|
||||
id="menu_ctrm_risk"
|
||||
icon="tradon-reporting-risk"/>
|
||||
<menuitem
|
||||
name="VaR"
|
||||
parent="menu_ctrm_risk"
|
||||
sequence="10"
|
||||
action="risk.act_pos_report_form"
|
||||
id="menu_ctrm_risk_var"/>
|
||||
<menuitem
|
||||
name="Stress Tests"
|
||||
parent="menu_ctrm_risk"
|
||||
sequence="20"
|
||||
id="menu_ctrm_risk_stress"/>
|
||||
<menuitem
|
||||
name="Sensitivities"
|
||||
parent="menu_ctrm_risk"
|
||||
sequence="30"
|
||||
id="menu_ctrm_risk_sensitivities"/>
|
||||
<menuitem
|
||||
name="Limit Monitoring"
|
||||
parent="menu_ctrm_risk"
|
||||
sequence="40"
|
||||
id="menu_ctrm_risk_limits"/>
|
||||
<menuitem
|
||||
name="Credit Risk"
|
||||
parent="menu_ctrm_risk"
|
||||
sequence="50"
|
||||
action="act_ctrm_credit_risk"
|
||||
id="menu_ctrm_risk_credit"/>
|
||||
|
||||
<menuitem
|
||||
name="Operations & Logistics"
|
||||
parent="purchase_trade.menu_global_reporting"
|
||||
sequence="40"
|
||||
id="menu_ctrm_operations"
|
||||
icon="tradon-reporting-operations"/>
|
||||
<menuitem
|
||||
name="Shipping Logistics"
|
||||
parent="menu_ctrm_operations"
|
||||
sequence="10"
|
||||
action="act_ctrm_shipping_logistics"
|
||||
id="menu_ctrm_operations_shipping"/>
|
||||
<menuitem
|
||||
name="Inventory"
|
||||
parent="menu_ctrm_operations"
|
||||
sequence="20"
|
||||
action="act_ctrm_inventory"
|
||||
id="menu_ctrm_operations_inventory"/>
|
||||
<menuitem
|
||||
name="Contract Performance"
|
||||
parent="menu_ctrm_operations"
|
||||
sequence="30"
|
||||
action="act_ctrm_contract_performance"
|
||||
id="menu_ctrm_operations_contract"/>
|
||||
<menuitem
|
||||
name="Scheduling"
|
||||
parent="menu_ctrm_operations"
|
||||
sequence="40"
|
||||
action="act_ctrm_scheduling"
|
||||
id="menu_ctrm_operations_scheduling"/>
|
||||
|
||||
<menuitem
|
||||
name="Finance & Accounting"
|
||||
parent="purchase_trade.menu_global_reporting"
|
||||
sequence="50"
|
||||
id="menu_ctrm_finance"
|
||||
icon="tradon-reporting-finance"/>
|
||||
<menuitem
|
||||
name="Accruals"
|
||||
parent="menu_ctrm_finance"
|
||||
sequence="10"
|
||||
action="act_ctrm_accruals"
|
||||
id="menu_ctrm_finance_accruals"/>
|
||||
<menuitem
|
||||
name="Hedge Accounting"
|
||||
parent="menu_ctrm_finance"
|
||||
sequence="20"
|
||||
id="menu_ctrm_finance_hedge_accounting"/>
|
||||
<menuitem
|
||||
name="Settlements and Invoicing"
|
||||
parent="menu_ctrm_finance"
|
||||
sequence="30"
|
||||
action="act_ctrm_settlements"
|
||||
id="menu_ctrm_finance_settlements"/>
|
||||
<menuitem
|
||||
name="Cash Flow Forecast"
|
||||
parent="menu_ctrm_finance"
|
||||
sequence="40"
|
||||
action="act_ctrm_cashflow"
|
||||
id="menu_ctrm_finance_cash_flow"/>
|
||||
<menuitem
|
||||
name="GL Reconciliation"
|
||||
parent="menu_ctrm_finance"
|
||||
sequence="50"
|
||||
action="act_ctrm_gl_reconciliation"
|
||||
id="menu_ctrm_finance_gl_reconciliation"/>
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -193,16 +193,29 @@ class Dashboard(ModelSQL, ModelView):
|
||||
self.chatbot = 'chatbot:' + json.dumps(dial, ensure_ascii=False)
|
||||
logger.info("EXITONCHANGE",self.chatbot)
|
||||
|
||||
@staticmethod
|
||||
def _empty_fx_rates():
|
||||
return (None, None, None, None, None, None, None, None, None, None)
|
||||
|
||||
@classmethod
|
||||
def _find_currency_by_code(cls, code):
|
||||
Currency = Pool().get('currency.currency')
|
||||
currencies = Currency.search([('name', '=', code)], limit=1)
|
||||
if not currencies:
|
||||
currencies = Currency.search([('code', '=', code)], limit=1)
|
||||
return currencies[0] if currencies else None
|
||||
|
||||
def get_last_five_fx_rates(self, from_code='USD', to_code='EUR'):
|
||||
"""
|
||||
Retourne (dernier_taux, avant_dernier_taux) pour le couple de devises.
|
||||
"""
|
||||
Currency = Pool().get('currency.currency')
|
||||
CurrencyRate = Pool().get('currency.currency.rate')
|
||||
|
||||
# Récupérer les devises EUR et USD
|
||||
from_currency = Currency.search([('name', '=', from_code)])[0]
|
||||
to_currency = Currency.search([('name', '=', to_code)])[0]
|
||||
from_currency = self._find_currency_by_code(from_code)
|
||||
to_currency = self._find_currency_by_code(to_code)
|
||||
if not from_currency or not to_currency:
|
||||
return self._empty_fx_rates()
|
||||
|
||||
# Recherche des taux de la devise de base (ex: USD)
|
||||
rates = CurrencyRate.search(
|
||||
@@ -212,7 +225,7 @@ class Dashboard(ModelSQL, ModelView):
|
||||
)
|
||||
|
||||
if not rates:
|
||||
return None, None, None, None, None
|
||||
return self._empty_fx_rates()
|
||||
|
||||
# Calcul du taux EUR/USD
|
||||
# Si la devise principale de la société est EUR, et que le taux stocké est
|
||||
@@ -232,7 +245,13 @@ class Dashboard(ModelSQL, ModelView):
|
||||
# last_rate = 1 / last_rate if last_rate else None
|
||||
# prev_rate = 1 / prev_rate if prev_rate else None
|
||||
|
||||
return round(1/f1,6), round(1/f2,6) if f2 else None, round(1/f3,6) if f3 else None, round(1/f4,6) if f4 else None, round(1/f5,6) if f5 else None, d1, d2, d3, d4, d5
|
||||
return (
|
||||
round(1/f1,6) if f1 else None,
|
||||
round(1/f2,6) if f2 else None,
|
||||
round(1/f3,6) if f3 else None,
|
||||
round(1/f4,6) if f4 else None,
|
||||
round(1/f5,6) if f5 else None,
|
||||
d1, d2, d3, d4, d5)
|
||||
|
||||
def get_tremor(self,name):
|
||||
Date = Pool().get('ir.date')
|
||||
@@ -430,7 +449,7 @@ class Dashboard(ModelSQL, ModelView):
|
||||
News = Pool().get('news.news')
|
||||
Date = Pool().get('ir.date')
|
||||
news_list = News.search([('active', '=', True)], limit=5, order=[('publish_date', 'DESC')])
|
||||
last_rate,prev_rate, = self.get_last_five_fx_rates()
|
||||
last_rate, prev_rate = self.get_last_five_fx_rates()[:2]
|
||||
if last_rate and prev_rate:
|
||||
variation = ((last_rate - prev_rate) / prev_rate) * 100 if prev_rate else 0
|
||||
direction = "📈" if variation > 0 else "📉"
|
||||
@@ -749,7 +768,11 @@ class BotWizard(Wizard):
|
||||
lms.lot_r_id = ls.getLotQt()[0].id
|
||||
lms.lot_matched_qt = lp.lot_quantity
|
||||
lms.lot_sale = s
|
||||
LotQt.match_lots([lmp],[lms])
|
||||
Lot = Pool().get('lot.lot')
|
||||
with Lot.skip_quantity_consistency():
|
||||
LotQt.match_lots([lmp],[lms])
|
||||
Lot.assert_lines_quantity_consistency(
|
||||
[p.lines[0], s.lines[0]])
|
||||
Sale._process_shipment([s])
|
||||
|
||||
elif do_ == "add":
|
||||
@@ -876,4 +899,4 @@ class BotAction(ModelSQL, ModelView):
|
||||
|
||||
@classmethod
|
||||
def state_dashboard(cls):
|
||||
return 1
|
||||
return 1
|
||||
|
||||
@@ -65,7 +65,6 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView):
|
||||
value = fields.Many2One(
|
||||
'analytic.dimension.value',
|
||||
'Value',
|
||||
required=True,
|
||||
domain=[
|
||||
('dimension', '=', Eval('dimension')),
|
||||
],
|
||||
@@ -76,4 +75,4 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView):
|
||||
'purchase.purchase',
|
||||
'Purchase',
|
||||
ondelete='CASCADE'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# Backlog - Market Price Import
|
||||
|
||||
Scope: code changes related only to `BR-PT-004 - Market Price Import`.
|
||||
|
||||
## PT-BL-001 - Enforce uniqueness at model level
|
||||
|
||||
- Status: done
|
||||
- Implementation commit: `eaae2e5`
|
||||
- Priority: high
|
||||
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
|
||||
- Requirement: enforce uniqueness of `(price, price_date)` at model level without adding a database constraint yet.
|
||||
- Work done:
|
||||
- added model-level validation on `price.price_value`
|
||||
- added Tryton message `price.msg_price_value_unique`
|
||||
- added targeted tests for duplicate create and duplicate write validation
|
||||
- Validation status:
|
||||
- `git diff --check` passed
|
||||
- automated Python tests still need to be run in a working Tryton Python environment
|
||||
- Expected code impact:
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- possibly `modules/price/price_value.py` if the validation belongs on the target model
|
||||
- Expected tests:
|
||||
- importing a row for an existing `(price, price_date)` skips or updates according to `Overwrite existing price`
|
||||
- creating/editing duplicate `price.price_value` records at model level is rejected if validation is implemented on `price.price_value`
|
||||
|
||||
## PT-BL-002 - Use locale-aware slash date parsing
|
||||
|
||||
- Status: open
|
||||
- Priority: medium
|
||||
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
|
||||
- Requirement: prefer the user's/default locale date format for ambiguous slash dates, then fall back to the current parsing order only if no locale preference is available.
|
||||
- Current behavior: `DD/MM/YYYY` is tried before `MM/DD/YYYY`.
|
||||
- Expected code impact:
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- Expected tests:
|
||||
- slash dates follow the configured/default locale when available
|
||||
- slash dates keep a deterministic fallback when locale is unavailable
|
||||
- existing accepted formats still work
|
||||
|
||||
## PT-BL-003 - Reject missing price_value
|
||||
|
||||
- Status: open
|
||||
- Priority: high
|
||||
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
|
||||
- Requirement: `price_value` is mandatory for imported rows. Missing `price_value` must be reported as a row error.
|
||||
- Current behavior: empty `price_value` is imported as an empty value.
|
||||
- Expected code impact:
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- Expected tests:
|
||||
- missing `price_value` is reported in `errors`
|
||||
- missing `high_price`, `low_price`, and `open_price` remain allowed
|
||||
- valid `price_value` still imports and updates correctly
|
||||
|
||||
## PT-BL-004 - Detect duplicate rows inside the same Excel file
|
||||
|
||||
- Status: open
|
||||
- Priority: high
|
||||
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
|
||||
- Requirement: duplicate rows for the same `(price_index, price_date)` inside the same Excel file must be reported as row errors. The duplicate row must not import or update data.
|
||||
- Expected code impact:
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- Expected tests:
|
||||
- duplicate `(price_index, price_date)` rows in the same import produce row errors
|
||||
- first occurrence behavior remains unchanged
|
||||
- duplicate rows do not overwrite previous imported or updated values
|
||||
|
||||
## PT-BL-005 - Categorize import result errors
|
||||
|
||||
- Status: open
|
||||
- Priority: medium
|
||||
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
|
||||
- Requirement: distinguish business validation errors from file, parsing, and technical errors in the import result.
|
||||
- Current behavior: row errors are grouped under one `Errors` section.
|
||||
- Expected code impact:
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- `modules/purchase_trade/view/import_prices_result_form.xml` only if the result display needs more structure
|
||||
- Expected tests:
|
||||
- result message separates business validation errors from parsing/technical errors
|
||||
- invalid workbook and missing required columns remain blocking `UserError`
|
||||
- row-level errors still let the import continue
|
||||
|
||||
## PT-BL-006 - Fix missing default currency on created price index
|
||||
|
||||
- Status: open
|
||||
- Priority: high
|
||||
- Source bug: [BUG-PT-001](../bugs.md#bug-pt-001---missing-default-currency-in-price-index-automatic-creation)
|
||||
- Source rule: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
|
||||
- Requirement: when the market price import automatically creates a missing `price.price`, the created price index must have default currency `USD`.
|
||||
- Current behavior: the created price index can miss the default currency even though the import specification expects `USD`.
|
||||
- Expected code impact:
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- Expected tests:
|
||||
- automatic price index creation assigns `price_currency` to the `USD` currency record
|
||||
- if the `USD` currency record cannot be found, behavior remains aligned with the rule for optional default references
|
||||
@@ -1,63 +0,0 @@
|
||||
# Bugs - Purchase Trade
|
||||
|
||||
This file is the bug register for `purchase_trade`.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Register every new bug in this file with a stable bug ID.
|
||||
2. Create a matching backlog entry for every new bug.
|
||||
3. Link the bug entry to the backlog entry.
|
||||
4. When fixed, record the implementation commit in the backlog entry.
|
||||
5. Keep the bug entry as history and update its status.
|
||||
|
||||
## Status Values
|
||||
|
||||
- `reported`
|
||||
- `confirmed`
|
||||
- `backlog-created`
|
||||
- `in-progress`
|
||||
- `to-be-tested`
|
||||
- `done`
|
||||
- `rejected`
|
||||
|
||||
## Bug Template
|
||||
|
||||
```md
|
||||
### BUG-PT-001 - Short title
|
||||
|
||||
- Status: `reported`
|
||||
- Area:
|
||||
- Reported date: `YYYY-MM-DD`
|
||||
- Reported by:
|
||||
- Related rule:
|
||||
- Backlog entry:
|
||||
- Severity:
|
||||
- Environment:
|
||||
- Description:
|
||||
- Steps to reproduce:
|
||||
- Expected behavior:
|
||||
- Actual behavior:
|
||||
- Evidence:
|
||||
- Notes:
|
||||
```
|
||||
|
||||
## Bugs
|
||||
|
||||
### BUG-PT-001 - Missing default currency in Price Index automatic creation
|
||||
|
||||
- Status: `backlog-created`
|
||||
- Area: Market Price Import
|
||||
- Reported date: `2026-05-08`
|
||||
- Reported by: user
|
||||
- Related rule: [BR-PT-004 - Market Price Import](business%20rules/market-price-import.md)
|
||||
- Backlog entry: [PT-BL-006](backlog/market-price-import-backlog.md#pt-bl-006---fix-missing-default-currency-on-created-price-index)
|
||||
- Severity:
|
||||
- Environment:
|
||||
- Description: during market price import, if a price index should be created, then the default currency defined to USD is missing.
|
||||
- Steps to reproduce:
|
||||
- import market prices with `Create price index if missing` enabled
|
||||
- include a `price_index` that does not already exist
|
||||
- Expected behavior: the automatically created `price.price` has default currency `USD`.
|
||||
- Actual behavior: the automatically created `price.price` has no default currency.
|
||||
- Evidence:
|
||||
- Notes:
|
||||
@@ -1,50 +0,0 @@
|
||||
# BR-PT-003 - Le freight amount des templates facture vient du fee de shipment
|
||||
|
||||
## Intent
|
||||
|
||||
Afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique.
|
||||
|
||||
## Scope
|
||||
|
||||
- Domaine: `purchase_trade`
|
||||
- Flux: templates facture achat et vente
|
||||
- Priorite: `importante`
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
Le `FREIGHT VALUE` d'une facture ne doit pas etre pris sur la facture elle-meme.
|
||||
|
||||
Il doit etre calcule a partir du `fee.fee` rattache au shipment (`shipment_in`) du lot physique relie a la facture.
|
||||
|
||||
Regle de navigation:
|
||||
|
||||
- retrouver le lot physique pertinent depuis la facture
|
||||
- retrouver son shipment
|
||||
- chercher le `fee.fee` avec:
|
||||
- `shipment_in = shipment.id`
|
||||
- `product.name = 'Maritime freight'`
|
||||
- utiliser `fee.get_amount()` comme montant de fret
|
||||
|
||||
La regle s'applique aussi bien aux factures d'achat qu'aux factures de vente.
|
||||
|
||||
Cote vente, la remontee doit passer par le lot physique qui fait le lien entre `purchase.line` et `sale.line`.
|
||||
|
||||
## Impacted Files
|
||||
|
||||
- `modules/purchase_trade/invoice.py`
|
||||
- `modules/purchase_trade/fee.py`
|
||||
- `modules/purchase_trade/lot.py`
|
||||
- Templates facture `.fodt` concernes par `FREIGHT VALUE`
|
||||
|
||||
## Tests
|
||||
|
||||
Couvrir les changements au plus proche du flux modifie:
|
||||
|
||||
- facture achat avec lot physique, shipment et fee `Maritime freight`
|
||||
- facture vente qui remonte au lot physique puis au shipment
|
||||
- cas sans fee maritime
|
||||
- cas sans lot physique pertinent
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Preciser les templates exacts quand une demande cible un document facture particulier.
|
||||
@@ -1,59 +0,0 @@
|
||||
# BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment
|
||||
|
||||
## Intent
|
||||
|
||||
Disposer d'un chemin unique et stable pour retrouver les informations logistiques et de facturation reliees a un contrat d'achat ou de vente.
|
||||
|
||||
## Scope
|
||||
|
||||
- Domaine: `purchase_trade`
|
||||
- Flux: navigation metier entre achat, vente, shipment et facture
|
||||
- Priorite: `structurante`
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
Le lot physique (`lot_type = physic`) porte simultanement le lien vers:
|
||||
|
||||
- la `purchase.line` via `lot.line`
|
||||
- la `sale.line` via `lot.sale_line`
|
||||
- le shipment via `lot.lot_shipment_in` / `lot.lot_shipment_internal` / `lot.lot_shipment_out`
|
||||
|
||||
Pour toute logique qui doit naviguer entre achat, vente, shipment et facture, il faut privilegier ce lot physique comme source de verite.
|
||||
|
||||
Depuis une facture d'achat:
|
||||
|
||||
- remonter a la `purchase.line`
|
||||
- puis au lot physique de la ligne
|
||||
- puis au shipment et aux donnees logistiques associees
|
||||
|
||||
Depuis une facture de vente:
|
||||
|
||||
- remonter a la `sale.line`
|
||||
- puis au lot physique matchant qui porte aussi la `purchase.line`
|
||||
- puis au shipment et aux donnees logistiques associees
|
||||
|
||||
## Typical Use Cases
|
||||
|
||||
- recuperer `bl_date`, `bl_number`, `controller`, `from_location`, `to_location`
|
||||
- retrouver une facture provisoire liee au lot
|
||||
- retrouver des fees rattaches au shipment
|
||||
|
||||
## Impacted Files
|
||||
|
||||
- `modules/purchase_trade/lot.py`
|
||||
- `modules/purchase_trade/purchase.py`
|
||||
- `modules/purchase_trade/sale.py`
|
||||
- `modules/purchase_trade/invoice.py`
|
||||
- Templates facture relies aux donnees logistiques, si le flux documentaire est concerne.
|
||||
|
||||
## Tests
|
||||
|
||||
Couvrir les changements au plus proche du flux modifie:
|
||||
|
||||
- navigation achat vers lot physique puis shipment
|
||||
- navigation vente vers lot physique puis shipment
|
||||
- cas sans lot physique pertinent
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Documenter au cas par cas les champs exposes au reporting facture quand ils dependent de cette navigation.
|
||||
@@ -1,106 +0,0 @@
|
||||
# BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat
|
||||
|
||||
## Intent
|
||||
|
||||
Conserver la coherence entre la quantite theorique de la ligne d'achat, le lot virtuel associe et les quantites ouvertes stockees dans `lot.qt`.
|
||||
|
||||
## Scope
|
||||
|
||||
- Domaine: `purchase_trade`
|
||||
- Flux: modification de `purchase.line.quantity_theorical` apres creation du contrat
|
||||
- Priorite: `bloquante`
|
||||
|
||||
## Inputs
|
||||
|
||||
- Une `purchase.line` existe deja.
|
||||
- Son champ `quantity_theorical` est modifie via `write`.
|
||||
- Un lot unique de type `virtual` est rattache a la ligne.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
Quand `purchase.line.quantity_theorical` est modifiee apres creation du contrat, le systeme doit recalculer le delta entre l'ancienne et la nouvelle valeur.
|
||||
|
||||
La regle s'applique au lot unique de type `virtual` rattache a la `purchase.line`.
|
||||
|
||||
Si `delta > 0`:
|
||||
|
||||
- augmenter la quantite courante du lot `virtual` via `set_current_quantity` pour conserver l'historique `lot.qt.hist`
|
||||
- augmenter le `lot.qt` ouvert existant
|
||||
- si aucun `lot.qt` ouvert n'existe, en creer un nouveau avec le delta
|
||||
|
||||
Si `delta < 0`:
|
||||
|
||||
- diminuer le `lot.qt` ouvert uniquement si la quantite ouverte disponible est suffisante
|
||||
- diminuer la quantite courante du lot `virtual` du meme delta
|
||||
- si aucun `lot.qt` ouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreur `Please unlink or unmatch lot`
|
||||
|
||||
Definition du `lot.qt` ouvert:
|
||||
|
||||
- `lot_p = virtual lot`
|
||||
- `lot_s = None`
|
||||
- `lot_shipment_in = None`
|
||||
- `lot_shipment_internal = None`
|
||||
- `lot_shipment_out = None`
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien.
|
||||
|
||||
## Examples
|
||||
|
||||
### Exemple E1 - Augmentation simple
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 120`
|
||||
- `lot.qt ouvert = 40`
|
||||
- Attendu:
|
||||
- lot `virtual` augmente de `20`
|
||||
- `lot.qt ouvert` passe de `40` a `60`
|
||||
|
||||
### Exemple E2 - Augmentation sans lot.qt ouvert
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 110`
|
||||
- aucun `lot.qt` ouvert
|
||||
- Attendu:
|
||||
- lot `virtual` augmente de `10`
|
||||
- creation d'un `lot.qt` ouvert a `10`
|
||||
|
||||
### Exemple E3 - Diminution possible
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 90`
|
||||
- `lot.qt ouvert = 25`
|
||||
- Attendu:
|
||||
- lot `virtual` diminue de `10`
|
||||
- `lot.qt ouvert` passe de `25` a `15`
|
||||
|
||||
### Exemple E4 - Diminution impossible
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 80`
|
||||
- `lot.qt ouvert = 5`
|
||||
- Attendu:
|
||||
- blocage avec `Please unlink or unmatch lot`
|
||||
|
||||
## Impacted Files
|
||||
|
||||
- `modules/purchase_trade/purchase.py`
|
||||
- `modules/purchase_trade/lot.py`
|
||||
|
||||
## Tests
|
||||
|
||||
Couvrir au minimum:
|
||||
|
||||
- augmentation avec `lot.qt` ouvert existant
|
||||
- augmentation sans `lot.qt` ouvert
|
||||
- diminution possible
|
||||
- diminution impossible avec erreur
|
||||
|
||||
## Source
|
||||
|
||||
- Decision metier documentee dans les commentaires de `purchase_trade.purchase.Line.write`.
|
||||
@@ -1,171 +0,0 @@
|
||||
# BR-PT-004 - Market Price Import
|
||||
|
||||
## Intent
|
||||
|
||||
Import dated market prices from an `.xlsx` Excel file into `price.price_value`, using `price.price` as the market price index.
|
||||
|
||||
## Scope
|
||||
|
||||
- Wizard: `purchase_trade.import_prices`
|
||||
- Input model: `purchase_trade.import_prices.start`
|
||||
- Result model: `purchase_trade.import_prices.result`
|
||||
- Target models:
|
||||
- `price.price`
|
||||
- `price.price_value`
|
||||
- Menu entry: under `price.menu_price`
|
||||
|
||||
## Inputs
|
||||
|
||||
The wizard reads only the first worksheet of an `.xlsx` file.
|
||||
|
||||
The first row is treated as the header row. Header names are normalized by lowercasing and removing non-alphanumeric characters, so labels such as `price_index`, `price index`, and `Price Index` map to the same field.
|
||||
|
||||
Required columns:
|
||||
|
||||
- `price_index`
|
||||
- `price_date`
|
||||
- `high_price`
|
||||
- `low_price`
|
||||
- `open_price`
|
||||
- `price_value`
|
||||
|
||||
The wizard options are:
|
||||
|
||||
- `Create price index if missing`
|
||||
- `Overwrite existing price`
|
||||
|
||||
## Date and Numeric Parsing
|
||||
|
||||
Accepted `price_date` values:
|
||||
|
||||
- Excel serial date numbers
|
||||
- `YYYY-MM-DD`
|
||||
- `DD/MM/YYYY`
|
||||
- `MM/DD/YYYY`
|
||||
|
||||
Numeric price fields accept decimal commas or decimal points. Empty numeric cells are imported as empty values. Invalid numeric values are reported as row errors.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
For each non-empty data row, starting from Excel row 2:
|
||||
|
||||
1. Trim and validate `price_index`.
|
||||
2. Parse `price_date`.
|
||||
3. Search `price.price` by exact `price_index`.
|
||||
4. If the price index is missing:
|
||||
- create it when `Create price index if missing` is checked
|
||||
- otherwise skip the row with `price_index missing`
|
||||
5. Search `price.price_value` by `(price, price_date)`.
|
||||
6. If an existing price value is found:
|
||||
- update it when `Overwrite existing price` is checked
|
||||
- otherwise skip the row with `price_date already exists`
|
||||
7. If no existing price value is found, create a new `price.price_value`.
|
||||
|
||||
## Created Price Index Defaults
|
||||
|
||||
When the wizard creates a missing `price.price`, it sets:
|
||||
|
||||
- `price_index = imported price_index`
|
||||
- `price_desc = imported price_index`
|
||||
- `price_curve_type = future`
|
||||
|
||||
It also tries to default these references when matching records exist:
|
||||
|
||||
- `price_type`: `price.fixtype` where `name = Market price`
|
||||
- `price_currency`: `currency.currency` where `code = USD`
|
||||
- `price_calendar`: `price.calendar` where `name = Argus EU`
|
||||
- `price_unit`: `product.uom` where `name = Mt`
|
||||
|
||||
If the `price_index` contains a `YYYY-MM` style period, the wizard derives a `product.month`:
|
||||
|
||||
- pattern accepted in the name: `YYYY-MM`, `YYYY/MM`, `YYYY_MM`, `YYYY.MM`, or `YYYY MM`
|
||||
- month name format: `MONYY`, for example `JUL26`
|
||||
- if no matching `product.month` exists, it is created with `is_cotation = True`
|
||||
|
||||
## Result Reporting
|
||||
|
||||
The result screen always shows counts and detail sections for:
|
||||
|
||||
- created price indexes
|
||||
- imported prices
|
||||
- updated existing prices
|
||||
- skipped records
|
||||
- errors
|
||||
|
||||
Row-level errors do not stop the whole import; the wizard records the error and continues with the next row.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Missing `price_index`: skipped.
|
||||
- Missing `price_date`: skipped.
|
||||
- Invalid `.xlsx` file: blocking `UserError`.
|
||||
- Missing required columns: blocking `UserError`.
|
||||
- Invalid date: row error.
|
||||
- Invalid numeric value: row error.
|
||||
- Existing `(price, price_date)` without overwrite option: skipped.
|
||||
- Existing `(price, price_date)` with overwrite option: updated.
|
||||
- Empty rows are ignored.
|
||||
|
||||
## Impacted Files
|
||||
|
||||
Direct `purchase_trade` files:
|
||||
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- `modules/purchase_trade/pricing.xml`
|
||||
- `modules/purchase_trade/view/import_prices_start_form.xml`
|
||||
- `modules/purchase_trade/view/import_prices_result_form.xml`
|
||||
- `modules/purchase_trade/__init__.py`
|
||||
- `modules/purchase_trade/tryton.cfg`
|
||||
- `modules/purchase_trade/tests/test_module.py`
|
||||
|
||||
External model dependencies:
|
||||
|
||||
- `modules/price/price.py`
|
||||
- `modules/price/price_value.py`
|
||||
- `modules/price/view/price_value_form.xml`
|
||||
|
||||
## Tests
|
||||
|
||||
Existing focused tests cover:
|
||||
|
||||
- missing price index skipped when creation is disabled
|
||||
- missing price index created when creation is enabled
|
||||
- default fields on newly created price indexes
|
||||
- period reuse/creation from `price_index`
|
||||
- existing `price_date` skipped when overwrite is disabled
|
||||
- existing `price_date` updated when overwrite is enabled
|
||||
- invalid row values collected as errors
|
||||
- result screen formatting
|
||||
|
||||
Recommended additional tests:
|
||||
|
||||
- `.xlsx` header normalization
|
||||
- missing required columns
|
||||
- Excel serial date parsing
|
||||
- empty row ignored
|
||||
- invalid workbook raises `UserError`
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Q: Should `(price, price_date)` be enforced unique at model/database level?
|
||||
- A: Enforce uniqueness at model level for now. Do not add a database constraint yet.
|
||||
|
||||
- Q: Should created price index defaults remain hardcoded to `Market price`, `USD`, `Argus EU`, and `Mt`?
|
||||
- A: Yes. Keep these defaults hardcoded for this import.
|
||||
|
||||
- Q: Should ambiguous slash dates prefer `DD/MM/YYYY` over `MM/DD/YYYY`, as currently implemented?
|
||||
- A: Prefer the user's/default locale date format when possible. Fall back to the current order only if no locale preference is available.
|
||||
- Comment: current code tries `DD/MM/YYYY` before `MM/DD/YYYY` and does not inspect locale. Implementing this answer requires a code change.
|
||||
|
||||
- Q: Should missing `price_value` be allowed, or should it skip/error while high/low/open remain optional?
|
||||
- A: Missing `price_value` is not allowed. Report the row as an error. `high_price`, `low_price`, and `open_price` remain optional.
|
||||
- Comment: current code allows empty `price_value` and imports it as an empty value. Implementing this answer requires a code change.
|
||||
|
||||
- Q: Should duplicate rows for the same `(price_index, price_date)` inside the same Excel file be treated as an error, skipped after the first row, or resolved by the overwrite option?
|
||||
- A: Report duplicate rows as row errors and do not import or update the duplicate row.
|
||||
|
||||
- Q: When `Create price index if missing` is enabled, should missing default reference records (`Market price`, `USD`, `Argus EU`, `Mt`) block index creation or remain optional as currently implemented?
|
||||
- A: Remain optional. Create the price index with the reference records that can be found.
|
||||
|
||||
- Q: Should the import result distinguish business validation errors from technical parsing errors?
|
||||
- A: Yes. Distinguish business validation errors from file, parsing, and technical errors in the import result.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user