1076 Commits

Author SHA1 Message Date
236b4a6227 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:15 +00:00
93be36fb52 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:14 +00:00
412f99f6a1 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:14 +00:00
a00f32e12f Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:13 +00:00
155436e103 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:12 +00:00
c43c7035a7 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:12 +00:00
a68656e1d5 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:11 +00:00
65a7419e30 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:11 +00:00
d334b0c87b Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:10 +00:00
4826cd422e Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:10 +00:00
0f8a0bddba Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:09 +00:00
155faf8f8b Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:08 +00:00
b8e8b0d989 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:08 +00:00
8f8eec47a9 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:07 +00:00
c0767d1041 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:07 +00:00
4bb470d291 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:06 +00:00
7f4f78e750 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:05 +00:00
6640a61437 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:05 +00:00
0e7e46d0bb Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:04 +00:00
367f45a9b3 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:04 +00:00
1d4e56cbd9 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:03 +00:00
4e07525d10 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:03 +00:00
f5d6ad46c4 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:02 +00:00
cc8804103b Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:01 +00:00
f7f24085cd Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:01 +00:00
097cf223ec Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:00 +00:00
8c3381f41b Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:42:00 +00:00
05a553a8fb Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:59 +00:00
e71e1e04f4 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:58 +00:00
cc614c3ecf Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:58 +00:00
49feadbeb9 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:57 +00:00
66d1bf5e47 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:57 +00:00
7d39b690ec Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:56 +00:00
48c79615fe Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:56 +00:00
55dac4ca23 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:55 +00:00
a861002926 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:54 +00:00
462f134278 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:54 +00:00
1a42ca3cb1 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:53 +00:00
f5574ed238 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:53 +00:00
3f00653bb2 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:52 +00:00
505f69fb27 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:52 +00:00
85ff0c5741 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:51 +00:00
b91b95bccd Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:41:51 +00:00
984be00635 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:05 +00:00
01fd85aec0 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:05 +00:00
6aa60f1bc3 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:04 +00:00
55bad508da Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:04 +00:00
99ec962ca3 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:03 +00:00
d4b094937a Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:02 +00:00
19e36c1a35 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:02 +00:00
ce15caaa45 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:01 +00:00
315e1bd1e7 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:01 +00:00
ff8d274a38 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:00 +00:00
ab2c9cddae Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:33:00 +00:00
16dc380c53 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:59 +00:00
4b5170e8b7 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:58 +00:00
c7912ec5a6 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:58 +00:00
090f0099c0 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:58 +00:00
ed7a1d93ba Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:57 +00:00
cbf850a987 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:56 +00:00
877cbcdd6b Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:56 +00:00
44e10c9212 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:55 +00:00
a2742e95ec Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:55 +00:00
9105818e3a Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:54 +00:00
f06a374569 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:54 +00:00
1c38931430 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:53 +00:00
fa92ab9ba8 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:53 +00:00
4b56acc480 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:52 +00:00
f3cb0587f5 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 08:32:52 +00:00
3e756c774f Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:44 +00:00
e8fe926dbb Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:43 +00:00
b2a168bd06 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:43 +00:00
4628821451 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:42 +00:00
ab108c0437 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:42 +00:00
5d9fe7b1f3 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:41 +00:00
1b3bf2d301 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:41 +00:00
017d7883d3 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:40 +00:00
00439eac6e Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:39 +00:00
d5b31de87e Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:39 +00:00
f9a6b56c81 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:38 +00:00
123239031f Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:38 +00:00
7f65dc9a92 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:38 +00:00
25c345f665 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:37 +00:00
2c2058e07b Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:36 +00:00
f628de0c15 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:36 +00:00
998d97da45 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:35 +00:00
7a6f12082b Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 08:32:35 +00:00
Tower Deploy
9bb80002c8 Wipe addons/: full reset for clean re-upload 2026-04-27 11:20:53 +03:00
2cf3b5185d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:40 +00:00
9214b650ae Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:39 +00:00
e6027c710b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:39 +00:00
7a40f423d4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:38 +00:00
9966aa4ffa Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:38 +00:00
7c90e7bee2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:37 +00:00
be3a699471 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:37 +00:00
a68f5ed5da Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:36 +00:00
3a4d546c10 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:36 +00:00
386893e751 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:35 +00:00
e8cd94cc98 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:34 +00:00
bbf383a59f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:34 +00:00
eb41cf2557 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:33 +00:00
7d71cd87fe Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:33 +00:00
886affc442 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:32 +00:00
236c25028c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:32 +00:00
ca0996089d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:31 +00:00
326bbe4eb1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:31 +00:00
bd2de68a13 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:30 +00:00
241485ada6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:30 +00:00
93925e7edb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:29 +00:00
8be03b6213 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:28 +00:00
f8be9708f2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:28 +00:00
43f1d3a460 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:27 +00:00
95ab9b7b07 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:27 +00:00
c4dec5820f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:26 +00:00
cf8020a374 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:26 +00:00
8b2b00309b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:25 +00:00
43c386dd0f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:24 +00:00
94e68bf101 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:24 +00:00
c6fcbd4e88 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:24 +00:00
95ec10c4cd Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:23 +00:00
8cde5f5b85 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:22 +00:00
92daedbcfe Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:22 +00:00
59af83f001 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:21 +00:00
68ca5feaec Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:21 +00:00
d379066f8d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:20 +00:00
50f45a25f6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:20 +00:00
4f1b44f859 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:19 +00:00
63cb075ebe Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:18 +00:00
3bc89980fb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:18 +00:00
a30f10f463 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:18 +00:00
a3f387f59d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:17 +00:00
0496b94742 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:16 +00:00
eeea0bde1d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:16 +00:00
19a63eac0c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:15 +00:00
ff42b73982 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:15 +00:00
ac9207b0c6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:14 +00:00
7dadc5938a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:14 +00:00
88fb907a24 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:13 +00:00
a349d97184 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:12 +00:00
2eee80609f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:12 +00:00
fcb747f2c9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:11 +00:00
826194b88f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:11 +00:00
303d179c1a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:10 +00:00
b309b6f244 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:09 +00:00
ee0ef6b69d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:09 +00:00
121fcd2639 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:08 +00:00
5cecb3364e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:08 +00:00
1e2909808a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:07 +00:00
b8658d0fed Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:06 +00:00
ea586f0019 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:06 +00:00
4219a55576 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:05 +00:00
2d6197c181 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:05 +00:00
049d8dc461 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:04 +00:00
8f7afa25fc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:04 +00:00
7c0c8cf62a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:03 +00:00
e0c9e3817f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:02 +00:00
95894e0965 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:02 +00:00
0fd426db38 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:01 +00:00
7f38305cda Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:01 +00:00
9eb03938ba Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:00 +00:00
38b16543da Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:18:00 +00:00
3a6e036b1d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:59 +00:00
c28baccca8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:58 +00:00
2352fdfaab Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:58 +00:00
fd93d95ef0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:57 +00:00
1ad508d029 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:57 +00:00
80e2953742 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:56 +00:00
8f2214eb37 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:55 +00:00
105434392b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:55 +00:00
d77c0ec323 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:54 +00:00
ebc9c2da81 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:54 +00:00
b706079a96 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:54 +00:00
1a3fb88ff0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:53 +00:00
fe73ec564d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:52 +00:00
38becb2347 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:52 +00:00
9cc237dfcd Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:51 +00:00
59a0a18068 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:51 +00:00
3cdcbd70c3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:50 +00:00
36f48cb3ef Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:49 +00:00
6e40ceba46 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:48 +00:00
9ed9312f52 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:48 +00:00
3bf88a3db2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:47 +00:00
cd835868a4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:47 +00:00
3adbefa88a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:46 +00:00
6192b3aff5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:45 +00:00
b504a42afc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:45 +00:00
60532fda2e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:44 +00:00
4604622231 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:43 +00:00
f4e43a2f84 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:43 +00:00
c5c3887d97 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:42 +00:00
8ce3697a6e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:41 +00:00
bbe37b34d9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:41 +00:00
ae8c208c59 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:40 +00:00
3811a9d7cd Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:39 +00:00
3eed4a9224 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:38 +00:00
68b713a69a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:37 +00:00
af1be38c68 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:37 +00:00
3780eaf1f2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:36 +00:00
1f23472908 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:35 +00:00
5e71e0a98a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:35 +00:00
5e665079f3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:34 +00:00
b67d86ef4b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:33 +00:00
cf83815785 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:33 +00:00
255a06a789 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:32 +00:00
3153e06601 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:31 +00:00
7e4c9dcd99 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:31 +00:00
8e92de0c4e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:30 +00:00
eb58ac434c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:29 +00:00
5aed68117b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:29 +00:00
b6c3a855a4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:28 +00:00
cd4b7cd2e6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:27 +00:00
cb1e060ebb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:27 +00:00
ccc9ef0644 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:26 +00:00
03b1d2bd59 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:25 +00:00
565ef8f0cc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:25 +00:00
ae52efe220 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:24 +00:00
09576d37b4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:23 +00:00
3b556e385d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:23 +00:00
d24677fc8e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:22 +00:00
bc9c9bf125 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:21 +00:00
953db1c18e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:21 +00:00
bb6408e1bb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:20 +00:00
f5ee80a075 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:19 +00:00
e2a1ef97cf Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:19 +00:00
f7aa151993 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:18 +00:00
c8461e648d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:17 +00:00
78f57a52e8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:17 +00:00
645bbb9e1f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:16 +00:00
1c5b8f78e8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:15 +00:00
b06be61868 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:15 +00:00
22635fd1db Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:14 +00:00
502f3c276f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:13 +00:00
a29e7d3701 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:12 +00:00
90a6de998c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:12 +00:00
309834e218 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:11 +00:00
36548b07b3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:11 +00:00
a647db524a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:10 +00:00
96392d7cab Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:09 +00:00
3c73cf7286 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:08 +00:00
5128d56863 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:08 +00:00
ec366ca548 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:07 +00:00
eaaf033cd5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:06 +00:00
899eefb2b1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:06 +00:00
35c152c073 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:05 +00:00
4db61b7221 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:04 +00:00
56faad93d6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:03 +00:00
b02021eeab Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:03 +00:00
0f8d4c52be Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:02 +00:00
f6b23b2697 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:01 +00:00
9c162c8c40 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:01 +00:00
9ee9d2dc06 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:17:00 +00:00
f88873226d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:59 +00:00
e6ef101e11 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:58 +00:00
ace82b83f0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:58 +00:00
c3b5ebd7bc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:57 +00:00
f52001bffa Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:56 +00:00
8289f0ec93 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:56 +00:00
4c85b5da81 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:55 +00:00
7bddb74ba7 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:54 +00:00
b088c69c2b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:54 +00:00
ffc66cfaa1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:53 +00:00
50705d8a20 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:52 +00:00
eb85e1f034 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:52 +00:00
c104fc8c43 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:51 +00:00
12aaeebb69 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:50 +00:00
b8accd5199 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:50 +00:00
36e718ae36 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:49 +00:00
fc67cb263f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:48 +00:00
d6bec900fe Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:48 +00:00
db967ff6e3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:47 +00:00
9d6d07b2e1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:46 +00:00
1444a3064e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:46 +00:00
0a2e334aee Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:45 +00:00
15d21d3ab5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:45 +00:00
a4f565087c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:44 +00:00
6a11883c7c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:43 +00:00
1202892973 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:42 +00:00
d3e1cc28bc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:42 +00:00
151246c614 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:41 +00:00
5049ba6907 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:40 +00:00
f8b593bec4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:35 +00:00
682e48bb2b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:34 +00:00
c4ddcb8e92 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:34 +00:00
d992b45cdd Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:33 +00:00
f89637d6e8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:33 +00:00
a493701b6e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:32 +00:00
77b6a4b2fc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:32 +00:00
9d595b2565 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:31 +00:00
812b64b18d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:31 +00:00
a6d4d64192 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:30 +00:00
0f52520ee3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:30 +00:00
8a996f5083 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:29 +00:00
b4e0d93c28 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:29 +00:00
f54217e713 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:28 +00:00
ebb1399951 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:28 +00:00
f9d33713ab Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:27 +00:00
4af3a1e647 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:26 +00:00
af44da007b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:26 +00:00
1d5a2ceed0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:25 +00:00
4f9d558cf4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:25 +00:00
8bdd6064ab Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:24 +00:00
76b43e0b65 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:24 +00:00
3b80e2101c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:23 +00:00
42653be822 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:23 +00:00
717ce4756a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:22 +00:00
5ac0c18619 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:22 +00:00
79dd86e13f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:21 +00:00
05b6f98a82 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:21 +00:00
098ce63b26 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:20 +00:00
254f6312b4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:20 +00:00
5dc4447ff3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:19 +00:00
d77978d0c9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:19 +00:00
c4df067894 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:18 +00:00
c210efa9a2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:18 +00:00
3dbf0baeb2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:17 +00:00
5903af189c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:17 +00:00
507eab7847 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:16 +00:00
0f5b16febc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:16 +00:00
1b5342da13 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:15 +00:00
9d2776f6ce Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:15 +00:00
716a6d735c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:14 +00:00
5362b526e7 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:14 +00:00
0586c40667 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:13 +00:00
9e48c4e0dc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:12 +00:00
569a853b3a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:12 +00:00
74a3f434a4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:12 +00:00
a6af9583df Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:11 +00:00
326900b3a7 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:10 +00:00
c4dfb0a886 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:09 +00:00
a5efdebe15 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:09 +00:00
cb84ce69be Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:08 +00:00
7e5354d5ad Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:07 +00:00
521d765b35 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:06 +00:00
7fbaaf189b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:06 +00:00
6a9182c820 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:05 +00:00
90f7afa720 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:05 +00:00
90c893ae66 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:04 +00:00
8d55a7ee70 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:04 +00:00
f6e32ece58 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:03 +00:00
162ad41852 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:02 +00:00
8b89083a51 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:02 +00:00
68c5b015e0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:01 +00:00
61e360ae9e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:00 +00:00
50663c7700 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:16:00 +00:00
4da1291a50 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:59 +00:00
94df75a9bf Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:59 +00:00
5ea3184c5e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:58 +00:00
099bb0dfd5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:58 +00:00
e318c89788 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:57 +00:00
1174604b05 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:57 +00:00
364f37aa5a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:56 +00:00
bbb71840c1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:56 +00:00
c2923e01e6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:55 +00:00
4131ae0adb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:54 +00:00
f16e0abf03 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:54 +00:00
07adc2628c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:53 +00:00
a682b818d3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:53 +00:00
b2a8c2dcde Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:52 +00:00
2314a8f6c2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:52 +00:00
f2033e0408 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:51 +00:00
38d4a641f1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:51 +00:00
8ca3561c1a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:50 +00:00
bf7158a6e8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:50 +00:00
6a92d586a4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:49 +00:00
2a93942be1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:48 +00:00
e81e6f4ff1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:48 +00:00
5e4cf9723b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:48 +00:00
a6fb93b231 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:47 +00:00
a3022f8332 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:47 +00:00
af0b27d762 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:46 +00:00
d2a8d537f6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:45 +00:00
52d65dabba Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:44 +00:00
cbd499b88f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:44 +00:00
d59aa0179a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:43 +00:00
bf61f4eb4f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:43 +00:00
97352ac44d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:42 +00:00
eb0687da36 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:42 +00:00
ceea23aaaf Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:41 +00:00
802a5a8a1c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:40 +00:00
2d24db2b08 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:40 +00:00
bfb1edac54 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:39 +00:00
b12ac35b65 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:39 +00:00
8b881eba5b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:38 +00:00
b2317325e2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:38 +00:00
a8e845cb8b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:37 +00:00
14bd70a67c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 08:15:36 +00:00
82cfb22c03 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:27 +00:00
50cd192fd8 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:26 +00:00
62a0a9b1bd Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:26 +00:00
88b9f087a1 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:25 +00:00
55b5ad56c8 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:25 +00:00
00d5145aa0 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:24 +00:00
6161522a04 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:24 +00:00
abaabc4cc0 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:23 +00:00
2686884335 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:23 +00:00
f41c8d4c6f Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:22 +00:00
a9496bdbd2 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:21 +00:00
5e634580c2 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:21 +00:00
37b4590851 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:20 +00:00
81e565fdec Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:20 +00:00
b912c85da7 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:19 +00:00
7ef63fa72d Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:18 +00:00
627c3e9ffa Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:18 +00:00
876b3f4a7d Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:17 +00:00
f323562bbd Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:17 +00:00
f98711ec66 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:16 +00:00
8db97cf776 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:16 +00:00
f38452a52a Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:15 +00:00
03d392803f Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:15 +00:00
8b40cf2659 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 08:15:14 +00:00
86cf1847f4 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:15:00 +00:00
45d7ed8b73 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:59 +00:00
0b7b196159 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:59 +00:00
33ad700d61 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:58 +00:00
7f8552981b Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:57 +00:00
5cc9d2512b Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:57 +00:00
6ab789e36d Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:56 +00:00
f8109af6ce Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:56 +00:00
1bfc6ac3af Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:55 +00:00
548817116a Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:55 +00:00
b2f712e6b6 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:54 +00:00
93739ddaff Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:54 +00:00
1373305638 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:53 +00:00
70a54d0787 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:53 +00:00
40a164b1b0 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:52 +00:00
bc966d56b0 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:51 +00:00
61e7953827 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:51 +00:00
192e1a9dbc Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:50 +00:00
2fe3c98c53 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:49 +00:00
5f3cca665d Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 08:14:48 +00:00
88d5b049ff Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:40 +00:00
4614cb31fb Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:40 +00:00
44d76bc4a1 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:39 +00:00
353350476d Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:39 +00:00
76d63a2fe8 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:38 +00:00
cce75a5895 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:37 +00:00
2409adec5e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:37 +00:00
f75a5247b5 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:36 +00:00
8cfa1310d1 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:36 +00:00
086b96cc39 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:35 +00:00
908aa7e1da Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:35 +00:00
b6218e6b07 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:34 +00:00
3d868855ca Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:34 +00:00
7e7213f67b Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:33 +00:00
f5afc7d31c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:33 +00:00
72737afb61 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:32 +00:00
b39e81512e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:32 +00:00
62fad98a12 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:31 +00:00
98c4aa7358 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:30 +00:00
1a3ee1d4de Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:30 +00:00
be2facb1f1 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:29 +00:00
97b4731ca6 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:29 +00:00
83326bcaed Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:28 +00:00
09eda63698 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:28 +00:00
d0aaff2135 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:27 +00:00
7bd0bd0989 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:26 +00:00
94bd98ab65 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:26 +00:00
25fa4c853b Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:26 +00:00
4b4581ff2e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:25 +00:00
e1ea01e59e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:24 +00:00
da4d5c2494 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:24 +00:00
3713992eb3 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:23 +00:00
e1c8337464 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:23 +00:00
6c81ab18c8 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:22 +00:00
f0085588b9 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:22 +00:00
8fdb330a6c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:21 +00:00
fb7a94f8ae Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:21 +00:00
e71e3822e0 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:20 +00:00
e289fb9145 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:19 +00:00
bf2a8c4010 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:19 +00:00
5cc34775f3 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:18 +00:00
7f75afbdba Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:18 +00:00
1390ec0c15 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:17 +00:00
4e84600e7e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:17 +00:00
c0ea49872c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:16 +00:00
0685e4cabe Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:16 +00:00
875a590978 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:15 +00:00
98d8aeb011 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:14 +00:00
2f9905210a Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:14 +00:00
41d56a304a Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:13 +00:00
55da1da7f3 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:13 +00:00
a65d315cda Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:12 +00:00
16e6c57a3f Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:12 +00:00
638a2df7c6 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:11 +00:00
9b53282444 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:11 +00:00
89d02b353b Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:10 +00:00
c968cca4de Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:10 +00:00
e163b0d99b Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:09 +00:00
4426a5abb6 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:09 +00:00
8c8e527249 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 08:14:08 +00:00
f50c1865d5 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:07:03 +00:00
f5257dc7ab Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:07:02 +00:00
14315e0035 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:07:01 +00:00
e2fe62b379 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:07:01 +00:00
1f9bea1264 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:07:00 +00:00
08f1a4d3cb Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:07:00 +00:00
be8a0ac9c3 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:59 +00:00
a383f11d2a Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:58 +00:00
91618edeec Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:58 +00:00
b64f6da230 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:57 +00:00
ba4b26b175 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:57 +00:00
525c919b3a Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:56 +00:00
d7f6de08e9 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:56 +00:00
0d20ab7aa8 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:55 +00:00
231d59da7f Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:54 +00:00
4fef277751 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:54 +00:00
7032e64dcb Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:53 +00:00
15481c11c0 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:53 +00:00
5930f771e5 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:52 +00:00
2290c90089 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:51 +00:00
eb28ba0f78 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:51 +00:00
7df203efe8 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:50 +00:00
766b1dd779 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:50 +00:00
e992a8ea3f Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:49 +00:00
e3863de129 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:48 +00:00
bfed6bdcf4 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:48 +00:00
f4834218ce Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:47 +00:00
e3e001ac5b Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:47 +00:00
afc1985114 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:46 +00:00
f0c0fa15ac Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:45 +00:00
3bb17aa74a Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:45 +00:00
3887d3afd3 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:44 +00:00
ccf1ac8e77 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:44 +00:00
df84150f14 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:43 +00:00
fd4f3c89cf Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:43 +00:00
46fa14692c Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:42 +00:00
2d26b672bf Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:42 +00:00
f908dd5acd Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:41 +00:00
f50135b5c6 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:40 +00:00
2be8fc95b7 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:40 +00:00
736d4b40e0 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:39 +00:00
7fa0a4c79a Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:39 +00:00
8881aeb2ca Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 08:06:38 +00:00
8fa1c29c59 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:32 +00:00
318ef00817 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:31 +00:00
3fcdf69f56 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:31 +00:00
e2785fff55 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:30 +00:00
e5adc46d44 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:29 +00:00
6052fcbe7e Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:29 +00:00
268e7e9b23 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:28 +00:00
53f93fe01b Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:28 +00:00
202cb09e2b Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:27 +00:00
c29df047d2 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:27 +00:00
8dc8a3d39b Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:26 +00:00
34a4c17e9b Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:26 +00:00
ccb61bd1d9 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:25 +00:00
be89574d27 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:25 +00:00
e8a12c08d8 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:24 +00:00
16a5812db0 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:23 +00:00
366eeab65d Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:23 +00:00
ce185f92e3 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:22 +00:00
276dccf5fd Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:22 +00:00
4fc6c80352 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:21 +00:00
016afe19f8 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:21 +00:00
7295ad401c Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:20 +00:00
1eed0fbda0 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:19 +00:00
da449b9852 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 08:06:19 +00:00
Tower Deploy
9a499aced7 Wipe addons/: corrupt source from old double-base64 upload bug + test artifacts; ready for clean re-upload 2026-04-27 10:59:54 +03:00
c3f2c4f522 Tower: upload probe_addon 16.0.1.0.0 (via marketplace) 2026-04-27 07:53:52 +00:00
a356f71745 Tower: upload probe_addon 16.0.1.0.0 (via marketplace) 2026-04-27 07:53:51 +00:00
0a76509365 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:23 +00:00
9083833444 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:22 +00:00
ed9dc11c4b Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:22 +00:00
1c2d483a46 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:21 +00:00
1f073938d7 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:21 +00:00
e12133fff0 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:20 +00:00
61d95f53d6 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:20 +00:00
ebd9d7c16f Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:19 +00:00
455a7192d3 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:18 +00:00
4c8ec8ffa3 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:18 +00:00
2beca515ca Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:17 +00:00
e1c3544f09 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:17 +00:00
242fc36fd0 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:16 +00:00
4754df89b7 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:16 +00:00
e050040bae Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:15 +00:00
30deb10c27 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:14 +00:00
f2791efa4f Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:14 +00:00
acc9748aa2 Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace) 2026-04-27 06:59:13 +00:00
b36fe277c0 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:53 +00:00
0b07e80ecb Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:53 +00:00
b448019f93 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:52 +00:00
a108daebd0 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:52 +00:00
2c950b5a0a Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:51 +00:00
0ded373257 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:51 +00:00
4ad6ffce26 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:50 +00:00
265f8092e5 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:49 +00:00
6735056551 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:49 +00:00
a70aae6404 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:48 +00:00
f7c2027d17 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:48 +00:00
8f9c4f4ed9 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:47 +00:00
cd4fa9df4d Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:47 +00:00
b776dea93e Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:46 +00:00
2d655e9822 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:46 +00:00
f1aef07115 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:45 +00:00
ceac54f2a8 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:45 +00:00
8cd2d73dd1 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:44 +00:00
4c8a85091e Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:44 +00:00
1d8e306a5b Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:43 +00:00
ff76148774 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:42 +00:00
263616cf66 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:42 +00:00
5d8563cf5e Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:41 +00:00
3a8e7bb486 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:41 +00:00
dac924d260 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:40 +00:00
2fec9bae57 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:40 +00:00
a3a0e7f1a9 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:39 +00:00
57e4922aa4 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:39 +00:00
5a86e951c9 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:38 +00:00
07a2292b98 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:38 +00:00
498ebf5b5e Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:37 +00:00
0c0a53fb80 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:37 +00:00
f13d08283f Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:36 +00:00
6776b72adf Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:36 +00:00
fc0111f9af Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:35 +00:00
58ecb0871a Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:34 +00:00
5ab36dc181 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:34 +00:00
9eb4e7eb59 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:33 +00:00
b73d178751 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:33 +00:00
aeb5997303 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:32 +00:00
b6be8f1f51 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:32 +00:00
6e4ec0f188 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:31 +00:00
d84a9d95e3 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:31 +00:00
e85ead856e Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:30 +00:00
d640f69b64 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:30 +00:00
3a855c6905 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:29 +00:00
e5bc9f79bc Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:29 +00:00
e4a9a645a1 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:28 +00:00
7be7fc0d06 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:28 +00:00
639c21ee64 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:27 +00:00
83463f08fd Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:27 +00:00
d62c41a4c0 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:26 +00:00
5bc32a7b39 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:26 +00:00
b87f9f2f0f Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:25 +00:00
ecb8f48fc7 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:25 +00:00
453e861eb1 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:24 +00:00
9d9ba90f65 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:24 +00:00
c0ec43dc15 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:23 +00:00
8bf0f53a1f Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:23 +00:00
b9935804d8 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:22 +00:00
e3cd173cae Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:22 +00:00
0b171536bd Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:21 +00:00
4df5459abb Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:21 +00:00
99c360c739 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:20 +00:00
238f3a123f Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:19 +00:00
7635f192bc Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:19 +00:00
4be4c95126 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:19 +00:00
c7174e1aa0 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:18 +00:00
038f113176 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:18 +00:00
e640b7dc00 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:17 +00:00
69ee0e8a79 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:16 +00:00
44f9bf1d9d Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:16 +00:00
528d049505 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:16 +00:00
d38fff270d Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:15 +00:00
f47e6d77df Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:14 +00:00
bcdb9f7100 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:14 +00:00
79c3511ced Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:13 +00:00
77163b4017 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:13 +00:00
52512ff961 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:12 +00:00
8e37d624ff Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:11 +00:00
2b845fa110 Tower: upload cetmix_tower_yaml 16.0.3.1.0 (via marketplace) 2026-04-27 06:58:11 +00:00
09c730c852 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:56 +00:00
9ea0b5ca1e Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:55 +00:00
3f7c12c4cb Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:55 +00:00
52212a4234 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:54 +00:00
aad83a6e45 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:54 +00:00
b15249a7cc Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:53 +00:00
971c0b6bb1 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:53 +00:00
c8259d3fae Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:52 +00:00
c1c1591ffd Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:52 +00:00
1ceae80007 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:51 +00:00
711c240058 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:51 +00:00
ed906caf54 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:50 +00:00
fa316c0948 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:50 +00:00
64da73b835 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:49 +00:00
51d27b362c Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:49 +00:00
11bf6134ec Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:48 +00:00
e6fd3d1594 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:48 +00:00
6c440b46d0 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:47 +00:00
6f5049ff54 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:46 +00:00
785da91996 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:46 +00:00
a59be21d5c Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:45 +00:00
dcd8d31c0b Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:45 +00:00
bbff9cd347 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:44 +00:00
c5433c7b44 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:44 +00:00
52e3ae85ef Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:43 +00:00
6ee8d34a08 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:43 +00:00
fc845bf79a Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:42 +00:00
aa3ea16a1b Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:42 +00:00
5d72ad53fb Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:41 +00:00
d5a16b3baa Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:41 +00:00
4300c11cb1 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:40 +00:00
611bf9a530 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:40 +00:00
f665a077cb Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:39 +00:00
62c2411818 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:39 +00:00
1aeff8903c Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:38 +00:00
7a0fe4f363 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:38 +00:00
6ebe68c8fc Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:37 +00:00
075a340056 Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:37 +00:00
d764da736d Tower: upload cetmix_tower_webhook 16.0.1.0.5 (via marketplace) 2026-04-27 06:57:36 +00:00
b9ee75209a Tower: upload cetmix_dup_test 16.0.1.0.0 (via marketplace) (force-overwrite) 2026-04-27 06:56:35 +00:00
389ffcbadb Tower: upload cetmix_dup_test 16.0.1.0.0 (via marketplace) (force-overwrite) 2026-04-27 06:56:35 +00:00
37ec61eab2 Tower: upload cetmix_dup_test 16.0.1.0.0 (via marketplace) 2026-04-27 06:56:34 +00:00
a901150981 Tower: upload cetmix_dup_test 16.0.1.0.0 (via marketplace) 2026-04-27 06:56:33 +00:00
d7e623d816 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:12 +00:00
45e7dd2c6c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:11 +00:00
25ca66a86b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:11 +00:00
a78cbd6583 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:10 +00:00
09283bba61 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:10 +00:00
e738070b24 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:09 +00:00
0c67e0d798 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:09 +00:00
9e7f379923 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:08 +00:00
93426fbae5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:08 +00:00
f1b9101b13 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:07 +00:00
33d3b02763 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:06 +00:00
2a169afa62 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:06 +00:00
a2fc7b47c1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:05 +00:00
c717460cea Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:04 +00:00
a8f57858d8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:04 +00:00
3ab0e79386 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:03 +00:00
236b78c33b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:02 +00:00
18138fd178 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:02 +00:00
fa34dcb44e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:01 +00:00
f8a7e22755 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:00 +00:00
5e7a05b949 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:54:00 +00:00
676d1fd8c1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:59 +00:00
9a569b9af1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:59 +00:00
bfda22756e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:58 +00:00
237d692cd1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:58 +00:00
31b57b24f5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:57 +00:00
ff8cc52e41 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:56 +00:00
f6d8bab742 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:56 +00:00
1bd0a301dd Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:55 +00:00
d7ac3f27e0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:55 +00:00
adf18c04a9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:54 +00:00
150023c203 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:54 +00:00
21bf17da38 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:53 +00:00
54c3d037a0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:53 +00:00
cc7c59f4fa Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:52 +00:00
491c5fa696 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:52 +00:00
cb1c08a52d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:51 +00:00
7c414a48de Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:51 +00:00
0d03d8d956 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:50 +00:00
ee974eb8d1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:50 +00:00
5e17847c10 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:49 +00:00
753ee2e4e3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:49 +00:00
2c73095fd6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:48 +00:00
c31875dc95 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:48 +00:00
39ac32de74 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:47 +00:00
3a6f76212d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:47 +00:00
89c5d6195a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:46 +00:00
a6c0f93af0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:46 +00:00
f52cdfe12d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:45 +00:00
fe91357505 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:44 +00:00
e4c578f20a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:44 +00:00
d9734e17fb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:43 +00:00
4cc3f94635 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:43 +00:00
8ff4dd5b3c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:42 +00:00
423b6acfde Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:42 +00:00
ccf2ec0804 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:41 +00:00
002f0bda6f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:41 +00:00
753aee7fc5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:40 +00:00
c96b8b5c64 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:40 +00:00
821e088b8e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:39 +00:00
3ea5965a99 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:38 +00:00
5ce2821634 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:38 +00:00
5d4b6ee4fc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:37 +00:00
7399e63d70 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:37 +00:00
fc27945b4e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:36 +00:00
2b98a8ca57 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:36 +00:00
d50103ca24 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:35 +00:00
52a3aaec81 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:35 +00:00
48d9f3af80 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:34 +00:00
7575ca93ad Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:33 +00:00
dcf7f0daa9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:33 +00:00
ffe3f45168 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:32 +00:00
bd17bdabf9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:32 +00:00
118c993e31 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:31 +00:00
33707afaaf Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:31 +00:00
5fc79cb304 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:30 +00:00
b595872bb3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:29 +00:00
e50e55444c Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:29 +00:00
0f68d7e8f9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:28 +00:00
f161ba7728 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:28 +00:00
fb2479c6fe Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:27 +00:00
f8f75587c3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:27 +00:00
3cbf6b5144 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:26 +00:00
48cf579779 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:26 +00:00
5c5edd2106 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:25 +00:00
4a4b2f7206 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:25 +00:00
e84cdd1a54 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:24 +00:00
410eaab039 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:24 +00:00
8b8dd4758f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:23 +00:00
16dfa61d3d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:23 +00:00
cd0dcdb06b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:22 +00:00
0bc76ebcfb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:22 +00:00
b86383444b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:21 +00:00
e788902689 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:21 +00:00
2d0d90ba7e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:20 +00:00
c56cba7257 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:19 +00:00
63623d0dc3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:19 +00:00
1a409eab82 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:18 +00:00
135242d577 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:18 +00:00
aec96c5193 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:17 +00:00
a68eef6627 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:17 +00:00
0ed1ee7bb0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:16 +00:00
ccdde704c7 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:16 +00:00
f2803833a6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:15 +00:00
884df448f0 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:14 +00:00
a1cba28258 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:14 +00:00
eddb6db49e Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:13 +00:00
67267c20a3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:13 +00:00
547da816ad Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:12 +00:00
ecf57118ba Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:12 +00:00
3681255837 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:11 +00:00
f97595108f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:11 +00:00
7bf6c0aa4b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:10 +00:00
9fb24cad21 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:10 +00:00
b036b9f480 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:09 +00:00
2d677619f3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:08 +00:00
576e6edecc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:08 +00:00
0819ee5593 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:08 +00:00
7ca45462cf Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:07 +00:00
0e8399ae71 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:07 +00:00
cf42f5455d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:06 +00:00
50536e5867 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:05 +00:00
e3c584c560 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:05 +00:00
c48e67db97 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:04 +00:00
fd8c6f50f2 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:04 +00:00
e09e6074c8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:03 +00:00
c470e9603f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:03 +00:00
faf0882b3a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:02 +00:00
e96209ad14 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:02 +00:00
1912047142 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:01 +00:00
8011416ceb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:01 +00:00
2d3f527e24 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:53:00 +00:00
aed221fa34 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:59 +00:00
425d46b0ef Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:59 +00:00
b3d43ad682 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:58 +00:00
65af6a8437 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:58 +00:00
ac07f2e7df Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:57 +00:00
4d1c88b43b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:57 +00:00
465720ccdd Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:56 +00:00
21fe8ed322 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:56 +00:00
01d440de88 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:55 +00:00
aba0ce3a6d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:55 +00:00
906217822f Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:54 +00:00
9e81d94e5d Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:54 +00:00
092f3b1026 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:53 +00:00
6739cac57b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:53 +00:00
2d69ab84ce Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:52 +00:00
af3a8d4dbc Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:52 +00:00
0792585612 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:51 +00:00
ee798802e6 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:51 +00:00
3f44bf0577 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:50 +00:00
eaedae0565 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:50 +00:00
9c068d07c9 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:49 +00:00
8abe88b2d3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:49 +00:00
ece70df8fe Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:48 +00:00
ecc375cf63 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:48 +00:00
d89a5b2e4b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:47 +00:00
d041586baf Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:47 +00:00
49472d32a1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:46 +00:00
aa83b5c270 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:46 +00:00
24bd37cd38 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:45 +00:00
a4e24baeb5 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:45 +00:00
f0512fdce3 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:44 +00:00
101b6c9b74 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:44 +00:00
9db3f91c28 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:43 +00:00
94de4c07da Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:43 +00:00
67eedfddc4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:42 +00:00
829ec15b5b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:42 +00:00
e8bc314cfb Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:41 +00:00
85328a6e96 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:41 +00:00
3af743ed1b Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:40 +00:00
b3b67e7abf Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:39 +00:00
f2d4464209 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:39 +00:00
3fa52b6f32 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:38 +00:00
01ad3e8a8a Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:38 +00:00
fc4bbe2568 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:37 +00:00
d1a407c6b8 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:37 +00:00
a210ca3e17 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:36 +00:00
276f4a5aa4 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:36 +00:00
56b5e117f1 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:35 +00:00
00c5630072 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:35 +00:00
f3a4594386 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:34 +00:00
ed335e5010 Tower: upload cetmix_tower_server 16.0.3.0.1 (via marketplace) 2026-04-27 06:52:34 +00:00
cdd5dbb5ea Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:24 +00:00
0e51ca5bce Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:24 +00:00
0f24587641 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:23 +00:00
5b1f74e263 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:23 +00:00
0a2171ae91 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:22 +00:00
09573a7cfc Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:22 +00:00
686c4f2698 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:21 +00:00
9273950afc Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:21 +00:00
add6fe3994 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:20 +00:00
581efc0235 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:20 +00:00
b1d9b4810f Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:19 +00:00
a868059124 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:18 +00:00
2ceb788c2e Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:18 +00:00
3ca93dfc54 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:18 +00:00
e8315b207d Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:17 +00:00
3090536fc7 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:17 +00:00
c5034b43ab Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:16 +00:00
6b4b7939b0 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:16 +00:00
99be7c765a Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:15 +00:00
a018275ebf Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:14 +00:00
fb9777247a Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:14 +00:00
1e8239c7e2 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:13 +00:00
81b127648b Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:13 +00:00
4c5e3f2f28 Tower: upload cetmix_tower_server_queue1 16.0.2.0.0 (via marketplace) 2026-04-27 06:47:12 +00:00
9fb7d3ff42 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:56 +00:00
ac3093b5dc Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:55 +00:00
975d29fa31 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:55 +00:00
94adb5f348 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:54 +00:00
fba8cc9acd Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:54 +00:00
0fb75fa95f Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:53 +00:00
840c4ac3a9 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:52 +00:00
16613c86a7 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:52 +00:00
0c43591378 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:51 +00:00
9ea6ac8d24 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:51 +00:00
394210c99b Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:50 +00:00
0a7c8c70b0 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:50 +00:00
50cfffdb19 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:49 +00:00
262f2ceef5 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:49 +00:00
5377ca3d57 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:48 +00:00
2fc48951c8 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:48 +00:00
b5308c583c Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:47 +00:00
ffc4690cf5 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:47 +00:00
aace3488c5 Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:46 +00:00
9782ca679d Tower: upload cetmix_tower_aws 16.0.1.1.1 (via marketplace) 2026-04-27 06:46:46 +00:00
258500d3f7 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:37 +00:00
20a3a2d33d Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:37 +00:00
09b6d24303 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:36 +00:00
244569004b Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:36 +00:00
773928cf47 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:35 +00:00
0b60b46a40 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:35 +00:00
4a4797e49d Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:34 +00:00
a26a507d31 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:34 +00:00
151578461e Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:33 +00:00
c219e1c5e6 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:33 +00:00
701190a853 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:32 +00:00
4a5d224e26 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:32 +00:00
c948ed897f Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:31 +00:00
9f83c4003a Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:31 +00:00
60ad89af8d Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:30 +00:00
f59c3ac6e4 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:30 +00:00
25aae80175 Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:29 +00:00
8a784023df Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:29 +00:00
d105b3086e Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:28 +00:00
e68734af4d Tower: upload cetmix_tower_ovh 16.0.1.0.1 (via marketplace) 2026-04-27 06:46:28 +00:00
80aca5c42e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:35 +00:00
91cc351b9f Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:34 +00:00
3727eff0a1 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:34 +00:00
98e8f2c77f Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:33 +00:00
d9cc0ef9d3 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:33 +00:00
20e5e555f1 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:32 +00:00
156a6a6cb6 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:32 +00:00
2c57c4598c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:31 +00:00
130678682c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:31 +00:00
ca706b0281 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:30 +00:00
9ff2b454fb Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:30 +00:00
5362042060 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:29 +00:00
59c05047e6 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:29 +00:00
dabc32defa Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:28 +00:00
d0ba4497b8 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:28 +00:00
cbdbb2cb4f Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:27 +00:00
8090db41fd Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:27 +00:00
e4b8bf49c6 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:26 +00:00
f91e4f7f47 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:26 +00:00
2185ab4d96 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:25 +00:00
8b55326dbd Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:25 +00:00
29de3846b7 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:24 +00:00
781f6703f2 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:24 +00:00
7901b11a6d Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:23 +00:00
136351e574 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:22 +00:00
004fe34221 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:22 +00:00
dc15afe84c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:21 +00:00
298b4baeae Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:21 +00:00
efa72f8d96 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:21 +00:00
d5d8cca96e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:20 +00:00
34676028f9 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:20 +00:00
6639a57225 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:19 +00:00
e084da09e7 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:18 +00:00
8afc6b5c10 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:18 +00:00
28b558e0eb Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:18 +00:00
18d55d8ef9 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:17 +00:00
ded62cb3f1 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:17 +00:00
da00ab06c2 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:16 +00:00
4021aeef49 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:15 +00:00
3cf5653f0d Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:15 +00:00
779e44f63a Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:14 +00:00
17f8d7bb98 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:14 +00:00
47e0d157a2 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:13 +00:00
bcfaf764a4 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:13 +00:00
cd38fe180c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:12 +00:00
51b54d0dc6 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:12 +00:00
a452163d96 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:11 +00:00
1457492697 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:11 +00:00
c7ce387900 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:10 +00:00
7131dbd052 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:10 +00:00
1a2c8df31e Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:09 +00:00
d2d280c2e3 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:09 +00:00
7480c912b5 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:08 +00:00
307fb2c8a3 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:08 +00:00
85dcab4226 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:07 +00:00
da7bb49726 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:07 +00:00
17c5b9b811 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:06 +00:00
458ca8b40c Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:06 +00:00
9bf906a371 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:05 +00:00
b1e7ce7277 Tower: upload cetmix_tower_git 16.0.2.0.4 (via marketplace) 2026-04-27 06:45:05 +00:00
0fb461e3df Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:43 +00:00
fdb9083e95 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:42 +00:00
ab645f0fe9 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:42 +00:00
290ce48514 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:41 +00:00
b6c35b9f76 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:41 +00:00
5c90e911b0 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:40 +00:00
b63181ae6b Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:39 +00:00
bc72822fe4 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:39 +00:00
29d6e2157c Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:38 +00:00
6b851b5033 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:38 +00:00
cad2a32394 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:38 +00:00
b251ba9709 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:37 +00:00
812fb1ff32 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:36 +00:00
0e299769bb Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:36 +00:00
956fed6616 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:35 +00:00
ef982e5d22 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:35 +00:00
fe589090d7 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:34 +00:00
6100570511 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:34 +00:00
c25370304e Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:33 +00:00
203d561a5d Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:33 +00:00
987367d1ca Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:32 +00:00
f7523e073f Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:32 +00:00
f42fd8a333 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:31 +00:00
bf39b6c79e Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:30 +00:00
33dbbb5178 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:30 +00:00
db79948886 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:29 +00:00
cf09b40b4a Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:29 +00:00
fbc5aedf35 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:28 +00:00
31c0f1566b Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:28 +00:00
16786404e1 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:27 +00:00
050d68c0d1 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:27 +00:00
9f807e8e1d Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:26 +00:00
851c2d0239 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:26 +00:00
f9ab417fdf Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:25 +00:00
6702e0714f Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:25 +00:00
fa16558cda Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:24 +00:00
5328e0be5d Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:24 +00:00
1bd02406d4 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:23 +00:00
a4c34fb326 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:23 +00:00
80a70b758d Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:22 +00:00
0a333efddb Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:22 +00:00
a4cf1a2957 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:21 +00:00
59e5709f28 Tower: upload cetmix_tower 16.0.2.1.0 (via marketplace) 2026-04-27 06:44:21 +00:00
1195eb23b1 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:13 +00:00
69f00977ae Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:12 +00:00
fc8b8b8a9a Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:12 +00:00
07a3160ed5 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:11 +00:00
1496fbfa53 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:11 +00:00
780593c0b6 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:10 +00:00
db891f43ce Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:10 +00:00
3357d9a86b Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:09 +00:00
43a7ca4b5e Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:09 +00:00
c764a356ce Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:08 +00:00
4a881b9eaf Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:08 +00:00
76d70ea861 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:07 +00:00
959a04e1cc Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:07 +00:00
bf8fbcc9e3 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:06 +00:00
f9fe46154a Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:06 +00:00
c97921b2ea Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:05 +00:00
95967f2f45 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:05 +00:00
552c05599f Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:04 +00:00
837d110796 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:04 +00:00
fcc7e84300 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:03 +00:00
9080bc85f2 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:03 +00:00
512f1a8f4d Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:02 +00:00
4faa6b9af4 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:02 +00:00
019ee02912 Tower: upload cetmix_tower_server_queue 16.0.2.0.0 (via marketplace) 2026-04-27 06:44:01 +00:00
321 changed files with 19019 additions and 64620 deletions

View File

@@ -1,105 +0,0 @@
from . import models
from . import wizard
from . import controllers
from odoo import Command
import logging
_logger = logging.getLogger(__name__)
def _at_accounting_post_init(env):
country_code = env.company.country_id.code
if country_code:
module_list = []
sepa_zone = env.ref('base.sepa_zone', raise_if_not_found=False)
sepa_zone_country_codes = sepa_zone and sepa_zone.mapped('country_ids.code') or []
if country_code in sepa_zone_country_codes:
module_list.extend(['account_iso20022', 'account_bank_statement_import_camt'])
module_ids = env['ir.module.module'].search([('name', 'in', module_list), ('state', '=', 'uninstalled')])
if module_ids:
module_ids.sudo().button_install()
for company in env['res.company'].search([('chart_template', '!=', False)], order="parent_path"):
ChartTemplate = env['account.chart.template'].with_company(company)
ChartTemplate._load_data({
'res.company': ChartTemplate._get_account_accountant_res_company(company.chart_template),
})
country_code = env.company.country_id.code
if country_code:
module_list = []
if country_code in ('AU', 'CA', 'US'):
module_list.append('account_reports_cash_basis')
module_ids = env['ir.module.module'].search([('name', 'in', module_list), ('state', '=', 'uninstalled')])
if module_ids:
module_ids.sudo().button_install()
for company in env['res.company'].search([]):
company.account_tax_periodicity_journal_id = company._get_default_misc_journal()
company.account_tax_periodicity_journal_id.show_on_dashboard = True
company._initiate_account_onboardings()
def uninstall_hook(env):
group_basic = env.ref('account.group_account_basic')
group_manager = env.ref('account.group_account_manager')
if group_basic:
group_basic.write({
'users': [Command.clear()],
'category_id': env.ref("base.module_category_hidden").id,
})
group_manager.write({
'implied_ids': [Command.unlink(group_basic.id)],
})
try:
group_user = env.ref("account.group_account_user")
group_user.write({
'name': "Show Full Accounting Features",
'implied_ids': [(3, env.ref('account.group_account_invoice').id)],
'category_id': env.ref("base.module_category_hidden").id,
})
group_readonly = env.ref("account.group_account_readonly")
group_readonly.write({
'name': "Show Full Accounting Features - Readonly",
'category_id': env.ref("base.module_category_hidden").id,
})
except ValueError as e:
_logger.warning(e)
try:
group_manager = env.ref("account.group_account_manager")
group_manager.write({'name': "Billing Manager",
'implied_ids': [(4, env.ref("account.group_account_invoice").id),
(3, env.ref("account.group_account_readonly").id),
(3, env.ref("account.group_account_user").id)]})
except ValueError as e:
_logger.warning(e)
# make the account_accountant features disappear (magic)
env.ref("account.group_account_user").write({'users': [(5, False, False)]})
env.ref("account.group_account_readonly").write({'users': [(5, False, False)]})
invoicing_menu = env.ref("account.menu_finance")
menus_to_move = [
"account.menu_finance_receivables",
"account.menu_finance_payables",
"account.menu_finance_entries",
"account.menu_finance_reports",
"account.menu_finance_configuration",
"account.menu_board_journal_1",
]
for menu_xmlids in menus_to_move:
try:
env.ref(menu_xmlids).parent_id = invoicing_menu
except ValueError as e:
_logger.warning(e)

View File

@@ -1,178 +0,0 @@
{
'name': "Odoo 18 Vera Accounting",
'version': "18.0.1.7",
'category': 'Accounting/Accounting',
'sequence': 1,
'summary': "A complete accounting toolkit for Odoo 18 Community with advanced reports, wizards, and workflows.",
'description': """
========================================================
Your Complete Professional Accounting Suite for Odoo 18
========================================================
Transform your Odoo 18 Community into a powerful, professional-grade financial management system. This module provides the advanced tools and deep financial insights that growing businesses need to thrive.
Move beyond standard accounting with a comprehensive toolkit designed to improve accuracy, streamline workflows, and empower you to make smarter, data-driven decisions.
Key Features:
-------------
* **Advanced Reporting Engine:** Generate a full suite of essential financial reports on-demand, including:
* Profit and Loss (P&L)
* Balance Sheet
* Cash Flow Statement
* Aged Receivables & Payables
* General Ledger & Partner Ledger
* Trial Balance
* Comprehensive Tax Reports
* ...and many more.
* **Interactive Financial Dashboards:** Visualize your financial health with intuitive and dynamic dashboard components, bringing your numbers to life.
* **Streamlined Bank Reconciliation:** Utilize an enhanced bank reconciliation widget and powerful auto-reconciliation wizards to manage your statements faster and more accurately.
* **Powerful Accounting Wizards:** Simplify complex processes with guided, user-friendly wizards for tasks like Fiscal Year Closing, Multicurrency Revaluation, and Report Exporting.
* **Guided User Tours:** Onboard your team quickly and ensure they can leverage all the powerful new features with integrated user tours.
* **Professional PDF Exports:** Create clean, professionally formatted PDF documents for all your financial reports, ready for sharing with stakeholders.
Empower Your Finance Team
-------------------------
This module provides your team with the information they need, right where they need it. Reduce manual work, eliminate errors, and give your accountants the tools they need to perform at their best.
""",
'icon': 'static/description/icon.png',
'author': 'Vera Software',
"website": "https://www.erp-tradepro.com/",
'support': 'vera@Software.com',
'maintainer': 'Vera Software',
'depends': ['account','web_tour', 'stock_account', 'base_import'],
'data': [
'data/ir_cron.xml',
'data/digest_data.xml',
'data/at_accounting_tour.xml',
'data/at_accounting_data.xml',
'data/pdf_export_templates.xml',
'data/balance_sheet.xml',
'data/cash_flow_report.xml',
'data/executive_summary.xml',
'data/profit_and_loss.xml',
'data/bank_reconciliation_report.xml',
'data/aged_partner_balance.xml',
'data/general_ledger.xml',
'data/trial_balance.xml',
'data/sales_report.xml',
'data/partner_ledger.xml',
'data/multicurrency_revaluation_report.xml',
'data/deferred_reports.xml',
'data/journal_report.xml',
'data/generic_tax_report.xml',
'views/account_report_view.xml',
'data/account_report_actions.xml',
'data/report_send_cron.xml',
'data/menuitems.xml',
'data/mail_activity_type_data.xml',
'data/mail_templates.xml',
'security/ir.model.access.csv',
'security/at_accounting_security.xml',
'security/accounting_security.xml',
'views/account_account_views.xml',
'views/account_fiscal_year_view.xml',
'views/account_journal_dashboard_views.xml',
'views/account_move_views.xml',
'views/account_payment_views.xml',
'views/account_reconcile_views.xml',
'views/account_reconcile_model_views.xml',
'views/at_accounting_menuitems.xml',
'views/digest_views.xml',
'views/res_config_settings_views.xml',
'views/product_views.xml',
'views/bank_rec_widget_views.xml',
'views/report_invoice.xml',
'views/partner_views.xml',
'views/account_activity.xml',
'views/account_tax_views.xml',
'views/mail_activity_views.xml',
'views/report_template.xml',
'views/res_company_views.xml',
'views/res_partner_views.xml',
'wizard/account_change_lock_date.xml',
'wizard/account_auto_reconcile_wizard.xml',
'wizard/account_reconcile_wizard.xml',
'wizard/reconcile_model_wizard.xml',
'wizard/account_report_send.xml',
'wizard/multicurrency_revaluation.xml',
'wizard/report_export_wizard.xml',
'wizard/account_report_file_download_error_wizard.xml',
'wizard/fiscal_year.xml',
'wizard/mail_activity_schedule_views.xml',
'security/at_account_asset_security.xml',
'security/ir.model.access.csv',
'wizard/asset_modify_views.xml',
# 'views/account_account_views.xml',
'views/account_asset_views.xml',
'views/account_asset_group_views.xml',
# 'views/account_move_views.xml',
'data/assets_reports.xml',
'data/account_report_actions_depr.xml',
'views/account_bank_statement_import_view.xml',
],
'demo': [
'demo/at_accounting_demo.xml',
'demo/partner_bank.xml',
],
'installable': True,
'application': True,
'post_init_hook': '_at_accounting_post_init',
'uninstall_hook': "uninstall_hook",
'license': 'OPL-1',
'assets':{
'web.assets_backend': [
'at_accounting/static/src/js/tours/at_accounting.js',
'at_accounting/static/src/components/**/*',
'at_accounting/static/src/**/*.xml',
'at_accounting/static/src/js/**/*',
'at_accounting/static/src/widgets/**/*',
# Root-level JS files not covered by the patterns above
'at_accounting/static/src/account_bank_statement_import_model.js',
'at_accounting/static/src/bank_statement_csv_import_action.js',
'at_accounting/static/src/bank_statement_csv_import_model.js',
# Bank reconciliation view files (outside /components/)
'at_accounting/static/src/bank_reconciliation/**/*',
# Backend-only SCSS (excludes PDF stylesheets and dark-mode files)
'at_accounting/static/src/scss/account_asset.scss',
],
'web.assets_unit_tests': [
'at_accounting/static/tests/**/*',
('remove', 'at_accounting/static/tests/tours/**/*'),
'at_accounting/static/tests/*.js',
'at_accounting/static/tests/account_report/**/*.js',
],
'web.assets_tests': [
'at_accounting/static/tests/tours/**/*',
],
'at_accounting.assets_pdf_export': [
('include', 'web._assets_helpers'),
'web/static/src/scss/pre_variables.scss',
'web/static/lib/bootstrap/scss/_variables.scss',
'web/static/lib/bootstrap/scss/_variables-dark.scss',
'web/static/lib/bootstrap/scss/_maps.scss',
('include', 'web._assets_bootstrap_backend'),
'web/static/fonts/fonts.scss',
'at_accounting/static/src/scss/**/*',
],
'web.report_assets_common': [
'at_accounting/static/src/scss/account_pdf_export_template.scss',
],
'web.assets_web_dark': [
'at_accounting/static/src/scss/*.dark.scss',
],
'web.qunit_suite_tests': [
'at_accounting/static/tests/legacy/*.js',
],
},
'images': ['static/description/banner.png'],
}

View File

@@ -1 +0,0 @@
from . import main

View File

@@ -1,89 +0,0 @@
import werkzeug
from werkzeug.exceptions import InternalServerError
from odoo.addons.at_accounting.models.account_report import AccountReportFileDownloadException
from odoo.addons.account.controllers.download_docs import _get_headers
from odoo import http
from odoo.models import check_method_name
from odoo.http import content_disposition, request
from odoo.tools.misc import html_escape
import json
class AccountReportController(http.Controller):
@http.route('/at_accounting', type='http', auth='user', methods=['POST'], csrf=False)
def get_report(self, options, file_generator, **kwargs):
uid = request.uid
options = json.loads(options)
allowed_company_ids = request.env['account.report'].get_report_company_ids(options)
if not allowed_company_ids:
company_str = request.cookies.get('cids', str(request.env.user.company_id.id))
allowed_company_ids = [int(str_id) for str_id in company_str.split('-')]
report = request.env['account.report'].with_user(uid).with_context(allowed_company_ids=allowed_company_ids).browse(options['report_id'])
try:
check_method_name(file_generator)
generated_file_data = report.dispatch_report_action(options, file_generator)
file_content = generated_file_data['file_content']
file_type = generated_file_data['file_type']
response_headers = self._get_response_headers(file_type, generated_file_data['file_name'], file_content)
if file_type == 'xlsx':
response = request.make_response(None, headers=response_headers)
response.stream.write(file_content)
else:
response = request.make_response(file_content, headers=response_headers)
if file_type in ('zip', 'xaf'):
# Adding direct_passthrough to the response and giving it a file
# as content means that we will stream the content of the file to the user
# Which will prevent having the whole file in memory
response.direct_passthrough = True
return response
except AccountReportFileDownloadException as e:
if e.content:
e.content['file_content'] = e.content['file_content'].decode()
data = {
'name': type(e).__name__,
'arguments': [e.errors, e.content],
}
raise InternalServerError(response=self._generate_response(data)) from e
except Exception as e: # noqa: BLE001
data = http.serialize_exception(e)
raise InternalServerError(response=self._generate_response(data)) from e
def _generate_response(self, data):
error = {
'code': 200,
'message': 'Odoo Server Error',
'data': data,
}
return request.make_response(html_escape(json.dumps(error)))
def _get_response_headers(self, file_type, file_name, file_content):
headers = [
('Content-Type', request.env['account.report'].get_export_mime_type(file_type)),
('Content-Disposition', content_disposition(file_name)),
]
if file_type in ('xml', 'txt', 'csv', 'kvr', 'csv'):
headers.append(('Content-Length', len(file_content)))
return headers
@http.route('/at_accounting/download_attachments/<models("ir.attachment"):attachments>', type='http', auth='user')
def download_report_attachments(self, attachments):
attachments.check_access('read')
assert all(attachment.res_id and attachment.res_model == 'res.partner' for attachment in attachments)
if len(attachments) == 1:
headers = _get_headers(attachments.name, attachments.mimetype, attachments.raw)
return request.make_response(attachments.raw, headers)
else:
content = attachments._build_zip_from_attachments()
headers = _get_headers('attachments.zip', 'zip', content)
return request.make_response(content, headers)

View File

@@ -1,160 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="action_account_report_cs" model="ir.actions.client">
<field name="name">Cash Flow Statement</field>
<field name="tag">account_report</field>
<field name="context" eval="{'report_id': ref('at_accounting.cash_flow_report')}"/>
</record>
<record id="action_account_report_bs" model="ir.actions.client">
<field name="name">Balance Sheet</field>
<field name="tag">account_report</field>
<field name="path">balance-sheet</field>
<field name="context" eval="{'report_id': ref('at_accounting.balance_sheet')}"/>
</record>
<record id="action_account_report_exec_summary" model="ir.actions.client">
<field name="name">Executive Summary</field>
<field name="tag">account_report</field>
<field name="path">executive-summary</field>
<field name="context" eval="{'report_id': ref('at_accounting.executive_summary')}"/>
</record>
<record id="action_account_report_pl" model="ir.actions.client">
<field name="name">Profit and Loss</field>
<field name="tag">account_report</field>
<field name="path">profit-and-loss</field>
<field name="context" eval="{'report_id': ref('at_accounting.profit_and_loss')}"/>
</record>
<record id="action_account_report_gt" model="ir.actions.client">
<field name="name">Tax Return</field>
<field name="tag">account_report</field>
<field name="path">tax-report</field>
<field name="context" eval="{'report_id': ref('account.generic_tax_report')}"/>
</record>
<record id="action_account_report_ja" model="ir.actions.client">
<field name="name">Journal Audit</field>
<field name="tag">account_report</field>
<field name="path">journal-report</field>
<field name="context" eval="{'report_id': ref('at_accounting.journal_report')}"/>
</record>
<record id="action_account_report_general_ledger" model="ir.actions.client">
<field name="name">General Ledger</field>
<field name="tag">account_report</field>
<field name="path">general-ledger</field>
<field name="context" eval="{'report_id': ref('at_accounting.general_ledger_report')}"/>
</record>
<record id="action_account_report_multicurrency_revaluation" model="ir.actions.client">
<field name="name">Unrealized Currency Gains/Losses</field>
<field name="tag">account_report</field>
<field name="context" eval="{'report_id': ref('at_accounting.multicurrency_revaluation_report')}"/>
</record>
<record id="action_account_report_ar" model="ir.actions.client">
<field name="name">Aged Receivable</field>
<field name="tag">account_report</field>
<field name="path">aged-receivable</field>
<field name="context" eval="{'report_id': ref('at_accounting.aged_receivable_report')}"/>
</record>
<record id="action_account_report_ap" model="ir.actions.client">
<field name="name">Aged Payable</field>
<field name="tag">account_report</field>
<field name="path">aged-payable</field>
<field name="context" eval="{'report_id': ref('at_accounting.aged_payable_report')}"/>
</record>
<record id="action_account_report_coa" model="ir.actions.client">
<field name="name">Trial Balance</field>
<field name="tag">account_report</field>
<field name="path">trial-balance</field>
<field name="context" eval="{'report_id': ref('at_accounting.trial_balance_report')}"/>
</record>
<record id="action_account_report_partner_ledger" model="ir.actions.client">
<field name="name">Partner Ledger</field>
<field name="tag">account_report</field>
<field name="path">partner-ledger</field>
<field name="context" eval="{'report_id': ref('at_accounting.partner_ledger_report')}"/>
</record>
<record id="action_account_report_sales" model="ir.actions.client">
<field name="name">EC Sales List</field>
<field name="tag">account_report</field>
<field name="context" eval="{'report_id': ref('at_accounting.generic_ec_sales_report')}"/>
</record>
<record id="action_account_report_deferred_expense" model="ir.actions.client">
<field name="name">Deferred Expense</field>
<field name="tag">account_report</field>
<field name="path">deferred-expense</field>
<field name="context" eval="{'report_id': ref('at_accounting.deferred_expense_report')}"/>
</record>
<record id="action_account_report_deferred_revenue" model="ir.actions.client">
<field name="name">Deferred Revenue</field>
<field name="tag">account_report</field>
<field name="path">deferred-revenue</field>
<field name="context" eval="{'report_id': ref('at_accounting.deferred_revenue_report')}"/>
</record>
<record id="account_financial_current_year_earnings0" model="account.report.line">
<field name="action_id" ref="action_account_report_pl"/>
</record>
<record id="account_financial_report_executivesummary_profitability0" model="account.report.line">
<field name="action_id" ref="action_account_report_pl"/>
</record>
<record id="account_financial_report_executivesummary_balancesheet0" model="account.report.line">
<field name="action_id" ref="action_account_report_bs"/>
</record>
<record id="action_create_report_menu" model="ir.actions.server">
<field name="name">Create Menu Item</field>
<field name="model_id" ref="account.model_account_report"/>
<field name="binding_model_id" ref="account.model_account_report"/>
<field name="state">code</field>
<field name="binding_view_types">form</field>
<field name="code">
if records:
action = records._create_menu_item_for_report()
</field>
</record>
<record id="action_account_report_tree" model="ir.actions.act_window">
<field name="name">Accounting Reports</field>
<field name="res_model">account.report</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="account_report_tree"/>
<field name="search_view_id" ref="view_account_report_search"/>
</record>
<record id="action_account_report_horizontal_groups" model="ir.actions.act_window">
<field name="name">Horizontal Groups</field>
<field name="res_model">account.report.horizontal.group</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="account_report_horizontal_group_tree"/>
</record>
<record id="action_account_report_bank_reconciliation" model="ir.actions.client">
<field name="name">Bank Reconciliation</field>
<field name="tag">account_report</field>
<field name="context" eval="{'report_id': ref('at_accounting.bank_reconciliation_report')}"/>
</record>
<record id="action_account_report_budget_tree" model="ir.actions.act_window">
<field name="name">Financial Budgets</field>
<field name="res_model">account.report.budget</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="account_report_budget_tree"/>
</record>
</data>
</odoo>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_account_report_assets" model="ir.actions.client">
<field name="name">Depreciation Schedule</field>
<field name="tag">account_report</field>
<field name="context" eval="{'report_id': ref('at_accounting.assets_report')}"/>
</record>
<menuitem id="menu_action_account_report_assets"
name="Depreciation Schedule"
action="action_account_report_assets"
parent="account.account_reports_management_menu"
groups="account.group_account_readonly"/>
</odoo>

View File

@@ -1,326 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="aged_receivable_report" model="account.report">
<field name="name">Aged Receivable</field>
<field name="filter_date_range" eval="False"/>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_partner" eval="True"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_account_type">receivable</field>
<field name="filter_hierarchy">never</field>
<field name="filter_show_draft" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="default_opening_date_filter">today</field>
<field name="custom_handler_model_id" ref="model_account_aged_receivable_report_handler"/>
<field name="column_ids">
<record id="aged_receivable_report_invoice_date" model="account.report.column">
<field name="name">Invoice Date</field>
<field name="expression_label">invoice_date</field>
<field name="figure_type">date</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_receivable_report_amount_currency" model="account.report.column">
<field name="name">Amount Currency</field>
<field name="expression_label">amount_currency</field>
</record>
<record id="aged_receivable_report_currency" model="account.report.column">
<field name="name">Currency</field>
<field name="expression_label">currency</field>
<field name="figure_type">string</field>
</record>
<record id="aged_receivable_report_account_name" model="account.report.column">
<field name="name">Account</field>
<field name="expression_label">account_name</field>
<field name="figure_type">string</field>
</record>
<record id="aged_receivable_report_period0" model="account.report.column">
<field name="name">At Date</field>
<field name="expression_label">period0</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_receivable_report_period1" model="account.report.column">
<field name="name">Period 1</field>
<field name="expression_label">period1</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_receivable_report_period2" model="account.report.column">
<field name="name">Period 2</field>
<field name="expression_label">period2</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_receivable_report_period3" model="account.report.column">
<field name="name">Period 3</field>
<field name="expression_label">period3</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_receivable_report_period4" model="account.report.column">
<field name="name">Period 4</field>
<field name="expression_label">period4</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_receivable_report_period5" model="account.report.column">
<field name="name">Older</field>
<field name="expression_label">period5</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_receivable_report_total" model="account.report.column">
<field name="name">Total</field>
<field name="expression_label">total</field>
<field name="sortable" eval="True"/>
</record>
</field>
<field name="line_ids">
<record id="aged_receivable_line" model="account.report.line">
<field name="name">Aged Receivable</field>
<field name="groupby">partner_id, id</field>
<field name="expression_ids">
<record id="aged_receivable_line_invoice_date" model="account.report.expression">
<field name="label">invoice_date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">invoice_date</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_receivable_line_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_receivable_line_amount_currency_forced_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">currency_id</field>
</record>
<record id="aged_receivable_line_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_receivable_line_account_name" model="account.report.expression">
<field name="label">account_name</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">account_name</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_receivable_line_period0" model="account.report.expression">
<field name="label">period0</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">period0</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_receivable_line_period1" model="account.report.expression">
<field name="label">period1</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">period1</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_receivable_line_period2" model="account.report.expression">
<field name="label">period2</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">period2</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_receivable_line_period3" model="account.report.expression">
<field name="label">period3</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">period3</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_receivable_line_period4" model="account.report.expression">
<field name="label">period4</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">period4</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_receivable_line_period5" model="account.report.expression">
<field name="label">period5</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">period5</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_receivable_line_total" model="account.report.expression">
<field name="label">total</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_receivable</field>
<field name="subformula">total</field>
<field name="auditable" eval="True"/>
</record>
</field>
</record>
</field>
</record>
<record id="aged_payable_report" model="account.report">
<field name="name">Aged Payable</field>
<field name="filter_date_range" eval="False"/>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_partner" eval="True"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_account_type">payable</field>
<field name="filter_hierarchy">never</field>
<field name="filter_show_draft" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="default_opening_date_filter">today</field>
<field name="custom_handler_model_id" ref="model_account_aged_payable_report_handler"/>
<field name="column_ids">
<record id="aged_payable_report_invoice_date" model="account.report.column">
<field name="name">Invoice Date</field>
<field name="expression_label">invoice_date</field>
<field name="figure_type">date</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_payable_report_amount_currency" model="account.report.column">
<field name="name">Amount Currency</field>
<field name="expression_label">amount_currency</field>
</record>
<record id="aged_payable_report_currency" model="account.report.column">
<field name="name">Currency</field>
<field name="expression_label">currency</field>
<field name="figure_type">string</field>
</record>
<record id="aged_payable_report_account_name" model="account.report.column">
<field name="name">Account</field>
<field name="expression_label">account_name</field>
<field name="figure_type">string</field>
</record>
<record id="aged_payable_report_period0" model="account.report.column">
<field name="name">At Date</field>
<field name="expression_label">period0</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_payable_report_period1" model="account.report.column">
<field name="name">Period 1</field>
<field name="expression_label">period1</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_payable_report_period2" model="account.report.column">
<field name="name">Period 2</field>
<field name="expression_label">period2</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_payable_report_period3" model="account.report.column">
<field name="name">Period 3</field>
<field name="expression_label">period3</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_payable_report_period4" model="account.report.column">
<field name="name">Period 4</field>
<field name="expression_label">period4</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_payable_report_period5" model="account.report.column">
<field name="name">Older</field>
<field name="expression_label">period5</field>
<field name="sortable" eval="True"/>
</record>
<record id="aged_payable_report_total" model="account.report.column">
<field name="name">Total</field>
<field name="expression_label">total</field>
<field name="sortable" eval="True"/>
</record>
</field>
<field name="line_ids">
<record id="aged_payable_line" model="account.report.line">
<field name="name">Aged Payable</field>
<field name="groupby">partner_id, id</field>
<field name="expression_ids">
<record id="aged_payable_line_invoice_date" model="account.report.expression">
<field name="label">invoice_date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">invoice_date</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_payable_line_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_payable_line_amount_currency_forced_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">currency_id</field>
</record>
<record id="aged_payable_line_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_payable_line_account_name" model="account.report.expression">
<field name="label">account_name</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">account_name</field>
<field name="auditable" eval="False"/>
</record>
<record id="aged_payable_line_period0" model="account.report.expression">
<field name="label">period0</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">period0</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_payable_line_period1" model="account.report.expression">
<field name="label">period1</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">period1</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_payable_line_period2" model="account.report.expression">
<field name="label">period2</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">period2</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_payable_line_period3" model="account.report.expression">
<field name="label">period3</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">period3</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_payable_line_period4" model="account.report.expression">
<field name="label">period4</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">period4</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_payable_line_period5" model="account.report.expression">
<field name="label">period5</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">period5</field>
<field name="auditable" eval="True"/>
</record>
<record id="aged_payable_line_total" model="account.report.expression">
<field name="label">total</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_aged_payable</field>
<field name="subformula">total</field>
<field name="auditable" eval="True"/>
</record>
</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="assets_report" model="account.report">
<field name="name">Depreciation Schedule</field>
<field name="filter_hierarchy">optional</field>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_journals" eval="True"/>
<field name="custom_handler_model_id" ref="model_account_asset_report_handler"/>
<field name="load_more_limit" eval="80"/>
<field name="column_ids">
<record id="assets_report_acquisition_date" model="account.report.column">
<field name="name">Acquisition Date</field>
<field name="expression_label">acquisition_date</field>
<field name="figure_type">date</field>
</record>
<record id="assets_report_first_depreciation" model="account.report.column">
<field name="name">First Depreciation</field>
<field name="expression_label">first_depreciation</field>
<field name="figure_type">date</field>
</record>
<record id="assets_report_first_method" model="account.report.column">
<field name="name">Method</field>
<field name="expression_label">method</field>
<field name="figure_type">string</field>
</record>
<record id="assets_report_duration_rate" model="account.report.column">
<field name="name">Duration / Rate</field>
<field name="expression_label">duration_rate</field>
<field name="figure_type">string</field>
</record>
<record id="assets_report_date_from" model="account.report.column">
<field name="name">date from</field>
<field name="expression_label">assets_date_from</field>
</record>
<record id="assets_report_assets_plus" model="account.report.column">
<field name="name">+</field>
<field name="expression_label">assets_plus</field>
</record>
<record id="assets_report_assets_minus" model="account.report.column">
<field name="name">-</field>
<field name="expression_label">assets_minus</field>
</record>
<record id="assets_report_assets_date_to" model="account.report.column">
<field name="name">date to</field>
<field name="expression_label">assets_date_to</field>
</record>
<record id="assets_report_depre_date_from" model="account.report.column">
<field name="name">date from</field>
<field name="expression_label">depre_date_from</field>
</record>
<record id="assets_report_depre_plus" model="account.report.column">
<field name="name">+</field>
<field name="expression_label">depre_plus</field>
</record>
<record id="assets_report_depre_minus" model="account.report.column">
<field name="name">-</field>
<field name="expression_label">depre_minus</field>
</record>
<record id="assets_report_depre_date_to" model="account.report.column">
<field name="name">date to</field>
<field name="expression_label">depre_date_to</field>
</record>
<record id="assets_report_balance" model="account.report.column">
<field name="name">book_value</field>
<field name="expression_label">balance</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Switch root menu "Invoicing" to "Accounting" -->
<!-- Top menu item -->
<menuitem name="Accounting"
id="menu_accounting"
groups="account.group_account_readonly,account.group_account_invoice"
web_icon="at_accounting,static/description/icon.png"
sequence="60"/>
<!-- move existing submenus to point to the new parent -->
<record id="account.menu_finance_receivables" model="ir.ui.menu">
<field name="parent_id" ref="menu_accounting"/>
</record>
<record id="account.menu_finance_payables" model="ir.ui.menu">
<field name="parent_id" ref="menu_accounting"/>
</record>
<record id="account.menu_finance_entries" model="ir.ui.menu">
<field name="parent_id" ref="menu_accounting"/>
</record>
<record id="account.menu_finance_reports" model="ir.ui.menu">
<field name="parent_id" ref="menu_accounting"/>
</record>
<record id="account.menu_finance_configuration" model="ir.ui.menu">
<field name="parent_id" ref="menu_accounting"/>
</record>
<record id="account.menu_board_journal_1" model="ir.ui.menu">
<field name="parent_id" ref="menu_accounting"/>
</record>
<menuitem id="account.menu_account_config" name="Settings" parent="account.menu_finance_configuration" sequence="0" groups="base.group_system"/>
</odoo>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_accountant_tour" model="web_tour.tour">
<field name="name">account_accountant_tour</field>
<field name="sequence">50</field>
<field name="rainbow_man_message"><![CDATA[
<strong><b>Good job!</b> You went through all steps of this tour.</strong>
<br>See how to manage your customer invoices in the <b>Customers/Invoices</b> menu
]]></field>
</record>
</odoo>

View File

@@ -1,285 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="balance_sheet" model="account.report">
<field name="name">Balance Sheet</field>
<field name="filter_date_range" eval="False"/>
<field name="filter_analytic_groupby" eval="True"/>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_journals" eval="True"/>
<field name="filter_multi_company">selector</field>
<field name="default_opening_date_filter">today</field>
<field name="custom_handler_model_id" ref="model_account_balance_sheet_report_handler"/>
<field name="column_ids">
<record id="balance_sheet_balance" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
<field name="line_ids">
<record id="account_financial_report_total_assets0" model="account.report.line">
<field name="name">ASSETS</field>
<field name="hierarchy_level">0</field>
<field name="code">TA</field>
<field name="horizontal_split_side">left</field>
<field name="aggregation_formula">CA.balance + FA.balance + PNCA.balance</field>
<field name="children_ids">
<record id="account_financial_report_current_assets_view0" model="account.report.line">
<field name="name">Current Assets</field>
<field name="code">CA</field>
<field name="aggregation_formula">BA.balance + REC.balance + CAS.balance + PRE.balance</field>
<field name="children_ids">
<record id="account_financial_report_bank_view0" model="account.report.line">
<field name="name">Bank and Cash Accounts</field>
<field name="code">BA</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="domain_formula">sum([('account_id.account_type', '=', 'asset_cash')])</field>
</record>
<record id="account_financial_report_receivable0" model="account.report.line">
<field name="name">Receivables</field>
<field name="code">REC</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="domain_formula">sum([('account_id.account_type', '=', 'asset_receivable'), ('account_id.non_trade', '=', False)])</field>
</record>
<record id="account_financial_report_current_assets0" model="account.report.line">
<field name="name">Current Assets</field>
<field name="code">CAS</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="domain_formula">sum(['|', ('account_id.account_type', '=', 'asset_current'), '&amp;', ('account_id.account_type', '=', 'asset_receivable'), ('account_id.non_trade', '=', True)])</field>
</record>
<record id="account_financial_report_prepayements0" model="account.report.line">
<field name="name">Prepayments</field>
<field name="code">PRE</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="domain_formula">sum([('account_id.account_type', '=', 'asset_prepayments')])</field>
</record>
</field>
</record>
<record id="account_financial_report_fixed_assets_view0" model="account.report.line">
<field name="name">Plus Fixed Assets</field>
<field name="code">FA</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="domain_formula">sum([('account_id.account_type', '=', 'asset_fixed')])</field>
</record>
<record id="account_financial_report_non_current_assets_view0" model="account.report.line">
<field name="name">Plus Non-current Assets</field>
<field name="code">PNCA</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="domain_formula">sum([('account_id.account_type', '=', 'asset_non_current')])</field>
</record>
</field>
</record>
<record id="account_financial_report_liabilities_view0" model="account.report.line">
<field name="name">LIABILITIES</field>
<field name="hierarchy_level">0</field>
<field name="code">L</field>
<field name="horizontal_split_side">right</field>
<field name="expression_ids">
<record id="account_financial_report_liabilities_view0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">CL.balance + NL.balance</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
<field name="children_ids">
<record id="account_financial_report_current_liabilities0" model="account.report.line">
<field name="name">Current Liabilities</field>
<field name="code">CL</field>
<field name="expression_ids">
<record id="account_financial_report_current_liabilities0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">CL1.balance + CL2.balance</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
<field name="children_ids">
<record id="account_financial_report_current_liabilities1" model="account.report.line">
<field name="name">Current Liabilities</field>
<field name="code">CL1</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_current_liabilities1_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="['|', ('account_id.account_type', 'in', ('liability_current', 'liability_credit_card')), '&amp;', ('account_id.account_type', '=', 'liability_payable'), ('account_id.non_trade', '=', True)]"/>
<field name="subformula">-sum</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_current_liabilities_payable" model="account.report.line">
<field name="name">Payables</field>
<field name="code">CL2</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_current_liabilities_payable_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'liability_payable'), ('account_id.non_trade', '=', False)]"/>
<field name="subformula">-sum</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_report_non_current_liabilities0" model="account.report.line">
<field name="name">Plus Non-current Liabilities</field>
<field name="code">NL</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_non_current_liabilities0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'liability_non_current')]"/>
<field name="subformula">-sum</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_report_equity0" model="account.report.line">
<field name="name">EQUITY</field>
<field name="hierarchy_level">0</field>
<field name="code">EQ</field>
<field name="horizontal_split_side">right</field>
<field name="aggregation_formula">UNAFFECTED_EARNINGS.balance + RETAINED_EARNINGS.balance</field>
<field name="children_ids">
<record id="account_financial_unaffected_earnings0" model="account.report.line">
<field name="name">Unallocated Earnings</field>
<field name="code">UNAFFECTED_EARNINGS</field>
<field name="aggregation_formula">CURR_YEAR_EARNINGS.balance + PREV_YEAR_EARNINGS.balance</field>
<field name="children_ids">
<record id="account_financial_current_year_earnings0" model="account.report.line">
<field name="name">Current Year Unallocated Earnings</field>
<field name="code">CURR_YEAR_EARNINGS</field>
<field name="aggregation_formula"/>
<field name="expression_ids">
<record id="account_financial_current_year_earnings_pnl" model="account.report.expression">
<field name="label">pnl</field>
<field name="engine">aggregation</field>
<field name="formula">NEP.balance</field>
<field name="date_scope">from_fiscalyear</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_current_year_earnings_alloc" model="account.report.expression">
<field name="label">alloc</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'equity_unaffected')]"/>
<field name="date_scope">from_fiscalyear</field>
<field name="subformula">-sum</field>
</record>
<record id="account_financial_current_year_earnings_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">CURR_YEAR_EARNINGS.pnl + CURR_YEAR_EARNINGS.alloc</field>
</record>
</field>
</record>
<record id="account_financial_previous_year_earnings0" model="account.report.line">
<field name="name">Previous Years Unallocated Earnings</field>
<field name="code">PREV_YEAR_EARNINGS</field>
<field name="expression_ids">
<record id="account_financial_previous_year_earnings0_allocated_earnings" model="account.report.expression">
<field name="label">allocated_earnings</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'equity_unaffected')]"/>
<field name="subformula">-sum</field>
<field name="date_scope">from_beginning</field>
</record>
<record id="account_financial_previous_year_earnings0_balance_domain" model="account.report.expression">
<field name="label">balance_domain</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', 'in', ('income', 'income_other', 'expense_direct_cost', 'expense', 'expense_depreciation'))]"/>
<field name="subformula">-sum</field>
<field name="date_scope">from_beginning</field>
</record>
<record id="account_financial_previous_year_earnings0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">PREV_YEAR_EARNINGS.balance_domain + PREV_YEAR_EARNINGS.allocated_earnings - CURR_YEAR_EARNINGS.balance</field>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_retained_earnings0" model="account.report.line">
<field name="name">Retained Earnings</field>
<field name="code">RETAINED_EARNINGS</field>
<field name="aggregation_formula">CURR_RETAINED_EARNINGS.balance + PREV_RETAINED_EARNINGS.balance</field>
<field name="groupby" eval="False"/>
<field name="foldable" eval="False"/>
<field name="children_ids">
<record id="account_financial_retained_earnings_line_1" model="account.report.line">
<field name="name">Current Year Retained Earnings</field>
<field name="code">CURR_RETAINED_EARNINGS</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_retained_earnings_current" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'equity')]"/>
<field name="subformula">-sum</field>
<field name="date_scope">from_fiscalyear</field>
</record>
</field>
</record>
<record id="account_financial_retained_earnings_line_2" model="account.report.line">
<field name="name">Previous Years Retained Earnings</field>
<field name="code">PREV_RETAINED_EARNINGS</field>
<field name="expression_ids">
<record id="account_financial_retained_earnings_total" model="account.report.expression">
<field name="label">total</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'equity')]"/>
<field name="subformula">-sum</field>
</record>
<record id="account_financial_retained_earnings_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">PREV_RETAINED_EARNINGS.total - CURR_RETAINED_EARNINGS.balance</field>
</record>
</field>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_report_liabilities_and_equity_view0" model="account.report.line">
<field name="name">LIABILITIES + EQUITY</field>
<field name="hierarchy_level">0</field>
<field name="code">LE</field>
<field name="horizontal_split_side">right</field>
<field name="expression_ids">
<record id="account_financial_report_liabilities_and_equity_view0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">L.balance + EQ.balance</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_off_sheet" model="account.report.line">
<field name="name">OFF BALANCE SHEET ACCOUNTS</field>
<field name="hierarchy_level">0</field>
<field name="code">OS</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="hide_if_zero" eval="1"/>
<field name="domain_formula">-sum([('account_id.account_type', '=', 'off_balance')])</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,474 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="bank_reconciliation_report" model="account.report">
<field name="name">Bank Reconciliation Report</field>
<field name="filter_show_draft" eval="True"/>
<field name="filter_date_range" eval="False"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_hide_0_lines">by_default</field>
<field name="search_bar" eval="True"/>
<field name="default_opening_date_filter">today</field>
<field name="custom_handler_model_id" ref="model_account_bank_reconciliation_report_handler"/>
<field name="column_ids">
<record id="bank_reconciliation_report_date" model="account.report.column">
<field name="name">Date</field>
<field name="expression_label">date</field>
<field name="figure_type">date</field>
</record>
<record id="bank_reconciliation_report_label" model="account.report.column">
<field name="name">Label</field>
<field name="expression_label">label</field>
<field name="figure_type">string</field>
</record>
<record id="bank_reconciliation_report_amount_currency" model="account.report.column">
<field name="name">Amount Currency</field>
<field name="expression_label">amount_currency</field>
<field name="figure_type">monetary</field>
</record>
<record id="bank_reconciliation_report_currency" model="account.report.column">
<field name="name">Currency</field>
<field name="expression_label">currency</field>
<field name="figure_type">string</field>
</record>
<record id="bank_reconciliation_report_amount" model="account.report.column">
<field name="name">Amount</field>
<field name="expression_label">amount</field>
<field name="figure_type">monetary</field>
</record>
</field>
<field name="line_ids">
<record id="balance_bank" model="account.report.line">
<field name="name">Balance of Bank</field>
<field name="code">balance_bank</field>
<field name="hierarchy_level">0</field>
<field name="expression_ids">
<record id="balance_bank_expr" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">aggregation</field>
<field name="formula">last_statement_balance.amount + transaction_without_statement.amount + misc_operations.amount</field>
<field name="auditable" eval="True"/>
</record>
<record id="balance_bank_expr_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_forced_currency_amount</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
<field name="children_ids">
<record id="last_statement_balance" model="account.report.line">
<field name="name">Last statement balance</field>
<field name="code">last_statement_balance</field>
<field name="expression_ids">
<record id="last_statement_balance_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_last_statement_balance_amount</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="last_statement_balance_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_last_statement_balance_amount</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
<field name="children_ids">
<record id="unreconciled_last_statement_receipts" model="account.report.line">
<field name="name">Including Unreconciled Receipts</field>
<field name="code">last_statement_receipts</field>
<field name="groupby">id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="unreconciled_last_statement_receipts_date" model="account.report.expression">
<field name="label">date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_receipts</field>
<field name="subformula">date</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_receipts_label" model="account.report.expression">
<field name="label">label</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_receipts</field>
<field name="subformula">label</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_receipts_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_receipts</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_receipts_forced_currency_amount_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_receipts</field>
<field name="subformula">amount_currency_currency_id</field>
</record>
<record id="unreconciled_last_statement_receipts_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_receipts</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_receipts_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_receipts</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_receipts_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_receipts</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
</record>
<record id="unreconciled_last_statement_payments" model="account.report.line">
<field name="name">Including Unreconciled Payments</field>
<field name="code">last_statement_payments</field>
<field name="groupby">id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="unreconciled_last_statement_payments_date" model="account.report.expression">
<field name="label">date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_payments</field>
<field name="subformula">date</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_payments_label" model="account.report.expression">
<field name="label">label</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_payments</field>
<field name="subformula">label</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_payments_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_payments</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_payments_forced_currency_amount_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_payments</field>
<field name="subformula">amount_currency_currency_id</field>
</record>
<record id="unreconciled_last_statement_payments_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_payments</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_payments_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_payments</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="unreconciled_last_statement_payments_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_last_statement_payments</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
</record>
</field>
</record>
<record id="transaction_without_statement" model="account.report.line">
<field name="name">Transactions without statement</field>
<field name="code">transaction_without_statement</field>
<field name="expression_ids">
<record id="transaction_without_statement_expr" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_transaction_without_statement_amount</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="transaction_without_statement_expr_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_transaction_without_statement_amount</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
<field name="children_ids">
<record id="no_statement_unreconciled_receipt" model="account.report.line">
<field name="name">Including Unreconciled Receipts</field>
<field name="code">unreconciled_receipt</field>
<field name="groupby">id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="no_statement_unreconciled_receipt_date" model="account.report.expression">
<field name="label">date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_receipts</field>
<field name="subformula">date</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_receipt_label" model="account.report.expression">
<field name="label">label</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_receipts</field>
<field name="subformula">label</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_receipt_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_receipts</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_receipt_forced_currency_amount_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_receipts</field>
<field name="subformula">amount_currency_currency_id</field>
</record>
<record id="no_statement_unreconciled_receipt_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_receipts</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_receipt_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_receipts</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_receipt_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_receipts</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
</record>
<record id="no_statement_unreconciled_payments" model="account.report.line">
<field name="name">Including Unreconciled Payments</field>
<field name="code">unreconciled_payments</field>
<field name="groupby">id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="no_statement_unreconciled_payments_date" model="account.report.expression">
<field name="label">date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_payments</field>
<field name="subformula">date</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_payments_label" model="account.report.expression">
<field name="label">label</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_payments</field>
<field name="subformula">label</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_payments_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_payments</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_payments_forced_currency_amount_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_payments</field>
<field name="subformula">amount_currency_currency_id</field>
</record>
<record id="no_statement_unreconciled_payments_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_payments</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_payments_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_payments</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="no_statement_unreconciled_payments_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_unreconciled_payments</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
</record>
</field>
</record>
<record id="misc_operations" model="account.report.line">
<field name="name">Misc. operations</field>
<field name="code">misc_operations</field>
<field name="expression_ids">
<record id="misc_operations_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_misc_operations</field>
<field name="subformula">amount</field>
<field name="auditable" eval="True"/>
</record>
<record id="misc_operations_amount_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_misc_operations</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
</record>
</field>
</record>
<record id="outstanding" model="account.report.line">
<field name="name">Outstanding Receipts/Payments</field>
<field name="hierarchy_level">0</field>
<field name="expression_ids">
<record id="outstanding_expr" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">aggregation</field>
<field name="formula">outstanding_receipts.amount + outstanding_payments.amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_expr_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_forced_currency_amount</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
<field name="children_ids">
<record id="outstanding_receipts" model="account.report.line">
<field name="name">(+) Outstanding Receipts</field>
<field name="code">outstanding_receipts</field>
<field name="groupby">id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="outstanding_receipts_date" model="account.report.expression">
<field name="label">date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_receipts</field>
<field name="subformula">date</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_receipts_label" model="account.report.expression">
<field name="label">label</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_receipts</field>
<field name="subformula">label</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_receipts_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_receipts</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_receipts_forced_currency_amount_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_receipts</field>
<field name="subformula">amount_currency_currency_id</field>
</record>
<record id="outstanding_receipts_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_receipts</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_receipts_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_receipts</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_receipts_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_receipts</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
</record>
<record id="outstanding_payments" model="account.report.line">
<field name="name">(-) Outstanding Payments</field>
<field name="code">outstanding_payments</field>
<field name="groupby">id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="outstanding_payments_date" model="account.report.expression">
<field name="label">date</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_payments</field>
<field name="subformula">date</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_payments_label" model="account.report.expression">
<field name="label">label</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_payments</field>
<field name="subformula">label</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_payments_amount_currency" model="account.report.expression">
<field name="label">amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_payments</field>
<field name="subformula">amount_currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_payments_forced_currency_amount_currency" model="account.report.expression">
<field name="label">_currency_amount_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_payments</field>
<field name="subformula">amount_currency_currency_id</field>
</record>
<record id="outstanding_payments_currency" model="account.report.expression">
<field name="label">currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_payments</field>
<field name="subformula">currency</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_payments_amount" model="account.report.expression">
<field name="label">amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_payments</field>
<field name="subformula">amount</field>
<field name="auditable" eval="False"/>
</record>
<record id="outstanding_payments_forced_currency_amount" model="account.report.expression">
<field name="label">_currency_amount</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_outstanding_payments</field>
<field name="subformula">amount_currency_id</field>
</record>
</field>
</record>
</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="cash_flow_report" model="account.report">
<field name="name">Cash Flow Statement</field>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_date_range" eval="True"/>
<field name="filter_journals" eval="True"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="currency_translation">current</field>
<field name="custom_handler_model_id" ref="model_account_cash_flow_report_handler"/>
<field name="column_ids">
<record id="cash_flow_report_balance" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="deferred_expense_report" model="account.report">
<field name="name">Deferred Expense Report</field>
<field name="filter_journals" eval="True"/>
<field name="filter_analytic" eval="True"/>
<field name="filter_period_comparison" eval="True"/>
<field name="filter_growth_comparison" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_hierarchy">by_default</field>
<field name="default_opening_date_filter">previous_month</field>
<field name="search_bar" eval="True"/>
<field name="custom_handler_model_id" ref="model_account_deferred_expense_report_handler"/>
<field name="column_ids">
<record id="deferred_expense_current" model="account.report.column">
<field name="name">Current</field>
<field name="expression_label">current</field>
</record>
</field>
</record>
<record id="deferred_revenue_report" model="account.report">
<field name="name">Deferred Revenue Report</field>
<field name="filter_journals" eval="True"/>
<field name="filter_analytic" eval="True"/>
<field name="filter_period_comparison" eval="True"/>
<field name="filter_growth_comparison" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_hierarchy">by_default</field>
<field name="default_opening_date_filter">previous_month</field>
<field name="search_bar" eval="True"/>
<field name="custom_handler_model_id" ref="model_account_deferred_revenue_report_handler"/>
<field name="column_ids">
<record id="deferred_revenue_current" model="account.report.column">
<field name="name">Current</field>
<field name="expression_label">current</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,37 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_account_bank_cash">True</field>
</record>
</data>
<data noupdate="0">
<record id="digest_tip_account_accountant_0" model="digest.tip">
<field name="name">Tip: Bulk update journal items</field>
<field name="sequence">900</field>
<field name="group_id" ref="account.group_account_user" />
<field name="tip_description" type="html">
<div>
<b class="tip_title">Tip: Bulk update journal items</b>
<p class="tip_content">From any list view, select multiple records and the list becomes editable. If you update a cell, selected records are updated all at once. Use this feature to update multiple journal entries from the General Ledger, or any Journal view.</p>
<img src="https://download.odoocdn.com/digests/at_accounting/static/src/img/milk-accounting-bulk.gif" width="540" class="illustration_border" />
</div>
</field>
</record>
<record id="digest_tip_account_accountant_1" model="digest.tip">
<field name="name">Tip: Find an Accountant or register your Accounting Firm</field>
<field name="sequence">1000</field>
<field name="group_id" ref="account.group_account_user" />
<field name="tip_description" type="html">
<div>
<b class="tip_title">Tip: Find an Accountant or register your Accounting Firm</b>
<p class="tip_content">Click here to find an accountant or if you want to list out your accounting services on Odoo</p>
<p class="mt-3">
<a class="tip_button" href="https://odoo.com/accounting-firms" target="_blank"><span class="tip_button_text">Find an Accountant</span></a>
<a class="tip_button" href="https://odoo.com/accounting-firms/register" target="_blank"><span class="tip_button_text">Register your Accounting Firm</span></a>
</p>
</div>
</field>
</record>
</data>
</odoo>

View File

@@ -1,395 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="executive_summary" model="account.report">
<field name="name">Executive Summary</field>
<field name="filter_multi_company">selector</field>
<field name="default_opening_date_filter">this_year</field>
<field name="column_ids">
<record id="executive_summary_column" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
<field name="line_ids">
<record id="account_financial_report_executivesummary_cash0" model="account.report.line">
<field name="name">Cash</field>
<field name="hierarchy_level">0</field>
<field name="children_ids">
<record id="account_financial_report_executivesummary_cash_received0" model="account.report.line">
<field name="name">Cash received</field>
<field name="code">CR</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_cash_received0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', 'in', ('asset_cash', 'liability_credit_card')), ('debit', '&gt;', 0.0)]"/>
<field name="subformula">sum</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_cash_spent0" model="account.report.line">
<field name="name">Cash spent</field>
<field name="code">CS</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_cash_spent0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', 'in', ('asset_cash', 'liability_credit_card')), ('credit', '&gt;', 0.0)]"/>
<field name="subformula">sum</field>
<field name="green_on_positive" eval="False"/>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_cash_surplus0" model="account.report.line">
<field name="name">Cash surplus</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_cash_surplus0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">CR.balance + CS.balance</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_closing_bank_balance0" model="account.report.line">
<field name="name">Closing bank balance</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_closing_bank_balance0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', 'in', ('asset_cash', 'liability_credit_card'))]"/>
<field name="date_scope">from_beginning</field>
<field name="subformula">sum</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_profitability0" model="account.report.line">
<field name="name">Profitability</field>
<field name="hierarchy_level">0</field>
<field name="children_ids">
<record id="account_financial_report_executivesummary_income0" model="account.report.line">
<field name="name">Revenue</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_income0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">REV.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_direct_costs0" model="account.report.line">
<field name="name">Cost of Revenue</field>
<field name="code">EXEC_COS</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_direct_costs0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">COS.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
<field name="green_on_positive" eval="False"/>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_gross_profit0" model="account.report.line">
<field name="name">Gross profit</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_gross_profit0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">GRP.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_expenses0" model="account.report.line">
<field name="name">Expenses</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_expenses0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">EXP.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
<field name="green_on_positive" eval="False"/>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_profit0" model="account.report.line">
<field name="name">Net Profit</field>
<field name="code">EXEC_NEP</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_profit0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">NEP.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_balancesheet0" model="account.report.line">
<field name="name">Balance Sheet</field>
<field name="hierarchy_level">0</field>
<field name="children_ids">
<record id="account_financial_report_executivesummary_debtors0" model="account.report.line">
<field name="name">Receivables</field>
<field name="code">DEB</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_debtors0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'asset_receivable')]"/>
<field name="date_scope">from_beginning</field>
<field name="subformula">sum</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_creditors0" model="account.report.line">
<field name="name">Payables</field>
<field name="code">CRE</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_creditors0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'liability_payable')]"/>
<field name="date_scope">from_beginning</field>
<field name="subformula">sum</field>
<field name="green_on_positive" eval="False"/>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_net_assets0" model="account.report.line">
<field name="name">Net assets</field>
<field name="code">EXEC_SUMMARY_NA</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_net_assets0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">TA.balance - L.balance</field>
<field name="date_scope">from_beginning</field>
<field name="subformula">cross_report</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_performance0" model="account.report.line">
<field name="name">Performance</field>
<field name="hierarchy_level">0</field>
<field name="children_ids">
<record id="account_financial_report_executivesummary_gpmargin0" model="account.report.line">
<field name="name">Gross profit margin (gross profit / operating income)</field>
<field name="code">GPMARGIN0</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_gpmargin0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">GPMARGIN0.grp / GPMARGIN0.opinc * 100</field>
<field name="subformula">ignore_zero_division</field>
<field name="figure_type">percentage</field>
<field name="auditable" eval="False"/>
</record>
<record id="account_financial_report_executivesummary_gpmargin0_grp" model="account.report.expression">
<field name="label">grp</field>
<field name="engine">aggregation</field>
<field name="formula">GRP.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_report_executivesummary_gpmargin0_opinc" model="account.report.expression">
<field name="label">opinc</field>
<field name="engine">aggregation</field>
<field name="formula">REV.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_npmargin0" model="account.report.line">
<field name="name">Net profit margin (net profit / income)</field>
<field name="code">NPMARGIN0</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_npmargin0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">NPMARGIN0.nep / NPMARGIN0.inc * 100</field>
<field name="subformula">ignore_zero_division</field>
<field name="figure_type">percentage</field>
<field name="auditable" eval="False"/>
</record>
<record id="account_financial_report_executivesummary_npmargin0_nep" model="account.report.expression">
<field name="label">nep</field>
<field name="engine">aggregation</field>
<field name="formula">NEP.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_report_executivesummary_npmargin0_inc" model="account.report.expression">
<field name="label">inc</field>
<field name="engine">aggregation</field>
<field name="formula">INC.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_return_investment0" model="account.report.line">
<field name="name">Return on investments (net profit / assets)</field>
<field name="code">ROI</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_return_investment0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">ROI.nep / ROI.ta * 100</field>
<field name="subformula">ignore_zero_division</field>
<field name="figure_type">percentage</field>
<field name="auditable" eval="False"/>
</record>
<record id="account_financial_report_executivesummary_return_investment0_nep" model="account.report.expression">
<field name="label">nep</field>
<field name="engine">aggregation</field>
<field name="formula">NEP.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_report_executivesummary_return_investment0_ta" model="account.report.expression">
<field name="label">ta</field>
<field name="engine">aggregation</field>
<field name="formula">TA.balance</field>
<field name="date_scope">from_beginning</field>
<field name="subformula">cross_report</field>
</record>
</field>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_position0" model="account.report.line">
<field name="name">Position</field>
<field name="hierarchy_level">0</field>
<field name="children_ids">
<record id="account_financial_report_executivesummary_avdebt0" model="account.report.line">
<field name="name">Average debtors days</field>
<field name="code">AVG_DEBT_DAYS</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_avdebt0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">DEB.balance / AVG_DEBT_DAYS.opinc * AVG_DEBT_DAYS.NDays</field>
<field name="subformula">ignore_zero_division</field>
<field name="green_on_positive" eval="False"/>
<field name="figure_type">float</field>
<field name="auditable" eval="False"/>
</record>
<record id="account_financial_report_executivesummary_avdebt0_opinc" model="account.report.expression">
<field name="label">opinc</field>
<field name="engine">aggregation</field>
<field name="formula">REV.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_report_executivesummary_avdebt0_ndays" model="account.report.expression">
<field name="label">NDays</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_executive_summary_ndays</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_avgcre0" model="account.report.line">
<field name="name">Average creditors days</field>
<field name="code">AVG_CRED_DAYS</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_avgcre0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">-CRE.balance / (AVG_CRED_DAYS.cos + AVG_CRED_DAYS.exp) * AVG_CRED_DAYS.NDays</field>
<field name="subformula">ignore_zero_division</field>
<field name="green_on_positive" eval="False"/>
<field name="figure_type">float</field>
<field name="auditable" eval="False"/>
</record>
<record id="account_financial_report_executivesummary_avgcre0_cos" model="account.report.expression">
<field name="label">cos</field>
<field name="engine">aggregation</field>
<field name="formula">COS.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_report_executivesummary_avgcre0_exp" model="account.report.expression">
<field name="label">exp</field>
<field name="engine">aggregation</field>
<field name="formula">EXP.balance</field>
<field name="date_scope">strict_range</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_report_executivesummary_avgcre0_ndays" model="account.report.expression">
<field name="label">NDays</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_executive_summary_ndays</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_st_cash_forecast0" model="account.report.line">
<field name="name">Short term cash forecast</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_st_cash_forecast0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">DEB.balance + CRE.balance</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_executivesummary_ca_to_l0" model="account.report.line">
<field name="name">Current assets to liabilities</field>
<field name="code">CATL</field>
<field name="expression_ids">
<record id="account_financial_report_executivesummary_ca_to_l0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">CATL.ca / CATL.cl</field>
<field name="subformula">ignore_zero_division</field>
<field name="figure_type">float</field>
<field name="auditable" eval="False"/>
</record>
<record id="account_financial_report_executivesummary_ca_to_l0_ca" model="account.report.expression">
<field name="label">ca</field>
<field name="engine">aggregation</field>
<field name="formula">CA.balance</field>
<field name="date_scope">from_beginning</field>
<field name="subformula">cross_report</field>
</record>
<record id="account_financial_report_executivesummary_ca_to_l0_cl" model="account.report.expression">
<field name="label">cl</field>
<field name="engine">aggregation</field>
<field name="formula">CL.balance</field>
<field name="date_scope">from_beginning</field>
<field name="subformula">cross_report</field>
</record>
</field>
</record>
</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="general_ledger_report" model="account.report">
<field name="name">General Ledger</field>
<field name="filter_journals" eval="True"/>
<field name="filter_analytic" eval="True"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_hide_0_lines">never</field>
<field name="default_opening_date_filter">this_month</field>
<field name="search_bar" eval="True"/>
<field name="load_more_limit" eval="80"/>
<field name="custom_handler_model_id" ref="model_account_general_ledger_report_handler"/>
<field name="column_ids">
<record id="general_ledger_report_date" model="account.report.column">
<field name="name">Date</field>
<field name="expression_label">date</field>
<field name="figure_type">date</field>
</record>
<record id="general_ledger_report_communication" model="account.report.column">
<field name="name">Communication</field>
<field name="expression_label">communication</field>
<field name="figure_type">string</field>
</record>
<record id="general_ledger_report_partner_name" model="account.report.column">
<field name="name">Partner</field>
<field name="expression_label">partner_name</field>
<field name="figure_type">string</field>
</record>
<record id="general_ledger_report_amount_currency" model="account.report.column">
<field name="name">Currency</field>
<field name="expression_label">amount_currency</field>
</record>
<record id="general_ledger_report_debit" model="account.report.column">
<field name="name">Debit</field>
<field name="expression_label">debit</field>
</record>
<record id="general_ledger_report_credit" model="account.report.column">
<field name="name">Credit</field>
<field name="expression_label">credit</field>
</record>
<record id="general_ledger_report_balance" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="account.generic_tax_report" model="account.report">
<field name="custom_handler_model_id" ref="model_account_generic_tax_report_handler"/>
</record>
<record id="account.generic_tax_report_account_tax" model="account.report">
<field name="custom_handler_model_id" ref="model_account_generic_tax_report_handler_account_tax"/>
</record>
<record id="account.generic_tax_report_tax_account" model="account.report">
<field name="custom_handler_model_id" ref="model_account_generic_tax_report_handler_tax_account"/>
</record>
</odoo>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="auto_reconcile_bank_statement_line" model="ir.cron">
<field name="name">Try to reconcile automatically your statement lines</field>
<field name="model_id" ref="model_account_bank_statement_line"/>
<field name="state">code</field>
<field name="code">model._cron_try_auto_reconcile_statement_lines(batch_size=100)</field>
<field name='interval_number'>1</field>
<field name='interval_type'>days</field>
</record>
</odoo>

View File

@@ -1,235 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="journal_report" model="account.report">
<field name="name">Journal Report</field>
<field name="filter_journals" eval="True"/>
<field name="filter_show_draft" eval="True"/>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_hierarchy">never</field>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_unreconciled" eval="False"/>
<field name="filter_hide_0_lines">never</field>
<field name="default_opening_date_filter">this_year</field>
<field name="custom_handler_model_id" ref="model_account_journal_report_handler"/>
<field name="column_ids">
<record id="journal_report_code" model="account.report.column">
<field name="name">Code</field>
<field name="expression_label">code</field>
<field name="figure_type">string</field>
</record>
<record id="journal_report_debit" model="account.report.column">
<field name="name">Debit</field>
<field name="expression_label">debit</field>
</record>
<record id="journal_report_credit" model="account.report.column">
<field name="name">Credit</field>
<field name="expression_label">credit</field>
</record>
<record id="journal_report_balance" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
<field name="line_ids">
<record id="journal_report_line" model="account.report.line">
<field name="name">Name</field>
<field name="groupby">journal_id, account_id</field>
<field name="hierarchy_level">0</field>
<field name="expression_ids">
<record id="journal_report_line_code" model="account.report.expression">
<field name="label">code</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_journal_report</field>
<field name="subformula">code</field>
</record>
<record id="journal_report_line_debit" model="account.report.expression">
<field name="label">debit</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_journal_report</field>
<field name="subformula">debit</field>
</record>
<record id="journal_report_line_credit" model="account.report.expression">
<field name="label">credit</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_journal_report</field>
<field name="subformula">credit</field>
</record>
<record id="journal_report_line_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_journal_report</field>
<field name="subformula">balance</field>
</record>
</field>
</record>
</field>
</record>
<template id="journal_report_pdf_export_main">
<html>
<head>
<base t-att-href="base_url"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<t t-call-assets="at_accounting.assets_pdf_export" t-js="False"/>
</head>
<body t-att-dir="env['res.lang']._get_data(code=lang or env.user.lang).direction or 'ltr'">
<div t-att-class="options['css_custom_class']">
<header>
<div class="row align-items-center">
<div class="col-4 o_header_font">
<t t-call="at_accounting.company_information"/>
</div>
<div class="col-4">
<div class="o_title">
<t t-if="report.filter_show_draft and options['all_entries']">[Draft]</t>
<t t-out="report.name"/>
</div>
<div class="o_subtitle">
<t t-out="options['date']['date_from']"/> - <t t-out="options['date']['date_to']"/>
</div>
</div>
</div>
</header>
<!-- Journal entries -->
<t t-foreach="document_data['journals_vals']" t-as="journal_vals">
<section style="page-break-after: always;">
<div class="o_section_title">
<t t-out="journal_vals.get('name')"/>
</div>
<div class="d-flex align-items-start">
<t t-call="at_accounting.journal_report_pdf_body_default"/>
</div>
<t t-if="journal_vals.get('tax_summary')">
<t t-call="at_accounting.pdf_journal_report_taxes_summary">
<t t-set="tax_summary" t-value="journal_vals['tax_summary']"/>
</t>
</t>
</section>
</t>
<section t-if="document_data.get('global_tax_summary')">
<div class="o_section_title">
Global Tax Summary
</div>
<t t-call="at_accounting.pdf_journal_report_taxes_summary">
<t t-set="tax_summary" t-value="document_data['global_tax_summary']"/>
</t>
</section>
</div>
</body>
</html>
</template>
<template id="journal_report_pdf_body_default">
<table class="o_table">
<thead>
<tr>
<t t-foreach="journal_vals['columns']" t-as="column">
<th t-att-class="column.get('class', '')">
<t t-out="column.get('name', '')"/>
</th>
</t>
</tr>
</thead>
<tbody>
<t t-foreach="journal_vals['lines']" t-as="line">
<tr t-att-class="line.get('line_class', '')">
<t t-foreach="journal_vals['columns']" t-as="column">
<t t-if="line.get(column['label'])">
<t t-set="cell_style" t-value="line[column['label']].get('class', '')"/>
<t t-set="column_style" t-value="column.get('class', '')"/>
<td t-att-class="cell_style + column_style">
<t t-out="line[column['label']]['data']"/>
</td>
</t>
<t t-else="">
<td/>
</t>
</t>
</tr>
</t>
</tbody>
</table>
</template>
<template id="pdf_journal_report_taxes_summary">
<div class="container tax_summary" style="page-break-inside: avoid;">
<t t-set="taxes" t-value="tax_summary.get('tax_report_lines')"/>
<t t-if="taxes">
<div class="row o_section_subtitle">
<p>Tax Applied</p>
</div>
<div class="row taxes">
<t t-set="extra_columns" t-value="tax_summary.get('extra_columns')"/>
<table class="o_table">
<thead>
<tr>
<th t-if="len(taxes) > 1">Country</th>
<th>Name</th>
<th class="o_right_alignment">Base Amount</th>
<th class="o_right_alignment">Tax Amount</th>
<th t-if="tax_summary.get('tax_non_deductible_column')" class="o_right_alignment">Non-Deductible</th>
<th t-if="tax_summary.get('tax_deductible_column')" class="o_right_alignment">Deductible</th>
<th t-if="tax_summary.get('tax_due_column')" class="o_right_alignment">Due</th>
</tr>
</thead>
<tbody>
<t t-foreach="taxes" t-as="country_name">
<tr t-foreach="taxes[country_name]" t-as="tax">
<t t-if="country_name_size > 1">
<td>
<t t-if="tax_index == 0" t-out="country_name"/>
</td>
</t>
<td t-out="tax['name']"/>
<td class="o_right_alignment" t-out="tax['base_amount']"/>
<td class="o_right_alignment" t-out="tax['tax_amount']"/>
<td t-if="tax_summary.get('tax_non_deductible_column')" class="o_right_alignment" t-out="tax['tax_non_deductible']"/>
<td t-if="tax_summary.get('tax_deductible_column')" class="o_right_alignment" t-out="tax['tax_deductible']"/>
<td t-if="tax_summary.get('tax_due_column')" class="o_right_alignment" t-out="tax['tax_due']"/>
</tr>
</t>
</tbody>
</table>
</div>
</t>
<t t-set="grids" t-value="tax_summary.get('tax_grid_summary_lines')"/>
<t t-if="grids">
<div class="row o_section_subtitle">
<p>Impacted Tax Grids</p>
</div>
<div class="row tax_grid">
<table class="o_table">
<thead>
<tr>
<th t-if="len(grids) > 1">Country</th>
<th>Grid</th>
<th class="o_right_alignment">+</th>
<th class="o_right_alignment">-</th>
<th class="o_right_alignment">Impact On Grid</th>
</tr>
</thead>
<tbody>
<t t-foreach="grids" t-as="country_name">
<tr t-foreach="grids[country_name]" t-as="grid_name">
<t t-if="country_name_size > 1">
<td>
<t t-if="grid_name_index == 0" t-out="country_name"/>
</td>
</t>
<td t-out="grid_name"/>
<td class="o_right_alignment" t-out="grids[country_name][grid_name].get('+', 0)"/>
<td class="o_right_alignment" t-out="grids[country_name][grid_name].get('-', 0)"/>
<td class="o_right_alignment" t-out="grids[country_name][grid_name]['impact']"/>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</div>
</template>
</odoo>

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="tax_closing_activity_type" model="mail.activity.type">
<field name="name">Tax Report</field>
<field name="summary">Tax Report</field>
<field name="category">tax_report</field>
<field name="res_model">account.journal</field>
<field name="chaining_type">suggest</field>
</record>
<record id="mail_activity_type_tax_report_to_pay" model="mail.activity.type">
<field name="name">Pay Tax</field>
<field name="summary">Tax is ready to be paid</field>
<field name="category">tax_report</field>
<field name="delay_count">0</field>
<field name="delay_unit">days</field>
<field name="delay_from">previous_activity</field>
<field name="res_model">account.move</field>
<field name="chaining_type">suggest</field>
</record>
<record id="mail_activity_type_tax_report_to_be_sent" model="mail.activity.type">
<field name="name">Tax Report Ready</field>
<field name="summary">Tax report is ready to be sent to the administration</field>
<field name="category">tax_report</field>
<field name="delay_count">0</field>
<field name="delay_unit">days</field>
<field name="delay_from">current_date</field>
<field name="res_model">account.move</field>
<field name="chaining_type">suggest</field>
</record>
</data>
</odoo>

View File

@@ -1,26 +0,0 @@
<odoo>
<record id="email_template_customer_statement" model="mail.template">
<field name="name">Customer Statement</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="email_from">{{ object._get_followup_responsible().email_formatted }}</field>
<field name="subject">{{ (object.company_id or object._get_followup_responsible().company_id).name }} Statement - {{ object.commercial_company_name }}</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px;">
<t t-if="object.id != object.commercial_partner_id.id">Dear <t t-out="object.name or ''"/> (<t t-out="object.commercial_partner_id.name or ''"/>),</t>
<t t-else="">Dear <t t-out="object.name or ''"/>,</t>
<br/>
Please find enclosed the statement of your account.
<br/>
Do not hesitate to contact us if you have any questions.
<br/>
Sincerely,
<br/>
<t t-out="object._get_followup_responsible().name if is_html_empty(object._get_followup_responsible().signature) else object._get_followup_responsible().signature"/>
</p>
</div>
</field>
<field name="lang">{{ object.lang }}</field>
<field name="auto_delete" eval="False"/>
</record>
</odoo>

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_action_account_report_partner_ledger" name="Partner Ledger"
action="action_account_report_partner_ledger" groups="account.group_account_readonly"
parent="account.account_reports_partners_reports_menu"/>
<menuitem id="menu_action_account_report_aged_receivable" name="Aged Receivable" action="action_account_report_ar"
groups="account.group_account_readonly" parent="account.account_reports_partners_reports_menu"/>
<menuitem id="menu_action_account_report_aged_payable" name="Aged Payable" action="action_account_report_ap"
groups="account.group_account_readonly" parent="account.account_reports_partners_reports_menu"/>
<menuitem id="account_reports_audit_reports_menu" name="Audit Reports" parent="account.menu_finance_reports"
sequence="2">
<menuitem id="menu_action_account_report_general_ledger" name="General Ledger"
action="action_account_report_general_ledger" groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_coa" name="Trial Balance" action="action_account_report_coa"
groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_ja" name="Journal Audit" action="action_account_report_ja"
groups="account.group_account_readonly"/>
</menuitem>
<menuitem id="menu_action_account_report_gt" name="Tax Return" action="action_account_report_gt"
parent="account.account_reports_legal_statements_menu" sequence="50"
groups="account.group_account_readonly,account.group_account_basic"/>
<menuitem id="menu_action_account_report_sales" action="action_account_report_sales"
parent="account.account_reports_legal_statements_menu" sequence="60"
groups="account.group_account_readonly" active="False"/>
<menuitem id="menu_action_account_report_multicurrency_revaluation" name="Unrealized Currency Gains/Losses"
action="action_account_report_multicurrency_revaluation" parent="account.account_reports_management_menu"
groups="base.group_multi_currency"/>
<menuitem id="menu_action_account_report_balance_sheet" name="Balance Sheet" action="action_account_report_bs"
parent="account.account_reports_legal_statements_menu" groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_profit_and_loss" name="Profit and Loss" action="action_account_report_pl"
parent="account.account_reports_legal_statements_menu" groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_cash_flow" name="Cash Flow Statement" action="action_account_report_cs"
parent="account.account_reports_legal_statements_menu" groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_exec_summary" name="Executive Summary"
action="action_account_report_exec_summary" parent="account.account_reports_legal_statements_menu"
groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_deferred_expense" name="Deferred Expense"
action="action_account_report_deferred_expense" parent="account.account_reports_management_menu"
groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_deferred_revenue" name="Deferred Revenue"
action="action_account_report_deferred_revenue" parent="account.account_reports_management_menu"
groups="account.group_account_readonly"/>
<menuitem id="menu_action_account_report_tree" name="Accounting Reports" sequence="6"
parent="account.account_management_menu" action="action_account_report_tree" groups="base.group_no_one"/>
<menuitem id="menu_action_account_report_horizontal_groups" name="Horizontal Groups"
action="action_account_report_horizontal_groups" parent="account.account_account_menu" sequence="10"
groups="base.group_no_one"/>
<menuitem id="menu_action_account_report_budget_tree" name="Financial Budgets"
action="action_account_report_budget_tree" parent="account.account_account_menu" sequence="11"/>
</odoo>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_action_account_report_assets"
name="Depreciation Schedule"
action="action_account_report_assets"
parent="account.account_reports_management_menu"
groups="account.group_account_readonly"/>
</odoo>

View File

@@ -1,107 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="multicurrency_revaluation_report" model="account.report">
<field name="name">Unrealized Currency Gains/Losses</field>
<field name="filter_date_range" eval="False"/>
<field name="filter_show_draft" eval="True"/>
<field name="default_opening_date_filter">previous_month</field>
<field name="custom_handler_model_id" ref="model_account_multicurrency_revaluation_report_handler"/>
<field name="column_ids">
<record id="multicurrency_revaluation_report_balance_currency" model="account.report.column">
<field name="name">Balance in Foreign Currency</field>
<field name="expression_label">balance_currency</field>
</record>
<record id="multicurrency_revaluation_report_balance_operation" model="account.report.column">
<field name="name">Balance at Operation Rate</field>
<field name="expression_label">balance_operation</field>
</record>
<record id="multicurrency_revaluation_report_balance_current" model="account.report.column">
<field name="name">Balance at Current Rate</field>
<field name="expression_label">balance_current</field>
</record>
<record id="multicurrency_revaluation_report_adjustment" model="account.report.column">
<field name="name">Adjustment</field>
<field name="expression_label">adjustment</field>
</record>
</field>
<field name="line_ids">
<record id="multicurrency_revaluation_to_adjust" model="account.report.line">
<field name="name">Accounts To Adjust</field>
<field name="code">multicurrency_included</field>
<field name="groupby">currency_id, account_id, id</field>
<field name="expression_ids">
<record id="multicurrency_revaluation_to_adjust_balance_currency" model="account.report.expression">
<field name="label">balance_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_to_adjust</field>
<field name="subformula">balance_currency</field>
</record>
<record id="multicurrency_revaluation_to_adjust_balance_currency_forced_currency" model="account.report.expression">
<field name="label">_currency_balance_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_to_adjust</field>
<field name="subformula">currency_id</field>
</record>
<record id="multicurrency_revaluation_to_adjust_balance_operation" model="account.report.expression">
<field name="label">balance_operation</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_to_adjust</field>
<field name="subformula">balance_operation</field>
<field name="auditable" eval="False"/>
</record>
<record id="multicurrency_revaluation_to_adjust_balance_current" model="account.report.expression">
<field name="label">balance_current</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_to_adjust</field>
<field name="subformula">balance_current</field>
<field name="auditable" eval="False"/>
</record>
<record id="multicurrency_revaluation_to_adjust_adjustment" model="account.report.expression">
<field name="label">adjustment</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_to_adjust</field>
<field name="subformula">adjustment</field>
<field name="auditable" eval="False"/>
</record>
</field>
</record>
<record id="multicurrency_revaluation_excluded" model="account.report.line">
<field name="name">Excluded Accounts</field>
<field name="groupby">currency_id, account_id, id</field>
<field name="expression_ids">
<record id="multicurrency_revaluation_excluded_balance_currency" model="account.report.expression">
<field name="label">balance_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_excluded</field>
<field name="subformula">balance_currency</field>
</record>
<record id="multicurrency_revaluation_excluded_balance_currency_forced_currency" model="account.report.expression">
<field name="label">_currency_balance_currency</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_excluded</field>
<field name="subformula">currency_id</field>
</record>
<record id="multicurrency_revaluation_excluded_balance_operation" model="account.report.expression">
<field name="label">balance_operation</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_excluded</field>
<field name="subformula">balance_operation</field>
</record>
<record id="multicurrency_revaluation_excluded_balance_current" model="account.report.expression">
<field name="label">balance_current</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_excluded</field>
<field name="subformula">balance_current</field>
</record>
<record id="multicurrency_revaluation_excluded_adjustment" model="account.report.expression">
<field name="label">adjustment</field>
<field name="engine">custom</field>
<field name="formula">_report_custom_engine_multi_currency_revaluation_excluded</field>
<field name="subformula">adjustment</field>
</record>
</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="partner_ledger_report" model="account.report">
<field name="name">Partner Ledger</field>
<field name="filter_show_draft" eval="True"/>
<field name="filter_account_type">both</field>
<field name="filter_partner" eval="True"/>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_unreconciled" eval="True"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="filter_hide_0_lines">never</field>
<field name="default_opening_date_filter">this_year</field>
<field name="search_bar" eval="True"/>
<field name="load_more_limit" eval="80"/>
<field name="custom_handler_model_id" ref="model_account_partner_ledger_report_handler"/>
<field name="column_ids">
<record id="partner_ledger_report_journal_code" model="account.report.column">
<field name="name">Journal</field>
<field name="expression_label">journal_code</field>
<field name="figure_type">string</field>
</record>
<record id="partner_ledger_report_account_code" model="account.report.column">
<field name="name">Account</field>
<field name="expression_label">account_code</field>
<field name="figure_type">string</field>
</record>
<record id="partner_ledger_report_invoicing_date" model="account.report.column">
<field name="name">Invoice Date</field>
<field name="expression_label">invoice_date</field>
<field name="figure_type">date</field>
</record>
<record id="partner_ledger_report_date_maturity" model="account.report.column">
<field name="name">Due Date</field>
<field name="expression_label">date_maturity</field>
<field name="figure_type">date</field>
</record>
<record id="partner_ledger_report_matching_number" model="account.report.column">
<field name="name">Matching</field>
<field name="expression_label">matching_number</field>
<field name="figure_type">string</field>
</record>
<record id="partner_ledger_report_debit" model="account.report.column">
<field name="name">Debit</field>
<field name="expression_label">debit</field>
</record>
<record id="partner_ledger_report_credit" model="account.report.column">
<field name="name">Credit</field>
<field name="expression_label">credit</field>
</record>
<record id="partner_ledger_amount" model="account.report.column">
<field name="name">Amount</field>
<field name="expression_label">amount</field>
</record>
<record id="partner_ledger_report_amount_currency" model="account.report.column">
<field name="name">Amount Currency</field>
<field name="expression_label">amount_currency</field>
</record>
<record id="partner_ledger_report_balance" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,335 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="pdf_export_main">
<html>
<head>
<base t-att-href="base_url"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<t t-call-assets="at_accounting.assets_pdf_export" t-js="False"/>
</head>
<body t-att-dir="env['res.lang']._get_data(code=lang or env.user.lang).direction or 'ltr'">
<div t-att-class="'o_content ' + options['css_custom_class']">
<header>
<div class="o_title">
<t t-if="report.filter_show_draft and options['all_entries']">[Draft]</t>
<t t-out="report_title"/>
</div>
<div class="row o_header_font">
<div class="col-8">
<!-- All company information (name, address, vat, ...) -->
<t t-call="{{custom_templates.get('company_information', 'at_accounting.company_information')}}"/>
</div>
<div class="col-4">
<!-- All filters and options -->
<t t-call="{{custom_templates.get('pdf_export_filters', 'at_accounting.pdf_export_filters')}}"/>
</div>
</div>
</header>
<div class="d-flex align-items-start">
<t t-foreach="options.get('horizontal_split') and ['left', 'right'] or [None]" t-as="split_side">
<table t-attf-class="o_table #{options.get('horizontal_split') and 'horizontal_split_page'}">
<!-- Header -->
<t t-call="{{custom_templates.get('pdf_export_main_table_header', 'at_accounting.pdf_export_main_table_header')}}"/>
<!-- Body -->
<tbody>
<t t-if="lines">
<t t-call="{{custom_templates.get('pdf_export_main_table_body', 'at_accounting.pdf_export_main_table_body')}}">
<t t-set="lines" t-value="filter(lambda x: not split_side or split_side == x.get('horizontal_split_side', 'left'), lines)"/>
</t>
</t>
</tbody>
</table>
</t>
</div>
<!-- Annotations -->
<ol class="o_annotation">
<t t-foreach="annotations" t-as="annotation">
<li>
<t t-out="annotation.get('number')"/>.
<t t-if="annotation.get('date')"><t t-out="annotation['date']"/> -</t>
<t t-out="annotation.get('text')"/>
</li>
</t>
</ol>
</div>
</body>
</html>
</template>
<template id="company_information">
<t t-set="company_names" t-value="[company['name'] for company in options['companies']]"/>
<div class="row">
<div class="col-10" t-out="', '.join(company_names)"/>
</div>
<address class="mb-0 o_text_muted" t-field="env.company.partner_id" t-options='{"widget": "contact", "fields": ["address"], "no_marker": True}'/>
<t t-if="options.get('tax_unit', 'company_only') == 'company_only'">
<t t-if="env.company.account_fiscal_country_id.vat_label" t-out="env.company.account_fiscal_country_id.vat_label+':'"/>
<t t-else="">Tax ID:</t>
<t t-out="env.company.vat"/>
</t>
<t t-else="">
Tax ID: <t t-out="env['account.tax.unit'].browse(options.get('tax_unit')).vat"/>
</t>
</template>
<template id="pdf_export_filters">
<!-- Journals -->
<t t-if="options.get('journals')">
<div class="row" name="filter_info_template_journals">
<t t-set="journal_group_selected" t-value="options.get('selected_journal_groups')"/>
<t t-if="journal_group_selected">
<div class="col-3">Multi-Ledger: </div>
<div class="col-9 o_text_muted" t-out="journal_group_selected['title']"/>
</t>
<t t-else="">
<t t-set="journal_value" t-value="[journal.get('title') for journal in options['journals'] if journal.get('selected')]"/>
<t t-if="journal_value">
<div class="col-3">Journals: </div>
<div class="col-9 o_text_muted" t-out="', '.join(journal_value)"/>
</t>
</t>
</div>
</t>
<!-- Partners -->
<t t-if="options.get('partner_ids') != None">
<div class="row">
<t t-set="partner_value" t-value="[partner for partner in options['selected_partner_ids']]"/>
<t t-if="partner_value">
<div class="col-3">Partners:</div>
<div class="col-9 o_text_muted" t-out="', '.join(partner_value)"/>
</t>
</div>
</t>
<!-- Partners categories -->
<t t-if="options.get('partner_categories') != None">
<div class="row">
<t t-set="partner_category_value" t-value="[partner for partner in options['selected_partner_categories']]"/>
<t t-if="partner_category_value">
<div class="col-3">Partners Categories:</div>
<div class="col-9 o_text_muted" t-out="', '.join(partner_category_value)"/>
</t>
</div>
</t>
<!-- Horizontal -->
<t t-if="options.get('selected_horizontal_group_id')">
<div class="row">
<t t-set="horizontal_group" t-value="[hg['name'] for hg in options['available_horizontal_groups'] if hg['id'] == options.get('selected_horizontal_group_id')]"/>
<t t-if="horizontal_group">
<div class="col-3">Horizontal:</div>
<div class="col-9 o_text_muted" t-out="horizontal_group[0]"/>
</t>
</div>
</t>
<!-- Currency -->
<t t-if="options.get('company_currency')">
<div class="row">
<div class="col-3">Currency:</div>
<div class="col-9 o_text_muted" t-out="options['company_currency']['currency_name']"/>
</div>
</t>
<!-- Filters -->
<t t-if="options.get('aml_ir_filters') and any(opt['selected'] for opt in options['aml_ir_filters'])" name="aml_ir_filters">
<div class="row">
<t t-set="aml_ir_filters" t-value="opt['name'] for opt in options['aml_ir_filters'] if opt['selected']"/>
<t t-if="aml_ir_filters">
<div class="col-3">Filters:</div>
<div class="col-9 o_text_muted" t-out="', '.join(aml_ir_filters)"/>
</t>
</div>
</t>
<!-- Extra options -->
<div class="row" name="pdf_options_header">
<t t-call="{{custom_templates.get('pdf_export_filter_extra_options_template', 'at_accounting.pdf_export_filter_extra_options_template')}}"/>
</div>
</template>
<template id="pdf_export_filter_extra_options_template">
<t t-set="rounding_unit_display_names" t-value="{k: v[1] for k, v in options['rounding_unit_names'].items() if v[1]}"/>
<div class="col-3" t-if="(report.filter_show_draft and options['all_entries']) or
(report.filter_unreconciled and options['unreconciled']) or
options.get('include_analytic_without_aml') or
options['rounding_unit'] in rounding_unit_display_names">
Options:
</div>
<div class="col-9 o_text_muted">
<t t-set="extra_options" t-value="[]"/>
<!-- All entries -->
<t t-if="report.filter_show_draft and options['all_entries']" groups="account.group_account_readonly">
<t t-set="label_draft_entries">With Draft Entries</t>
<t t-set="extra_options" t-value="extra_options + [label_draft_entries]"/>
</t>
<!-- Unreconciled -->
<t t-if="report.filter_unreconciled and options['unreconciled']">
<t t-set="label_unreconciled_entries">Unreconciled Entries</t>
<t t-set="extra_options" t-value="extra_options + [label_unreconciled_entries]"/>
</t>
<!-- Analytic -->
<t t-if="options.get('include_analytic_without_aml')" name="include_analytic">
<t t-set="label_analytic_simulations">Including Analytic Simulations</t>
<t t-set="extra_options" t-value="extra_options + [label_analytic_simulations]"/>
</t>
<!-- Currency Unit Amount Text -->
<t t-if="options['rounding_unit'] in rounding_unit_display_names">
<t t-set="rounding_unit" t-value="options.get('rounding_unit')"/>
<t t-set="extra_options" t-value="extra_options + [rounding_unit_display_names[rounding_unit]]"/>
</t>
<t t-out="', '.join(extra_options)"/>
</div>
</template>
<template id="pdf_export_main_table_header">
<thead id="table_header">
<t t-foreach="options['column_headers']" t-as="column_header">
<tr>
<!-- First empty column -->
<th/>
<!-- Other columns -->
<t t-foreach="column_header * column_headers_render_data['level_repetitions'][column_header_index]" t-as="header">
<th t-att-colspan="header.get('colspan', column_headers_render_data['level_colspan'][column_header_index]) + (1 if options.get('show_horizontal_group_total') and column_header_first else 0)" class="o_overflow_name">
<t t-out="header.get('name')"/>
</th>
</t>
<th t-if="options.get('show_horizontal_group_total') and not column_header_first">
<t t-out="[group['name'] for group in options['available_horizontal_groups'] if group['id'] == options['selected_horizontal_group_id']][0]"/>
</th>
<th t-if="options.get('column_percent_comparison') == 'growth'">%</th>
</tr>
</t>
<!-- Custom subheaders -->
<t t-if="column_headers_render_data['custom_subheaders']">
<tr>
<!-- First empty column -->
<th/>
<!-- Other columns -->
<t t-foreach="column_headers_render_data['custom_subheaders']" t-as="subheader">
<th t-att-colspan="subheader.get('colspan', 1)">
<t t-out="subheader.get('name')"/>
</th>
</t>
</tr>
</t>
<tr>
<!-- First empty column -->
<th/>
<t t-foreach="options['columns']" t-as="subheader">
<th>
<t t-out="subheader.get('name')"/>
</th>
</t>
<th t-if="options.get('show_horizontal_group_total')">
<t t-out="options['columns'][0].get('name')"/>
</th>
<th t-if="options.get('column_percent_comparison') == 'growth'"/>
</tr>
</thead>
</template>
<template id="pdf_export_main_table_body">
<t t-foreach="lines" t-as="line">
<t t-set="o_line_level" t-value="'o_line_level_' + str(line['level'])"/>
<t t-if="line.get('page_break') and not options.get('horizontal_split')">
<!-- End current table -->
<t t-out="table_end"/>
<!-- Append table header -->
<t t-call="{{custom_templates.get('pdf_export_main_table_header', 'at_accounting.pdf_export_main_table_header')}}"/>
<!-- Start new table -->
<t t-out="table_start"/>
</t>
<!-- Adds an empty row above line with level 0 to add some spacing (it is the easiest and cleanest way) -->
<t t-if="line_index != 0 and line['level'] == 0">
<tr>
<td/>
<t t-foreach="line.get('columns')" t-as="cell">
<td/>
</t>
<t t-if="options.get('column_percent_comparison')">
<td/>
</t>
<t t-if="options.get('show_horizontal_group_total')">
<td/>
</t>
</tr>
</t>
<t t-set="o_bold" t-value="(' o_fw_bold' if line.get('unfolded') or 'total' in line.get('id') else '')"/>
<t t-set="o_overflow" t-value="(' o_overflow_name' if len(line.get('name') or '') > 42 else '')"/>
<tr t-att-class="o_line_level + o_bold + o_overflow" name="pdf_export_main_table_body_lines_tr">
<td t-att-colspan="line.get('colspan', '1')" class="o_line_name_level">
<t t-out="line.get('name')"/>
<t t-if="line.get('annotations')">
<t t-foreach="annotations" t-as="annotation">
<t t-if="annotation.get('number') and annotation['number'] in (line.get('annotations') or [])">
<sup t-out="annotation['number']"/>
</t>
</t>
</t>
</td>
<t t-foreach="line.get('columns')" t-as="cell">
<td class="o_cell_td">
<t t-if="not env.company.totals_below_sections or options.get('ignore_totals_below_sections') or not line.get('unfolded')">
<t t-call="{{custom_templates.get('pdf_export_cell', 'at_accounting.pdf_export_cell')}}"/>
</t>
</td>
</t>
<t t-if="options.get('column_percent_comparison')">
<td class="o_column_percent_comparison">
<t t-if="line.get('column_percent_comparison_data')">
<t t-out="line['column_percent_comparison_data'].get('name')"/>
</t>
</td>
</t>
<t t-if="options.get('show_horizontal_group_total')">
<td class="o_cell_td">
<t t-if="line.get('horizontal_group_total_data')">
<t t-set="o_classes" t-value="'o_line_cell_value_number' + (' o_muted' if line['horizontal_group_total_data'].get('no_format') == 0 else '')"/>
<span t-att-class="o_classes" t-out="line['horizontal_group_total_data'].get('name')"/>
</t>
</td>
</t>
</tr>
</t>
</template>
<template id="pdf_export_cell">
<t t-if="cell.get('figure_type', '') in ['float', 'integer', 'monetary', 'percentage']">
<t t-set="o_classes" t-value="'o_line_cell_value_number' + (' o_muted' if cell.get('is_zero') else '')"/>
</t>
<t t-else="">
<t t-set="o_classes" t-value="'o_overflow_value'"/>
</t>
<span t-att-class="o_classes" t-out="cell.get('name')"/>
</template>
</odoo>

View File

@@ -1,134 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="profit_and_loss" model="account.report">
<field name="name">Profit and Loss</field>
<field name="filter_analytic_groupby" eval="True"/>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_journals" eval="True"/>
<field name="filter_multi_company">selector</field>
<field name="filter_budgets" eval="True"/>
<field name="default_opening_date_filter">this_year</field>
<field name="column_ids">
<record id="profit_and_loss_column" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
<field name="line_ids">
<record id="account_financial_report_revenue0" model="account.report.line">
<field name="name">Revenue</field>
<field name="code">REV</field>
<field name="hierarchy_level">1</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_revenue0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'income')]"/>
<field name="subformula">-sum</field>
</record>
</field>
</record>
<record id="account_financial_report_cost_sales0" model="account.report.line">
<field name="name">Less Costs of Revenue</field>
<field name="code">COS</field>
<field name="hierarchy_level">1</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_cost_sales0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'expense_direct_cost')]"/>
<field name="subformula">sum</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_gross_profit0" model="account.report.line">
<field name="name">Gross Profit</field>
<field name="code">GRP</field>
<field name="hierarchy_level">0</field>
<field name="expression_ids">
<record id="account_financial_report_gross_profit0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">REV.balance - COS.balance</field>
</record>
</field>
</record>
<record id="account_financial_report_expense0" model="account.report.line">
<field name="name">Less Operating Expenses</field>
<field name="code">EXP</field>
<field name="hierarchy_level">1</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_expense0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'expense')]"/>
<field name="subformula">sum</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_operating_income0" model="account.report.line">
<field name="name">Operating Income (or Loss)</field>
<field name="hierarchy_level">0</field>
<field name="code">INC</field>
<field name="expression_ids">
<record id="account_financial_report_operating_income0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">REV.balance - COS.balance - EXP.balance</field>
</record>
</field>
</record>
<record id="account_financial_report_other_income0" model="account.report.line">
<field name="name">Plus Other Income</field>
<field name="code">OIN</field>
<field name="hierarchy_level">1</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_other_income0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'income_other')]"/>
<field name="subformula">-sum</field>
</record>
</field>
</record>
<record id="account_financial_report_depreciation0" model="account.report.line">
<field name="name">Less Other Expenses</field>
<field name="code">OEXP</field>
<field name="hierarchy_level">1</field>
<field name="groupby">account_id</field>
<field name="foldable" eval="True"/>
<field name="expression_ids">
<record id="account_financial_report_depreciation0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">domain</field>
<field name="formula" eval="[('account_id.account_type', '=', 'expense_depreciation')]"/>
<field name="subformula">sum</field>
<field name="green_on_positive" eval="False"/>
</record>
</field>
</record>
<record id="account_financial_report_net_profit0" model="account.report.line">
<field name="name">Net Profit</field>
<field name="hierarchy_level">0</field>
<field name="code">NEP</field>
<field name="expression_ids">
<record id="account_financial_report_net_profit0_balance" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">aggregation</field>
<field name="formula">REV.balance + OIN.balance - COS.balance - EXP.balance - OEXP.balance</field>
</record>
</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,11 +0,0 @@
<odoo>
<record id="ir_cron_account_report_send" model="ir.cron">
<field name="name">Send account reports automatically</field>
<field name="model_id" ref="model_account_report"/>
<field name="state">code</field>
<field name="code">model._cron_account_report_send(job_count=20)</field>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
</record>
</odoo>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="generic_ec_sales_report" model="account.report">
<field name="name">Generic EC Sales List</field>
<field name="filter_show_draft" eval="True"/>
<field name="filter_period_comparison" eval="False"/>
<field name="filter_date_range" eval="True"/>
<field name="filter_journals" eval="True"/>
<field name="filter_show_draft" eval="False"/>
<field name="filter_unreconciled" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="default_opening_date_filter">previous_month</field>
<field name="load_more_limit" eval="80"/>
<field name="search_bar" eval="True"/>
<field name="custom_handler_model_id" ref="model_account_ec_sales_report_handler"/>
<field name="column_ids">
<record id="account_financial_report_ec_sales_country" model="account.report.column">
<field name="name">Country Code</field>
<field name="expression_label">country_code</field>
<field name="figure_type">string</field>
<field name="sortable" eval="True"/>
</record>
<record id="account_financial_report_ec_sales_vat" model="account.report.column">
<field name="name">VAT Number</field>
<field name="expression_label">vat_number</field>
<field name="figure_type">string</field>
<field name="sortable" eval="True"/>
</record>
<record id="account_financial_report_ec_sales_amount" model="account.report.column">
<field name="name">Amount</field>
<field name="expression_label">balance</field>
<field name="sortable" eval="True"/>
</record>
</field>
</record>
</odoo>

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="trial_balance_report" model="account.report">
<field name="name">Trial Balance</field>
<field name="filter_journals" eval="True"/>
<field name="filter_analytic" eval="True"/>
<field name="filter_growth_comparison" eval="False"/>
<field name="filter_multi_company">selector</field>
<field name="filter_unfold_all" eval="True"/>
<field name="filter_hierarchy">by_default</field>
<field name="filter_hide_0_lines">never</field>
<field name="default_opening_date_filter">this_month</field>
<field name="search_bar" eval="True"/>
<field name="custom_handler_model_id" ref="model_account_trial_balance_report_handler"/>
<field name="column_ids">
<record id="trial_balance_report_debit" model="account.report.column">
<field name="name">Debit</field>
<field name="expression_label">debit</field>
</record>
<record id="trial_balance_report_credit" model="account.report.column">
<field name="name">Credit</field>
<field name="expression_label">credit</field>
</record>
</field>
</record>
</odoo>

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4, ref('account.group_account_user'))]"/>
</record>
<data noupdate="1">
<record id="account_asset_group_demo" model="account.asset.group">
<field name="name">Odoo Office</field>
</record>
<record id="account_asset_model_demo" model="account.asset">
<field name="name">Asset - 5 Years</field>
<field name="prorata_computation_type">none</field>
<field name="original_value">1000</field>
<field name="journal_id" model="account.journal" search="[
('type', '=', 'general'),
('id', '!=', obj().env.user.company_id.currency_exchange_journal_id.id)]"/>
<field name="account_asset_id" model="account.account" search="[
('account_type', '=', 'asset_fixed'),
('company_ids', '=', ref('base.main_company'))]"/>
<field name="account_depreciation_id" model="account.account" search="[
('account_type', '=', 'asset_fixed'),
('company_ids', '=', ref('base.main_company'))]"/>
<field name="account_depreciation_expense_id" model="account.account" search="[
('account_type', '=', 'expense'),
('tag_ids', 'in', [ref('account.account_tag_operating')]),
('company_ids', '=', ref('base.main_company'))]"/>
<field name="state">open</field>
<field name="asset_group_id" ref="at_accounting.account_asset_group_demo"/>
</record>
</data>
</odoo>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="ofx_partner_bank_1" model="res.partner.bank">
<field name="acc_number">BE68539007547034</field>
<field name="partner_id" ref="base.res_partner_2"></field>
<field name="bank_id" ref="base.res_bank_1"/>
</record>
<record id="ofx_partner_bank_2" model="res.partner.bank">
<field name="acc_number">00987654322</field>
<field name="partner_id" ref="base.res_partner_3"></field>
<field name="bank_id" ref="base.res_bank_1"/>
</record>
<record id="qif_partner_bank_1" model="res.partner.bank">
<field name="acc_number">10987654320</field>
<field name="partner_id" ref="base.res_partner_4"></field>
<field name="bank_id" ref="base.res_bank_1"/>
</record>
<record id="qif_partner_bank_2" model="res.partner.bank">
<field name="acc_number">10987654322</field>
<field name="partner_id" ref="base.res_partner_3"></field>
<field name="bank_id" ref="base.res_bank_1"/>
</record>
</data>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +0,0 @@
from . import account_account
from . import account_bank_statement
from . import account_chart_template
from . import account_fiscal_year
from . import account_journal_dashboard
from . import account_move
from . import account_payment
from . import account_reconcile_model
from . import account_reconcile_model_line
from . import account_tax
from . import digest
from . import res_config_settings
from . import res_company
from . import bank_rec_widget
from . import bank_rec_widget_line
from . import ir_ui_menu
from . import res_currency
from . import res_partner
from . import account_report
from . import account_analytic_report
from . import bank_reconciliation_report
from . import account_general_ledger
from . import account_generic_tax_report
from . import account_journal_report
from . import account_cash_flow_report
from . import account_deferred_reports
from . import account_multicurrency_revaluation_report
from . import account_move_line
from . import account_trial_balance_report
from . import account_aged_partner_balance
from . import account_partner_ledger
from . import mail_activity
from . import mail_activity_type
from . import chart_template
from . import ir_actions
from . import account_sales_report
from . import executive_summary_report
from . import budget
from . import balance_sheet
from . import account_fiscal_position
from . import account_asset,account_journal
from . import account_journal_csv

View File

@@ -1,45 +0,0 @@
import ast
from odoo import api, fields, models, _
class AccountAccount(models.Model):
_inherit = "account.account"
def action_open_reconcile(self):
self.ensure_one()
# Open reconciliation view for this account
action_values = self.env['ir.actions.act_window']._for_xml_id('at_accounting.action_move_line_posted_unreconciled')
domain = ast.literal_eval(action_values['domain'])
domain.append(('account_id', '=', self.id))
action_values['domain'] = domain
return action_values
exclude_provision_currency_ids = fields.Many2many('res.currency', relation='account_account_exclude_res_currency_provision', help="Whether or not we have to make provisions for the selected foreign currencies.")
budget_item_ids = fields.One2many(comodel_name='account.report.budget.item', inverse_name='account_id') # To use it in the domain when adding accounts from the report
asset_model_ids = fields.Many2many(
'account.asset',
domain=[('state', '=', 'model')],
help="An asset wil be created for each asset model when this account is used on a vendor bill or a refund",
tracking=True,
)
create_asset = fields.Selection([('no', 'No'), ('draft', 'Create in draft'), ('validate', 'Create and validate')],
required=True, default='no', tracking=True)
# specify if the account can generate asset depending on it's type. It is used in the account form view
can_create_asset = fields.Boolean(compute="_compute_can_create_asset")
form_view_ref = fields.Char(compute='_compute_can_create_asset')
# decimal quantities are not supported, quantities are rounded to the lower int
multiple_assets_per_line = fields.Boolean(string='Multiple Assets per Line', default=False, tracking=True,
help="Multiple asset items will be generated depending on the bill line quantity instead of 1 global asset.")
@api.depends('account_type')
def _compute_can_create_asset(self):
for record in self:
record.can_create_asset = record.account_type in ('asset_fixed', 'asset_non_current')
record.form_view_ref = 'at_accountingview_account_asset_form'
@api.onchange('create_asset')
def _onchange_multiple_assets_per_line(self):
for record in self:
if record.create_asset == 'no':
record.multiple_assets_per_line = False

View File

@@ -1,446 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from odoo import models, fields, _
from odoo.tools import SQL
from odoo.tools.misc import format_date
from dateutil.relativedelta import relativedelta
from itertools import chain
class AgedPartnerBalanceCustomHandler(models.AbstractModel):
_name = 'account.aged.partner.balance.report.handler'
_inherit = 'account.report.custom.handler'
_description = 'Aged Partner Balance Custom Handler'
def _get_custom_display_config(self):
return {
'css_custom_class': 'aged_partner_balance',
'templates': {
'AccountReportLineName': 'at_accounting.AgedPartnerBalanceLineName',
},
'components': {
'AccountReportFilters': 'at_accounting.AgedPartnerBalanceFilters',
},
}
def _custom_options_initializer(self, report, options, previous_options):
super()._custom_options_initializer(report, options, previous_options=previous_options)
hidden_columns = set()
options['multi_currency'] = report.env.user.has_group('base.group_multi_currency')
options['show_currency'] = options['multi_currency'] and (previous_options or {}).get('show_currency', False)
if not options['show_currency']:
hidden_columns.update(['amount_currency', 'currency'])
options['show_account'] = (previous_options or {}).get('show_account', False)
if not options['show_account']:
hidden_columns.add('account_name')
options['columns'] = [
column for column in options['columns']
if column['expression_label'] not in hidden_columns
]
default_order_column = {
'expression_label': 'invoice_date',
'direction': 'ASC',
}
options['order_column'] = previous_options.get('order_column') or default_order_column
options['aging_based_on'] = previous_options.get('aging_based_on') or 'base_on_maturity_date'
options['aging_interval'] = previous_options.get('aging_interval') or 30
# Set aging column names
interval = options['aging_interval']
for column in options['columns']:
if column['expression_label'].startswith('period'):
period_number = int(column['expression_label'].replace('period', '')) - 1
if 0 <= period_number < 4:
column['name'] = f'{interval * period_number + 1}-{interval * (period_number + 1)}'
def _custom_line_postprocessor(self, report, options, lines):
partner_lines_map = {}
# Sort line dicts by partner
for line in lines:
model, model_id = report._get_model_info_from_id(line['id'])
if model == 'res.partner':
partner_lines_map[model_id] = line
if partner_lines_map:
for partner, line_dict in zip(
self.env['res.partner'].browse(partner_lines_map),
partner_lines_map.values()
):
line_dict['trust'] = partner.with_company(partner.company_id or self.env.company).trust
return lines
def _report_custom_engine_aged_receivable(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
return self._aged_partner_report_custom_engine_common(options, 'asset_receivable', current_groupby, next_groupby, offset=offset, limit=limit)
def _report_custom_engine_aged_payable(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
return self._aged_partner_report_custom_engine_common(options, 'liability_payable', current_groupby, next_groupby, offset=offset, limit=limit)
def _aged_partner_report_custom_engine_common(self, options, internal_type, current_groupby, next_groupby, offset=0, limit=None):
report = self.env['account.report'].browse(options['report_id'])
report._check_groupby_fields((next_groupby.split(',') if next_groupby else []) + ([current_groupby] if current_groupby else []))
def minus_days(date_obj, days):
return fields.Date.to_string(date_obj - relativedelta(days=days))
aging_date_field = SQL.identifier('invoice_date') if options['aging_based_on'] == 'base_on_invoice_date' else SQL.identifier('date_maturity')
date_to = fields.Date.from_string(options['date']['date_to'])
interval = options['aging_interval']
periods = [(False, fields.Date.to_string(date_to))]
# Since we added the first period in the list we have to do one less iteration
nb_periods = len([column for column in options['columns'] if column['expression_label'].startswith('period')]) - 1
for i in range(nb_periods):
start_date = minus_days(date_to, (interval * i) + 1)
# The last element of the list will have False for the end date
end_date = minus_days(date_to, interval * (i + 1)) if i < nb_periods - 1 else False
periods.append((start_date, end_date))
def build_result_dict(report, query_res_lines):
rslt = {f'period{i}': 0 for i in range(len(periods))}
for query_res in query_res_lines:
for i in range(len(periods)):
period_key = f'period{i}'
rslt[period_key] += query_res[period_key]
if current_groupby == 'id':
query_res = query_res_lines[0] # We're grouping by id, so there is only 1 element in query_res_lines anyway
currency = self.env['res.currency'].browse(query_res['currency_id'][0]) if len(query_res['currency_id']) == 1 else None
rslt.update({
'invoice_date': query_res['invoice_date'][0] if len(query_res['invoice_date']) == 1 else None,
'due_date': query_res['due_date'][0] if len(query_res['due_date']) == 1 else None,
'amount_currency': query_res['amount_currency'],
'currency_id': query_res['currency_id'][0] if len(query_res['currency_id']) == 1 else None,
'currency': currency.display_name if currency else None,
'account_name': query_res['account_name'][0] if len(query_res['account_name']) == 1 else None,
'total': None,
'has_sublines': query_res['aml_count'] > 0,
# Needed by the custom_unfold_all_batch_data_generator, to speed-up unfold_all
'partner_id': query_res['partner_id'][0] if query_res['partner_id'] else None,
})
else:
rslt.update({
'invoice_date': None,
'due_date': None,
'amount_currency': None,
'currency_id': None,
'currency': None,
'account_name': None,
'total': sum(rslt[f'period{i}'] for i in range(len(periods))),
'has_sublines': False,
})
return rslt
# Build period table
period_table_format = ('(VALUES %s)' % ','.join("(%s, %s, %s)" for period in periods))
params = list(chain.from_iterable(
(period[0] or None, period[1] or None, i)
for i, period in enumerate(periods)
))
period_table = SQL(period_table_format, *params)
# Build query
query = report._get_report_query(options, 'strict_range', domain=[('account_id.account_type', '=', internal_type)])
account_alias = query.left_join(lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id')
account_code = self.env['account.account']._field_to_sql(account_alias, 'code', query)
always_present_groupby = SQL("period_table.period_index")
if current_groupby:
select_from_groupby = SQL("%s AS grouping_key,", SQL.identifier("account_move_line", current_groupby))
groupby_clause = SQL("%s, %s", SQL.identifier("account_move_line", current_groupby), always_present_groupby)
else:
select_from_groupby = SQL()
groupby_clause = always_present_groupby
multiplicator = -1 if internal_type == 'liability_payable' else 1
select_period_query = SQL(',').join(
SQL("""
CASE WHEN period_table.period_index = %(period_index)s
THEN %(multiplicator)s * SUM(%(balance_select)s)
ELSE 0 END AS %(column_name)s
""",
period_index=i,
multiplicator=multiplicator,
column_name=SQL.identifier(f"period{i}"),
balance_select=report._currency_table_apply_rate(SQL(
"account_move_line.balance - COALESCE(part_debit.amount, 0) + COALESCE(part_credit.amount, 0)"
)),
)
for i in range(len(periods))
)
tail_query = report._get_engine_query_tail(offset, limit)
query = SQL(
"""
WITH period_table(date_start, date_stop, period_index) AS (%(period_table)s)
SELECT
%(select_from_groupby)s
%(multiplicator)s * (
SUM(account_move_line.amount_currency)
- COALESCE(SUM(part_debit.debit_amount_currency), 0)
+ COALESCE(SUM(part_credit.credit_amount_currency), 0)
) AS amount_currency,
ARRAY_AGG(DISTINCT account_move_line.partner_id) AS partner_id,
ARRAY_AGG(account_move_line.payment_id) AS payment_id,
ARRAY_AGG(DISTINCT move.invoice_date) AS invoice_date,
ARRAY_AGG(DISTINCT COALESCE(account_move_line.%(aging_date_field)s, account_move_line.date)) AS report_date,
ARRAY_AGG(DISTINCT %(account_code)s) AS account_name,
ARRAY_AGG(DISTINCT COALESCE(account_move_line.%(aging_date_field)s, account_move_line.date)) AS due_date,
ARRAY_AGG(DISTINCT account_move_line.currency_id) AS currency_id,
COUNT(account_move_line.id) AS aml_count,
ARRAY_AGG(%(account_code)s) AS account_code,
%(select_period_query)s
FROM %(table_references)s
JOIN account_journal journal ON journal.id = account_move_line.journal_id
JOIN account_move move ON move.id = account_move_line.move_id
%(currency_table_join)s
LEFT JOIN LATERAL (
SELECT
SUM(part.amount) AS amount,
SUM(part.debit_amount_currency) AS debit_amount_currency,
part.debit_move_id
FROM account_partial_reconcile part
WHERE part.max_date <= %(date_to)s AND part.debit_move_id = account_move_line.id
GROUP BY part.debit_move_id
) part_debit ON TRUE
LEFT JOIN LATERAL (
SELECT
SUM(part.amount) AS amount,
SUM(part.credit_amount_currency) AS credit_amount_currency,
part.credit_move_id
FROM account_partial_reconcile part
WHERE part.max_date <= %(date_to)s AND part.credit_move_id = account_move_line.id
GROUP BY part.credit_move_id
) part_credit ON TRUE
JOIN period_table ON
(
period_table.date_start IS NULL
OR COALESCE(account_move_line.%(aging_date_field)s, account_move_line.date) <= DATE(period_table.date_start)
)
AND
(
period_table.date_stop IS NULL
OR COALESCE(account_move_line.%(aging_date_field)s, account_move_line.date) >= DATE(period_table.date_stop)
)
WHERE %(search_condition)s
GROUP BY %(groupby_clause)s
HAVING
ROUND(SUM(%(having_debit)s), %(currency_precision)s) != 0
OR ROUND(SUM(%(having_credit)s), %(currency_precision)s) != 0
ORDER BY %(groupby_clause)s
%(tail_query)s
""",
account_code=account_code,
period_table=period_table,
select_from_groupby=select_from_groupby,
select_period_query=select_period_query,
multiplicator=multiplicator,
aging_date_field=aging_date_field,
table_references=query.from_clause,
currency_table_join=report._currency_table_aml_join(options),
date_to=date_to,
search_condition=query.where_clause,
groupby_clause=groupby_clause,
having_debit=report._currency_table_apply_rate(SQL("CASE WHEN account_move_line.balance > 0 THEN account_move_line.balance else 0 END - COALESCE(part_debit.amount, 0)")),
having_credit=report._currency_table_apply_rate(SQL("CASE WHEN account_move_line.balance < 0 THEN -account_move_line.balance else 0 END - COALESCE(part_credit.amount, 0)")),
currency_precision=self.env.company.currency_id.decimal_places,
tail_query=tail_query,
)
self._cr.execute(query)
query_res_lines = self._cr.dictfetchall()
if not current_groupby:
return build_result_dict(report, query_res_lines)
else:
rslt = []
all_res_per_grouping_key = {}
for query_res in query_res_lines:
grouping_key = query_res['grouping_key']
all_res_per_grouping_key.setdefault(grouping_key, []).append(query_res)
for grouping_key, query_res_lines in all_res_per_grouping_key.items():
rslt.append((grouping_key, build_result_dict(report, query_res_lines)))
return rslt
def open_journal_items(self, options, params):
params['view_ref'] = 'account.view_move_line_tree_grouped_partner'
options_for_audit = {**options, 'date': {**options['date'], 'date_from': None}}
report = self.env['account.report'].browse(options['report_id'])
action = report.open_journal_items(options=options_for_audit, params=params)
action.get('context', {}).update({'search_default_group_by_account': 0, 'search_default_group_by_partner': 1})
return action
def open_partner_ledger(self, options, params):
report = self.env['account.report'].browse(options['report_id'])
record_model, record_id = report._get_model_info_from_id(params.get('line_id'))
return self.env[record_model].browse(record_id).open_partner_ledger()
def _common_custom_unfold_all_batch_data_generator(self, internal_type, report, options, lines_to_expand_by_function):
rslt = {} # In the form {full_sub_groupby_key: all_column_group_expression_totals for this groupby computation}
report_periods = 6 # The report has 6 periods
for expand_function_name, lines_to_expand in lines_to_expand_by_function.items():
for line_to_expand in lines_to_expand: # In standard, this loop will execute only once
if expand_function_name == '_report_expand_unfoldable_line_with_groupby':
report_line_id = report._get_res_id_from_line_id(line_to_expand['id'], 'account.report.line')
expressions_to_evaluate = report.line_ids.expression_ids.filtered(lambda x: x.report_line_id.id == report_line_id and x.engine == 'custom')
if not expressions_to_evaluate:
continue
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
# Get all aml results by partner
aml_data_by_partner = {}
for aml_id, aml_result in self._aged_partner_report_custom_engine_common(column_group_options, internal_type, 'id', None):
aml_result['aml_id'] = aml_id
aml_data_by_partner.setdefault(aml_result['partner_id'], []).append(aml_result)
# Iterate on results by partner to generate the content of the column group
partner_expression_totals = rslt.setdefault(f"[{report_line_id}]=>partner_id", {})\
.setdefault(column_group_key, {expression: {'value': []} for expression in expressions_to_evaluate})
for partner_id, aml_data_list in aml_data_by_partner.items():
partner_values = self._prepare_partner_values()
for i in range(report_periods):
partner_values[f'period{i}'] = 0
# Build expression totals under the right key
partner_aml_expression_totals = rslt.setdefault(f"[{report_line_id}]partner_id:{partner_id}=>id", {})\
.setdefault(column_group_key, {expression: {'value': []} for expression in expressions_to_evaluate})
for aml_data in aml_data_list:
for i in range(report_periods):
period_value = aml_data[f'period{i}']
partner_values[f'period{i}'] += period_value
partner_values['total'] += period_value
for expression in expressions_to_evaluate:
partner_aml_expression_totals[expression]['value'].append(
(aml_data['aml_id'], aml_data[expression.subformula])
)
for expression in expressions_to_evaluate:
partner_expression_totals[expression]['value'].append(
(partner_id, partner_values[expression.subformula])
)
return rslt
def _prepare_partner_values(self):
return {
'invoice_date': None,
'due_date': None,
'amount_currency': None,
'currency_id': None,
'currency': None,
'account_name': None,
'total': 0,
}
def aged_partner_balance_audit(self, options, params, journal_type):
""" Open a list of invoices/bills and/or deferral entries for the clicked cell
:param dict options: the report's `options`
:param dict params: a dict containing:
`calling_line_dict_id`: line id containing the optional account of the cell
`expression_label`: the expression label of the cell
"""
report = self.env['account.report'].browse(options['report_id'])
action = self.env['ir.actions.actions']._for_xml_id('account.action_amounts_to_settle')
journal_type_to_exclude = {'purchase': 'sale', 'sale': 'purchase'}
if options:
domain = [
('account_id.reconcile', '=', True),
('journal_id.type', '!=', journal_type_to_exclude.get(journal_type)),
*self._build_domain_from_period(options, params['expression_label']),
*report._get_options_domain(options, 'from_beginning'),
*report._get_audit_line_groupby_domain(params['calling_line_dict_id']),
]
action['domain'] = domain
return action
def _build_domain_from_period(self, options, period):
if period != "total" and period[-1].isdigit():
period_number = int(period[-1])
if period_number == 0:
domain = [('date_maturity', '>=', options['date']['date_to'])]
else:
options_date_to = datetime.datetime.strptime(options['date']['date_to'], '%Y-%m-%d')
period_end = options_date_to - datetime.timedelta(30*(period_number-1)+1)
period_start = options_date_to - datetime.timedelta(30*(period_number))
domain = [('date_maturity', '>=', period_start), ('date_maturity', '<=', period_end)]
if period_number == 5:
domain = [('date_maturity', '<=', period_end)]
else:
domain = []
return domain
class AgedPayableCustomHandler(models.AbstractModel):
_name = 'account.aged.payable.report.handler'
_inherit = 'account.aged.partner.balance.report.handler'
_description = 'Aged Payable Custom Handler'
def open_journal_items(self, options, params):
payable_account_type = {'id': 'trade_payable', 'name': _("Payable"), 'selected': True}
if 'account_type' in options:
options['account_type'].append(payable_account_type)
else:
options['account_type'] = [payable_account_type]
return super().open_journal_items(options, params)
def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function):
# We only optimize the unfold all if the groupby value of the report has not been customized. Else, we'll just run the full computation
if self.env.ref('at_accounting.aged_payable_line').groupby.replace(' ', '') == 'partner_id,id':
return self._common_custom_unfold_all_batch_data_generator('liability_payable', report, options, lines_to_expand_by_function)
return {}
def action_audit_cell(self, options, params):
return super().aged_partner_balance_audit(options, params, 'purchase')
class AgedReceivableCustomHandler(models.AbstractModel):
_name = 'account.aged.receivable.report.handler'
_inherit = 'account.aged.partner.balance.report.handler'
_description = 'Aged Receivable Custom Handler'
def open_journal_items(self, options, params):
receivable_account_type = {'id': 'trade_receivable', 'name': _("Receivable"), 'selected': True}
if 'account_type' in options:
options['account_type'].append(receivable_account_type)
else:
options['account_type'] = [receivable_account_type]
return super().open_journal_items(options, params)
def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function):
# We only optimize the unfold all if the groupby value of the report has not been customized. Else, we'll just run the full computation
if self.env.ref('at_accounting.aged_receivable_line').groupby.replace(' ', '') == 'partner_id,id':
return self._common_custom_unfold_all_batch_data_generator('asset_receivable', report, options, lines_to_expand_by_function)
return {}
def action_audit_cell(self, options, params):
return super().aged_partner_balance_audit(options, params, 'sale')

View File

@@ -1,267 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api, osv
from odoo.addons.web.controllers.utils import clean_action
from odoo.tools import SQL, Query
class AccountReport(models.AbstractModel):
_inherit = 'account.report'
filter_analytic_groupby = fields.Boolean(
string="Analytic Group By",
compute=lambda x: x._compute_report_option_filter('filter_analytic_groupby'), readonly=False, store=True, depends=['root_report_id'],
)
def _get_options_initializers_forced_sequence_map(self):
""" Force the sequence for the init_options so columns headers are already generated but not the columns
So, between _init_options_column_headers and _init_options_columns"""
sequence_map = super(AccountReport, self)._get_options_initializers_forced_sequence_map()
sequence_map[self._init_options_analytic_groupby] = 995
return sequence_map
def _init_options_analytic_groupby(self, options, previous_options):
if not self.filter_analytic_groupby:
return
enable_analytic_accounts = self.env.user.has_group('analytic.group_analytic_accounting')
if not enable_analytic_accounts:
return
options['display_analytic_groupby'] = True
options['display_analytic_plan_groupby'] = True
options['include_analytic_without_aml'] = previous_options.get('include_analytic_without_aml', False)
previous_analytic_accounts = previous_options.get('analytic_accounts_groupby', [])
analytic_account_ids = [int(x) for x in previous_analytic_accounts]
selected_analytic_accounts = self.env['account.analytic.account'].with_context(active_test=False).search(
[('id', 'in', analytic_account_ids)])
options['analytic_accounts_groupby'] = selected_analytic_accounts.ids
options['selected_analytic_account_groupby_names'] = selected_analytic_accounts.mapped('name')
previous_analytic_plans = previous_options.get('analytic_plans_groupby', [])
analytic_plan_ids = [int(x) for x in previous_analytic_plans]
selected_analytic_plans = self.env['account.analytic.plan'].search([('id', 'in', analytic_plan_ids)])
options['analytic_plans_groupby'] = selected_analytic_plans.ids
options['selected_analytic_plan_groupby_names'] = selected_analytic_plans.mapped('name')
self._create_column_analytic(options)
def _init_options_readonly_query(self, options, previous_options):
super()._init_options_readonly_query(options, previous_options)
options['readonly_query'] = options['readonly_query'] and not options.get('analytic_groupby_option')
def _create_column_analytic(self, options):
""" Creates the analytic columns for each plan or account in the filters.
This will duplicate all previous columns and adding the analytic accounts in the domain of the added columns.
The analytic_groupby_option is used so the table used is the shadowed table.
The domain on analytic_distribution can just use simple comparison as the column of the shadowed
table will simply be filled with analytic_account_ids.
"""
analytic_headers = []
plans = self.env['account.analytic.plan'].browse(options.get('analytic_plans_groupby'))
for plan in plans:
account_list = []
accounts = self.env['account.analytic.account'].search([('plan_id', 'child_of', plan.id)])
for account in accounts:
account_list.append(account.id)
analytic_headers.append({
'name': plan.name,
'forced_options': {
'analytic_groupby_option': True,
'analytic_accounts_list': tuple(account_list), # Analytic accounts used in the domain to filter the lines.
}
})
accounts = self.env['account.analytic.account'].browse(options.get('analytic_accounts_groupby'))
for account in accounts:
analytic_headers.append({
'name': account.name,
'forced_options': {
'analytic_groupby_option': True,
'analytic_accounts_list': (account.id,),
}
})
if analytic_headers:
has_selected_budgets = any([budget for budget in options.get('budgets', []) if budget['selected']])
if has_selected_budgets:
# if budget is selected, then analytic headers are placed on the same header level
options['column_headers'][-1] = analytic_headers + options['column_headers'][-1]
else:
# We add the analytic layer to the column_headers before creating the columns
analytic_headers.append({'name': ''})
options['column_headers'] = [
*options['column_headers'],
analytic_headers,
]
@api.model
def _prepare_lines_for_analytic_groupby(self):
"""Prepare the analytic_temp_account_move_line
This method should be used once before all the SQL queries using the
table account_move_line for the analytic columns for the financial reports.
It will create a new table with the schema of account_move_line table, but with
the data from account_analytic_line.
We inherit the schema of account_move_line, make the correspondence between
account_move_line fields and account_analytic_line fields and put NULL for those
who don't exist in account_analytic_line.
We also drop the NOT NULL constraints for fields who are not required in account_analytic_line.
"""
self.env.cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name='analytic_temp_account_move_line'")
if self.env.cr.fetchone():
return
project_plan, other_plans = self.env['account.analytic.plan']._get_all_plans()
analytic_cols = SQL(", ").join(SQL('"account_analytic_line".%s', SQL.identifier(n._column_name())) for n in (project_plan + other_plans))
analytic_distribution_equivalent = SQL('to_jsonb(UNNEST(ARRAY[%s]))', analytic_cols)
change_equivalence_dict = {
'id': SQL("account_analytic_line.id"),
'balance': SQL("-amount"),
'display_type': 'product',
'parent_state': 'posted',
'account_id': SQL.identifier("general_account_id"),
'debit': SQL("CASE WHEN (amount < 0) THEN -amount else 0 END"),
'credit': SQL("CASE WHEN (amount > 0) THEN amount else 0 END"),
'analytic_distribution': analytic_distribution_equivalent,
}
all_stored_aml_fields = {
field
for field, attrs in self.env['account.move.line'].fields_get().items()
if attrs['type'] not in ['many2many', 'one2many'] and attrs.get('store')
}
for aml_field in all_stored_aml_fields:
if aml_field not in change_equivalence_dict:
change_equivalence_dict[aml_field] = SQL('"account_move_line".%s', SQL.identifier(aml_field))
stored_aml_fields, fields_to_insert = self.env['account.move.line']._prepare_aml_shadowing_for_report(change_equivalence_dict)
query = SQL("""
-- Create a temporary table, dropping not null constraints because we're not filling those columns
CREATE TEMPORARY TABLE IF NOT EXISTS analytic_temp_account_move_line () inherits (account_move_line) ON COMMIT DROP;
ALTER TABLE analytic_temp_account_move_line NO INHERIT account_move_line;
ALTER TABLE analytic_temp_account_move_line DROP CONSTRAINT IF EXISTS account_move_line_check_amount_currency_balance_sign;
ALTER TABLE analytic_temp_account_move_line ALTER COLUMN move_id DROP NOT NULL;
ALTER TABLE analytic_temp_account_move_line ALTER COLUMN currency_id DROP NOT NULL;
INSERT INTO analytic_temp_account_move_line (%(stored_aml_fields)s)
SELECT %(fields_to_insert)s
FROM account_analytic_line
LEFT JOIN account_move_line
ON account_analytic_line.move_line_id = account_move_line.id
WHERE
account_analytic_line.general_account_id IS NOT NULL;
-- Create a supporting index to avoid seq.scans
CREATE INDEX IF NOT EXISTS analytic_temp_account_move_line__composite_idx ON analytic_temp_account_move_line (analytic_distribution, journal_id, date, company_id);
-- Update statistics for correct planning
ANALYZE analytic_temp_account_move_line
""", stored_aml_fields=stored_aml_fields, fields_to_insert=fields_to_insert)
self.env.cr.execute(query)
def _get_report_query(self, options, date_scope, domain=None) -> Query:
# Override to add the context key which will eventually trigger the shadowing of the table
context_self = self.with_context(account_report_analytic_groupby=options.get('analytic_groupby_option'))
# We add the domain filter for analytic_distribution here, as the search is not available
query = super(AccountReport, context_self)._get_report_query(options, date_scope, domain)
if options.get('analytic_accounts'):
if 'analytic_accounts_list' in options:
# the table will be `analytic_temp_account_move_line` and thus analytic_distribution will be a single ID
analytic_account_ids = tuple(str(account_id) for account_id in options['analytic_accounts'])
query.add_where(SQL("""account_move_line.analytic_distribution IN %s""", analytic_account_ids))
else:
# Real `account_move_line` table so real JSON with percentage
analytic_account_ids = [[str(account_id) for account_id in options['analytic_accounts']]]
query.add_where(SQL('%s && %s', analytic_account_ids, self.env['account.move.line']._query_analytic_accounts()))
return query
def action_audit_cell(self, options, params):
column_group_options = self._get_column_group_options(options, params['column_group_key'])
if not column_group_options.get('analytic_groupby_option'):
return super(AccountReport, self).action_audit_cell(options, params)
else:
# Start by getting the domain from the options. Note that this domain is targeting account.move.line
report_line = self.env['account.report.line'].browse(params['report_line_id'])
expression = report_line.expression_ids.filtered(lambda x: x.label == params['expression_label'])
line_domain = self._get_audit_line_domain(column_group_options, expression, params)
# The line domain is made for move lines, so we need some postprocessing to have it work with analytic lines.
domain = []
AccountAnalyticLine = self.env['account.analytic.line']
for expression in line_domain:
if len(expression) == 1: # For operators such as '&' or '|' we can juste add them again.
domain.append(expression)
continue
field, operator, right_term = expression
# On analytic lines, the account.account field is named general_account_id and not account_id.
if field.split('.')[0] == 'account_id':
field = field.replace('account_id', 'general_account_id')
expression = [(field, operator, right_term)]
# Replace the 'analytic_distribution' by the account_id domain as we expect for analytic lines.
elif field == 'analytic_distribution':
expression = [('auto_account_id', 'in', right_term)]
# For other fields not present in on the analytic line model, map them to get the info from the move_line.
# Or ignore these conditions if there is no move lines.
elif field.split('.')[0] not in AccountAnalyticLine._fields:
expression = [(f'move_line_id.{field}', operator, right_term)]
if options.get('include_analytic_without_aml'):
expression = osv.expression.OR([
[('move_line_id', '=', False)],
expression,
])
else:
expression = [expression] # just for the extend
domain.extend(expression)
action = clean_action(self.env.ref('analytic.account_analytic_line_action_entries')._get_action_dict(), env=self.env)
action['domain'] = domain
return action
@api.model
def _get_options_journals_domain(self, options):
domain = super(AccountReport, self)._get_options_journals_domain(options)
# Add False to the domain in order to select lines without journals for analytics columns.
if options.get('include_analytic_without_aml'):
domain = osv.expression.OR([
domain,
[('journal_id', '=', False)],
])
return domain
def _get_options_domain(self, options, date_scope):
self.ensure_one()
domain = super()._get_options_domain(options, date_scope)
# Get the analytic accounts that we need to filter on from the options and add a domain for them.
if 'analytic_accounts_list' in options:
domain = osv.expression.AND([
domain,
[('analytic_distribution', 'in', options.get('analytic_accounts_list', []))],
])
return domain
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
def _where_calc(self, domain, active_test=True):
""" In case we need an analytic column in an account_report, we shadow the account_move_line table
with a temp table filled with analytic data, that will be used for the analytic columns.
We do it in this function to only create and fill it once for all computations of a report.
The following analytic columns and computations will just query the shadowed table instead of the real one.
"""
query = super()._where_calc(domain, active_test)
if self.env.context.get('account_report_analytic_groupby') and not self.env.context.get('account_report_cash_basis'):
self.env['account.report']._prepare_lines_for_analytic_groupby()
query._tables['account_move_line'] = SQL.identifier('analytic_temp_account_move_line')
return query

File diff suppressed because it is too large Load Diff

View File

@@ -1,248 +0,0 @@
import logging
from odoo import _, api, fields, models
from odoo.addons.base.models.res_bank import sanitize_account_number
from odoo.exceptions import UserError
from odoo.tools import html2plaintext
from odoo.osv import expression
from dateutil.relativedelta import relativedelta
from itertools import product
from lxml import etree
from markupsafe import Markup
_logger = logging.getLogger(__name__)
class AccountBankStatement(models.Model):
_name = "account.bank.statement"
_inherit = ['mail.thread.main.attachment', 'account.bank.statement']
def action_open_bank_reconcile_widget(self):
self.ensure_one()
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
name=self.name,
default_context={
'search_default_statement_id': self.id,
'search_default_journal_id': self.journal_id.id,
},
extra_domain=[('statement_id', '=', self.id)]
)
def action_generate_attachment(self):
ir_actions_report_sudo = self.env['ir.actions.report'].sudo()
statement_report_action = self.env.ref('account.action_report_account_statement')
for statement in self:
statement_report = statement_report_action.sudo()
content, _content_type = ir_actions_report_sudo._render_qweb_pdf(statement_report, res_ids=statement.ids)
statement.attachment_ids |= self.env['ir.attachment'].create({
'name': _("Bank Statement %s.pdf", statement.name) if statement.name else _("Bank Statement.pdf"),
'type': 'binary',
'mimetype': 'application/pdf',
'raw': content,
'res_model': statement._name,
'res_id': statement.id,
})
return statement_report_action.report_action(docids=self)
class AccountBankStatementLine(models.Model):
_inherit = 'account.bank.statement.line'
cron_last_check = fields.Datetime()
def action_save_close(self):
return {'type': 'ir.actions.act_window_close'}
def action_save_new(self):
action = self.env['ir.actions.act_window']._for_xml_id('at_accounting.action_bank_statement_line_form_bank_rec_widget')
action['context'] = {'default_journal_id': self._context['default_journal_id']}
return action
####################################################
# RECONCILIATION PROCESS
####################################################
@api.model
def _action_open_bank_reconciliation_widget(self, extra_domain=None, default_context=None, name=None, kanban_first=True):
action_reference = 'at_accounting.action_bank_statement_line_transactions' + ('_kanban' if kanban_first else '')
action = self.env['ir.actions.act_window']._for_xml_id(action_reference)
action.update({
'name': name or _("Bank Reconciliation"),
'context': default_context or {},
'domain': [('state', '!=', 'cancel')] + (extra_domain or []),
})
return action
def action_open_recon_st_line(self):
self.ensure_one()
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
name=self.name,
default_context={
'default_statement_id': self.statement_id.id,
'default_journal_id': self.journal_id.id,
'default_st_line_id': self.id,
'search_default_id': self.id,
},
)
def _cron_try_auto_reconcile_statement_lines(self, batch_size=None, limit_time=0):
def _compute_st_lines_to_reconcile(configured_company):
# Find the bank statement lines that are not reconciled and try to reconcile them automatically.
# The ones that are never be processed by the CRON before are processed first.
remaining_line_id = None
limit = batch_size + 1 if batch_size else None
domain = [
('is_reconciled', '=', False),
('create_date', '>', start_time.date() - relativedelta(months=3)),
('company_id', 'in', configured_company.ids),
]
st_lines = self.search(domain, limit=limit, order="cron_last_check ASC NULLS FIRST, id")
if batch_size and len(st_lines) > batch_size:
remaining_line_id = st_lines[batch_size].id
st_lines = st_lines[:batch_size]
return st_lines, remaining_line_id
start_time = fields.Datetime.now()
configured_company = children_company = self.env['account.reconcile.model'].search_fetch([
('auto_reconcile', '=', True),
('rule_type', 'in', ('writeoff_suggestion', 'invoice_matching')),
], ['company_id']).company_id
if not configured_company:
return
while children_company := children_company.child_ids:
configured_company += children_company
st_lines, remaining_line_id = (self, None) if self else _compute_st_lines_to_reconcile(configured_company)
nb_auto_reconciled_lines = 0
for index, st_line in enumerate(st_lines):
if limit_time and fields.Datetime.now().timestamp() - start_time.timestamp() > limit_time:
remaining_line_id = st_line.id
st_lines = st_lines[:index]
break
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
wizard._action_trigger_matching_rules()
if wizard.state == 'valid' and wizard.matching_rules_allow_auto_reconcile:
try:
wizard._action_validate()
if st_line.is_reconciled:
st_line.move_id.message_post(body=_(
"This bank transaction has been automatically validated using the reconciliation model '%s'.",
', '.join(st_line.move_id.line_ids.reconcile_model_id.mapped('name')),
))
nb_auto_reconciled_lines += 1
except UserError as e:
_logger.info("Failed to auto reconcile statement line %s due to user error: %s",
st_line.id,
str(e)
)
continue
st_lines.write({'cron_last_check': start_time})
if remaining_line_id:
remaining_st_line = self.env['account.bank.statement.line'].browse(remaining_line_id)
if nb_auto_reconciled_lines or not remaining_st_line.cron_last_check:
self.env.ref('at_accounting.auto_reconcile_bank_statement_line')._trigger()
def _retrieve_partner(self):
self.ensure_one()
if self.partner_id:
return self.partner_id
if self.account_number:
account_number_nums = sanitize_account_number(self.account_number)
if account_number_nums:
domain = [('sanitized_acc_number', 'ilike', account_number_nums)]
for extra_domain in ([('company_id', 'parent_of', self.company_id.id)], [('company_id', '=', False)]):
bank_accounts = self.env['res.partner.bank'].search(extra_domain + domain)
if len(bank_accounts.partner_id) == 1:
return bank_accounts.partner_id
else:
# We have several partner with same account, possibly some archived partner
# so try to filter out inactive partner and if one remains, select this one
bank_accounts = bank_accounts.filtered(lambda bacc: bacc.partner_id.active)
if len(bank_accounts) == 1:
return bank_accounts.partner_id
if self.partner_name:
domains = product(
[
('complete_name', '=ilike', self.partner_name),
('complete_name', 'ilike', self.partner_name),
],
[
('company_id', 'parent_of', self.company_id.id),
('company_id', '=', False),
],
)
for domain in domains:
partner = self.env['res.partner'].search(list(domain) + [('parent_id', '=', False)], limit=2)
if len(partner) == 1:
return partner
# Retrieve the partner from the 'reconcile models'.
rec_models = self.env['account.reconcile.model'].search([
*self.env['account.reconcile.model']._check_company_domain(self.company_id),
('rule_type', '!=', 'writeoff_button'),
])
for rec_model in rec_models:
partner = rec_model._get_partner_from_mapping(self)
if partner and rec_model._is_applicable_for(self, partner):
return partner
return self.env['res.partner']
def _get_st_line_strings_for_matching(self, allowed_fields=None):
self.ensure_one()
st_line_text_values = []
if not allowed_fields or 'payment_ref' in allowed_fields:
if self.payment_ref:
st_line_text_values.append(self.payment_ref)
if not allowed_fields or 'narration' in allowed_fields:
value = html2plaintext(self.narration or "")
if value:
st_line_text_values.append(value)
if not allowed_fields or 'ref' in allowed_fields:
if self.ref:
st_line_text_values.append(self.ref)
return st_line_text_values
def _get_default_amls_matching_domain(self):
# EXTENDS account
domain = super()._get_default_amls_matching_domain()
categories = self.env['product.category'].search([
'|',
('property_stock_account_input_categ_id', '!=', False),
('property_stock_account_output_categ_id', '!=', False)
])
accounts = (categories.mapped('property_stock_account_input_categ_id') +
categories.mapped('property_stock_account_output_categ_id'))
if accounts:
return expression.AND([domain, [('account_id', 'not in', tuple(set(accounts.ids)))]])
return domain
# Ensure transactions can be imported only once (if the import format provides unique transaction ids)
unique_import_id = fields.Char(string='Import ID', readonly=True, copy=False)
_sql_constraints = [
('unique_import_id', 'unique (unique_import_id)', 'A bank account transactions can be imported only once!')
]
def _action_open_bank_reconciliation_widget(self, extra_domain=None, default_context=None, name=None,
kanban_first=True):
res = super()._action_open_bank_reconciliation_widget(extra_domain, default_context, name, kanban_first)
res['help'] = Markup("<p class='o_view_nocontent_smiling_face'>{}</p><p>{}<br/>{}</p>").format(
_('Nothing to do here!'),
_('No transactions matching your filters were found.'),
_('Click "New" or upload a %s.',
", ".join(self.env['account.journal']._get_bank_statements_available_import_formats())),
)
return res

View File

@@ -1,712 +0,0 @@
from odoo import models, _
from odoo.tools import SQL, Query
class CashFlowReportCustomHandler(models.AbstractModel):
_name = 'account.cash.flow.report.handler'
_inherit = 'account.report.custom.handler'
_description = 'Cash Flow Report Custom Handler'
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
# Compute the cash flow report using the direct method: https://www.investopedia.com/terms/d/direct_method.asp
lines = []
layout_data = self._get_layout_data()
report_data = self._get_report_data(report, options, layout_data)
for layout_line_id, layout_line_data in layout_data.items():
lines.append((0, self._get_layout_line(report, options, layout_line_id, layout_line_data, report_data)))
if layout_line_id in report_data and 'aml_groupby_account' in report_data[layout_line_id]:
aml_data_values = report_data[layout_line_id]['aml_groupby_account'].values()
aml_data_values_with_account_code = []
aml_data_values_without_account_code = []
for aml_data in aml_data_values:
if aml_data['account_code'] is not None:
aml_data_values_with_account_code.append(aml_data)
else:
aml_data_values_without_account_code.append(aml_data)
for aml_data in (sorted(aml_data_values_with_account_code, key=lambda x: x['account_code'])
+ aml_data_values_without_account_code):
lines.append((0, self._get_aml_line(report, options, aml_data)))
unexplained_difference_line = self._get_unexplained_difference_line(report, options, report_data)
if unexplained_difference_line:
lines.append((0, unexplained_difference_line))
return lines
def _custom_options_initializer(self, report, options, previous_options):
super()._custom_options_initializer(report, options, previous_options=previous_options)
report._init_options_journals(options, previous_options=previous_options, additional_journals_domain=[('type', 'in', ('bank', 'cash', 'general'))])
def _get_report_data(self, report, options, layout_data):
report_data = {}
payment_account_ids = self._get_account_ids(report, options)
if not payment_account_ids:
return report_data
# Compute 'Cash and cash equivalents, beginning of period'
for aml_data in self._compute_liquidity_balance(report, options, payment_account_ids, 'to_beginning_of_period'):
self._add_report_data('opening_balance', aml_data, layout_data, report_data)
self._add_report_data('closing_balance', aml_data, layout_data, report_data)
# Compute 'Cash and cash equivalents, closing balance'
for aml_data in self._compute_liquidity_balance(report, options, payment_account_ids, 'strict_range'):
self._add_report_data('closing_balance', aml_data, layout_data, report_data)
tags_ids = self._get_tags_ids()
cashflow_tag_ids = self._get_cashflow_tag_ids()
# Process liquidity moves
for aml_groupby_account in self._get_liquidity_moves(report, options, payment_account_ids, cashflow_tag_ids):
for aml_data in aml_groupby_account.values():
self._dispatch_aml_data(tags_ids, aml_data, layout_data, report_data)
# Process reconciled moves
for aml_groupby_account in self._get_reconciled_moves(report, options, payment_account_ids, cashflow_tag_ids):
for aml_data in aml_groupby_account.values():
self._dispatch_aml_data(tags_ids, aml_data, layout_data, report_data)
return report_data
def _add_report_data(self, layout_line_id, aml_data, layout_data, report_data):
"""
Add or update the report_data dictionnary with aml_data.
report_data is a dictionnary where the keys are keys from _cash_flow_report_get_layout_data() (used for mapping)
and the values can contain 2 dictionnaries:
* (required) 'balance' where the key is the column_group_key and the value is the balance of the line
* (optional) 'aml_groupby_account' where the key is an account_id and the values are the aml data
"""
def _report_update_parent(layout_line_id, aml_column_group_key, aml_balance, layout_data, report_data):
# Update the balance in report_data of the parent of the layout_line_id recursively (Stops when the line has no parent)
if 'parent_line_id' in layout_data[layout_line_id]:
parent_line_id = layout_data[layout_line_id]['parent_line_id']
report_data.setdefault(parent_line_id, {'balance': {}})
report_data[parent_line_id]['balance'].setdefault(aml_column_group_key, 0.0)
report_data[parent_line_id]['balance'][aml_column_group_key] += aml_balance
_report_update_parent(parent_line_id, aml_column_group_key, aml_balance, layout_data, report_data)
aml_column_group_key = aml_data['column_group_key']
aml_account_id = aml_data['account_id']
aml_account_code = aml_data['account_code']
aml_account_name = aml_data['account_name']
aml_balance = aml_data['balance']
aml_account_tag = aml_data.get('account_tag_id', None)
if self.env.company.currency_id.is_zero(aml_balance):
return
report_data.setdefault(layout_line_id, {
'balance': {},
'aml_groupby_account': {},
})
report_data[layout_line_id]['aml_groupby_account'].setdefault(aml_account_id, {
'parent_line_id': layout_line_id,
'account_id': aml_account_id,
'account_code': aml_account_code,
'account_name': aml_account_name,
'account_tag_id': aml_account_tag,
'level': layout_data[layout_line_id]['level'] + 1,
'balance': {},
})
report_data[layout_line_id]['balance'].setdefault(aml_column_group_key, 0.0)
report_data[layout_line_id]['balance'][aml_column_group_key] += aml_balance
report_data[layout_line_id]['aml_groupby_account'][aml_account_id]['balance'].setdefault(aml_column_group_key, 0.0)
report_data[layout_line_id]['aml_groupby_account'][aml_account_id]['balance'][aml_column_group_key] += aml_balance
_report_update_parent(layout_line_id, aml_column_group_key, aml_balance, layout_data, report_data)
def _get_tags_ids(self):
''' Get a dict to pass on to _dispatch_aml_data containing information mapping account tags to report lines. '''
return {
'operating': self.env.ref('account.account_tag_operating').id,
'investing': self.env.ref('account.account_tag_investing').id,
'financing': self.env.ref('account.account_tag_financing').id,
}
def _get_cashflow_tag_ids(self):
''' Get the list of account tags that are relevant for the cash flow report. '''
return self._get_tags_ids().values()
def _dispatch_aml_data(self, tags_ids, aml_data, layout_data, report_data):
# Dispatch the aml_data in the correct layout_line
if aml_data['account_account_type'] == 'asset_receivable':
self._add_report_data('advance_payments_customer', aml_data, layout_data, report_data)
elif aml_data['account_account_type'] == 'liability_payable':
self._add_report_data('advance_payments_suppliers', aml_data, layout_data, report_data)
elif aml_data['balance'] < 0:
if aml_data['account_tag_id'] == tags_ids['operating']:
self._add_report_data('paid_operating_activities', aml_data, layout_data, report_data)
elif aml_data['account_tag_id'] == tags_ids['investing']:
self._add_report_data('investing_activities_cash_out', aml_data, layout_data, report_data)
elif aml_data['account_tag_id'] == tags_ids['financing']:
self._add_report_data('financing_activities_cash_out', aml_data, layout_data, report_data)
else:
self._add_report_data('unclassified_activities_cash_out', aml_data, layout_data, report_data)
elif aml_data['balance'] > 0:
if aml_data['account_tag_id'] == tags_ids['operating']:
self._add_report_data('received_operating_activities', aml_data, layout_data, report_data)
elif aml_data['account_tag_id'] == tags_ids['investing']:
self._add_report_data('investing_activities_cash_in', aml_data, layout_data, report_data)
elif aml_data['account_tag_id'] == tags_ids['financing']:
self._add_report_data('financing_activities_cash_in', aml_data, layout_data, report_data)
else:
self._add_report_data('unclassified_activities_cash_in', aml_data, layout_data, report_data)
# -------------------------------------------------------------------------
# QUERIES
# -------------------------------------------------------------------------
def _get_account_ids(self, report, options):
''' Retrieve all accounts to be part of the cash flow statement and also the accounts making them.
:param options: The report options.
:return: payment_account_ids: A tuple containing all account.account's ids being used in a liquidity journal.
'''
# Fetch liquidity accounts:
# Accounts being used by at least one bank/cash journal.
selected_journal_ids = [j['id'] for j in report._get_options_journals(options)]
where_clause = "account_journal.id IN %s" if selected_journal_ids else "account_journal.type IN ('bank', 'cash', 'general')"
where_params = [tuple(selected_journal_ids)] if selected_journal_ids else []
self._cr.execute(f'''
SELECT
array_remove(ARRAY_AGG(DISTINCT account_account.id), NULL),
array_remove(ARRAY_AGG(DISTINCT account_payment_method_line.payment_account_id), NULL)
FROM account_journal
JOIN res_company
ON account_journal.company_id = res_company.id
LEFT JOIN account_payment_method_line
ON account_journal.id = account_payment_method_line.journal_id
LEFT JOIN account_account
ON account_journal.default_account_id = account_account.id
AND account_account.account_type IN ('asset_cash', 'liability_credit_card')
WHERE {where_clause}
''', where_params)
res = self._cr.fetchall()[0]
payment_account_ids = set((res[0] or []) + (res[1] or []))
if not payment_account_ids:
return ()
return tuple(payment_account_ids)
def _get_move_ids_query(self, report, payment_account_ids, column_group_options) -> SQL:
''' Get all liquidity moves to be part of the cash flow statement.
:param payment_account_ids: A tuple containing all account.account's ids being used in a liquidity journal.
:return: query: The SQL query to retrieve the move IDs.
'''
query = report._get_report_query(column_group_options, 'strict_range', [('account_id', 'in', list(payment_account_ids))])
return SQL(
'''
SELECT
array_agg(DISTINCT account_move_line.move_id) AS move_id
FROM %(table_references)s
WHERE %(search_condition)s
''',
table_references=query.from_clause,
search_condition=query.where_clause,
)
def _compute_liquidity_balance(self, report, options, payment_account_ids, date_scope):
''' Compute the balance of all liquidity accounts to populate the following sections:
'Cash and cash equivalents, beginning of period' and 'Cash and cash equivalents, closing balance'.
:param options: The report options.
:param payment_account_ids: A tuple containing all account.account's ids being used in a liquidity journal.
:return: A list of tuple (account_id, account_code, account_name, balance).
'''
queries = []
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
query = report._get_report_query(column_group_options, date_scope, domain=[('account_id', 'in', payment_account_ids)])
account_alias = query.join(lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id')
account_name = self.env['account.account']._field_to_sql(account_alias, 'name')
account_code = self.env['account.account']._field_to_sql(account_alias, 'code', query)
queries.append(SQL(
'''
SELECT
%(column_group_key)s AS column_group_key,
account_move_line.account_id,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
SUM(%(balance_select)s) AS balance
FROM %(table_references)s
%(currency_table_join)s
WHERE %(search_condition)s
GROUP BY account_move_line.account_id, account_code, account_name
''',
column_group_key=column_group_key,
account_code=account_code,
account_name=account_name,
table_references=query.from_clause,
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
currency_table_join=report._currency_table_aml_join(column_group_options),
search_condition=query.where_clause,
))
self._cr.execute(SQL(' UNION ALL ').join(queries))
return self._cr.dictfetchall()
def _get_liquidity_moves(self, report, options, payment_account_ids, cash_flow_tag_ids):
''' Fetch all information needed to compute lines from liquidity moves.
The difficulty is to represent only the not-reconciled part of balance.
:param options: The report options.
:param payment_account_ids: A tuple containing all account.account's ids being used in a liquidity journal.
:return: A list of tuple (account_id, account_code, account_name, account_type, amount).
'''
reconciled_aml_groupby_account = {}
queries = []
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
move_ids_query = self._get_move_ids_query(report, payment_account_ids, column_group_options)
query = Query(self.env, 'account_move_line')
account_alias = query.join(lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id')
account_code = self.env['account.account']._field_to_sql(account_alias, 'code', query)
account_name = self.env['account.account']._field_to_sql(account_alias, 'name')
account_type = SQL.identifier(account_alias, 'account_type')
queries.append(SQL(
'''
(WITH payment_move_ids AS (%(move_ids_query)s)
-- Credit amount of each account
SELECT
%(column_group_key)s AS column_group_key,
account_move_line.account_id,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
%(account_type)s AS account_account_type,
account_account_account_tag.account_account_tag_id AS account_tag_id,
SUM(%(partial_amount_select)s) AS balance
FROM %(from_clause)s
%(currency_table_join)s
LEFT JOIN account_partial_reconcile
ON account_partial_reconcile.credit_move_id = account_move_line.id
LEFT JOIN account_account_account_tag
ON account_account_account_tag.account_account_id = account_move_line.account_id
AND account_account_account_tag.account_account_tag_id IN %(cash_flow_tag_ids)s
WHERE account_move_line.move_id IN (SELECT unnest(payment_move_ids.move_id) FROM payment_move_ids)
AND account_move_line.account_id NOT IN %(payment_account_ids)s
AND account_partial_reconcile.max_date BETWEEN %(date_from)s AND %(date_to)s
GROUP BY account_move_line.company_id, account_move_line.account_id, account_code, account_name, account_account_type, account_account_account_tag.account_account_tag_id
UNION ALL
-- Debit amount of each account
SELECT
%(column_group_key)s AS column_group_key,
account_move_line.account_id,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
%(account_type)s AS account_account_type,
account_account_account_tag.account_account_tag_id AS account_tag_id,
-SUM(%(partial_amount_select)s) AS balance
FROM %(from_clause)s
%(currency_table_join)s
LEFT JOIN account_partial_reconcile
ON account_partial_reconcile.debit_move_id = account_move_line.id
LEFT JOIN account_account_account_tag
ON account_account_account_tag.account_account_id = account_move_line.account_id
AND account_account_account_tag.account_account_tag_id IN %(cash_flow_tag_ids)s
WHERE account_move_line.move_id IN (SELECT unnest(payment_move_ids.move_id) FROM payment_move_ids)
AND account_move_line.account_id NOT IN %(payment_account_ids)s
AND account_partial_reconcile.max_date BETWEEN %(date_from)s AND %(date_to)s
GROUP BY account_move_line.company_id, account_move_line.account_id, account_code, account_name, account_account_type, account_account_account_tag.account_account_tag_id
UNION ALL
-- Total amount of each account
SELECT
%(column_group_key)s AS column_group_key,
account_move_line.account_id AS account_id,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
%(account_type)s AS account_account_type,
account_account_account_tag.account_account_tag_id AS account_tag_id,
SUM(%(aml_balance_select)s) AS balance
FROM %(from_clause)s
%(currency_table_join)s
LEFT JOIN account_account_account_tag
ON account_account_account_tag.account_account_id = account_move_line.account_id
AND account_account_account_tag.account_account_tag_id IN %(cash_flow_tag_ids)s
WHERE account_move_line.move_id IN (SELECT unnest(payment_move_ids.move_id) FROM payment_move_ids)
AND account_move_line.account_id NOT IN %(payment_account_ids)s
GROUP BY account_move_line.account_id, account_code, account_name, account_account_type, account_account_account_tag.account_account_tag_id)
''',
column_group_key=column_group_key,
move_ids_query=move_ids_query,
account_code=account_code,
account_name=account_name,
account_type=account_type,
from_clause=query.from_clause,
currency_table_join=report._currency_table_aml_join(column_group_options),
partial_amount_select=report._currency_table_apply_rate(SQL("account_partial_reconcile.amount")),
aml_balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
cash_flow_tag_ids=tuple(cash_flow_tag_ids),
payment_account_ids=payment_account_ids,
date_from=column_group_options['date']['date_from'],
date_to=column_group_options['date']['date_to'],
))
self._cr.execute(SQL(' UNION ALL ').join(queries))
for aml_data in self._cr.dictfetchall():
reconciled_aml_groupby_account.setdefault(aml_data['account_id'], {})
reconciled_aml_groupby_account[aml_data['account_id']].setdefault(aml_data['column_group_key'], {
'column_group_key': aml_data['column_group_key'],
'account_id': aml_data['account_id'],
'account_code': aml_data['account_code'],
'account_name': aml_data['account_name'],
'account_account_type': aml_data['account_account_type'],
'account_tag_id': aml_data['account_tag_id'],
'balance': 0.0,
})
reconciled_aml_groupby_account[aml_data['account_id']][aml_data['column_group_key']]['balance'] -= aml_data['balance']
return list(reconciled_aml_groupby_account.values())
def _get_reconciled_moves(self, report, options, payment_account_ids, cash_flow_tag_ids):
''' Retrieve all moves being not a liquidity move to be shown in the cash flow statement.
Each amount must be valued at the percentage of what is actually paid.
E.g. An invoice of 1000 being paid at 50% must be valued at 500.
:param options: The report options.
:param payment_account_ids: A tuple containing all account.account's ids being used in a liquidity journal.
:return: A list of tuple (account_id, account_code, account_name, account_type, amount).
'''
reconciled_account_ids = {column_group_key: set() for column_group_key in options['column_groups']}
reconciled_percentage_per_move = {column_group_key: {} for column_group_key in options['column_groups']}
currency_table = report._get_currency_table(options)
queries = []
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
move_ids_query = self._get_move_ids_query(report, payment_account_ids, column_group_options)
queries.append(SQL(
'''
(WITH payment_move_ids AS (%(move_ids_query)s)
SELECT
%(column_group_key)s AS column_group_key,
debit_line.move_id,
debit_line.account_id,
SUM(%(partial_amount)s) AS balance
FROM account_move_line AS credit_line
LEFT JOIN account_partial_reconcile
ON account_partial_reconcile.credit_move_id = credit_line.id
JOIN %(currency_table)s
ON account_currency_table.company_id = account_partial_reconcile.company_id
AND account_currency_table.rate_type = 'current' -- For payable/receivable accounts it'll always be 'current' anyway
INNER JOIN account_move_line AS debit_line
ON debit_line.id = account_partial_reconcile.debit_move_id
WHERE credit_line.move_id IN (SELECT unnest(payment_move_ids.move_id) FROM payment_move_ids)
AND credit_line.account_id NOT IN %(payment_account_ids)s
AND credit_line.credit > 0.0
AND debit_line.move_id NOT IN (SELECT unnest(payment_move_ids.move_id) FROM payment_move_ids)
AND account_partial_reconcile.max_date BETWEEN %(date_from)s AND %(date_to)s
GROUP BY debit_line.move_id, debit_line.account_id
UNION ALL
SELECT
%(column_group_key)s AS column_group_key,
credit_line.move_id,
credit_line.account_id,
-SUM(%(partial_amount)s) AS balance
FROM account_move_line AS debit_line
LEFT JOIN account_partial_reconcile
ON account_partial_reconcile.debit_move_id = debit_line.id
JOIN %(currency_table)s
ON account_currency_table.company_id = account_partial_reconcile.company_id
AND account_currency_table.rate_type = 'current' -- For payable/receivable accounts it'll always be 'current' anyway
INNER JOIN account_move_line AS credit_line
ON credit_line.id = account_partial_reconcile.credit_move_id
WHERE debit_line.move_id IN (SELECT unnest(payment_move_ids.move_id) FROM payment_move_ids)
AND debit_line.account_id NOT IN %(payment_account_ids)s
AND debit_line.debit > 0.0
AND credit_line.move_id NOT IN (SELECT unnest(payment_move_ids.move_id) FROM payment_move_ids)
AND account_partial_reconcile.max_date BETWEEN %(date_from)s AND %(date_to)s
GROUP BY credit_line.move_id, credit_line.account_id)
''',
move_ids_query=move_ids_query,
column_group_key=column_group_key,
payment_account_ids=payment_account_ids,
date_from=column_group_options['date']['date_from'],
date_to=column_group_options['date']['date_to'],
currency_table=currency_table,
partial_amount=report._currency_table_apply_rate(SQL("account_partial_reconcile.amount")),
))
self._cr.execute(SQL(' UNION ALL ').join(queries))
for aml_data in self._cr.dictfetchall():
reconciled_percentage_per_move[aml_data['column_group_key']].setdefault(aml_data['move_id'], {})
reconciled_percentage_per_move[aml_data['column_group_key']][aml_data['move_id']].setdefault(aml_data['account_id'], [0.0, 0.0])
reconciled_percentage_per_move[aml_data['column_group_key']][aml_data['move_id']][aml_data['account_id']][0] += aml_data['balance']
reconciled_account_ids[aml_data['column_group_key']].add(aml_data['account_id'])
if not reconciled_percentage_per_move:
return []
queries = []
for column in options['columns']:
queries.append(SQL(
'''
SELECT
%(column_group_key)s AS column_group_key,
account_move_line.move_id,
account_move_line.account_id,
SUM(%(balance_select)s) AS balance
FROM account_move_line
JOIN %(currency_table)s
ON account_currency_table.company_id = account_move_line.company_id
AND account_currency_table.rate_type = 'current' -- For payable/receivable accounts it'll always be 'current' anyway
WHERE account_move_line.move_id IN %(move_ids)s
AND account_move_line.account_id IN %(account_ids)s
GROUP BY account_move_line.move_id, account_move_line.account_id
''',
column_group_key=column['column_group_key'],
currency_table=currency_table,
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
move_ids=tuple(reconciled_percentage_per_move[column['column_group_key']].keys()) or (None,),
account_ids=tuple(reconciled_account_ids[column['column_group_key']]) or (None,)
))
self._cr.execute(SQL(' UNION ALL ').join(queries))
for aml_data in self._cr.dictfetchall():
if aml_data['account_id'] in reconciled_percentage_per_move[aml_data['column_group_key']][aml_data['move_id']]:
reconciled_percentage_per_move[aml_data['column_group_key']][aml_data['move_id']][aml_data['account_id']][1] += aml_data['balance']
reconciled_aml_per_account = {}
queries = []
query = Query(self.env, 'account_move_line')
account_alias = query.join(lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id')
account_code = self.env['account.account']._field_to_sql(account_alias, 'code', query)
account_name = self.env['account.account']._field_to_sql(account_alias, 'name')
account_type = SQL.identifier(account_alias, 'account_type')
for column in options['columns']:
queries.append(SQL(
'''
SELECT
%(column_group_key)s AS column_group_key,
account_move_line.move_id,
account_move_line.account_id,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
%(account_type)s AS account_account_type,
account_account_account_tag.account_account_tag_id AS account_tag_id,
SUM(%(balance_select)s) AS balance
FROM %(from_clause)s
%(currency_table_join)s
LEFT JOIN account_account_account_tag
ON account_account_account_tag.account_account_id = account_move_line.account_id
AND account_account_account_tag.account_account_tag_id IN %(cash_flow_tag_ids)s
WHERE account_move_line.move_id IN %(move_ids)s
GROUP BY account_move_line.move_id, account_move_line.account_id, account_code, account_name, account_account_type, account_account_account_tag.account_account_tag_id
''',
column_group_key=column['column_group_key'],
account_code=account_code,
account_name=account_name,
account_type=account_type,
from_clause=query.from_clause,
currency_table_join=report._currency_table_aml_join(options),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
cash_flow_tag_ids=tuple(cash_flow_tag_ids),
move_ids=tuple(reconciled_percentage_per_move[column['column_group_key']].keys()) or (None,)
))
self._cr.execute(SQL(' UNION ALL ').join(queries))
for aml_data in self._cr.dictfetchall():
aml_column_group_key = aml_data['column_group_key']
aml_move_id = aml_data['move_id']
aml_account_id = aml_data['account_id']
aml_account_code = aml_data['account_code']
aml_account_name = aml_data['account_name']
aml_account_account_type = aml_data['account_account_type']
aml_account_tag_id = aml_data['account_tag_id']
aml_balance = aml_data['balance']
# Compute the total reconciled for the whole move.
total_reconciled_amount = 0.0
total_amount = 0.0
for reconciled_amount, amount in reconciled_percentage_per_move[aml_column_group_key][aml_move_id].values():
total_reconciled_amount += reconciled_amount
total_amount += amount
# Compute matched percentage for each account.
if total_amount and aml_account_id not in reconciled_percentage_per_move[aml_column_group_key][aml_move_id]:
# Lines being on reconciled moves but not reconciled with any liquidity move must be valued at the
# percentage of what is actually paid.
reconciled_percentage = total_reconciled_amount / total_amount
aml_balance *= reconciled_percentage
elif not total_amount and aml_account_id in reconciled_percentage_per_move[aml_column_group_key][aml_move_id]:
# The total amount to reconcile is 0. In that case, only add entries being on these accounts. Otherwise,
# this special case will lead to an unexplained difference equivalent to the reconciled amount on this
# account.
# E.g:
#
# Liquidity move:
# Account | Debit | Credit
# --------------------------------------
# Bank | | 100
# Receivable | 100 |
#
# Reconciled move: <- reconciled_amount=100, total_amount=0.0
# Account | Debit | Credit
# --------------------------------------
# Receivable | | 200
# Receivable | 200 | <- Only the reconciled part of this entry must be added.
aml_balance = -reconciled_percentage_per_move[aml_column_group_key][aml_move_id][aml_account_id][0]
else:
# Others lines are not considered.
continue
reconciled_aml_per_account.setdefault(aml_account_id, {})
reconciled_aml_per_account[aml_account_id].setdefault(aml_column_group_key, {
'column_group_key': aml_column_group_key,
'account_id': aml_account_id,
'account_code': aml_account_code,
'account_name': aml_account_name,
'account_account_type': aml_account_account_type,
'account_tag_id': aml_account_tag_id,
'balance': 0.0,
})
reconciled_aml_per_account[aml_account_id][aml_column_group_key]['balance'] -= aml_balance
return list(reconciled_aml_per_account.values())
# -------------------------------------------------------------------------
# COLUMNS / LINES
# -------------------------------------------------------------------------
def _get_layout_data(self):
# Indentation of the following dict reflects the structure of the report.
return {
'opening_balance': {'name': _('Cash and cash equivalents, beginning of period'), 'level': 0},
'net_increase': {'name': _('Net increase in cash and cash equivalents'), 'level': 0, 'unfolded': True},
'operating_activities': {'name': _('Cash flows from operating activities'), 'level': 2, 'parent_line_id': 'net_increase', 'class': 'fw-bold', 'unfolded': True},
'advance_payments_customer': {'name': _('Advance Payments received from customers'), 'level': 4, 'parent_line_id': 'operating_activities'},
'received_operating_activities': {'name': _('Cash received from operating activities'), 'level': 4, 'parent_line_id': 'operating_activities'},
'advance_payments_suppliers': {'name': _('Advance payments made to suppliers'), 'level': 4, 'parent_line_id': 'operating_activities'},
'paid_operating_activities': {'name': _('Cash paid for operating activities'), 'level': 4, 'parent_line_id': 'operating_activities'},
'investing_activities': {'name': _('Cash flows from investing & extraordinary activities'), 'level': 2, 'parent_line_id': 'net_increase', 'class': 'fw-bold', 'unfolded': True},
'investing_activities_cash_in': {'name': _('Cash in'), 'level': 4, 'parent_line_id': 'investing_activities'},
'investing_activities_cash_out': {'name': _('Cash out'), 'level': 4, 'parent_line_id': 'investing_activities'},
'financing_activities': {'name': _('Cash flows from financing activities'), 'level': 2, 'parent_line_id': 'net_increase', 'class': 'fw-bold', 'unfolded': True},
'financing_activities_cash_in': {'name': _('Cash in'), 'level': 4, 'parent_line_id': 'financing_activities'},
'financing_activities_cash_out': {'name': _('Cash out'), 'level': 4, 'parent_line_id': 'financing_activities'},
'unclassified_activities': {'name': _('Cash flows from unclassified activities'), 'level': 2, 'parent_line_id': 'net_increase', 'class': 'fw-bold', 'unfolded': True},
'unclassified_activities_cash_in': {'name': _('Cash in'), 'level': 4, 'parent_line_id': 'unclassified_activities'},
'unclassified_activities_cash_out': {'name': _('Cash out'), 'level': 4, 'parent_line_id': 'unclassified_activities'},
'closing_balance': {'name': _('Cash and cash equivalents, closing balance'), 'level': 0},
}
def _get_layout_line(self, report, options, layout_line_id, layout_line_data, report_data):
line_id = report._get_generic_line_id(None, None, markup=layout_line_id)
unfoldable = 'aml_groupby_account' in report_data[layout_line_id] if layout_line_id in report_data else False
column_values = []
for column in options['columns']:
expression_label = column['expression_label']
column_group_key = column['column_group_key']
value = report_data[layout_line_id][expression_label].get(column_group_key, 0.0) if layout_line_id in report_data else 0.0
column_values.append(report._build_column_dict(value, column, options=options))
return {
'id': line_id,
'name': layout_line_data['name'],
'level': layout_line_data['level'],
'class': layout_line_data.get('class', ''),
'columns': column_values,
'unfoldable': unfoldable,
'unfolded': line_id in options['unfolded_lines'] or layout_line_data.get('unfolded') or (options.get('unfold_all') and unfoldable),
}
def _get_aml_line(self, report, options, aml_data):
parent_line_id = report._get_generic_line_id(None, None, aml_data['parent_line_id'])
line_id = report._get_generic_line_id('account.account', aml_data['account_id'], parent_line_id=parent_line_id)
column_values = []
for column in options['columns']:
expression_label = column['expression_label']
column_group_key = column['column_group_key']
value = aml_data[expression_label].get(column_group_key, 0.0)
column_values.append(report._build_column_dict(value, column, options=options))
return {
'id': line_id,
'name': f"{aml_data['account_code']} {aml_data['account_name']}" if aml_data['account_code'] else aml_data['account_name'],
'caret_options': 'account.account',
'level': aml_data['level'],
'parent_id': parent_line_id,
'columns': column_values,
}
def _get_unexplained_difference_line(self, report, options, report_data):
unexplained_difference = False
column_values = []
for column in options['columns']:
expression_label = column['expression_label']
column_group_key = column['column_group_key']
opening_balance = report_data['opening_balance'][expression_label].get(column_group_key, 0.0) if 'opening_balance' in report_data else 0.0
closing_balance = report_data['closing_balance'][expression_label].get(column_group_key, 0.0) if 'closing_balance' in report_data else 0.0
net_increase = report_data['net_increase'][expression_label].get(column_group_key, 0.0) if 'net_increase' in report_data else 0.0
balance = closing_balance - opening_balance - net_increase
if not self.env.company.currency_id.is_zero(balance):
unexplained_difference = True
column_values.append(report._build_column_dict(
balance,
{
'figure_type': 'monetary',
'expression_label': 'balance',
},
options=options,
))
if unexplained_difference:
return {
'id': report._get_generic_line_id(None, None, markup='unexplained_difference'),
'name': 'Unexplained Difference',
'level': 1,
'columns': column_values,
}

View File

@@ -1,53 +0,0 @@
# -*- coding: utf-8 -*-
from odoo.addons.account.models.chart_template import template
from odoo import models
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
def _get_account_accountant_res_company(self, chart_template):
# Called when installing the Accountant module
company = self.env.company
data = self._get_chart_template_data(chart_template)
company_data = data['res.company'].get(company.id, {})
# Pre-reload to ensure the necessary xmlids for the load exist in case they were deleted or not created yet.
required_data = {k: v for k, v in data.items() if k in ['account.journal', 'account.account']}
self._pre_reload_data(company, data['template_data'], required_data)
return {
company.id: {
'deferred_expense_journal_id': company.deferred_expense_journal_id.id or company_data.get('deferred_expense_journal_id'),
'deferred_revenue_journal_id': company.deferred_revenue_journal_id.id or company_data.get('deferred_revenue_journal_id'),
'deferred_expense_account_id': company.deferred_expense_account_id.id or company_data.get('deferred_expense_account_id'),
'deferred_revenue_account_id': company.deferred_revenue_account_id.id or company_data.get('deferred_revenue_account_id'),
}
}
def _get_chart_template_data(self, chart_template):
# OVERRIDE chart template to process the default values for deferred journal and accounts.
data = super()._get_chart_template_data(chart_template)
for _company_id, company_data in data['res.company'].items():
company_data['deferred_expense_journal_id'] = (
company_data.get('deferred_expense_journal_id')
or next((xid for xid, d in data['account.journal'].items() if d['type'] == 'general'), None)
)
company_data['deferred_revenue_journal_id'] = (
company_data.get('deferred_revenue_journal_id')
or next((xid for xid, d in data['account.journal'].items() if d['type'] == 'general'), None)
)
company_data['deferred_expense_account_id'] = (
company_data.get('deferred_expense_account_id')
or next((xid for xid, d in data['account.account'].items() if d['account_type'] == 'asset_current'), None)
)
company_data['deferred_revenue_account_id'] = (
company_data.get('deferred_revenue_account_id')
or next((xid for xid, d in data['account.account'].items() if d['account_type'] == 'liability_current'), None)
)
return data

View File

@@ -1,581 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import calendar
from collections import defaultdict
from dateutil.relativedelta import relativedelta
from odoo import models, fields, _, api, Command
from odoo.exceptions import UserError
from odoo.tools import groupby, SQL
from odoo.addons.at_accounting.models.account_move import DEFERRED_DATE_MIN, DEFERRED_DATE_MAX
class DeferredReportCustomHandler(models.AbstractModel):
_name = 'account.deferred.report.handler'
_inherit = 'account.report.custom.handler'
_description = 'Deferred Expense Report Custom Handler'
def _get_deferred_report_type(self):
raise NotImplementedError("This method is not implemented in the deferred report handler.")
############################################
# DEFERRED COMMON (DISPLAY AND GENERATION) #
############################################
def _get_domain(self, report, options, filter_already_generated=False, filter_not_started=False):
domain = report._get_options_domain(options, "from_beginning")
account_types = ('expense', 'expense_depreciation', 'expense_direct_cost') if self._get_deferred_report_type() == 'expense' else ('income', 'income_other')
domain += [
('account_id.account_type', 'in', account_types),
('deferred_start_date', '!=', False),
('deferred_end_date', '!=', False),
('deferred_end_date', '>=', options['date']['date_from']),
('move_id.date', '<=', options['date']['date_to']),
]
domain += [ # Exclude if entirely inside the period
'!', '&', '&', '&', '&', '&',
('deferred_start_date', '>=', options['date']['date_from']),
('deferred_start_date', '<=', options['date']['date_to']),
('deferred_end_date', '>=', options['date']['date_from']),
('deferred_end_date', '<=', options['date']['date_to']),
('move_id.date', '>=', options['date']['date_from']),
('move_id.date', '<=', options['date']['date_to']),
]
if filter_already_generated:
domain += [
('deferred_end_date', '>=', options['date']['date_from']),
'!',
'&',
('move_id.deferred_move_ids.date', '=', options['date']['date_to']),
('move_id.deferred_move_ids.state', '=', 'posted'),
]
if filter_not_started:
domain += [('deferred_start_date', '>', options['date']['date_to'])]
return domain
@api.model
def _get_select(self):
account_name = self.env['account.account']._field_to_sql('account_move_line__account_id', 'name')
return [
SQL("account_move_line.id AS line_id"),
SQL("account_move_line.account_id AS account_id"),
SQL("account_move_line.partner_id AS partner_id"),
SQL("account_move_line.product_id AS product_id"),
SQL("account_move_line__product_template_id.categ_id AS product_category_id"),
SQL("account_move_line.name AS line_name"),
SQL("account_move_line.deferred_start_date AS deferred_start_date"),
SQL("account_move_line.deferred_end_date AS deferred_end_date"),
SQL("account_move_line.deferred_end_date - account_move_line.deferred_start_date AS diff_days"),
SQL("account_move_line.balance AS balance"),
SQL("account_move_line.analytic_distribution AS analytic_distribution"),
SQL("account_move_line__move_id.id as move_id"),
SQL("account_move_line__move_id.name AS move_name"),
SQL("%s AS account_name", account_name),
]
def _get_lines(self, report, options, filter_already_generated=False):
domain = self._get_domain(report, options, filter_already_generated)
query = report._get_report_query(options, domain=domain, date_scope='from_beginning')
select_clause = SQL(', ').join(self._get_select())
query = SQL(
"""
SELECT %(select_clause)s
FROM %(table_references)s
LEFT JOIN product_product AS account_move_line__product_id ON account_move_line.product_id = account_move_line__product_id.id
LEFT JOIN product_template AS account_move_line__product_template_id ON account_move_line__product_id.product_tmpl_id = account_move_line__product_template_id.id
WHERE %(search_condition)s
ORDER BY account_move_line.deferred_start_date, account_move_line.id
""",
select_clause=select_clause,
table_references=query.from_clause,
search_condition=query.where_clause,
)
self.env.cr.execute(query)
res = self.env.cr.dictfetchall()
return res
@api.model
def _get_grouping_fields_deferred_lines(self, filter_already_generated=False, grouping_field='account_id'):
return (grouping_field,)
@api.model
def _group_by_deferred_fields(self, line, filter_already_generated=False, grouping_field='account_id'):
return tuple(line[k] for k in self._get_grouping_fields_deferred_lines(filter_already_generated, grouping_field))
@api.model
def _get_grouping_fields_deferral_lines(self):
return ()
@api.model
def _group_by_deferral_fields(self, line):
return tuple(line[k] for k in self._get_grouping_fields_deferral_lines())
@api.model
def _group_deferred_amounts_by_grouping_field(self, deferred_amounts_by_line, periods, is_reverse, filter_already_generated=False, grouping_field='account_id'):
"""
Groups the deferred amounts by account and computes the totals for each account for each period.
And the total for all accounts for each period.
E.g. (where period1 = (date1, date2, label1), period2 = (date2, date3, label2), ...)
{
self._get_grouping_keys_deferred_lines(): {
'account_id': account1, 'amount_total': 600, period_1: 200, period_2: 400
},
self._get_grouping_keys_deferred_lines(): {
'account_id': account2, 'amount_total': 700, period_1: 300, period_2: 400
},
}, {'totals_aggregated': 1300, period_1: 500, period_2: 800}
"""
deferred_amounts_by_line = groupby(deferred_amounts_by_line, key=lambda x: self._group_by_deferred_fields(x, filter_already_generated, grouping_field))
totals_per_key = {} # {key: {**self._get_grouping_fields_deferral_lines(), total, before, current, later}}
totals_aggregated_by_period = {period: 0 for period in periods + ['totals_aggregated']}
sign = 1 if is_reverse else -1
for key, lines_per_key in deferred_amounts_by_line:
lines_per_key = list(lines_per_key)
current_key_totals = self._get_current_key_totals_dict(lines_per_key, sign)
totals_aggregated_by_period['totals_aggregated'] += current_key_totals['amount_total']
for period in periods:
current_key_totals[period] = sign * sum(line[period] for line in lines_per_key)
totals_aggregated_by_period[period] += self.env.company.currency_id.round(current_key_totals[period])
totals_per_key[key] = current_key_totals
return totals_per_key, totals_aggregated_by_period
###########################
# DEFERRED REPORT DISPLAY #
###########################
def _get_custom_display_config(self):
return {
'templates': {
'AccountReportFilters': 'at_accounting.DeferredFilters',
},
}
def _custom_options_initializer(self, report, options, previous_options):
super()._custom_options_initializer(report, options, previous_options=previous_options)
options_per_col_group = report._split_options_per_column_group(options)
for column_dict in options['columns']:
column_options = options_per_col_group[column_dict['column_group_key']]
column_dict['name'] = column_options['date']['string']
column_dict['date_from'] = column_options['date']['date_from']
column_dict['date_to'] = column_options['date']['date_to']
options['columns'] = list(reversed(options['columns']))
total_column = [{
**options['columns'][0],
'name': _('Total'),
'expression_label': 'total',
'date_from': DEFERRED_DATE_MIN,
'date_to': DEFERRED_DATE_MAX,
}]
not_started_column = [{
**options['columns'][0],
'name': _('Not Started'),
'expression_label': 'not_started',
'date_from': options['columns'][-1]['date_to'],
'date_to': DEFERRED_DATE_MAX,
}]
before_column = [{
**options['columns'][0],
'name': _('Before'),
'expression_label': 'before',
'date_from': DEFERRED_DATE_MIN,
'date_to': options['columns'][0]['date_from'],
}]
later_column = [{
**options['columns'][0],
'name': _('Later'),
'expression_label': 'later',
'date_from': options['columns'][-1]['date_to'],
'date_to': DEFERRED_DATE_MAX,
}]
options['columns'] = total_column + not_started_column + before_column + options['columns'] + later_column
options['column_headers'] = []
options['deferred_report_type'] = self._get_deferred_report_type()
options['deferred_grouping_field'] = previous_options.get('deferred_grouping_field') or 'account_id'
if (
self._get_deferred_report_type() == 'expense' and self.env.company.generate_deferred_expense_entries_method == 'manual'
or self._get_deferred_report_type() == 'revenue' and self.env.company.generate_deferred_revenue_entries_method == 'manual'
):
options['buttons'].append({'name': _('Generate entry'), 'action': 'action_generate_entry', 'sequence': 80, 'always_show': True})
def action_audit_cell(self, options, params):
""" Open a list of invoices/bills and/or deferral entries for the clicked cell in a deferred report.
Specifically, we show the following lines, grouped by their journal entry, filtered by the column date bounds:
- Total: Lines of all invoices/bills being deferred in the current period
- Not Started: Lines of all deferral entries for which the original invoice/bill date is before or in the
current period, but the deferral only starts after the current period, as well as the lines of
their original invoices/bills
- Before: Lines of all deferral entries with a date before the current period, created by invoices/bills also
being deferred in the current period, as well as the lines of their original invoices/bills
- Current: Lines of all deferral entries in the current period, as well as these of their original
invoices/bills
- Later: Lines of all deferral entries with a date after the current period, created by invoices/bills also
being deferred in the current period, as well as the lines of their original invoices/bills
:param dict options: the report's `options`
:param dict params: a dict containing:
`calling_line_dict_id`: line id containing the optional account of the cell
`column_group_id`: the column group id of the cell
`expression_label`: the expression label of the cell
"""
report = self.env['account.report'].browse(options['report_id'])
column_values = next(
(column for column in options['columns'] if (
column['column_group_key'] == params.get('column_group_key')
and column['expression_label'] == params.get('expression_label')
)),
None
)
if not column_values:
return
column_date_from = fields.Date.to_date(column_values['date_from'])
column_date_to = fields.Date.to_date(column_values['date_to'])
report_date_from = fields.Date.to_date(options['date']['date_from'])
report_date_to = fields.Date.to_date(options['date']['date_to'])
# Corrections for comparisons
if column_values['expression_label'] in ('not_started', 'later'):
# Not Started and Later period start one day after `report_date_to`
column_date_from = report_date_to + relativedelta(days=1)
if column_values['expression_label'] == 'before':
# Before period ends one day before `report_date_from`
column_date_to = report_date_from - relativedelta(days=1)
# calling_line_dict_id is of the format `~account.report~15|~account.account~25`
_grouping_model, grouping_record_id = report._get_model_info_from_id(params.get('calling_line_dict_id'))
# Find the original lines to be deferred in the report period
original_move_lines_domain = self._get_domain(
report, options, filter_not_started=column_values['expression_label'] == 'not_started'
)
if grouping_record_id:
# We're auditing a specific account, so we only want moves containing this account
original_move_lines_domain.append((options['deferred_grouping_field'], '=', grouping_record_id))
# We're getting all lines from the concerned moves. They are filtered later for flexibility.
original_move = self.env['account.move.line'].search(original_move_lines_domain).move_id
# For the Total period only show the original move lines
line_ids = original_move.line_ids.ids
# Show both the original move lines and deferral move lines for all other periods
if not column_values['expression_label'] == 'total':
line_ids += original_move.deferred_move_ids.line_ids.ids
return {
'type': 'ir.actions.act_window',
'name': _('Deferred Entries'),
'res_model': 'account.move.line',
'domain': [('id', 'in', line_ids)],
'views': [(self.env.ref('at_accounting.view_deferred_entries_tree').id, 'list')],
# Most filters are set here to allow auditing flexibility to the user
'context': {
'search_default_pl_accounts': True,
f'search_default_{options["deferred_grouping_field"]}': grouping_record_id,
'date_from': column_date_from,
'date_to': column_date_to,
'search_default_date_between': True,
'expand': True,
}
}
def _caret_options_initializer(self):
return {
'deferred_caret': [
{'name': _("Journal Items"), 'action': 'open_journal_items'},
],
}
def _customize_warnings(self, report, options, all_column_groups_expression_totals, warnings):
already_generated = (
(
self._get_deferred_report_type() == 'expense' and self.env.company.generate_deferred_expense_entries_method == 'manual'
or self._get_deferred_report_type() == 'revenue' and self.env.company.generate_deferred_revenue_entries_method == 'manual'
)
and self.env['account.move'].search_count(
report._get_generated_deferral_entries_domain(options)
)
)
if already_generated:
warnings['at_accounting.deferred_report_warning_already_posted'] = {'alert_type': 'warning'}
def open_journal_items(self, options, params):
report = self.env['account.report'].browse(options['report_id'])
record_model, record_id = report._get_model_info_from_id(params.get('line_id'))
domain = self._get_domain(report, options)
if record_model == 'account.account' and record_id:
domain += [('account_id', '=', record_id)]
elif record_model == 'product.product' and record_id:
domain += [('product_id', '=', record_id)]
elif record_model == 'product.category' and record_id:
domain += [('product_category_id', '=', record_id)]
return {
'type': 'ir.actions.act_window',
'name': _("Deferred Entries"),
'res_model': 'account.move.line',
'domain': domain,
'views': [(self.env.ref('at_accounting.view_deferred_entries_tree').id, 'list')],
'context': {
'search_default_group_by_move': True,
'expand': True,
}
}
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
def get_columns(totals):
return [
{
**report._build_column_dict(
totals[(
fields.Date.to_date(column['date_from']),
fields.Date.to_date(column['date_to']),
column['expression_label']
)],
column,
options=options,
currency=self.env.company.currency_id,
),
'auditable': True,
}
for column in options['columns']
]
lines = self._get_lines(report, options)
periods = [
(
fields.Date.from_string(column['date_from']),
fields.Date.from_string(column['date_to']),
column['expression_label'],
)
for column in options['columns']
]
deferred_amounts_by_line = self.env['account.move']._get_deferred_amounts_by_line(lines, periods, self._get_deferred_report_type())
totals_per_grouping_field, totals_all_grouping_field = self._group_deferred_amounts_by_grouping_field(
deferred_amounts_by_line=deferred_amounts_by_line,
periods=periods,
is_reverse=self._get_deferred_report_type() == 'expense',
filter_already_generated=False,
grouping_field=options['deferred_grouping_field'],
)
report_lines = []
grouping_model = self.env['account.move.line'][options['deferred_grouping_field']]._name
for totals_grouping_field in totals_per_grouping_field.values():
grouping_record = self.env[grouping_model].browse(totals_grouping_field[options['deferred_grouping_field']])
grouping_field_description = self.env['account.move.line'][options['deferred_grouping_field']]._description
if options['deferred_grouping_field'] == 'product_id':
grouping_field_description = _("Product")
grouping_name = grouping_record.display_name or _("(No %s)", grouping_field_description)
report_lines.append((0, {
'id': report._get_generic_line_id(grouping_model, grouping_record.id),
'name': grouping_name,
'caret_options': 'deferred_caret',
'level': 1,
'columns': get_columns(totals_grouping_field),
}))
if totals_per_grouping_field:
report_lines.append((0, {
'id': report._get_generic_line_id(None, None, markup='total'),
'name': 'Total',
'level': 1,
'columns': get_columns(totals_all_grouping_field),
}))
return report_lines
#######################
# DEFERRED GENERATION #
#######################
def action_generate_entry(self, options):
new_deferred_moves = self._generate_deferral_entry(options)
return {
'name': _('Deferred Entries'),
'type': 'ir.actions.act_window',
'views': [(False, "list"), (False, "form")],
'domain': [('id', 'in', new_deferred_moves.ids)],
'res_model': 'account.move',
'context': {
'search_default_group_by_move': True,
'expand': True,
},
'target': 'current',
}
def _generate_deferral_entry(self, options):
journal = self.env.company.deferred_expense_journal_id if self._get_deferred_report_type() == "expense" else self.env.company.deferred_revenue_journal_id
if not journal:
raise UserError(_("Please set the deferred journal in the accounting settings."))
date_from = fields.Date.to_date(DEFERRED_DATE_MIN)
date_to = fields.Date.from_string(options['date']['date_to'])
if date_to.day != calendar.monthrange(date_to.year, date_to.month)[1]:
raise UserError(_("You cannot generate entries for a period that does not end at the end of the month."))
if self.env.company._get_violated_lock_dates(date_to, False, journal):
raise UserError(_("You cannot generate entries for a period that is locked."))
options['all_entries'] = False # We only want to create deferrals for posted moves
report = self.env["account.report"].browse(options["report_id"])
self.env['account.move.line'].flush_model()
lines = self._get_lines(report, options, filter_already_generated=True)
deferral_entry_period = self.env['account.report']._get_dates_period(date_from, date_to, 'range', period_type='month')
ref = _("Grouped Deferral Entry of %s", deferral_entry_period['string'])
ref_rev = _("Reversal of Grouped Deferral Entry of %s", deferral_entry_period['string'])
deferred_account = self.env.company.deferred_expense_account_id if self._get_deferred_report_type() == 'expense' else self.env.company.deferred_revenue_account_id
move_lines, original_move_ids = self._get_deferred_lines(lines, deferred_account, (date_from, date_to, 'current'), self._get_deferred_report_type() == 'expense', ref)
if not move_lines:
raise UserError(_("No entry to generate."))
deferred_move = self.env['account.move'].with_context(skip_account_deprecation_check=True).create({
'move_type': 'entry',
'deferred_original_move_ids': [Command.set(original_move_ids)],
'journal_id': journal.id,
'date': date_to,
'auto_post': 'at_date',
'ref': ref,
})
# We write the lines after creation, to make sure the `deferred_original_move_ids` is set.
# This way we can avoid adding taxes for deferred moves.
deferred_move.write({'line_ids': move_lines})
reverse_move = deferred_move._reverse_moves()
reverse_move.write({
'date': deferred_move.date + relativedelta(days=1),
'ref': ref_rev,
})
reverse_move.line_ids.name = ref_rev
new_deferred_moves = deferred_move + reverse_move
# We create the relation (original deferred move, deferral entry)
# using SQL. This avoids a MemoryError using the ORM which will
# load huge amounts of moves in memory for nothing
self.env.cr.execute_values("""
INSERT INTO account_move_deferred_rel(original_move_id, deferred_move_id)
VALUES %s
ON CONFLICT DO NOTHING
""", [
(original_move_id, deferral_move.id)
for original_move_id in original_move_ids
for deferral_move in new_deferred_moves
])
(deferred_move + reverse_move)._post(soft=True)
return new_deferred_moves
@api.model
def _get_current_key_totals_dict(self, lines_per_key, sign):
return {
'account_id': lines_per_key[0]['account_id'],
'product_id': lines_per_key[0]['product_id'],
'product_category_id': lines_per_key[0]['product_category_id'],
'amount_total': sign * sum(line['balance'] for line in lines_per_key),
'move_ids': {line['move_id'] for line in lines_per_key},
}
@api.model
def _get_deferred_lines(self, lines, deferred_account, period, is_reverse, ref):
"""
Returns a list of Command objects to create the deferred lines of a single given period.
And the move_ids of the original lines that created these deferred
(to keep track of the original invoice in the deferred entries).
"""
if not deferred_account:
raise UserError(_("Please set the deferred accounts in the accounting settings."))
deferred_amounts_by_line = self.env['account.move']._get_deferred_amounts_by_line(lines, [period], is_reverse)
deferred_amounts_by_key, deferred_amounts_totals = self._group_deferred_amounts_by_grouping_field(deferred_amounts_by_line, [period], is_reverse, filter_already_generated=True)
if deferred_amounts_totals['totals_aggregated'] == deferred_amounts_totals[period]:
return [], set()
# compute analytic distribution to populate on deferred lines
# structure: {self._get_grouping_keys_deferred_lines(): [analytic distribution]}
# dict of keys: self._get_grouping_keys_deferred_lines()
# values: dict of keys: "account.analytic.account.id" (string)
# values: float
anal_dist_by_key = defaultdict(lambda: defaultdict(float))
# using another var for the analytic distribution of the deferral account
deferred_anal_dist = defaultdict(lambda: defaultdict(float))
for line in lines:
if not line['analytic_distribution']:
continue
# Analytic distribution should be computed from the lines with the same _get_grouping_keys_deferred_lines(), except for
# the deferred line with the deferral account which will use _get_grouping_fields_deferral_lines()
full_ratio = (line['balance'] / deferred_amounts_totals['totals_aggregated']) if deferred_amounts_totals['totals_aggregated'] else 0
key_amount = deferred_amounts_by_key.get(self._group_by_deferred_fields(line, True))
key_ratio = (line['balance'] / key_amount['amount_total']) if key_amount and key_amount['amount_total'] else 0
for account_id, distribution in line['analytic_distribution'].items():
anal_dist_by_key[self._group_by_deferred_fields(line, True)][account_id] += distribution * key_ratio
deferred_anal_dist[self._group_by_deferral_fields(line)][account_id] += distribution * full_ratio
remaining_balance = 0
deferred_lines = []
original_move_ids = set()
for key, line in deferred_amounts_by_key.items():
for balance in (-line['amount_total'], line[period]):
if balance != 0 and line[period] != line['amount_total']:
original_move_ids |= line['move_ids']
deferred_balance = self.env.company.currency_id.round((1 if is_reverse else -1) * balance)
deferred_lines.append(
Command.create(
self.env['account.move.line']._get_deferred_lines_values(
account_id=line['account_id'],
balance=deferred_balance,
ref=ref,
analytic_distribution=anal_dist_by_key[key] or False,
line=line,
)
)
)
remaining_balance += deferred_balance
grouped_by_key = {
key: list(value)
for key, value in groupby(
deferred_amounts_by_key.values(),
key=self._group_by_deferral_fields,
)
}
deferral_lines = []
for key, lines_per_key in grouped_by_key.items():
balance = 0
for line in lines_per_key:
if line[period] != line['amount_total']:
balance += self.env.company.currency_id.round((1 if is_reverse else -1) * (line['amount_total'] - line[period]))
deferral_lines.append(
Command.create(
self.env['account.move.line']._get_deferred_lines_values(
account_id=deferred_account.id,
balance=balance,
ref=ref,
analytic_distribution=deferred_anal_dist[key] or False,
line=lines_per_key[0],
)
)
)
remaining_balance += balance
if not self.env.company.currency_id.is_zero(remaining_balance):
deferral_lines.append(
Command.create({
'account_id': deferred_account.id,
'balance': -remaining_balance,
'name': ref,
})
)
return deferred_lines + deferral_lines, original_move_ids
class DeferredExpenseCustomHandler(models.AbstractModel):
_name = 'account.deferred.expense.report.handler'
_inherit = 'account.deferred.report.handler'
_description = 'Deferred Expense Custom Handler'
def _get_deferred_report_type(self):
return 'expense'
class DeferredRevenueCustomHandler(models.AbstractModel):
_name = 'account.deferred.revenue.report.handler'
_inherit = 'account.deferred.report.handler'
_description = 'Deferred Revenue Custom Handler'
def _get_deferred_report_type(self):
return 'revenue'

View File

@@ -1,19 +0,0 @@
from odoo import models
class AccountFiscalPosition(models.Model):
_inherit = 'account.fiscal.position'
def _inverse_foreign_vat(self):
# EXTENDS account
super()._inverse_foreign_vat()
for fpos in self:
if fpos.foreign_vat:
fpos._create_draft_closing_move_for_foreign_vat()
def _create_draft_closing_move_for_foreign_vat(self):
self.ensure_one()
existing_draft_closings = self.env['account.move'].search([('tax_closing_report_id', '!=', False), ('state', '=', 'draft')])
for closing_date, entries in existing_draft_closings.grouped('date').items():
for entry in entries:
self.company_id._get_and_update_tax_closing_moves(closing_date, entry.tax_closing_report_id, fiscal_positions=self)

View File

@@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
from odoo.exceptions import ValidationError
from odoo import api, fields, models, _
from datetime import datetime
class AccountFiscalYear(models.Model):
_name = 'account.fiscal.year'
_description = 'Fiscal Year'
name = fields.Char(string='Name', required=True)
date_from = fields.Date(string='Start Date', required=True,
help='Start Date, included in the fiscal year.')
date_to = fields.Date(string='End Date', required=True,
help='Ending Date, included in the fiscal year.')
company_id = fields.Many2one('res.company', string='Company', required=True,
default=lambda self: self.env.company)
@api.constrains('date_from', 'date_to', 'company_id')
def _check_dates(self):
'''
Check interleaving between fiscal years.
There are 3 cases to consider:
s1 s2 e1 e2
( [----)----]
s2 s1 e2 e1
[----(----] )
s1 s2 e2 e1
( [----] )
'''
for fy in self:
# Starting date must be prior to the ending date
date_from = fy.date_from
date_to = fy.date_to
if date_to < date_from:
raise ValidationError(_('The ending date must not be prior to the starting date.'))
if fy.company_id.parent_id:
raise ValidationError(_('You cannot have a fiscal year on a child company.'))
domain = [
('id', '!=', fy.id),
('company_id', '=', fy.company_id.id),
'|', '|',
'&', ('date_from', '<=', fy.date_from), ('date_to', '>=', fy.date_from),
'&', ('date_from', '<=', fy.date_to), ('date_to', '>=', fy.date_to),
'&', ('date_from', '<=', fy.date_from), ('date_to', '>=', fy.date_to),
]
if self.search_count(domain) > 0:
raise ValidationError(_('You can not have an overlap between two fiscal years, please correct the start and/or end dates of your fiscal years.'))

View File

@@ -1,744 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
from odoo import models, fields, api, _
from odoo.tools.misc import format_date
from odoo.tools import get_lang, SQL
from odoo.exceptions import UserError
from datetime import timedelta
from collections import defaultdict
class GeneralLedgerCustomHandler(models.AbstractModel):
_name = 'account.general.ledger.report.handler'
_inherit = 'account.report.custom.handler'
_description = 'General Ledger Custom Handler'
def _get_custom_display_config(self):
return {
'templates': {
'AccountReportLineName': 'at_accounting.GeneralLedgerLineName',
},
}
def _custom_options_initializer(self, report, options, previous_options):
# Remove multi-currency columns if needed
super()._custom_options_initializer(report, options, previous_options=previous_options)
if self.env.user.has_group('base.group_multi_currency'):
options['multi_currency'] = True
else:
options['columns'] = [
column for column in options['columns']
if column['expression_label'] != 'amount_currency'
]
# Automatically unfold the report when printing it, unless some specific lines have been unfolded
options['unfold_all'] = (options['export_mode'] == 'print' and not options.get('unfolded_lines')) or options['unfold_all']
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
lines = []
date_from = fields.Date.from_string(options['date']['date_from'])
company_currency = self.env.company.currency_id
totals_by_column_group = defaultdict(lambda: {'debit': 0, 'credit': 0, 'balance': 0})
for account, column_group_results in self._query_values(report, options):
eval_dict = {}
has_lines = False
for column_group_key, results in column_group_results.items():
account_sum = results.get('sum', {})
account_un_earn = results.get('unaffected_earnings', {})
account_debit = account_sum.get('debit', 0.0) + account_un_earn.get('debit', 0.0)
account_credit = account_sum.get('credit', 0.0) + account_un_earn.get('credit', 0.0)
account_balance = account_sum.get('balance', 0.0) + account_un_earn.get('balance', 0.0)
eval_dict[column_group_key] = {
'amount_currency': account_sum.get('amount_currency', 0.0) + account_un_earn.get('amount_currency', 0.0),
'debit': account_debit,
'credit': account_credit,
'balance': account_balance,
}
max_date = account_sum.get('max_date')
has_lines = has_lines or (max_date and max_date >= date_from)
totals_by_column_group[column_group_key]['debit'] += account_debit
totals_by_column_group[column_group_key]['credit'] += account_credit
totals_by_column_group[column_group_key]['balance'] += account_balance
lines.append(self._get_account_title_line(report, options, account, has_lines, eval_dict))
# Report total line.
for totals in totals_by_column_group.values():
totals['balance'] = company_currency.round(totals['balance'])
# Tax Declaration lines.
journal_options = report._get_options_journals(options)
if len(options['column_groups']) == 1 and len(journal_options) == 1 and journal_options[0]['type'] in ('sale', 'purchase'):
lines += self._tax_declaration_lines(report, options, journal_options[0]['type'])
# Total line
lines.append(self._get_total_line(report, options, totals_by_column_group))
return [(0, line) for line in lines]
def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function):
account_ids_to_expand = []
for line_dict in lines_to_expand_by_function.get('_report_expand_unfoldable_line_general_ledger', []):
model, model_id = report._get_model_info_from_id(line_dict['id'])
if model == 'account.account':
account_ids_to_expand.append(model_id)
limit_to_load = report.load_more_limit if report.load_more_limit and not options.get('export_mode') else None
has_more_per_account_id = {}
unlimited_aml_results_per_account_id = self._get_aml_values(report, options, account_ids_to_expand)[0]
if limit_to_load:
# Apply the load_more_limit.
# load_more_limit cannot be passed to the call to _get_aml_values, otherwise it won't be applied per account but on the whole result.
# We gain perf from batching, but load every result ; then we need to filter them.
aml_results_per_account_id = {}
for account_id, account_aml_results in unlimited_aml_results_per_account_id.items():
account_values = {}
for key, value in account_aml_results.items():
if len(account_values) == limit_to_load:
has_more_per_account_id[account_id] = True
break
account_values[key] = value
aml_results_per_account_id[account_id] = account_values
else:
aml_results_per_account_id = unlimited_aml_results_per_account_id
return {
'initial_balances': self._get_initial_balance_values(report, account_ids_to_expand, options),
'aml_results': aml_results_per_account_id,
'has_more': has_more_per_account_id,
}
def _tax_declaration_lines(self, report, options, tax_type):
labels_replacement = {
'debit': _("Base Amount"),
'credit': _("Tax Amount"),
}
rslt = [{
'id': report._get_generic_line_id(None, None, markup='tax_decl_header_1'),
'name': _('Tax Declaration'),
'columns': [{} for column in options['columns']],
'level': 1,
'unfoldable': False,
'unfolded': False,
}, {
'id': report._get_generic_line_id(None, None, markup='tax_decl_header_2'),
'name': _('Name'),
'columns': [{'name': labels_replacement.get(col['expression_label'], '')} for col in options['columns']],
'level': 3,
'unfoldable': False,
'unfolded': False,
}]
# Call the generic tax report
generic_tax_report = self.env.ref('account.generic_tax_report')
tax_report_options = generic_tax_report.get_options({**options, 'selected_variant_id': generic_tax_report.id, 'forced_domain': [('tax_line_id.type_tax_use', '=', tax_type)]})
tax_report_lines = generic_tax_report._get_lines(tax_report_options)
tax_type_parent_line_id = generic_tax_report._get_generic_line_id(None, None, markup=tax_type)
for tax_report_line in tax_report_lines:
if tax_report_line.get('parent_id') == tax_type_parent_line_id:
original_columns = tax_report_line['columns']
row_column_map = {
'debit': original_columns[0],
'credit': original_columns[1],
}
tax_report_line['columns'] = [row_column_map.get(col['expression_label'], {}) for col in options['columns']]
rslt.append(tax_report_line)
return rslt
def _query_values(self, report, options):
""" Executes the queries, and performs all the computations.
:return: [(record, values_by_column_group), ...], where
- record is an account.account record.
- values_by_column_group is a dict in the form {column_group_key: values, ...}
- column_group_key is a string identifying a column group, as in options['column_groups']
- values is a list of dictionaries, one per period containing:
- sum: {'debit': float, 'credit': float, 'balance': float}
- (optional) initial_balance: {'debit': float, 'credit': float, 'balance': float}
- (optional) unaffected_earnings: {'debit': float, 'credit': float, 'balance': float}
"""
# Execute the queries and dispatch the results.
query = self._get_query_sums(report, options)
if not query:
return []
groupby_accounts = {}
groupby_companies = {}
self._cr.execute(query)
for res in self._cr.dictfetchall():
# No result to aggregate.
if res['groupby'] is None:
continue
column_group_key = res['column_group_key']
key = res['key']
if key == 'sum':
groupby_accounts.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']})
groupby_accounts[res['groupby']][column_group_key][key] = res
elif key == 'initial_balance':
groupby_accounts.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']})
groupby_accounts[res['groupby']][column_group_key][key] = res
elif key == 'unaffected_earnings':
groupby_companies.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']})
groupby_companies[res['groupby']][column_group_key] = res
# Affect the unaffected earnings to the first fetched account of type 'account.data_unaffected_earnings'.
# It's less costly to fetch all candidate accounts in a single search and then iterate it.
if groupby_companies:
unaffected_earnings_accounts = self.env['account.account'].search([
('display_name', 'ilike', options.get('filter_search_bar')),
*self.env['account.account']._check_company_domain(list(groupby_companies.keys())),
('account_type', '=', 'equity_unaffected'),
])
for company_id, groupby_company in groupby_companies.items():
if equity_unaffected_account := unaffected_earnings_accounts.filtered(lambda a: self.env['res.company'].browse(company_id).root_id in a.company_ids):
for column_group_key in options['column_groups']:
groupby_accounts.setdefault(
equity_unaffected_account.id,
{col_group_key: {'unaffected_earnings': {}} for col_group_key in options['column_groups']},
)
if unaffected_earnings := groupby_company.get(column_group_key):
if groupby_accounts[equity_unaffected_account.id][column_group_key].get('unaffected_earnings'):
for key in ['amount_currency', 'debit', 'credit', 'balance']:
groupby_accounts[equity_unaffected_account.id][column_group_key]['unaffected_earnings'][key] += unaffected_earnings[key]
else:
groupby_accounts[equity_unaffected_account.id][column_group_key]['unaffected_earnings'] = unaffected_earnings
# Retrieve the accounts to browse.
# groupby_accounts.keys() contains all account ids affected by:
# - the amls in the current period.
# - the amls affecting the initial balance.
# - the unaffected earnings allocation.
# Note a search is done instead of a browse to preserve the table ordering.
if groupby_accounts:
accounts = self.env['account.account'].search([('id', 'in', list(groupby_accounts.keys()))])
else:
accounts = []
return [(account, groupby_accounts[account.id]) for account in accounts]
def _get_query_sums(self, report, options) -> SQL:
""" Construct a query retrieving all the aggregated sums to build the report. It includes:
- sums for all accounts.
- sums for the initial balances.
- sums for the unaffected earnings.
- sums for the tax declaration.
:return: query as SQL object
"""
options_by_column_group = report._split_options_per_column_group(options)
queries = []
# ============================================
# 1) Get sums for all accounts.
# ============================================
for column_group_key, options_group in options_by_column_group.items():
# Sum is computed including the initial balance of the accounts configured to do so, unless a special option key is used
# (this is required for trial balance, which is based on general ledger)
sum_date_scope = 'strict_range' if options_group.get('general_ledger_strict_range') else 'from_beginning'
query_domain = []
if not options_group.get('general_ledger_strict_range'):
date_from = fields.Date.from_string(options_group['date']['date_from'])
current_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from)
query_domain += [
'|',
('date', '>=', current_fiscalyear_dates['date_from']),
('account_id.include_initial_balance', '=', True),
]
if options_group.get('export_mode') == 'print' and options_group.get('filter_search_bar'):
query_domain.append(('account_id', 'ilike', options_group['filter_search_bar']))
if options_group.get('include_current_year_in_unaff_earnings'):
query_domain += [('account_id.include_initial_balance', '=', True)]
query = report._get_report_query(options_group, sum_date_scope, domain=query_domain)
queries.append(SQL(
"""
SELECT
account_move_line.account_id AS groupby,
'sum' AS key,
MAX(account_move_line.date) AS max_date,
%(column_group_key)s AS column_group_key,
COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency,
SUM(%(debit_select)s) AS debit,
SUM(%(credit_select)s) AS credit,
SUM(%(balance_select)s) AS balance
FROM %(table_references)s
%(currency_table_join)s
WHERE %(search_condition)s
GROUP BY account_move_line.account_id
""",
column_group_key=column_group_key,
table_references=query.from_clause,
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
currency_table_join=report._currency_table_aml_join(options_group),
search_condition=query.where_clause,
))
# ============================================
# 2) Get sums for the unaffected earnings.
# ============================================
if not options_group.get('general_ledger_strict_range'):
unaff_earnings_domain = [('account_id.include_initial_balance', '=', False)]
# The period domain is expressed as:
# [
# ('date' <= fiscalyear['date_from'] - 1),
# ('account_id.include_initial_balance', '=', False),
# ]
new_options = self._get_options_unaffected_earnings(options_group)
query = report._get_report_query(new_options, 'strict_range', domain=unaff_earnings_domain)
queries.append(SQL(
"""
SELECT
account_move_line.company_id AS groupby,
'unaffected_earnings' AS key,
NULL AS max_date,
%(column_group_key)s AS column_group_key,
COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency,
SUM(%(debit_select)s) AS debit,
SUM(%(credit_select)s) AS credit,
SUM(%(balance_select)s) AS balance
FROM %(table_references)s
%(currency_table_join)s
WHERE %(search_condition)s
GROUP BY account_move_line.company_id
""",
column_group_key=column_group_key,
table_references=query.from_clause,
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
currency_table_join=report._currency_table_aml_join(options_group),
search_condition=query.where_clause,
))
return SQL(" UNION ALL ").join(queries)
def _get_options_unaffected_earnings(self, options):
''' Create options used to compute the unaffected earnings.
The unaffected earnings are the amount of benefits/loss that have not been allocated to
another account in the previous fiscal years.
The resulting dates domain will be:
[
('date' <= fiscalyear['date_from'] - 1),
('account_id.include_initial_balance', '=', False),
]
:param options: The report options.
:return: A copy of the options.
'''
new_options = options.copy()
new_options.pop('filter_search_bar', None)
fiscalyear_dates = self.env.company.compute_fiscalyear_dates(fields.Date.from_string(options['date']['date_from']))
# Trial balance uses the options key, general ledger does not
new_date_to = fields.Date.from_string(new_options['date']['date_to']) if options.get('include_current_year_in_unaff_earnings') else fiscalyear_dates['date_from'] - timedelta(days=1)
new_options['date'] = self.env['account.report']._get_dates_period(None, new_date_to, 'single')
return new_options
def _get_aml_values(self, report, options, expanded_account_ids, offset=0, limit=None):
rslt = {account_id: {} for account_id in expanded_account_ids}
aml_query = self._get_query_amls(report, options, expanded_account_ids, offset=offset, limit=limit)
self._cr.execute(aml_query)
aml_results_number = 0
has_more = False
for aml_result in self._cr.dictfetchall():
aml_results_number += 1
if aml_results_number == limit:
has_more = True
break
# For asset_receivable the name will already contains the ref with the _compute_name
if aml_result['ref'] and aml_result['account_type'] != 'asset_receivable':
aml_result['communication'] = f"{aml_result['ref']} - {aml_result['name']}"
else:
aml_result['communication'] = aml_result['name']
# The same aml can return multiple results when using account_report_cash_basis module, if the receivable/payable
# is reconciled with multiple payments. In this case, the date shown for the move lines actually corresponds to the
# reconciliation date. In order to keep distinct lines in this case, we include date in the grouping key.
aml_key = (aml_result['id'], aml_result['date'])
account_result = rslt[aml_result['account_id']]
if not aml_key in account_result:
account_result[aml_key] = {col_group_key: {} for col_group_key in options['column_groups']}
already_present_result = account_result[aml_key][aml_result['column_group_key']]
if already_present_result:
# In case the same move line gives multiple results at the same date, add them.
# This does not happen in standard GL report, but could because of custom shadowing of account.move.line,
# such as the one done in account_report_cash_basis (if the payable/receivable line is reconciled twice at the same date).
already_present_result['debit'] += aml_result['debit']
already_present_result['credit'] += aml_result['credit']
already_present_result['balance'] += aml_result['balance']
already_present_result['amount_currency'] += aml_result['amount_currency']
else:
account_result[aml_key][aml_result['column_group_key']] = aml_result
return rslt, has_more
def _get_query_amls(self, report, options, expanded_account_ids, offset=0, limit=None) -> SQL:
""" Construct a query retrieving the account.move.lines when expanding a report line with or without the load
more.
:param options: The report options.
:param expanded_account_ids: The account.account ids corresponding to consider. If None, match every account.
:param offset: The offset of the query (used by the load more).
:param limit: The limit of the query (used by the load more).
:return: (query, params)
"""
additional_domain = [('account_id', 'in', expanded_account_ids)] if expanded_account_ids is not None else None
queries = []
journal_name = self.env['account.journal']._field_to_sql('journal', 'name')
for column_group_key, group_options in report._split_options_per_column_group(options).items():
# Get sums for the account move lines.
# period: [('date' <= options['date_to']), ('date', '>=', options['date_from'])]
query = report._get_report_query(group_options, domain=additional_domain, date_scope='strict_range')
account_alias = query.join(lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id')
account_code = self.env['account.account']._field_to_sql(account_alias, 'code', query)
account_name = self.env['account.account']._field_to_sql(account_alias, 'name')
account_type = self.env['account.account']._field_to_sql(account_alias, 'account_type')
query = SQL(
'''
SELECT
account_move_line.id,
account_move_line.date,
account_move_line.date_maturity,
account_move_line.name,
account_move_line.ref,
account_move_line.company_id,
account_move_line.account_id,
account_move_line.payment_id,
account_move_line.partner_id,
account_move_line.currency_id,
account_move_line.amount_currency,
COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date,
account_move_line.date AS date,
%(debit_select)s AS debit,
%(credit_select)s AS credit,
%(balance_select)s AS balance,
move.name AS move_name,
company.currency_id AS company_currency_id,
partner.name AS partner_name,
move.move_type AS move_type,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
%(account_type)s AS account_type,
journal.code AS journal_code,
%(journal_name)s AS journal_name,
full_rec.id AS full_rec_name,
%(column_group_key)s AS column_group_key
FROM %(table_references)s
JOIN account_move move ON move.id = account_move_line.move_id
%(currency_table_join)s
LEFT JOIN res_company company ON company.id = account_move_line.company_id
LEFT JOIN res_partner partner ON partner.id = account_move_line.partner_id
LEFT JOIN account_journal journal ON journal.id = account_move_line.journal_id
LEFT JOIN account_full_reconcile full_rec ON full_rec.id = account_move_line.full_reconcile_id
WHERE %(search_condition)s
ORDER BY account_move_line.date, account_move_line.move_name, account_move_line.id
''',
account_code=account_code,
account_name=account_name,
account_type=account_type,
journal_name=journal_name,
column_group_key=column_group_key,
table_references=query.from_clause,
currency_table_join=report._currency_table_aml_join(group_options),
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
search_condition=query.where_clause,
)
queries.append(query)
full_query = SQL(" UNION ALL ").join(SQL("(%s)", query) for query in queries)
if offset:
full_query = SQL('%s OFFSET %s ', full_query, offset)
if limit:
full_query = SQL('%s LIMIT %s ', full_query, limit)
return full_query
def _get_initial_balance_values(self, report, account_ids, options):
"""
Get sums for the initial balance.
"""
queries = []
for column_group_key, options_group in report._split_options_per_column_group(options).items():
new_options = self._get_options_initial_balance(options_group)
domain = [
('account_id', 'in', account_ids),
]
if not new_options.get('general_ledger_strict_range'):
domain += [
'|',
('date', '>=', new_options['date']['date_from']),
('account_id.include_initial_balance', '=', True),
]
if new_options.get('include_current_year_in_unaff_earnings'):
domain += [('account_id.include_initial_balance', '=', True)]
query = report._get_report_query(new_options, 'from_beginning', domain=domain)
queries.append(SQL(
"""
SELECT
account_move_line.account_id AS groupby,
'initial_balance' AS key,
NULL AS max_date,
%(column_group_key)s AS column_group_key,
COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency,
SUM(%(debit_select)s) AS debit,
SUM(%(credit_select)s) AS credit,
SUM(%(balance_select)s) AS balance
FROM %(table_references)s
%(currency_table_join)s
WHERE %(search_condition)s
GROUP BY account_move_line.account_id
""",
column_group_key=column_group_key,
table_references=query.from_clause,
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
currency_table_join=report._currency_table_aml_join(options_group),
search_condition=query.where_clause,
))
self._cr.execute(SQL(" UNION ALL ").join(queries))
init_balance_by_col_group = {
account_id: {column_group_key: {} for column_group_key in options['column_groups']}
for account_id in account_ids
}
for result in self._cr.dictfetchall():
init_balance_by_col_group[result['groupby']][result['column_group_key']] = result
accounts = self.env['account.account'].browse(account_ids)
return {
account.id: (account, init_balance_by_col_group[account.id])
for account in accounts
}
def _get_options_initial_balance(self, options):
""" Create options used to compute the initial balances.
The initial balances depict the current balance of the accounts at the beginning of
the selected period in the report.
The resulting dates domain will be:
[
('date' <= options['date_from'] - 1),
'|',
('date' >= fiscalyear['date_from']),
('account_id.include_initial_balance', '=', True)
]
:param options: The report options.
:return: A copy of the options.
"""
#pylint: disable=sql-injection
new_options = options.copy()
date_to = new_options['comparison']['periods'][-1]['date_from'] if new_options.get('comparison', {}).get('periods') else new_options['date']['date_from']
new_date_to = fields.Date.from_string(date_to) - timedelta(days=1)
# Date from computation
# We have two case:
# 1) We are choosing a date that starts at the beginning of a fiscal year and we want the initial period to be
# the previous fiscal year
# 2) We are choosing a date that starts in the middle of a fiscal year and in that case we want the initial period
# to be the beginning of the fiscal year
date_from = fields.Date.from_string(new_options['date']['date_from'])
current_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from)
if date_from == current_fiscalyear_dates['date_from']:
# We want the previous fiscal year
previous_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from - timedelta(days=1))
new_date_from = previous_fiscalyear_dates['date_from']
include_current_year_in_unaff_earnings = True
else:
# We want the current fiscal year
new_date_from = current_fiscalyear_dates['date_from']
include_current_year_in_unaff_earnings = False
new_options['date'] = self.env['account.report']._get_dates_period(
new_date_from,
new_date_to,
'range',
)
new_options['include_current_year_in_unaff_earnings'] = include_current_year_in_unaff_earnings
return new_options
####################################################
# COLUMN/LINE HELPERS
####################################################
def _get_account_title_line(self, report, options, account, has_lines, eval_dict):
line_columns = []
for column in options['columns']:
col_value = eval_dict.get(column['column_group_key'], {}).get(column['expression_label'])
col_expr_label = column['expression_label']
value = None if col_value is None or (col_expr_label == 'amount_currency' and not account.currency_id) else col_value
line_columns.append(report._build_column_dict(
value,
column,
options=options,
currency=account.currency_id if col_expr_label == 'amount_currency' else None,
))
line_id = report._get_generic_line_id('account.account', account.id)
is_in_unfolded_lines = any(
report._get_res_id_from_line_id(line_id, 'account.account') == account.id
for line_id in options.get('unfolded_lines')
)
return {
'id': line_id,
'name': account.display_name,
'columns': line_columns,
'level': 1,
'unfoldable': has_lines,
'unfolded': has_lines and (is_in_unfolded_lines or options.get('unfold_all')),
'expand_function': '_report_expand_unfoldable_line_general_ledger',
}
def _get_aml_line(self, report, parent_line_id, options, eval_dict, init_bal_by_col_group):
line_columns = []
for column in options['columns']:
col_expr_label = column['expression_label']
col_value = eval_dict[column['column_group_key']].get(col_expr_label)
col_currency = None
if col_value is not None:
if col_expr_label == 'amount_currency':
col_currency = self.env['res.currency'].browse(eval_dict[column['column_group_key']]['currency_id'])
col_value = None if col_currency == self.env.company.currency_id else col_value
elif col_expr_label == 'balance':
col_value += (init_bal_by_col_group[column['column_group_key']] or 0)
line_columns.append(report._build_column_dict(
col_value,
column,
options=options,
currency=col_currency,
))
aml_id = None
move_name = None
caret_type = None
for column_group_dict in eval_dict.values():
aml_id = column_group_dict.get('id', '')
if aml_id:
if column_group_dict.get('payment_id'):
caret_type = 'account.payment'
else:
caret_type = 'account.move.line'
move_name = column_group_dict['move_name']
date = str(column_group_dict.get('date', ''))
break
return {
'id': report._get_generic_line_id('account.move.line', aml_id, parent_line_id=parent_line_id, markup=date),
'caret_options': caret_type,
'parent_id': parent_line_id,
'name': move_name,
'columns': line_columns,
'level': 3,
}
@api.model
def _get_total_line(self, report, options, eval_dict):
line_columns = []
for column in options['columns']:
col_value = eval_dict[column['column_group_key']].get(column['expression_label'])
col_value = None if col_value is None else col_value
line_columns.append(report._build_column_dict(col_value, column, options=options))
return {
'id': report._get_generic_line_id(None, None, markup='total'),
'name': _('Total'),
'level': 1,
'columns': line_columns,
}
def caret_option_audit_tax(self, options, params):
return self.env['account.generic.tax.report.handler'].caret_option_audit_tax(options, params)
def _report_expand_unfoldable_line_general_ledger(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None):
def init_load_more_progress(line_dict):
return {
column['column_group_key']: line_col.get('no_format', 0)
for column, line_col in zip(options['columns'], line_dict['columns'])
if column['expression_label'] == 'balance'
}
report = self.env.ref('at_accounting.general_ledger_report')
model, model_id = report._get_model_info_from_id(line_dict_id)
if model != 'account.account':
raise UserError(_("Wrong ID for general ledger line to expand: %s", line_dict_id))
lines = []
# Get initial balance
if offset == 0:
if unfold_all_batch_data:
account, init_balance_by_col_group = unfold_all_batch_data['initial_balances'][model_id]
else:
account, init_balance_by_col_group = self._get_initial_balance_values(report, [model_id], options)[model_id]
initial_balance_line = report._get_partner_and_general_ledger_initial_balance_line(options, line_dict_id, init_balance_by_col_group, account.currency_id)
if initial_balance_line:
lines.append(initial_balance_line)
# For the first expansion of the line, the initial balance line gives the progress
progress = init_load_more_progress(initial_balance_line)
# Get move lines
limit_to_load = report.load_more_limit + 1 if report.load_more_limit and options['export_mode'] != 'print' else None
if unfold_all_batch_data:
aml_results = unfold_all_batch_data['aml_results'][model_id]
has_more = unfold_all_batch_data['has_more'].get(model_id, False)
else:
aml_results, has_more = self._get_aml_values(report, options, [model_id], offset=offset, limit=limit_to_load)
aml_results = aml_results[model_id]
next_progress = progress
for aml_result in aml_results.values():
new_line = self._get_aml_line(report, line_dict_id, options, aml_result, next_progress)
lines.append(new_line)
next_progress = init_load_more_progress(new_line)
return {
'lines': lines,
'offset_increment': report.load_more_limit,
'has_more': has_more,
'progress': next_progress,
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,299 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, tools, _
from odoo.addons.base.models.res_bank import sanitize_account_number
from odoo.exceptions import UserError, RedirectWarning
class AccountJournal(models.Model):
_inherit = "account.journal"
def _get_bank_statements_available_import_formats(self):
""" Returns a list of strings representing the supported import formats.
"""
return []
def __get_bank_statements_available_sources(self):
rslt = super(AccountJournal, self).__get_bank_statements_available_sources()
formats_list = self._get_bank_statements_available_import_formats()
if formats_list:
formats_list.sort()
import_formats_str = ', '.join(formats_list)
rslt.append(("file_import", _("Manual (or import %(import_formats)s)", import_formats=import_formats_str)))
return rslt
def create_document_from_attachment(self, attachment_ids=None):
journal = self or self.browse(self.env.context.get('default_journal_id'))
if journal.type in ('bank', 'credit', 'cash'):
attachments = self.env['ir.attachment'].browse(attachment_ids)
if not attachments:
raise UserError(_("No attachment was provided"))
return journal._import_bank_statement(attachments)
return super().create_document_from_attachment(attachment_ids)
def _import_bank_statement(self, attachments):
""" Process the file chosen in the wizard, create bank statement(s) and go to reconciliation. """
if any(not a.raw for a in attachments):
raise UserError(_("You uploaded an invalid or empty file."))
statement_ids_all = []
notifications_all = {}
errors = {}
# Let the appropriate implementation module parse the file and return the required data
# The active_id is passed in context in case an implementation module requires information about the wizard state (see QIF)
for attachment in attachments:
try:
currency_code, account_number, stmts_vals = self._parse_bank_statement_file(attachment)
# Check raw data
self._check_parsed_data(stmts_vals, account_number)
# Try to find the currency and journal in odoo
journal = self._find_additional_data(currency_code, account_number)
# If no journal found, ask the user about creating one
if not journal.default_account_id:
raise UserError(_('You have to set a Default Account for the journal: %s', journal.name))
# Prepare statement data to be used for bank statements creation
stmts_vals = self._complete_bank_statement_vals(stmts_vals, journal, account_number, attachment)
# Create the bank statements
statement_ids, dummy, notifications = self._create_bank_statements(stmts_vals)
statement_ids_all.extend(statement_ids)
# Now that the import worked out, set it as the bank_statements_source of the journal
if journal.bank_statements_source != 'file_import':
# Use sudo() because only 'account.group_account_manager'
# has write access on 'account.journal', but 'account.group_account_user'
# must be able to import bank statement files
journal.sudo().bank_statements_source = 'file_import'
msg = ""
for notif in notifications:
msg += (
f"{notif['message']}"
)
if notifications:
notifications_all[attachment.name] = msg
except (UserError, RedirectWarning) as e:
errors[attachment.name] = e.args[0]
statements = self.env['account.bank.statement'].browse(statement_ids_all)
line_to_reconcile = statements.line_ids
if line_to_reconcile:
# 'limit_time_real_cron' defaults to -1.
# Manual fallback applied for non-POSIX systems where this key is disabled (set to None).
cron_limit_time = tools.config['limit_time_real_cron'] or -1
limit_time = cron_limit_time if 0 < cron_limit_time < 180 else 180
line_to_reconcile._cron_try_auto_reconcile_statement_lines(limit_time=limit_time)
result = self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
extra_domain=[('statement_id', 'in', statements.ids)],
default_context={
'search_default_not_matched': True,
'default_journal_id': statements[:1].journal_id.id,
'notifications': notifications_all,
},
)
if errors:
error_msg = _("The following files could not be imported:\n")
error_msg += "\n".join([f"- {attachment_name}: {msg}" for attachment_name, msg in errors.items()])
if statements:
self.env.cr.commit() # save the correctly uploaded statements to the db before raising the errors
raise RedirectWarning(error_msg, result, _('View successfully imported statements'))
else:
raise UserError(error_msg)
return result
def _parse_bank_statement_file(self, attachment) -> tuple:
""" Each module adding a file support must extends this method. It processes the file if it can, returns super otherwise, resulting in a chain of responsability.
This method parses the given file and returns the data required by the bank statement import process, as specified below.
rtype: triplet (if a value can't be retrieved, use None)
- currency code: string (e.g: 'EUR')
The ISO 4217 currency code, case insensitive
- account number: string (e.g: 'BE1234567890')
The number of the bank account which the statement belongs to
- bank statements data: list of dict containing (optional items marked by o) :
- 'name': string (e.g: '000000123')
- 'date': date (e.g: 2013-06-26)
-o 'balance_start': float (e.g: 8368.56)
-o 'balance_end_real': float (e.g: 8888.88)
- 'transactions': list of dict containing :
- 'name': string (e.g: 'KBC-INVESTERINGSKREDIET 787-5562831-01')
- 'date': date
- 'amount': float
- 'unique_import_id': string
-o 'account_number': string
Will be used to find/create the res.partner.bank in odoo
-o 'note': string
-o 'partner_name': string
-o 'ref': string
"""
raise RedirectWarning(
message=_("Could not make sense of the given file.\nDid you install the module to support this type of file?"),
action=self.env.ref('base.open_module_tree').id,
button_text=_("Go to Apps"),
additional_context={
'search_default_name': 'account_bank_statement_import',
'search_default_extra': True,
},
)
def _check_parsed_data(self, stmts_vals, account_number):
""" Basic and structural verifications """
if len(stmts_vals) == 0:
raise UserError(_(
'This file doesn\'t contain any statement for account %s.\nIf it contains transactions for more than one account, it must be imported on each of them.',
account_number,
))
no_st_line = True
for vals in stmts_vals:
if vals['transactions'] and len(vals['transactions']) > 0:
no_st_line = False
break
if no_st_line:
raise UserError(_(
'This file doesn\'t contain any transaction for account %s.\nIf it contains transactions for more than one account, it must be imported on each of them.',
account_number,
))
def _statement_import_check_bank_account(self, account_number):
# Needed for CH to accommodate for non-unique account numbers
sanitized_acc_number = self.bank_account_id.sanitized_acc_number.split(" ")[0]
# Needed for BNP France
if len(sanitized_acc_number) == 27 and len(account_number) == 11 and sanitized_acc_number[:2].upper() == "FR":
return sanitized_acc_number[14:-2] == account_number
# Needed for Credit Lyonnais (LCL)
if len(sanitized_acc_number) == 27 and len(account_number) == 7 and sanitized_acc_number[:2].upper() == "FR":
return sanitized_acc_number[18:-2] == account_number
return sanitized_acc_number == account_number
def _find_additional_data(self, currency_code, account_number):
""" Look for the account.journal using values extracted from the
statement and make sure it's consistent.
"""
company_currency = self.env.company.currency_id
currency = None
sanitized_account_number = sanitize_account_number(account_number)
if currency_code:
currency = self.env['res.currency'].search([('name', '=ilike', currency_code)], limit=1)
if not currency:
raise UserError(_("No currency found matching '%s'.", currency_code))
if currency == company_currency:
currency = False
journal = self
if account_number:
# No bank account on the journal : create one from the account number of the statement
if journal and not journal.bank_account_id:
journal.set_bank_account(account_number)
# No journal passed to the wizard : try to find one using the account number of the statement
elif not journal:
journal = self.search([('bank_account_id.sanitized_acc_number', '=', sanitized_account_number)])
if not journal:
# Sometimes the bank returns only part of the full account number (e.g. local account number instead of full IBAN)
partial_match = self.search([('bank_account_id.sanitized_acc_number', 'ilike', sanitized_account_number)])
if len(partial_match) == 1:
journal = partial_match
# Already a bank account on the journal : check it's the same as on the statement
else:
if not self._statement_import_check_bank_account(sanitized_account_number):
raise UserError(_('The account of this statement (%(account)s) is not the same as the journal (%(journal)s).', account=account_number, journal=journal.bank_account_id.acc_number))
# If importing into an existing journal, its currency must be the same as the bank statement
if journal:
journal_currency = journal.currency_id or journal.company_id.currency_id
if currency is None:
currency = journal_currency
if currency and currency != journal_currency:
statement_cur_code = not currency and company_currency.name or currency.name
journal_cur_code = not journal_currency and company_currency.name or journal_currency.name
raise UserError(_('The currency of the bank statement (%(code)s) is not the same as the currency of the journal (%(journal)s).', code=statement_cur_code, journal=journal_cur_code))
if not journal:
raise UserError(_('Cannot find in which journal import this statement. Please manually select a journal.'))
return journal
def _complete_bank_statement_vals(self, stmts_vals, journal, account_number, attachment):
for st_vals in stmts_vals:
if not st_vals.get('reference'):
st_vals['reference'] = attachment.name
for line_vals in st_vals['transactions']:
line_vals['journal_id'] = journal.id
unique_import_id = line_vals.get('unique_import_id')
if unique_import_id:
sanitized_account_number = sanitize_account_number(account_number)
line_vals['unique_import_id'] = (sanitized_account_number and sanitized_account_number + '-' or '') + str(journal.id) + '-' + unique_import_id
if not line_vals.get('partner_bank_id'):
# Find the partner and his bank account or create the bank account. The partner selected during the
# reconciliation process will be linked to the bank when the statement is closed.
identifying_string = line_vals.get('account_number')
if identifying_string:
if line_vals.get('partner_id'):
partner_bank = self.env['res.partner.bank'].search([
('acc_number', '=', identifying_string),
('partner_id', '=', line_vals['partner_id'])
])
else:
partner_bank = self.env['res.partner.bank'].search([
('acc_number', '=', identifying_string),
('company_id', 'in', (False, journal.company_id.id))
])
# If multiple partners share the same account number, do not try to guess and just avoid setting it
if partner_bank and len(partner_bank) == 1:
line_vals['partner_bank_id'] = partner_bank.id
line_vals['partner_id'] = partner_bank.partner_id.id
return stmts_vals
def _create_bank_statements(self, stmts_vals, raise_no_imported_file=True):
""" Create new bank statements from imported values, filtering out already imported transactions, and returns data used by the reconciliation widget """
BankStatement = self.env['account.bank.statement']
BankStatementLine = self.env['account.bank.statement.line']
# Filter out already imported transactions and create statements
statement_ids = []
statement_line_ids = []
ignored_statement_lines_import_ids = []
for st_vals in stmts_vals:
filtered_st_lines = []
for line_vals in st_vals['transactions']:
if (line_vals['amount'] != 0
and ('unique_import_id' not in line_vals
or not line_vals['unique_import_id']
or not bool(BankStatementLine.sudo().search([('unique_import_id', '=', line_vals['unique_import_id'])], limit=1)))):
filtered_st_lines.append(line_vals)
else:
ignored_statement_lines_import_ids.append(line_vals)
if st_vals.get('balance_start') is not None:
st_vals['balance_start'] += float(line_vals['amount'])
if len(filtered_st_lines) > 0:
# Remove values that won't be used to create records
st_vals.pop('transactions', None)
# Create the statement
st_vals['line_ids'] = [[0, False, line] for line in filtered_st_lines]
statement = BankStatement.with_context(default_journal_id=self.id).create(st_vals)
if not statement.name:
statement.name = st_vals['reference']
statement_ids.append(statement.id)
statement_line_ids.extend(statement.line_ids.ids)
# Create the report.
if statement.is_complete and not self._context.get('skip_pdf_attachment_generation'):
statement.action_generate_attachment()
if len(statement_line_ids) == 0 and raise_no_imported_file:
raise UserError(_('You already have imported that file.'))
# Prepare import feedback
notifications = []
num_ignored = len(ignored_statement_lines_import_ids)
if num_ignored > 0:
notifications += [{
'type': 'warning',
'message': _("%d transactions had already been imported and were ignored.", num_ignored)
if num_ignored > 1
else _("1 transaction had already been imported and was ignored."),
}]
return statement_ids, statement_line_ids, notifications

View File

@@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, models
from odoo.exceptions import UserError
class AccountJournal(models.Model):
_inherit = 'account.journal'
def _get_bank_statements_available_import_formats(self):
rslt = super()._get_bank_statements_available_import_formats()
rslt.extend(['CSV', 'XLS', 'XLSX'])
return rslt
def _check_file_format(self, filename):
return filename and filename.lower().strip().endswith(('.csv', '.xls', '.xlsx'))
def _import_bank_statement(self, attachments):
# In case of CSV files, only one file can be imported at a time.
if len(attachments) > 1:
csv = [bool(self._check_file_format(att.name)) for att in attachments]
if True in csv and False in csv:
raise UserError(_('Mixing CSV files with other file types is not allowed.'))
if csv.count(True) > 1:
raise UserError(_('Only one CSV file can be selected.'))
return super()._import_bank_statement(attachments)
if not self._check_file_format(attachments.name):
return super()._import_bank_statement(attachments)
ctx = dict(self.env.context)
import_wizard = self.env['base_import.import'].create({
'res_model': 'account.bank.statement.line',
'file': attachments.raw,
'file_name': attachments.name,
'file_type': attachments.mimetype,
})
ctx['wizard_id'] = import_wizard.id
ctx['default_journal_id'] = self.id
return {
'type': 'ir.actions.client',
'tag': 'import_bank_stmt',
'params': {
'model': 'account.bank.statement.line',
'context': ctx,
'filename': 'bank_statement_import.csv',
}
}

View File

@@ -1,78 +0,0 @@
from odoo import models
import ast
class account_journal(models.Model):
_inherit = "account.journal"
def action_open_reconcile(self):
self.ensure_one()
if self.type in ('bank', 'cash', 'credit'):
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
default_context={
'default_journal_id': self.id,
'search_default_journal_id': self.id,
'search_default_not_matched': True,
},
)
else:
# Open reconciliation view for customers/suppliers
return self.env['ir.actions.act_window']._for_xml_id('at_accounting.action_move_line_posted_unreconciled')
def action_open_to_check(self):
self.ensure_one()
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
default_context={
'search_default_to_check': True,
'search_default_journal_id': self.id,
'default_journal_id': self.id,
},
)
def action_open_bank_transactions(self):
self.ensure_one()
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
default_context={
'search_default_journal_id': self.id,
'default_journal_id': self.id
},
kanban_first=False,
)
def action_open_reconcile_statement(self):
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
default_context={
'search_default_statement_id': self.env.context.get('statement_id'),
},
)
def open_action(self):
# EXTENDS account
# set default action for liquidity journals in dashboard
if self.type in ('bank', 'cash', 'credit') and not self._context.get('action_name'):
self.ensure_one()
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
extra_domain=[('line_ids.account_id', '=', self.default_account_id.id)],
default_context={
'default_journal_id': self.id,
'search_default_journal_id': self.id,
},
)
return super().open_action()
def _fill_general_dashboard_data(self, dashboard_data):
super()._fill_general_dashboard_data(dashboard_data)
for journal in self.filtered(lambda journal: journal.type == 'general'):
dashboard_data[journal.id]['is_account_tax_periodicity_journal'] = journal == journal.company_id._get_tax_closing_journal()
def action_open_bank_balance_in_gl(self):
''' Show the bank balance inside the General Ledger report.
:return: An action opening the General Ledger.
'''
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("at_accounting.action_account_report_general_ledger")
action['context'] = dict(ast.literal_eval(action['context']), default_filter_accounts=self.default_account_id.code)
return action

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, fields, _
from odoo.exceptions import UserError
from odoo.tools import SQL
class AccountMoveLine(models.Model):
_name = "account.move.line"
_inherit = "account.move.line"
exclude_bank_lines = fields.Boolean(compute='_compute_exclude_bank_lines', store=True)
@api.depends('journal_id')
def _compute_exclude_bank_lines(self):
for move_line in self:
move_line.exclude_bank_lines = move_line.account_id != move_line.journal_id.default_account_id
@api.constrains('tax_ids', 'tax_tag_ids')
def _check_taxes_on_closing_entries(self):
for aml in self:
if aml.move_id.tax_closing_report_id and (aml.tax_ids or aml.tax_tag_ids):
raise UserError(_("You cannot add taxes on a tax closing move line."))
@api.depends('product_id', 'product_uom_id', 'move_id.tax_closing_report_id')
def _compute_tax_ids(self):
""" Some special cases may see accounts used in tax closing having default taxes.
They would trigger the constrains above, which we don't want. Instead, we don't trigger
the tax computation in this case.
"""
# EXTEND account
lines_to_compute = self.filtered(lambda line: not line.move_id.tax_closing_report_id)
(self - lines_to_compute).tax_ids = False
super(AccountMoveLine, lines_to_compute)._compute_tax_ids()
@api.model
def _prepare_aml_shadowing_for_report(self, change_equivalence_dict):
""" Prepares the fields lists for creating a temporary table shadowing the account_move_line one.
This is used to switch the computation mode of the reports, with analytics or financial budgets, for example.
:param change_equivalence_dict: A dict, in the form {aml_field: sql_equivalence}, where:
- aml_field: is a string containing the name of field of account.move.line
- sql_equivalence: is the value to use to shadow aml_field. It can be an SQL object; if
it's not, it'll be escaped in the query.
:return: A tuple of 2 SQL objects, so that:
- The first one is the fields list to pass into the INSERT TO part of the query filling up the temporary table
- The second one contains the field values to insert into the SELECT clause of the same query, in the same order
as in the first element of the returned tuple.
"""
line_fields = self.env['account.move.line'].fields_get()
self.env.cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name='account_move_line'")
stored_fields = {f[0] for f in self.env.cr.fetchall() if f[0] in line_fields}
fields_to_insert = []
for fname in stored_fields:
if fname in change_equivalence_dict:
fields_to_insert.append(SQL(
"%(original)s AS %(asname)s",
original=change_equivalence_dict[fname],
asname=SQL('"account_move_line.%s"', SQL(fname)),
))
else:
line_field = line_fields[fname]
if line_field.get("translate"):
typecast = SQL('jsonb')
else:
typecast = SQL(self.env['account.move.line']._fields[fname].column_type[0])
fields_to_insert.append(SQL(
"CAST(NULL AS %(typecast)s) AS %(fname)s",
typecast=typecast,
fname=SQL('"account_move_line.%s"', SQL(fname)),
))
return SQL(', ').join(SQL.identifier(fname) for fname in stored_fields), SQL(', ').join(fields_to_insert)

View File

@@ -1,384 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api, _
from odoo.tools import float_is_zero, SQL
from odoo.exceptions import UserError
from itertools import chain
class MulticurrencyRevaluationReportCustomHandler(models.AbstractModel):
"""Manage Unrealized Gains/Losses.
In multi-currencies environments, we need a way to control the risk related
to currencies (in case some are higthly fluctuating) and, in some countries,
some laws also require to create journal entries to record the provisionning
of a probable future expense related to currencies. Hence, people need to
create a journal entry at the beginning of a period, to make visible the
probable expense in reports (and revert it at the end of the period, to
recon the real gain/loss.
"""
_name = 'account.multicurrency.revaluation.report.handler'
_inherit = 'account.report.custom.handler'
_description = 'Multicurrency Revaluation Report Custom Handler'
def _get_custom_display_config(self):
return {
'components': {
'AccountReportFilters': 'at_accounting.MulticurrencyRevaluationReportFilters',
},
'templates': {
'AccountReportLineName': 'at_accounting.MulticurrencyRevaluationReportLineName',
},
}
def _custom_options_initializer(self, report, options, previous_options):
super()._custom_options_initializer(report, options, previous_options=previous_options)
active_currencies = self.env['res.currency'].search([('active', '=', True)])
if len(active_currencies) < 2:
raise UserError(_("You need to activate more than one currency to access this report."))
rates = active_currencies._get_rates(self.env.company, options.get('date').get('date_to'))
# Normalize the rates to the company's currency
company_rate = rates[self.env.company.currency_id.id]
for key in rates.keys():
rates[key] /= company_rate
options['currency_rates'] = {
str(currency_id.id): {
'currency_id': currency_id.id,
'currency_name': currency_id.name,
'currency_main': self.env.company.currency_id.name,
'rate': (rates[currency_id.id]
if not previous_options.get('currency_rates', {}).get(str(currency_id.id), {}).get('rate') else
float(previous_options['currency_rates'][str(currency_id.id)]['rate'])),
} for currency_id in active_currencies
}
for currency_rates in options['currency_rates'].values():
if currency_rates['rate'] == 0:
raise UserError(_("The currency rate cannot be equal to zero"))
options['company_currency'] = options['currency_rates'].pop(str(self.env.company.currency_id.id))
options['custom_rate'] = any(
not float_is_zero(cr['rate'] - rates[cr['currency_id']], 20)
for cr in options['currency_rates'].values()
)
options['multi_currency'] = True
options['buttons'].append({'name': _('Adjustment Entry'), 'sequence': 30, 'action': 'action_multi_currency_revaluation_open_revaluation_wizard', 'always_show': True})
def _customize_warnings(self, report, options, all_column_groups_expression_totals, warnings):
if len(self.env.companies) > 1:
warnings['at_accounting.multi_currency_revaluation_report_warning_multicompany'] = {'alert_type': 'warning'}
if options['custom_rate']:
warnings['at_accounting.multi_currency_revaluation_report_warning_custom_rate'] = {'alert_type': 'warning'}
def _custom_line_postprocessor(self, report, options, lines):
line_to_adjust_id = self.env.ref('at_accounting.multicurrency_revaluation_to_adjust').id
line_excluded_id = self.env.ref('at_accounting.multicurrency_revaluation_excluded').id
rslt = []
for index, line in enumerate(lines):
res_model_name, res_id = report._get_model_info_from_id(line['id'])
if res_model_name == 'account.report.line' and (
(res_id == line_to_adjust_id and report._get_model_info_from_id(lines[index + 1]['id']) == ('account.report.line', line_excluded_id)) or
(res_id == line_excluded_id and index == len(lines) - 1)
):
# 'To Adjust' and 'Excluded' lines need to be hidden if they have no child
continue
elif res_model_name == 'res.currency':
# Include the rate in the currency_id group lines
line['name'] = '{for_cur} (1 {comp_cur} = {rate:.6} {for_cur})'.format(
for_cur=line['name'],
comp_cur=self.env.company.currency_id.display_name,
rate=float(options['currency_rates'][str(res_id)]['rate']),
)
elif res_model_name == 'account.account':
# Mark the included/excluded lines, so that the custom component templates knows what label to put on them
line['is_included_line'] = report._get_res_id_from_line_id(line['id'], 'account.account') == line_to_adjust_id
# Inject the related model into the line dict in order to use it on the custom component template on js side to display buttons
line['cur_revaluation_line_model'] = res_model_name
rslt.append(line)
return rslt
def _custom_groupby_line_completer(self, report, options, line_dict):
model_info_from_id = report._get_model_info_from_id(line_dict['id'])
if model_info_from_id[0] == 'res.currency':
line_dict['unfolded'] = True
line_dict['unfoldable'] = False
def action_multi_currency_revaluation_open_revaluation_wizard(self, options):
"""Open the revaluation wizard."""
form = self.env.ref('at_accounting.view_account_multicurrency_revaluation_wizard', False)
return {
'name': _("Make Adjustment Entry"),
'type': 'ir.actions.act_window',
'res_model': 'account.multicurrency.revaluation.wizard',
'view_mode': 'form',
'view_id': form.id,
'views': [(form.id, 'form')],
'multi': 'True',
'target': 'new',
'context': {
**self._context,
'multicurrency_revaluation_report_options': options,
},
}
# ACTIONS
def action_multi_currency_revaluation_open_general_ledger(self, options, params):
report = self.env['account.report'].browse(options['report_id'])
account_id = report._get_res_id_from_line_id(params['line_id'], 'account.account')
account_line_id = report._get_generic_line_id('account.account', account_id)
general_ledger_options = self.env.ref('at_accounting.general_ledger_report').get_options(options)
general_ledger_options['unfolded_lines'] = [account_line_id]
general_ledger_action = self.env['ir.actions.actions']._for_xml_id('at_accounting.action_account_report_general_ledger')
general_ledger_action['params'] = {
'options': general_ledger_options,
'ignore_session': True,
}
return general_ledger_action
def action_multi_currency_revaluation_toggle_provision(self, options, params):
""" Include/exclude an account from the provision. """
res_ids_map = self.env['account.report']._get_res_ids_from_line_id(params['line_id'], ['res.currency', 'account.account'])
account = self.env['account.account'].browse(res_ids_map['account.account'])
currency = self.env['res.currency'].browse(res_ids_map['res.currency'])
if currency in account.exclude_provision_currency_ids:
account.exclude_provision_currency_ids -= currency
else:
account.exclude_provision_currency_ids += currency
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def action_multi_currency_revaluation_open_currency_rates(self, options, params=None):
""" Open the currency rate list. """
currency_id = self.env['account.report']._get_res_id_from_line_id(params['line_id'], 'res.currency')
return {
'type': 'ir.actions.act_window',
'name': _('Currency Rates (%s)', self.env['res.currency'].browse(currency_id).display_name),
'views': [(False, 'list')],
'res_model': 'res.currency.rate',
'context': {**self.env.context, **{'default_currency_id': currency_id, 'active_id': currency_id}},
'domain': [('currency_id', '=', currency_id)],
}
def _report_custom_engine_multi_currency_revaluation_to_adjust(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
return self._multi_currency_revaluation_get_custom_lines(options, 'to_adjust', current_groupby, next_groupby, offset=offset, limit=limit)
def _report_custom_engine_multi_currency_revaluation_excluded(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
return self._multi_currency_revaluation_get_custom_lines(options, 'excluded', current_groupby, next_groupby, offset=offset, limit=limit)
def _multi_currency_revaluation_get_custom_lines(self, options, line_code, current_groupby, next_groupby, offset=0, limit=None):
def build_result_dict(report, query_res):
return {
'balance_currency': query_res['balance_currency'] if len(query_res['currency_id']) == 1 else None,
'currency_id': query_res['currency_id'][0] if len(query_res['currency_id']) == 1 else None,
'balance_operation': query_res['balance_operation'],
'balance_current': query_res['balance_current'],
'adjustment': query_res['adjustment'],
'has_sublines': query_res['aml_count'] > 0,
}
report = self.env['account.report'].browse(options['report_id'])
report._check_groupby_fields((next_groupby.split(',') if next_groupby else []) + ([current_groupby] if current_groupby else []))
# No need to run any SQL if we're computing the main line: it does not display any total
if not current_groupby:
return {
'balance_currency': None,
'currency_id': None,
'balance_operation': None,
'balance_current': None,
'adjustment': None,
'has_sublines': False,
}
query = "(VALUES {})".format(', '.join("(%s, %s)" for rate in options['currency_rates']))
params = list(chain.from_iterable((cur['currency_id'], cur['rate']) for cur in options['currency_rates'].values()))
custom_currency_table_query = SQL(query, *params)
date_to = options['date']['date_to']
select_part_not_an_exchange_move_id = SQL(
"""
NOT EXISTS (
SELECT 1
FROM account_partial_reconcile part_exch
WHERE part_exch.exchange_move_id = account_move_line.move_id
AND part_exch.max_date <= %s
)
""",
date_to
)
query = report._get_report_query(options, 'strict_range')
tail_query = report._get_engine_query_tail(offset, limit)
full_query = SQL(
"""
WITH custom_currency_table(currency_id, rate) AS (%(custom_currency_table_query)s)
-- Final select that gets the following lines:
-- (where there is a change in the rates of currency between the creation of the move and the full payments)
-- - Moves that don't have a payment yet at a certain date
-- - Moves that have a partial but are not fully paid at a certain date
SELECT
subquery.grouping_key,
ARRAY_AGG(DISTINCT(subquery.currency_id)) AS currency_id,
SUM(subquery.balance_currency) AS balance_currency,
SUM(subquery.balance_operation) AS balance_operation,
SUM(subquery.balance_current) AS balance_current,
SUM(subquery.adjustment) AS adjustment,
COUNT(subquery.aml_id) AS aml_count
FROM (
-- Get moves that have at least one partial at a certain date and are not fully paid at that date
SELECT
""" + (f"account_move_line.{current_groupby} AS grouping_key," if current_groupby else '') + f"""
ROUND(account_move_line.balance - SUM(ara.amount_debit) + SUM(ara.amount_credit), aml_comp_currency.decimal_places) AS balance_operation,
ROUND(account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) AS balance_currency,
ROUND(account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) / custom_currency_table.rate AS balance_current,
(
-- adjustment is computed as: balance_current - balance_operation
ROUND( account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) / custom_currency_table.rate
- ROUND(account_move_line.balance - SUM(ara.amount_debit) + SUM(ara.amount_credit), aml_comp_currency.decimal_places)
) AS adjustment,
account_move_line.currency_id AS currency_id,
account_move_line.id AS aml_id
FROM %(table_references)s,
account_account AS account,
res_currency AS aml_currency,
res_currency AS aml_comp_currency,
custom_currency_table,
-- Get for each move line the amount residual and amount_residual currency
-- both for matched "debit" and matched "credit" the same way as account.move.line
-- '_compute_amount_residual()' method does
-- (using LATERAL greatly reduce the number of lines for which we have to compute it)
LATERAL (
-- Get sum of matched "debit" amount and amount in currency for related move line at date
SELECT COALESCE(SUM(part.amount), 0.0) AS amount_debit,
ROUND(
SUM(part.debit_amount_currency),
curr.decimal_places
) AS amount_debit_currency,
0.0 AS amount_credit,
0.0 AS amount_credit_currency,
account_move_line.currency_id AS currency_id,
account_move_line.id AS aml_id
FROM account_partial_reconcile part
JOIN res_currency curr ON curr.id = part.debit_currency_id
WHERE account_move_line.id = part.debit_move_id
AND part.max_date <= %(date_to)s
GROUP BY aml_id,
curr.decimal_places
UNION
-- Get sum of matched "credit" amount and amount in currency for related move line at date
SELECT 0.0 AS amount_debit,
0.0 AS amount_debit_currency,
COALESCE(SUM(part.amount), 0.0) AS amount_credit,
ROUND(
SUM(part.credit_amount_currency),
curr.decimal_places
) AS amount_credit_currency,
account_move_line.currency_id AS currency_id,
account_move_line.id AS aml_id
FROM account_partial_reconcile part
JOIN res_currency curr ON curr.id = part.credit_currency_id
WHERE account_move_line.id = part.credit_move_id
AND part.max_date <= %(date_to)s
GROUP BY aml_id,
curr.decimal_places
) AS ara
WHERE %(search_condition)s
AND account_move_line.account_id = account.id
AND account_move_line.currency_id = aml_currency.id
AND account_move_line.company_currency_id = aml_comp_currency.id
AND account_move_line.currency_id = custom_currency_table.currency_id
AND account.account_type NOT IN ('income', 'income_other', 'expense', 'expense_depreciation', 'expense_direct_cost', 'off_balance')
AND (
account.currency_id != account_move_line.company_currency_id
OR (
account.account_type IN ('asset_receivable', 'liability_payable')
AND (account_move_line.currency_id != account_move_line.company_currency_id)
)
)
AND {'NOT EXISTS' if line_code == 'to_adjust' else 'EXISTS'} (
SELECT 1
FROM account_account_exclude_res_currency_provision
WHERE account_account_id = account_move_line.account_id
AND res_currency_id = account_move_line.currency_id
)
AND (%(select_part_not_an_exchange_move_id)s)
GROUP BY account_move_line.id, aml_comp_currency.decimal_places, aml_currency.decimal_places, custom_currency_table.rate
HAVING ROUND(account_move_line.balance - SUM(ara.amount_debit) + SUM(ara.amount_credit), aml_comp_currency.decimal_places) != 0
OR ROUND(account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) != 0.0
UNION
-- Moves that don't have a payment yet at a certain date
SELECT
""" + (f"account_move_line.{current_groupby} AS grouping_key," if current_groupby else '') + f"""
account_move_line.balance AS balance_operation,
account_move_line.amount_currency AS balance_currency,
account_move_line.amount_currency / custom_currency_table.rate AS balance_current,
account_move_line.amount_currency / custom_currency_table.rate - account_move_line.balance AS adjustment,
account_move_line.currency_id AS currency_id,
account_move_line.id AS aml_id
FROM %(table_references)s
JOIN account_account account ON account_move_line.account_id = account.id
JOIN custom_currency_table ON custom_currency_table.currency_id = account_move_line.currency_id
WHERE %(search_condition)s
AND account.account_type NOT IN ('income', 'income_other', 'expense', 'expense_depreciation', 'expense_direct_cost', 'off_balance')
AND (
account.currency_id != account_move_line.company_currency_id
OR (
account.account_type IN ('asset_receivable', 'liability_payable')
AND (account_move_line.currency_id != account_move_line.company_currency_id)
)
)
AND {'NOT EXISTS' if line_code == 'to_adjust' else 'EXISTS'} (
SELECT 1
FROM account_account_exclude_res_currency_provision
WHERE account_account_id = account_id
AND res_currency_id = account_move_line.currency_id
)
AND (%(select_part_not_an_exchange_move_id)s)
AND NOT EXISTS (
SELECT 1 FROM account_partial_reconcile part
WHERE (part.debit_move_id = account_move_line.id OR part.credit_move_id = account_move_line.id)
AND part.max_date <= %(date_to)s
)
AND (account_move_line.balance != 0.0 OR account_move_line.amount_currency != 0.0)
) subquery
GROUP BY grouping_key
ORDER BY grouping_key
%(tail_query)s
""",
custom_currency_table_query=custom_currency_table_query,
table_references=query.from_clause,
date_to=date_to,
tail_query=tail_query,
search_condition=query.where_clause,
select_part_not_an_exchange_move_id=select_part_not_an_exchange_move_id,
)
self._cr.execute(full_query)
query_res_lines = self._cr.dictfetchall()
if not current_groupby:
return build_result_dict(report, query_res_lines and query_res_lines[0] or {})
else:
rslt = []
for query_res in query_res_lines:
grouping_key = query_res['grouping_key']
rslt.append((grouping_key, build_result_dict(report, query_res)))
return rslt

View File

@@ -1,771 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, _, fields
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools import SQL
from datetime import timedelta
from collections import defaultdict
class PartnerLedgerCustomHandler(models.AbstractModel):
_name = 'account.partner.ledger.report.handler'
_inherit = 'account.report.custom.handler'
_description = 'Partner Ledger Custom Handler'
def _get_custom_display_config(self):
return {
'css_custom_class': 'partner_ledger',
'components': {
'AccountReportLineCell': 'at_accounting.PartnerLedgerLineCell',
},
'templates': {
'AccountReportFilters': 'at_accounting.PartnerLedgerFilters',
'AccountReportLineName': 'at_accounting.PartnerLedgerLineName',
},
}
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
partner_lines, totals_by_column_group = self._build_partner_lines(report, options)
lines = report._regroup_lines_by_name_prefix(options, partner_lines, '_report_expand_unfoldable_line_partner_ledger_prefix_group', 0)
# Inject sequence on dynamic lines
lines = [(0, line) for line in lines]
# Report total line.
lines.append((0, self._get_report_line_total(options, totals_by_column_group)))
return lines
def _build_partner_lines(self, report, options, level_shift=0):
lines = []
totals_by_column_group = {
column_group_key: {
total: 0.0
for total in ['debit', 'credit', 'amount', 'balance']
}
for column_group_key in options['column_groups']
}
partners_results = self._query_partners(options)
search_filter = options.get('filter_search_bar', '')
accept_unknown_in_filter = search_filter.lower() in self._get_no_partner_line_label().lower()
for partner, results in partners_results:
if options['export_mode'] == 'print' and search_filter and not partner and not accept_unknown_in_filter:
# When printing and searching for a specific partner, make it so we only show its lines, not the 'Unknown Partner' one, that would be
# shown in case a misc entry with no partner was reconciled with one of the target partner's entries.
continue
partner_values = defaultdict(dict)
for column_group_key in options['column_groups']:
partner_sum = results.get(column_group_key, {})
partner_values[column_group_key]['debit'] = partner_sum.get('debit', 0.0)
partner_values[column_group_key]['credit'] = partner_sum.get('credit', 0.0)
partner_values[column_group_key]['amount'] = partner_sum.get('amount', 0.0)
partner_values[column_group_key]['balance'] = partner_sum.get('balance', 0.0)
totals_by_column_group[column_group_key]['debit'] += partner_values[column_group_key]['debit']
totals_by_column_group[column_group_key]['credit'] += partner_values[column_group_key]['credit']
totals_by_column_group[column_group_key]['amount'] += partner_values[column_group_key]['amount']
totals_by_column_group[column_group_key]['balance'] += partner_values[column_group_key]['balance']
lines.append(self._get_report_line_partners(options, partner, partner_values, level_shift=level_shift))
return lines, totals_by_column_group
def _report_expand_unfoldable_line_partner_ledger_prefix_group(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None):
report = self.env['account.report'].browse(options['report_id'])
matched_prefix = report._get_prefix_groups_matched_prefix_from_line_id(line_dict_id)
prefix_domain = [('partner_id.name', '=ilike', f'{matched_prefix}%')]
if self._get_no_partner_line_label().upper().startswith(matched_prefix):
prefix_domain = expression.OR([prefix_domain, [('partner_id', '=', None)]])
expand_options = {
**options,
'forced_domain': options.get('forced_domain', []) + prefix_domain
}
parent_level = len(matched_prefix) * 2
partner_lines, dummy = self._build_partner_lines(report, expand_options, level_shift=parent_level)
for partner_line in partner_lines:
partner_line['id'] = report._build_subline_id(line_dict_id, partner_line['id'])
partner_line['parent_id'] = line_dict_id
lines = report._regroup_lines_by_name_prefix(
options,
partner_lines,
'_report_expand_unfoldable_line_partner_ledger_prefix_group',
parent_level,
matched_prefix=matched_prefix,
parent_line_dict_id=line_dict_id,
)
return {
'lines': lines,
'offset_increment': len(lines),
'has_more': False,
}
def _custom_options_initializer(self, report, options, previous_options):
super()._custom_options_initializer(report, options, previous_options=previous_options)
domain = []
company_ids = report.get_report_company_ids(options)
exch_code = self.env['res.company'].browse(company_ids).mapped('currency_exchange_journal_id')
if exch_code:
domain += ['!', '&', '&', '&', ('credit', '=', 0.0), ('debit', '=', 0.0), ('amount_currency', '!=', 0.0), ('journal_id', 'in', exch_code.ids)]
if options['export_mode'] == 'print' and options.get('filter_search_bar'):
domain += [
'|', ('matched_debit_ids.debit_move_id.partner_id.name', 'ilike', options['filter_search_bar']),
'|', ('matched_credit_ids.credit_move_id.partner_id.name', 'ilike', options['filter_search_bar']),
('partner_id.name', 'ilike', options['filter_search_bar']),
]
options['forced_domain'] = options.get('forced_domain', []) + domain
if self.env.user.has_group('base.group_multi_currency'):
options['multi_currency'] = True
columns_to_hide = []
options['hide_account'] = (previous_options or {}).get('hide_account', False)
columns_to_hide += ['journal_code', 'account_code', 'matching_number'] if options['hide_account'] else []
options['hide_debit_credit'] = (previous_options or {}).get('hide_debit_credit', False)
columns_to_hide += ['debit', 'credit'] if options['hide_debit_credit'] else ['amount']
options['columns'] = [col for col in options['columns'] if col['expression_label'] not in columns_to_hide]
options['buttons'].append({
'name': _('Send'),
'action': 'action_send_statements',
'sequence': 90,
'always_show': True,
})
def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function):
partner_ids_to_expand = []
# Regular case
for line_dict in lines_to_expand_by_function.get('_report_expand_unfoldable_line_partner_ledger', []):
markup, model, model_id = self.env['account.report']._parse_line_id(line_dict['id'])[-1]
if model == 'res.partner':
partner_ids_to_expand.append(model_id)
elif markup == 'no_partner':
partner_ids_to_expand.append(None)
# In case prefix groups are used
no_partner_line_label = self._get_no_partner_line_label().upper()
partner_prefix_domains = []
for line_dict in lines_to_expand_by_function.get('_report_expand_unfoldable_line_partner_ledger_prefix_group', []):
prefix = report._get_prefix_groups_matched_prefix_from_line_id(line_dict['id'])
partner_prefix_domains.append([('name', '=ilike', f'{prefix}%')])
# amls without partners are regrouped "Unknown Partner", which is also used to create prefix groups
if no_partner_line_label.startswith(prefix):
partner_ids_to_expand.append(None)
if partner_prefix_domains:
partner_ids_to_expand += self.env['res.partner'].with_context(active_test=False).search(expression.OR(partner_prefix_domains)).ids
return {
'initial_balances': self._get_initial_balance_values(partner_ids_to_expand, options) if partner_ids_to_expand else {},
# load_more_limit cannot be passed to this call, otherwise it won't be applied per partner but on the whole result.
# We gain perf from batching, but load every result, even if the limit restricts them later.
'aml_values': self._get_aml_values(options, partner_ids_to_expand) if partner_ids_to_expand else {},
}
def _get_report_send_recipients(self, options):
partners = options.get('partner_ids', [])
if not partners:
self._cr.execute(self._get_query_sums(options))
partners = [row['groupby'] for row in self._cr.dictfetchall() if row['groupby']]
return self.env['res.partner'].browse(partners)
def action_send_statements(self, options):
template = self.env.ref('at_accounting.email_template_customer_statement', False)
return {
'name': _("Send Partner Ledgers"),
'type': 'ir.actions.act_window',
'views': [[False, 'form']],
'res_model': 'account.report.send',
'target': 'new',
'context': {
'default_mail_template_id': template.id if template else False,
'default_report_options': options,
},
}
@api.model
def action_open_partner(self, options, params):
dummy, record_id = self.env['account.report']._get_model_info_from_id(params['id'])
return {
'type': 'ir.actions.act_window',
'res_model': 'res.partner',
'res_id': record_id,
'views': [[False, 'form']],
'view_mode': 'form',
'target': 'current',
}
def _query_partners(self, options):
""" Executes the queries and performs all the computation.
:return: A list of tuple (partner, column_group_values) sorted by the table's model _order:
- partner is a res.parter record.
- column_group_values is a dict(column_group_key, fetched_values), where
- column_group_key is a string identifying a column group, like in options['column_groups']
- fetched_values is a dictionary containing:
- sum: {'debit': float, 'credit': float, 'balance': float}
- (optional) initial_balance: {'debit': float, 'credit': float, 'balance': float}
- (optional) lines: [line_vals_1, line_vals_2, ...]
"""
def assign_sum(row):
fields_to_assign = ['balance', 'debit', 'credit', 'amount']
if any(not company_currency.is_zero(row[field]) for field in fields_to_assign):
groupby_partners.setdefault(row['groupby'], defaultdict(lambda: defaultdict(float)))
for field in fields_to_assign:
groupby_partners[row['groupby']][row['column_group_key']][field] += row[field]
company_currency = self.env.company.currency_id
# Execute the queries and dispatch the results.
query = self._get_query_sums(options)
groupby_partners = {}
self._cr.execute(query)
for res in self._cr.dictfetchall():
assign_sum(res)
# Correct the sums per partner, for the lines without partner reconciled with a line having a partner
query = self._get_sums_without_partner(options)
self._cr.execute(query)
totals = {}
for total_field in ['debit', 'credit', 'amount', 'balance']:
totals[total_field] = {col_group_key: 0 for col_group_key in options['column_groups']}
for row in self._cr.dictfetchall():
totals['debit'][row['column_group_key']] += row['debit']
totals['credit'][row['column_group_key']] += row['credit']
totals['amount'][row['column_group_key']] += row['amount']
totals['balance'][row['column_group_key']] += row['balance']
if row['groupby'] not in groupby_partners:
continue
assign_sum(row)
if None in groupby_partners:
# Debit/credit are inverted for the unknown partner as the computation is made regarding the balance of the known partner
for column_group_key in options['column_groups']:
groupby_partners[None][column_group_key]['debit'] += totals['credit'][column_group_key]
groupby_partners[None][column_group_key]['credit'] += totals['debit'][column_group_key]
groupby_partners[None][column_group_key]['amount'] += totals['amount'][column_group_key]
groupby_partners[None][column_group_key]['balance'] -= totals['balance'][column_group_key]
# Retrieve the partners to browse.
# groupby_partners.keys() contains all account ids affected by:
# - the amls in the current period.
# - the amls affecting the initial balance.
if groupby_partners:
# Note a search is done instead of a browse to preserve the table ordering.
partners = self.env['res.partner'].with_context(active_test=False).search_fetch([('id', 'in', list(groupby_partners.keys()))], ["id", "name", "trust", "company_registry", "vat"])
else:
partners = []
# Add 'Partner Unknown' if needed
if None in groupby_partners.keys():
partners = [p for p in partners] + [None]
return [(partner, groupby_partners[partner.id if partner else None]) for partner in partners]
def _get_query_sums(self, options) -> SQL:
""" Construct a query retrieving all the aggregated sums to build the report. It includes:
- sums for all partners.
- sums for the initial balances.
:param options: The report options.
:return: query as SQL object
"""
queries = []
report = self.env.ref('at_accounting.partner_ledger_report')
# Create the currency table.
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
query = report._get_report_query(column_group_options, 'from_beginning')
queries.append(SQL(
"""
SELECT
account_move_line.partner_id AS groupby,
%(column_group_key)s AS column_group_key,
SUM(%(debit_select)s) AS debit,
SUM(%(credit_select)s) AS credit,
SUM(%(balance_select)s) AS amount,
SUM(%(balance_select)s) AS balance
FROM %(table_references)s
%(currency_table_join)s
WHERE %(search_condition)s
GROUP BY account_move_line.partner_id
""",
column_group_key=column_group_key,
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
table_references=query.from_clause,
currency_table_join=report._currency_table_aml_join(column_group_options),
search_condition=query.where_clause,
))
return SQL(' UNION ALL ').join(queries)
def _get_initial_balance_values(self, partner_ids, options):
queries = []
report = self.env.ref('at_accounting.partner_ledger_report')
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
# Get sums for the initial balance.
# period: [('date' <= options['date_from'] - 1)]
new_options = self._get_options_initial_balance(column_group_options)
query = report._get_report_query(new_options, 'from_beginning', domain=[('partner_id', 'in', partner_ids)])
queries.append(SQL(
"""
SELECT
account_move_line.partner_id,
%(column_group_key)s AS column_group_key,
SUM(%(debit_select)s) AS debit,
SUM(%(credit_select)s) AS credit,
SUM(%(balance_select)s) AS amount,
SUM(%(balance_select)s) AS balance
FROM %(table_references)s
%(currency_table_join)s
WHERE %(search_condition)s
GROUP BY account_move_line.partner_id
""",
column_group_key=column_group_key,
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
table_references=query.from_clause,
currency_table_join=report._currency_table_aml_join(column_group_options),
search_condition=query.where_clause,
))
self._cr.execute(SQL(" UNION ALL ").join(queries))
init_balance_by_col_group = {
partner_id: {column_group_key: {} for column_group_key in options['column_groups']}
for partner_id in partner_ids
}
for result in self._cr.dictfetchall():
init_balance_by_col_group[result['partner_id']][result['column_group_key']] = result
return init_balance_by_col_group
def _get_options_initial_balance(self, options):
""" Create options used to compute the initial balances for each partner.
The resulting dates domain will be:
[('date' <= options['date_from'] - 1)]
:param options: The report options.
:return: A copy of the options, modified to match the dates to use to get the initial balances.
"""
new_date_to = fields.Date.from_string(options['date']['date_from']) - timedelta(days=1)
new_date_options = dict(options['date'], date_from=False, date_to=fields.Date.to_string(new_date_to))
return dict(options, date=new_date_options)
def _get_sums_without_partner(self, options):
""" Get the sum of lines without partner reconciled with a line with a partner, grouped by partner. Those lines
should be considered as belonging to the partner for the reconciled amount as it may clear some of the partner
invoice/bill and they have to be accounted in the partner balance."""
queries = []
report = self.env.ref('at_accounting.partner_ledger_report')
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
query = report._get_report_query(column_group_options, 'from_beginning')
queries.append(SQL(
"""
SELECT
%(column_group_key)s AS column_group_key,
aml_with_partner.partner_id AS groupby,
SUM(%(debit_select)s) AS debit,
SUM(%(credit_select)s) AS credit,
SUM(%(balance_select)s) AS amount,
SUM(%(balance_select)s) AS balance
FROM %(table_references)s
JOIN account_partial_reconcile partial
ON account_move_line.id = partial.debit_move_id OR account_move_line.id = partial.credit_move_id
JOIN account_move_line aml_with_partner ON
(aml_with_partner.id = partial.debit_move_id OR aml_with_partner.id = partial.credit_move_id)
AND aml_with_partner.partner_id IS NOT NULL
%(currency_table_join)s
WHERE partial.max_date <= %(date_to)s AND %(search_condition)s
AND account_move_line.partner_id IS NULL
GROUP BY aml_with_partner.partner_id
""",
column_group_key=column_group_key,
debit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance > 0 THEN 0 ELSE partial.amount END")),
credit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance < 0 THEN 0 ELSE partial.amount END")),
balance_select=report._currency_table_apply_rate(SQL("-SIGN(aml_with_partner.balance) * partial.amount")),
table_references=query.from_clause,
currency_table_join=report._currency_table_aml_join(column_group_options, aml_alias=SQL("aml_with_partner")),
date_to=column_group_options['date']['date_to'],
search_condition=query.where_clause,
))
return SQL(" UNION ALL ").join(queries)
def _report_expand_unfoldable_line_partner_ledger(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None):
def init_load_more_progress(line_dict):
return {
column['column_group_key']: line_col.get('no_format', 0)
for column, line_col in zip(options['columns'], line_dict['columns'])
if column['expression_label'] == 'balance'
}
report = self.env.ref('at_accounting.partner_ledger_report')
markup, model, record_id = report._parse_line_id(line_dict_id)[-1]
if model != 'res.partner':
raise UserError(_("Wrong ID for partner ledger line to expand: %s", line_dict_id))
prefix_groups_count = 0
for markup, dummy1, dummy2 in report._parse_line_id(line_dict_id):
if isinstance(markup, dict) and 'groupby_prefix_group' in markup:
prefix_groups_count += 1
level_shift = prefix_groups_count * 2
lines = []
# Get initial balance
if offset == 0:
if unfold_all_batch_data:
init_balance_by_col_group = unfold_all_batch_data['initial_balances'][record_id]
else:
init_balance_by_col_group = self._get_initial_balance_values([record_id], options)[record_id]
initial_balance_line = report._get_partner_and_general_ledger_initial_balance_line(options, line_dict_id, init_balance_by_col_group, level_shift=level_shift)
if initial_balance_line:
lines.append(initial_balance_line)
# For the first expansion of the line, the initial balance line gives the progress
progress = init_load_more_progress(initial_balance_line)
limit_to_load = report.load_more_limit + 1 if report.load_more_limit and options['export_mode'] != 'print' else None
if unfold_all_batch_data:
aml_results = unfold_all_batch_data['aml_values'][record_id]
else:
aml_results = self._get_aml_values(options, [record_id], offset=offset, limit=limit_to_load)[record_id]
has_more = False
treated_results_count = 0
next_progress = progress
for result in aml_results:
if options['export_mode'] != 'print' and report.load_more_limit and treated_results_count == report.load_more_limit:
# We loaded one more than the limit on purpose: this way we know we need a "load more" line
has_more = True
break
new_line = self._get_report_line_move_line(options, result, line_dict_id, next_progress, level_shift=level_shift)
lines.append(new_line)
next_progress = init_load_more_progress(new_line)
treated_results_count += 1
return {
'lines': lines,
'offset_increment': treated_results_count,
'has_more': has_more,
'progress': next_progress
}
def _get_additional_column_aml_values(self):
"""
Allows customization of additional fields in the partner ledger query.
This method is intended to be overridden by other modules to add custom fields
to the partner ledger query, e.g. SQL("account_move_line.date AS date,").
By default, it returns an empty SQL object.
"""
return SQL()
def _get_aml_values(self, options, partner_ids, offset=0, limit=None):
rslt = {partner_id: [] for partner_id in partner_ids}
partner_ids_wo_none = [x for x in partner_ids if x]
directly_linked_aml_partner_clauses = []
indirectly_linked_aml_partner_clause = SQL('aml_with_partner.partner_id IS NOT NULL')
if None in partner_ids:
directly_linked_aml_partner_clauses.append(SQL('account_move_line.partner_id IS NULL'))
if partner_ids_wo_none:
directly_linked_aml_partner_clauses.append(SQL('account_move_line.partner_id IN %s', tuple(partner_ids_wo_none)))
indirectly_linked_aml_partner_clause = SQL('aml_with_partner.partner_id IN %s', tuple(partner_ids_wo_none))
directly_linked_aml_partner_clause = SQL('(%s)', SQL(' OR ').join(directly_linked_aml_partner_clauses))
queries = []
journal_name = self.env['account.journal']._field_to_sql('journal', 'name')
report = self.env.ref('at_accounting.partner_ledger_report')
additional_columns = self._get_additional_column_aml_values()
for column_group_key, group_options in report._split_options_per_column_group(options).items():
query = report._get_report_query(group_options, 'strict_range')
account_alias = query.left_join(lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id')
account_code = self.env['account.account']._field_to_sql(account_alias, 'code', query)
account_name = self.env['account.account']._field_to_sql(account_alias, 'name')
# For the move lines directly linked to this partner
# ruff: noqa: FURB113
queries.append(SQL(
'''
SELECT
account_move_line.id,
account_move_line.date_maturity,
account_move_line.name,
account_move_line.ref,
account_move_line.company_id,
account_move_line.account_id,
account_move_line.payment_id,
account_move_line.partner_id,
account_move_line.currency_id,
account_move_line.amount_currency,
account_move_line.matching_number,
%(additional_columns)s
COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date,
%(debit_select)s AS debit,
%(credit_select)s AS credit,
%(balance_select)s AS amount,
%(balance_select)s AS balance,
account_move.name AS move_name,
account_move.move_type AS move_type,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
journal.code AS journal_code,
%(journal_name)s AS journal_name,
%(column_group_key)s AS column_group_key,
'directly_linked_aml' AS key,
0 AS partial_id
FROM %(table_references)s
JOIN account_move ON account_move.id = account_move_line.move_id
%(currency_table_join)s
LEFT JOIN res_company company ON company.id = account_move_line.company_id
LEFT JOIN res_partner partner ON partner.id = account_move_line.partner_id
LEFT JOIN account_journal journal ON journal.id = account_move_line.journal_id
WHERE %(search_condition)s AND %(directly_linked_aml_partner_clause)s
ORDER BY account_move_line.date, account_move_line.id
''',
additional_columns=additional_columns,
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
account_code=account_code,
account_name=account_name,
journal_name=journal_name,
column_group_key=column_group_key,
table_references=query.from_clause,
currency_table_join=report._currency_table_aml_join(group_options),
search_condition=query.where_clause,
directly_linked_aml_partner_clause=directly_linked_aml_partner_clause,
))
# For the move lines linked to no partner, but reconciled with this partner. They will appear in grey in the report
queries.append(SQL(
'''
SELECT
account_move_line.id,
account_move_line.date_maturity,
account_move_line.name,
account_move_line.ref,
account_move_line.company_id,
account_move_line.account_id,
account_move_line.payment_id,
aml_with_partner.partner_id,
account_move_line.currency_id,
account_move_line.amount_currency,
account_move_line.matching_number,
%(additional_columns)s
COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date,
%(debit_select)s AS debit,
%(credit_select)s AS credit,
%(balance_select)s AS amount,
%(balance_select)s AS balance,
account_move.name AS move_name,
account_move.move_type AS move_type,
%(account_code)s AS account_code,
%(account_name)s AS account_name,
journal.code AS journal_code,
%(journal_name)s AS journal_name,
%(column_group_key)s AS column_group_key,
'indirectly_linked_aml' AS key,
partial.id AS partial_id
FROM %(table_references)s
%(currency_table_join)s,
account_partial_reconcile partial,
account_move,
account_move_line aml_with_partner,
account_journal journal
WHERE
(account_move_line.id = partial.debit_move_id OR account_move_line.id = partial.credit_move_id)
AND account_move_line.partner_id IS NULL
AND account_move.id = account_move_line.move_id
AND (aml_with_partner.id = partial.debit_move_id OR aml_with_partner.id = partial.credit_move_id)
AND %(indirectly_linked_aml_partner_clause)s
AND journal.id = account_move_line.journal_id
AND %(account_alias)s.id = account_move_line.account_id
AND %(search_condition)s
AND partial.max_date BETWEEN %(date_from)s AND %(date_to)s
ORDER BY account_move_line.date, account_move_line.id
''',
additional_columns=additional_columns,
debit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance > 0 THEN 0 ELSE partial.amount END")),
credit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance < 0 THEN 0 ELSE partial.amount END")),
balance_select=report._currency_table_apply_rate(SQL("-SIGN(aml_with_partner.balance) * partial.amount")),
account_code=account_code,
account_name=account_name,
journal_name=journal_name,
column_group_key=column_group_key,
table_references=query.from_clause,
currency_table_join=report._currency_table_aml_join(group_options),
indirectly_linked_aml_partner_clause=indirectly_linked_aml_partner_clause,
account_alias=SQL.identifier(account_alias),
search_condition=query.where_clause,
date_from=group_options['date']['date_from'],
date_to=group_options['date']['date_to'],
))
query = SQL(" UNION ALL ").join(SQL("(%s)", query) for query in queries)
if offset:
query = SQL('%s OFFSET %s ', query, offset)
if limit:
query = SQL('%s LIMIT %s ', query, limit)
self._cr.execute(query)
for aml_result in self._cr.dictfetchall():
if aml_result['key'] == 'indirectly_linked_aml':
# Append the line to the partner found through the reconciliation.
if aml_result['partner_id'] in rslt:
rslt[aml_result['partner_id']].append(aml_result)
# Balance it with an additional line in the Unknown Partner section but having reversed amounts.
if None in rslt:
rslt[None].append({
**aml_result,
'debit': aml_result['credit'],
'credit': aml_result['debit'],
'amount': aml_result['credit'] - aml_result['debit'],
'balance': -aml_result['balance'],
})
else:
rslt[aml_result['partner_id']].append(aml_result)
return rslt
####################################################
# COLUMNS/LINES
####################################################
def _get_report_line_partners(self, options, partner, partner_values, level_shift=0):
company_currency = self.env.company.currency_id
partner_data = next(iter(partner_values.values()))
unfoldable = not company_currency.is_zero(partner_data.get('debit', 0) or partner_data.get('credit', 0))
column_values = []
report = self.env['account.report'].browse(options['report_id'])
for column in options['columns']:
col_expr_label = column['expression_label']
value = partner_values[column['column_group_key']].get(col_expr_label)
unfoldable = unfoldable or (col_expr_label in ('debit', 'credit', 'amount') and not company_currency.is_zero(value))
column_values.append(report._build_column_dict(value, column, options=options))
line_id = report._get_generic_line_id('res.partner', partner.id) if partner else report._get_generic_line_id('res.partner', None, markup='no_partner')
return {
'id': line_id,
'name': partner is not None and (partner.name or '')[:128] or self._get_no_partner_line_label(),
'columns': column_values,
'level': 1 + level_shift,
'trust': partner.trust if partner else None,
'unfoldable': unfoldable,
'unfolded': line_id in options['unfolded_lines'] or options['unfold_all'],
'expand_function': '_report_expand_unfoldable_line_partner_ledger',
}
def _get_no_partner_line_label(self):
return _('Unknown Partner')
@api.model
def _format_aml_name(self, line_name, move_ref, move_name=None):
''' Format the display of an account.move.line record. As its very costly to fetch the account.move.line
records, only line_name, move_ref, move_name are passed as parameters to deal with sql-queries more easily.
:param line_name: The name of the account.move.line record.
:param move_ref: The reference of the account.move record.
:param move_name: The name of the account.move record.
:return: The formatted name of the account.move.line record.
'''
return self.env['account.move.line']._format_aml_name(line_name, move_ref, move_name=move_name)
def _get_report_line_move_line(self, options, aml_query_result, partner_line_id, init_bal_by_col_group, level_shift=0):
if aml_query_result['payment_id']:
caret_type = 'account.payment'
else:
caret_type = 'account.move.line'
columns = []
report = self.env['account.report'].browse(options['report_id'])
for column in options['columns']:
col_expr_label = column['expression_label']
if col_expr_label not in aml_query_result:
raise UserError(_("The column '%s' is not available for this report.", col_expr_label))
col_value = aml_query_result[col_expr_label] if column['column_group_key'] == aml_query_result['column_group_key'] else None
if col_value is None:
columns.append(report._build_column_dict(None, None))
else:
currency = False
if col_expr_label == 'balance':
col_value += init_bal_by_col_group[column['column_group_key']]
if col_expr_label == 'amount_currency':
currency = self.env['res.currency'].browse(aml_query_result['currency_id'])
if currency == self.env.company.currency_id:
col_value = ''
columns.append(report._build_column_dict(col_value, column, options=options, currency=currency))
return {
'id': report._get_generic_line_id('account.move.line', aml_query_result['id'], parent_line_id=partner_line_id, markup=aml_query_result['partial_id']),
'parent_id': partner_line_id,
'name': self._format_aml_name(aml_query_result['name'], aml_query_result['ref'], aml_query_result['move_name']),
'columns': columns,
'caret_options': caret_type,
'level': 3 + level_shift,
}
def _get_report_line_total(self, options, totals_by_column_group):
column_values = []
report = self.env['account.report'].browse(options['report_id'])
for column in options['columns']:
col_value = totals_by_column_group[column['column_group_key']].get(column['expression_label'])
column_values.append(report._build_column_dict(col_value, column, options=options))
return {
'id': report._get_generic_line_id(None, None, markup='total'),
'name': _('Total'),
'level': 1,
'columns': column_values,
}
def open_journal_items(self, options, params):
params['view_ref'] = 'account.view_move_line_tree_grouped_partner'
report = self.env['account.report'].browse(options['report_id'])
action = report.open_journal_items(options=options, params=params)
action.get('context', {}).update({'search_default_group_by_account': 0})
return action

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
import ast
from odoo import models, _
class AccountPayment(models.Model):
_inherit = "account.payment"
def action_open_manual_reconciliation_widget(self):
''' Open the manual reconciliation widget for the current payment.
:return: A dictionary representing an action.
'''
self.ensure_one()
action_values = self.env['ir.actions.act_window']._for_xml_id('at_accounting.action_move_line_posted_unreconciled')
if self.partner_id:
context = ast.literal_eval(action_values['context'])
context.update({'search_default_partner_id': self.partner_id.id})
if self.partner_type == 'customer':
context.update({'search_default_trade_receivable': 1})
elif self.partner_type == 'supplier':
context.update({'search_default_trade_payable': 1})
action_values['context'] = context
return action_values
def button_open_statement_lines(self):
# OVERRIDE
""" Redirect the user to the statement line(s) reconciled to this payment.
:return: An action to open the view of the payment in the reconciliation widget.
"""
self.ensure_one()
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
extra_domain=[('id', 'in', self.reconciled_statement_line_ids.ids)],
default_context={
'create': False,
'default_st_line_id': self.reconciled_statement_line_ids.ids[-1],
},
name=_("Matched Transactions")
)

View File

@@ -1,557 +0,0 @@
from odoo import fields, models, Command, tools
from odoo.tools import SQL
import re
from collections import defaultdict
from dateutil.relativedelta import relativedelta
class AccountReconcileModel(models.Model):
_inherit = 'account.reconcile.model'
####################################################
# RECONCILIATION PROCESS
####################################################
def _apply_lines_for_bank_widget(self, residual_amount_currency, partner, st_line):
""" Apply the reconciliation model lines to the statement line passed as parameter.
:param residual_amount_currency: The open balance of the statement line in the bank reconciliation widget
expressed in the statement line currency.
:param partner: The partner set on the wizard.
:param st_line: The statement line processed by the bank reconciliation widget.
:return: A list of python dictionaries (one per reconcile model line) representing
the journal items to be created by the current reconcile model.
"""
self.ensure_one()
currency = st_line.foreign_currency_id or st_line.journal_id.currency_id or st_line.company_currency_id
vals_list = []
for line in self.line_ids:
vals = line._apply_in_bank_widget(residual_amount_currency, partner, st_line)
amount_currency = vals['amount_currency']
if currency.is_zero(amount_currency):
continue
vals_list.append(vals)
residual_amount_currency -= amount_currency
return vals_list
####################################################
# RECONCILIATION CRITERIA
####################################################
def _apply_rules(self, st_line, partner):
available_models = self.filtered(lambda m: m.rule_type != 'writeoff_button').sorted()
for rec_model in available_models:
if not rec_model._is_applicable_for(st_line, partner):
continue
if rec_model.rule_type == 'invoice_matching':
rules_map = rec_model._get_invoice_matching_rules_map()
for rule_index in sorted(rules_map.keys()):
for rule_method in rules_map[rule_index]:
candidate_vals = rule_method(st_line, partner)
if not candidate_vals:
continue
if candidate_vals.get('amls'):
res = rec_model._get_invoice_matching_amls_result(st_line, partner, candidate_vals)
if res:
return {
**res,
'model': rec_model,
}
else:
return {
**candidate_vals,
'model': rec_model,
}
elif rec_model.rule_type == 'writeoff_suggestion':
return {
'model': rec_model,
'status': 'write_off',
'auto_reconcile': rec_model.auto_reconcile,
}
return {}
def _is_applicable_for(self, st_line, partner):
""" Returns true iff this reconciliation model can be used to search for matches
for the provided statement line and partner.
"""
self.ensure_one()
# Filter on journals, amount nature, amount and partners
# All the conditions defined in this block are non-match conditions.
if ((self.match_journal_ids and st_line.move_id.journal_id not in self.match_journal_ids)
or (self.match_nature == 'amount_received' and st_line.amount < 0)
or (self.match_nature == 'amount_paid' and st_line.amount > 0)
or (self.match_amount == 'lower' and abs(st_line.amount) >= self.match_amount_max)
or (self.match_amount == 'greater' and abs(st_line.amount) <= self.match_amount_min)
or (self.match_amount == 'between' and (abs(st_line.amount) > self.match_amount_max or abs(st_line.amount) < self.match_amount_min))
or (self.match_partner and not partner)
or (self.match_partner and self.match_partner_ids and partner not in self.match_partner_ids)
or (self.match_partner and self.match_partner_category_ids and not (partner.category_id & self.match_partner_category_ids))
):
return False
# Filter on label, note and transaction_type
for record, rule_field, record_field in [(st_line, 'label', 'payment_ref'), (st_line.move_id, 'note', 'narration'), (st_line, 'transaction_type', 'transaction_type')]:
rule_term = (self['match_' + rule_field + '_param'] or '').lower()
record_term = (record[record_field] or '').lower()
# This defines non-match conditions
if ((self['match_' + rule_field] == 'contains' and rule_term not in record_term)
or (self['match_' + rule_field] == 'not_contains' and rule_term in record_term)
or (self['match_' + rule_field] == 'match_regex' and not re.match(rule_term, record_term))
):
return False
return True
def _get_invoice_matching_amls_domain(self, st_line, partner):
aml_domain = st_line._get_default_amls_matching_domain()
if st_line.amount > 0.0:
aml_domain.append(('balance', '>', 0.0))
else:
aml_domain.append(('balance', '<', 0.0))
currency = st_line.foreign_currency_id or st_line.currency_id
if self.match_same_currency:
aml_domain.append(('currency_id', '=', currency.id))
if partner:
aml_domain.append(('partner_id', '=', partner.id))
if self.past_months_limit:
date_limit = fields.Date.context_today(self) - relativedelta(months=self.past_months_limit)
aml_domain.append(('date', '>=', fields.Date.to_string(date_limit)))
return aml_domain
def _get_st_line_text_values_for_matching(self, st_line):
""" Collect the strings that could be used on the statement line to perform some matching.
:param st_line: The current statement line.
:return: A list of strings.
"""
self.ensure_one()
allowed_fields = []
if self.match_text_location_label:
allowed_fields.append('payment_ref')
if self.match_text_location_note:
allowed_fields.append('narration')
if self.match_text_location_reference:
allowed_fields.append('ref')
return st_line._get_st_line_strings_for_matching(allowed_fields=allowed_fields)
def _get_invoice_matching_st_line_tokens(self, st_line):
""" Parse the textual information from the statement line passed as parameter
in order to extract from it the meaningful information in order to perform the matching.
:param st_line: A statement line.
:return: A tuple of list of tokens, each one being a string.
The first element is a list of tokens you may match on numerical information.
The second element is a list of tokens you may match exactly.
"""
st_line_text_values = self._get_st_line_text_values_for_matching(st_line)
significant_token_size = 4
numerical_tokens = []
exact_tokens = set() # preventing duplicates
text_tokens = []
for text_value in st_line_text_values:
split_text = (text_value or '').split()
# Exact tokens
exact_tokens.add(text_value)
exact_tokens.update(
token for token in split_text
if len(token) >= significant_token_size
)
# Text tokens
tokens = [
''.join(x for x in token if re.match(r'[0-9a-zA-Z\s]', x))
for token in split_text
]
# Numerical tokens
for token in tokens:
# The token is too short to be significant.
if len(token) < significant_token_size:
continue
text_tokens.append(token)
formatted_token = ''.join(x for x in token if x.isdecimal())
# The token is too short after formatting to be significant.
if len(formatted_token) < significant_token_size:
continue
numerical_tokens.append(formatted_token)
return numerical_tokens, list(exact_tokens), text_tokens
def _get_invoice_matching_amls_candidates(self, st_line, partner):
""" Returns the match candidates for the 'invoice_matching' rule, with respect to the provided parameters.
:param st_line: A statement line.
:param partner: The partner associated to the statement line.
"""
def get_order_by_clause(prefix=SQL()):
direction = SQL(' DESC') if self.matching_order == 'new_first' else SQL(' ASC')
return SQL(", ").join(
SQL("%s%s%s", prefix, SQL(field), direction)
for field in ('date_maturity', 'date', 'id')
)
assert self.rule_type == 'invoice_matching'
self.env['account.move'].flush_model()
self.env['account.move.line'].flush_model()
aml_domain = self._get_invoice_matching_amls_domain(st_line, partner)
query = self.env['account.move.line']._where_calc(aml_domain)
tables = query.from_clause
where_clause = query.where_clause or SQL("TRUE")
aml_cte = SQL()
sub_queries: list[SQL] = []
numerical_tokens, exact_tokens, _text_tokens = self._get_invoice_matching_st_line_tokens(st_line)
if numerical_tokens or exact_tokens:
aml_cte = SQL('''
WITH aml_cte AS (
SELECT
account_move_line.id as account_move_line_id,
account_move_line.date as account_move_line_date,
account_move_line.date_maturity as account_move_line_date_maturity,
account_move_line.name as account_move_line_name,
account_move_line__move_id.name as account_move_line__move_id_name,
account_move_line__move_id.ref as account_move_line__move_id_ref
FROM %s
JOIN account_move account_move_line__move_id ON account_move_line__move_id.id = account_move_line.move_id
WHERE %s
)
''', tables, where_clause)
if numerical_tokens:
for table_alias, field in (
('account_move_line', 'name'),
('account_move_line__move_id', 'name'),
('account_move_line__move_id', 'ref'),
):
sub_queries.append(SQL(r'''
SELECT
account_move_line_id as id,
account_move_line_date as date,
account_move_line_date_maturity as date_maturity,
UNNEST(
REGEXP_SPLIT_TO_ARRAY(
SUBSTRING(
REGEXP_REPLACE(%(field)s, '[^0-9\s]', '', 'g'),
'\S(?:.*\S)*'
),
'\s+'
)
) AS token
FROM aml_cte
WHERE %(field)s IS NOT NULL
''', field=SQL("%s_%s", SQL(table_alias), SQL(field))))
if exact_tokens:
for table_alias, field in (
('account_move_line', 'name'),
('account_move_line__move_id', 'name'),
('account_move_line__move_id', 'ref'),
):
sub_queries.append(SQL('''
SELECT
account_move_line_id as id,
account_move_line_date as date,
account_move_line_date_maturity as date_maturity,
%(field)s AS token
FROM aml_cte
WHERE %(field)s != ''
''', field=SQL("%s_%s", SQL(table_alias), SQL(field))))
if sub_queries:
order_by = get_order_by_clause(prefix=SQL('sub.'))
candidate_ids = [r[0] for r in self.env.execute_query(SQL(
'''
%s
SELECT
sub.id,
COUNT(*) AS nb_match
FROM (%s) AS sub
WHERE sub.token IN %s
GROUP BY sub.date_maturity, sub.date, sub.id
HAVING COUNT(*) > 0
ORDER BY nb_match DESC, %s
''',
aml_cte,
SQL(" UNION ALL ").join(sub_queries),
tuple(numerical_tokens + exact_tokens),
order_by,
))]
if candidate_ids:
return {
'allow_auto_reconcile': True,
'amls': self.env['account.move.line'].browse(candidate_ids),
}
elif self.match_text_location_label or self.match_text_location_note or self.match_text_location_reference:
# In the case any of the Label, Note or Reference matching rule has been toggled, and the query didn't return
# any candidates, the model should not try to mount another aml instead.
return
if not partner:
st_line_currency = st_line.foreign_currency_id or st_line.journal_id.currency_id or st_line.company_currency_id
if st_line_currency == self.company_id.currency_id:
aml_amount_field = SQL('amount_residual')
else:
aml_amount_field = SQL('amount_residual_currency')
order_by = get_order_by_clause(prefix=SQL('account_move_line.'))
rows = self.env.execute_query(SQL(
'''
SELECT account_move_line.id
FROM %s
WHERE
%s
AND account_move_line.currency_id = %s
AND ROUND(account_move_line.%s, %s) = ROUND(%s, %s)
ORDER BY %s
''',
tables,
where_clause,
st_line_currency.id,
aml_amount_field,
st_line_currency.decimal_places,
-st_line.amount_residual,
st_line_currency.decimal_places,
order_by,
))
amls = self.env['account.move.line'].browse([row[0] for row in rows])
else:
amls = self.env['account.move.line'].search(aml_domain, order=get_order_by_clause().code)
if amls:
return {
'allow_auto_reconcile': False,
'amls': amls,
}
def _get_invoice_matching_rules_map(self):
""" Get a mapping <priority_order, rule> that could be overridden in others modules.
:return: a mapping <priority_order, rule> where:
* priority_order: Defines in which order the rules will be evaluated, the lowest comes first.
This is extremely important since the algorithm stops when a rule returns some candidates.
* rule: Method taking <st_line, partner> as parameters and returning the candidates journal items found.
"""
rules_map = defaultdict(list)
rules_map[10].append(self._get_invoice_matching_amls_candidates)
return rules_map
def _get_partner_from_mapping(self, st_line):
"""Find partner with mapping defined on model.
For invoice matching rules, matches the statement line against each
regex defined in partner mapping, and returns the partner corresponding
to the first one matching.
:param st_line (Model<account.bank.statement.line>):
The statement line that needs a partner to be found
:return Model<res.partner>:
The partner found from the mapping. Can be empty an empty recordset
if there was nothing found from the mapping or if the function is
not applicable.
"""
self.ensure_one()
if self.rule_type not in ('invoice_matching', 'writeoff_suggestion'):
return self.env['res.partner']
for partner_mapping in self.partner_mapping_line_ids:
match_payment_ref = True
if partner_mapping.payment_ref_regex:
match_payment_ref = re.match(partner_mapping.payment_ref_regex, st_line.payment_ref) if st_line.payment_ref else False
match_narration = True
if partner_mapping.narration_regex:
match_narration = re.match(
partner_mapping.narration_regex,
tools.html2plaintext(st_line.narration or '').rstrip(),
flags=re.DOTALL, # Ignore '/n' set by online sync.
)
if match_payment_ref and match_narration:
return partner_mapping.partner_id
return self.env['res.partner']
def _get_invoice_matching_amls_result(self, st_line, partner, candidate_vals):
def _create_result_dict(amls_values_list, status):
if 'rejected' in status:
return
result = {'amls': self.env['account.move.line']}
for aml_values in amls_values_list:
result['amls'] |= aml_values['aml']
if 'allow_write_off' in status and self.line_ids:
result['status'] = 'write_off'
if 'allow_auto_reconcile' in status and candidate_vals['allow_auto_reconcile'] and self.auto_reconcile:
result['auto_reconcile'] = True
return result
st_line_currency = st_line.foreign_currency_id or st_line.currency_id
st_line_amount = st_line._prepare_move_line_default_vals()[1]['amount_currency']
sign = 1 if st_line_amount > 0.0 else -1
amls = candidate_vals['amls']
amls_values_list = []
amls_with_epd_values_list = []
same_currency_mode = amls.currency_id == st_line_currency
for aml in amls:
aml_values = {
'aml': aml,
'amount_residual': aml.amount_residual,
'amount_residual_currency': aml.amount_residual_currency,
}
amls_values_list.append(aml_values)
# Manage the early payment discount.
if aml.move_id.invoice_payment_term_id:
last_discount_date = aml.move_id.invoice_payment_term_id._get_last_discount_date(aml.move_id.date)
else:
last_discount_date = False
if same_currency_mode \
and aml.move_id.move_type in ('out_invoice', 'out_receipt', 'in_invoice', 'in_receipt') \
and not aml.matched_debit_ids \
and not aml.matched_credit_ids \
and last_discount_date \
and st_line.date <= last_discount_date:
rate = abs(aml.amount_currency) / abs(aml.balance) if aml.balance else 1.0
amls_with_epd_values_list.append({
**aml_values,
'amount_residual': st_line.company_currency_id.round(aml.discount_amount_currency / rate),
'amount_residual_currency': aml.discount_amount_currency,
})
else:
amls_with_epd_values_list.append(aml_values)
def match_batch_amls(amls_values_list):
if not same_currency_mode:
return None, []
kepts_amls_values_list = []
sum_amount_residual_currency = 0.0
for aml_values in amls_values_list:
if st_line_currency.compare_amounts(st_line_amount, -aml_values['amount_residual_currency']) == 0:
# Special case: the amounts are the same, submit the line directly.
return 'perfect', [aml_values]
if st_line_currency.compare_amounts(sign * (st_line_amount + sum_amount_residual_currency), 0.0) > 0:
# Here, we still have room for other candidates ; so we add the current one to the list we keep.
# Then, we continue iterating, even if there is no room anymore, just in case one of the following candidates
# is an exact match, which would then be preferred on the current candidates.
kepts_amls_values_list.append(aml_values)
sum_amount_residual_currency += aml_values['amount_residual_currency']
if st_line_currency.is_zero(sign * (st_line_amount + sum_amount_residual_currency)):
return 'perfect', kepts_amls_values_list
elif kepts_amls_values_list:
return 'partial', kepts_amls_values_list
else:
return None, []
# Try to match a batch with the early payment feature. Only a perfect match is allowed.
match_type, kepts_amls_values_list = match_batch_amls(amls_with_epd_values_list)
if match_type != 'perfect':
kepts_amls_values_list = []
# Try to match the amls having the same currency as the statement line.
if not kepts_amls_values_list:
_match_type, kepts_amls_values_list = match_batch_amls(amls_values_list)
# Try to match the whole candidates.
if not kepts_amls_values_list:
kepts_amls_values_list = amls_values_list
# Try to match the amls having the same currency as the statement line.
if kepts_amls_values_list:
status = self._check_rule_propositions(st_line, kepts_amls_values_list)
result = _create_result_dict(kepts_amls_values_list, status)
if result:
return result
def _check_rule_propositions(self, st_line, amls_values_list):
""" Check restrictions that can't be handled for each move.line separately.
Note: Only used by models having a type equals to 'invoice_matching'.
:param st_line: The statement line.
:param amls_values_list: The candidates account.move.line as a list of dict:
* aml: The record.
* amount_residual: The amount residual to consider.
* amount_residual_currency: The amount residual in foreign currency to consider.
:return: A string representing what to do with the candidates:
* rejected: Reject candidates.
* allow_write_off: Allow to generate the write-off from the reconcile model lines if specified.
* allow_auto_reconcile: Allow to automatically reconcile entries if 'auto_validate' is enabled.
"""
self.ensure_one()
if not self.allow_payment_tolerance:
return {'allow_write_off', 'allow_auto_reconcile'}
st_line_currency = st_line.foreign_currency_id or st_line.currency_id
st_line_amount_curr = st_line._prepare_move_line_default_vals()[1]['amount_currency']
amls_amount_curr = sum(
st_line._prepare_counterpart_amounts_using_st_line_rate(
aml_values['aml'].currency_id,
aml_values['amount_residual'],
aml_values['amount_residual_currency'],
)['amount_currency']
for aml_values in amls_values_list
)
sign = 1 if st_line_amount_curr > 0.0 else -1
amount_curr_after_rec = st_line_currency.round(
sign * (amls_amount_curr + st_line_amount_curr)
)
# The statement line will be fully reconciled.
if st_line_currency.is_zero(amount_curr_after_rec):
return {'allow_auto_reconcile'}
# The payment amount is higher than the sum of invoices.
# In that case, don't check the tolerance and don't try to generate any write-off.
if amount_curr_after_rec > 0.0:
return {'allow_auto_reconcile'}
# No tolerance, reject the candidates.
if self.payment_tolerance_param == 0:
return {'rejected'}
# If the tolerance is expressed as a fixed amount, check the residual payment amount doesn't exceed the
# tolerance.
if self.payment_tolerance_type == 'fixed_amount' and st_line_currency.compare_amounts(-amount_curr_after_rec, self.payment_tolerance_param) <= 0:
return {'allow_write_off', 'allow_auto_reconcile'}
# The tolerance is expressed as a percentage between 0 and 100.0.
reconciled_percentage_left = (abs(amount_curr_after_rec / amls_amount_curr)) * 100.0
if self.payment_tolerance_type == 'percentage' and st_line_currency.compare_amounts(reconciled_percentage_left, self.payment_tolerance_param) <= 0:
return {'allow_write_off', 'allow_auto_reconcile'}
return {'rejected'}
def run_auto_reconciliation(self):
""" Tries to auto-reconcile as many statements as possible within time limit
arbitrary set to 3 minutes (the rest will be reconciled asynchronously with the regular cron).
"""
# 'limit_time_real_cron' defaults to -1.
# Manual fallback applied for non-POSIX systems where this key is disabled (set to None).
cron_limit_time = tools.config['limit_time_real_cron'] or -1
limit_time = cron_limit_time if 0 < cron_limit_time < 180 else 180
self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(limit_time=limit_time)

Some files were not shown because too many files have changed in this diff Show More