Compare commits

...

899 Commits

Author SHA1 Message Date
1ed351afcb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:27 +00:00
37d902bbf1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:27 +00:00
4620f1f15d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:26 +00:00
0919e928a2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:25 +00:00
6ccd6f551d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:24 +00:00
d9dbe67b8c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:23 +00:00
00ecdb1eee Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:23 +00:00
a32918ffab Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:22 +00:00
599cc43b87 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:21 +00:00
3e951f4586 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:20 +00:00
179eb40651 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:20 +00:00
1ea0f65bad Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:19 +00:00
37a5973caa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:18 +00:00
3bfd304755 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:17 +00:00
422687b588 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:17 +00:00
e8760f7d0d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:16 +00:00
d7f5f98734 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:15 +00:00
e224f85ce5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:15 +00:00
9793d30a0b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:14 +00:00
7ee8c79c11 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:13 +00:00
c5b1f87197 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:12 +00:00
6d4b838700 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:12 +00:00
32ebb495b2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:11 +00:00
1b0b3816fd Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:10 +00:00
461a147883 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:09 +00:00
96a99e98a6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:09 +00:00
3f1e67983a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:08 +00:00
480a145045 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:07 +00:00
4d6d6e3a0b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:06 +00:00
561595705c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:05 +00:00
6fe848fe50 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:03 +00:00
340836e114 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:02 +00:00
8550a2c003 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:01 +00:00
b9cb1e1d6c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:16:00 +00:00
058d098354 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:59 +00:00
05e11743bc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:58 +00:00
7bd3c34d30 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:57 +00:00
365e38e9bd Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:56 +00:00
da1339981b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:55 +00:00
21e3832c7a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:54 +00:00
cdf57466e0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:53 +00:00
1dd7159ebf Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:52 +00:00
a7aac0a6fc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:51 +00:00
46ddc4e83d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:50 +00:00
f598856a85 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:49 +00:00
e4fab2bcd0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:48 +00:00
2b9c28b17d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:47 +00:00
aa240a5294 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:46 +00:00
3a3f69f769 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:44 +00:00
e1550843f2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:43 +00:00
6e9bd26cc2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:43 +00:00
0274e8118a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:41 +00:00
fbee040fbe Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:40 +00:00
cc39e82b47 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:39 +00:00
cc173aa77f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:38 +00:00
49fd715c87 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:37 +00:00
be38f82271 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:36 +00:00
3d6d13e7f6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:35 +00:00
057fe4b3d5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:34 +00:00
e4b13e539e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:33 +00:00
995b9e65ac Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:32 +00:00
bc01210496 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:31 +00:00
cab4aebd03 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:29 +00:00
7ecc317e64 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:28 +00:00
30d46fbca0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:27 +00:00
9b2180deef Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:26 +00:00
9ad33e290f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:25 +00:00
787efca2b6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:24 +00:00
16ced6a395 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:23 +00:00
773c3cdd6e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:21 +00:00
4629806e3e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:20 +00:00
2129a6d505 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:19 +00:00
bf6096bf45 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:18 +00:00
e8bf47a6a9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:17 +00:00
7eb70e2623 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:16 +00:00
89253bd00a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:15 +00:00
b55ae4fe9d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:14 +00:00
bb68ff0cc5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:13 +00:00
66add92264 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:12 +00:00
2588c66508 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:10 +00:00
5b4a3aab11 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:09 +00:00
88118d9448 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:08 +00:00
d389ec4902 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:07 +00:00
81bc19c9ca Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:06 +00:00
5859333ac1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:04 +00:00
403db72dc7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:03 +00:00
1e058fdb9a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:02 +00:00
8c015d75be Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:15:00 +00:00
1ffc9fe53f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:59 +00:00
ea982c7dc5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:58 +00:00
d6922f99a3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:58 +00:00
a04bf4be8b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:57 +00:00
af2de3d58f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:55 +00:00
485d66cb19 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:55 +00:00
18e3ab1723 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:54 +00:00
9abef0c065 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:53 +00:00
8aff2d5df6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:51 +00:00
926071995c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:51 +00:00
69fc239de1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:49 +00:00
08bfaa13cf Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:48 +00:00
43d06b9ead Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:47 +00:00
100f596f31 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:46 +00:00
6b1e846f86 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:45 +00:00
2a3bd8b770 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:43 +00:00
cad4fdef9e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:43 +00:00
f25a7de844 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:41 +00:00
da3727481e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:40 +00:00
44c80d21ea Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:39 +00:00
0f815cd793 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:38 +00:00
962f5792a3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:37 +00:00
9527852970 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:36 +00:00
b5aef11d8e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:35 +00:00
90e3b774fe Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:33 +00:00
d12ac3d874 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:32 +00:00
f5593b775d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:31 +00:00
64268fad89 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:30 +00:00
9e9b83e383 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:29 +00:00
b0959026a5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:28 +00:00
c94d4b2692 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:26 +00:00
feb7eac8d0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:25 +00:00
deb912fe74 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:24 +00:00
f48188cdc2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:23 +00:00
ef2a582cbb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:22 +00:00
efa56c6aee Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:21 +00:00
3b45b08cd1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:19 +00:00
8ad2d11c45 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:18 +00:00
35b29fa27c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:17 +00:00
8f09fa4305 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:16 +00:00
b5b245876c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:14 +00:00
b16b052934 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:13 +00:00
b069c94742 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:12 +00:00
03e8bb9e34 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:10 +00:00
bf629028b6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:09 +00:00
3395d3b27d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:08 +00:00
e14357c691 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:07 +00:00
5b071517d5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:06 +00:00
83906f95f7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:04 +00:00
164d5e4e27 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:03 +00:00
1575a75869 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:02 +00:00
963b0d0dfa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:14:00 +00:00
f65e31fabc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:58 +00:00
d0bc1094b5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:57 +00:00
e927eea9d2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:56 +00:00
db23098f86 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:55 +00:00
0acea56f70 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:54 +00:00
3b1e6031ed Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:53 +00:00
e600110516 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:52 +00:00
52330d8399 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:50 +00:00
b0cc42047c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:49 +00:00
e94654c113 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:48 +00:00
5ed65f685d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:47 +00:00
a997f2fa4b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:46 +00:00
7ff298551b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:45 +00:00
83dc76f9da Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:44 +00:00
603a8f1c72 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:42 +00:00
2618f5bc15 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:41 +00:00
53ed74871d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:40 +00:00
1c2bbaf08e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:39 +00:00
e1001bc697 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:38 +00:00
5e517adbef Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:37 +00:00
823f4a4078 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:36 +00:00
40caeba39e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:35 +00:00
ee4a4afa94 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:34 +00:00
c7fc09061d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:33 +00:00
3292953c6e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:32 +00:00
0c6035f883 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:30 +00:00
d5dec2ce97 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:29 +00:00
db7810ce15 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:28 +00:00
b2657e9c92 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:27 +00:00
48483a2e25 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:26 +00:00
cc2215e75f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:25 +00:00
baf34a96a9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:23 +00:00
c412cb62b4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:22 +00:00
58abc5c6df Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:21 +00:00
0b69dbf4cc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:20 +00:00
0c44c5f90d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:19 +00:00
c1886be4a6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:18 +00:00
832c6c58e0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:16 +00:00
b918b2d54b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:15 +00:00
731f8ef60c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:14 +00:00
23c946dee2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:13 +00:00
4b70c0b1b8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:11 +00:00
a80a309f6b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:10 +00:00
76b546e0d2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:09 +00:00
e12cbe8a1d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:08 +00:00
53f96e1c12 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:07 +00:00
c6fcc52911 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:05 +00:00
001f689a99 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:04 +00:00
dd63749c5f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:02 +00:00
d0a6a9ba6f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:01 +00:00
3d9224af3c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:13:00 +00:00
2547699573 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:59 +00:00
12d9818809 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:57 +00:00
019c26cbd2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:56 +00:00
a2abda6b07 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:55 +00:00
649d3a13dd Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:54 +00:00
96e8abf5de Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:53 +00:00
2989ad5a81 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:51 +00:00
dc5a6616a4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:50 +00:00
b1e930eafb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:49 +00:00
c1521a6372 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:48 +00:00
269beaf624 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:47 +00:00
13e5e7cde8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:45 +00:00
4bbcae399e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:44 +00:00
14899e48d3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:43 +00:00
0e5a2af28e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:42 +00:00
83277217f9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:40 +00:00
4d226ec086 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:39 +00:00
2d41262428 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:38 +00:00
8dfd64f53a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:37 +00:00
ea73d93e8a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:36 +00:00
1e4e023c1c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:35 +00:00
41ecea74c6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:33 +00:00
ac2ff0476b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:32 +00:00
147c1e5050 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:31 +00:00
2a6c7e3a87 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:30 +00:00
5c2ae7b2c4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:28 +00:00
0fed636461 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:28 +00:00
d1772985ab Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:26 +00:00
697315c47f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:25 +00:00
ae802507a3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:24 +00:00
cb69646f9f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:23 +00:00
91a454851c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:22 +00:00
7eb66dd5a4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:20 +00:00
67db32090b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:19 +00:00
45091483a3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:18 +00:00
b4f0e672b3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:17 +00:00
0603a3d6af Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:16 +00:00
0e403e40f7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:15 +00:00
8e857c5d16 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:12:14 +00:00
27f1d35c41 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:35 +00:00
25ab8eb3a5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:34 +00:00
5e11c33d38 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:33 +00:00
8c450b3275 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:32 +00:00
fc8f57dc1e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:31 +00:00
3eba6d66aa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:30 +00:00
848aae224c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:29 +00:00
5133ee78c9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:27 +00:00
083852cb99 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:26 +00:00
53a7b6a318 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:25 +00:00
ae3a204c7e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:24 +00:00
431b209881 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:23 +00:00
2e2bbff9b0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:22 +00:00
c666b76c2a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:21 +00:00
92f51cc251 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:20 +00:00
a91d88866f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:19 +00:00
f55d2cb472 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:18 +00:00
720276e095 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:17 +00:00
5e3ad11d2a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:15 +00:00
219f706143 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:14 +00:00
9ca4e55cc9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:13 +00:00
254fb1d885 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:12 +00:00
c78fc717af Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:11 +00:00
2512b32b28 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:10 +00:00
b11ad8e295 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:09 +00:00
e9d4d08962 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:08 +00:00
3f9f298838 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:07 +00:00
fe351faefe Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:05 +00:00
e3aa3d8990 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:04 +00:00
5253193596 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:03 +00:00
3001e7c0b2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:01 +00:00
ec436f6e9c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:09:00 +00:00
7872aa608b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:59 +00:00
da801c6eaf Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:57 +00:00
2ebda56c3f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:56 +00:00
a4b2164f7c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:55 +00:00
58bf5a2def Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:54 +00:00
7c33f0b44f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:53 +00:00
bdde4082d7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:52 +00:00
1b90a3e867 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:50 +00:00
d524c5e1d4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:49 +00:00
f01f577792 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:48 +00:00
27ce1c54a5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:47 +00:00
128525d282 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:46 +00:00
32003002ac Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:45 +00:00
94e116c52d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:44 +00:00
c1ba536d03 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:42 +00:00
9b7541ccc3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:41 +00:00
5f3f5baf37 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:40 +00:00
8d12539329 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:39 +00:00
f5719810a4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:38 +00:00
db0cb50deb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:37 +00:00
2b65de6758 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:36 +00:00
030be683b1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:34 +00:00
14d5ad7f29 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:33 +00:00
531ad91474 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:32 +00:00
d4a490c757 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:31 +00:00
9f15d65490 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:30 +00:00
a2a74e8744 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:29 +00:00
c21e46ddeb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:27 +00:00
da07d2e445 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:26 +00:00
6dc553fde9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:25 +00:00
f99799a954 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:24 +00:00
9e23e1317c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:23 +00:00
32d774fcd5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:22 +00:00
e207f08083 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:20 +00:00
47a23fed67 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:19 +00:00
c1a763bddc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:18 +00:00
7d30801033 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:17 +00:00
84e8400cb4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:16 +00:00
3e481ba6ab Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:15 +00:00
e33e0077a1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:13 +00:00
75ba8a98cb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:12 +00:00
0366582790 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:11 +00:00
f689083840 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:10 +00:00
48703f32da Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:09 +00:00
2de2a5d904 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:08 +00:00
a785bff7bc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:07 +00:00
545a23229d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:05 +00:00
1a354517a3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:04 +00:00
d99608dfe0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:03 +00:00
7fd8ff1230 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:02 +00:00
e4f3261e8a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:08:01 +00:00
f76ece3c19 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:59 +00:00
61be87a6a0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:58 +00:00
32ef4a1135 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:57 +00:00
105f6a4b1d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:56 +00:00
76487f4782 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:55 +00:00
d854dde695 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:54 +00:00
0d2ab5ef0f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:53 +00:00
eef53a3aaf Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:07:01 +00:00
5cd06f5379 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:59 +00:00
ba653ff7da Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:58 +00:00
8910346aa4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:58 +00:00
d004eb3298 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:57 +00:00
953d506294 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:55 +00:00
2d1a474823 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:54 +00:00
4aaf162b07 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:53 +00:00
55919440aa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:52 +00:00
def3919e36 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:51 +00:00
23729f436a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:50 +00:00
a498382b27 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:49 +00:00
ef2464809f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:48 +00:00
c5bc923362 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:47 +00:00
b921e79306 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:46 +00:00
7d31a8c951 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:45 +00:00
cbd4217d7d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:44 +00:00
a9a25dbf23 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:43 +00:00
e9d49c9a58 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:42 +00:00
0682aecf37 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:41 +00:00
2637250db2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:40 +00:00
d33628aebd Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:39 +00:00
a80add2939 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:37 +00:00
41de310ee1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:37 +00:00
a6bebfd99c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:35 +00:00
76e4d29876 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:34 +00:00
8ade4a6e05 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:33 +00:00
a6f421a104 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:32 +00:00
8aa7c35368 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:31 +00:00
6c8caf18a8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:30 +00:00
66986764f8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:29 +00:00
fa4f2d6714 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:27 +00:00
8f445713f2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:26 +00:00
40fb8f2fa6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:25 +00:00
33d3610337 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:24 +00:00
569ed5fa45 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:22 +00:00
ffb2695771 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:21 +00:00
0b2dad4a11 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:20 +00:00
f2c0f1dbf1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:19 +00:00
1f4d827be0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:18 +00:00
77237f7463 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:17 +00:00
275e79a729 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:17 +00:00
589c24b30c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:15 +00:00
9f7dc4f18b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:14 +00:00
7243cebeb7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:13 +00:00
968080a32f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:12 +00:00
711ae46103 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:11 +00:00
a2bbff1742 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:10 +00:00
2cfc0fbb9e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:09 +00:00
0f5db71fcc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:08 +00:00
ab3cdca6ac Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:07 +00:00
b6b317cf39 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:06 +00:00
a572280a1b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:05 +00:00
0bba7d8660 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:04 +00:00
b83c259bc9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:03 +00:00
275005173e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:02 +00:00
e894d52fb3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:06:00 +00:00
d93f8decd8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:59 +00:00
d3b0c242da Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:58 +00:00
b198163958 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:57 +00:00
a947ad7169 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:56 +00:00
fb945d1f13 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:55 +00:00
b70d7ffd84 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:54 +00:00
799550985c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:53 +00:00
0507e609d0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:52 +00:00
6e6c57734f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:51 +00:00
3a8001bbf5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:50 +00:00
c5531c9622 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:48 +00:00
5fc1508fbb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:47 +00:00
5b2176f740 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:47 +00:00
8e0f4bedc1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:45 +00:00
d0a6262774 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:44 +00:00
50c3b59309 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:43 +00:00
382b052b20 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:42 +00:00
a942abb5d8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:41 +00:00
8d8980a351 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:40 +00:00
9147825a27 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:39 +00:00
a5bfe360bc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:38 +00:00
9b0de5b906 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:36 +00:00
0a961e60f2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:36 +00:00
b56eef73af Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:35 +00:00
c01bf9817e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:34 +00:00
aad8160ddb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:33 +00:00
01ad9545ef Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:31 +00:00
891226fb3a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:30 +00:00
51df143937 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:29 +00:00
4bd584cc1c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:28 +00:00
2983906b42 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:27 +00:00
9b503f6da4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:26 +00:00
3be92de4da Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:24 +00:00
f2b145fbe3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:23 +00:00
b441195cbf Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:22 +00:00
c680ca48b9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:21 +00:00
3f3f72f1b2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:20 +00:00
d24265e1f7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:19 +00:00
a1debc4b51 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:18 +00:00
3f87848fb0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:16 +00:00
6a5d7bd5c9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:15 +00:00
3e5e94610b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:14 +00:00
8fef2569a6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:13 +00:00
b67e36219b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:12 +00:00
8b9bc53b4b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:11 +00:00
7f0c5bb6d6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:09 +00:00
a624a78e89 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:08 +00:00
2d007bddd6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:07 +00:00
3b0fd1a653 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:06 +00:00
6dfec1ece8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:05 +00:00
f2f9f1356e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:04 +00:00
8013a8466b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:02 +00:00
1ea2132ae1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:05:01 +00:00
f625656478 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:59 +00:00
97c12ce152 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:58 +00:00
8f4d684054 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:57 +00:00
7345062f82 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:56 +00:00
745334a16a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:55 +00:00
b2ede24091 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:54 +00:00
1c8f09b64c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:53 +00:00
c1c520274f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:52 +00:00
d000fead4c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:51 +00:00
0de0a6d5aa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:50 +00:00
0e872ab117 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:49 +00:00
528f191d56 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:48 +00:00
ce4ee639d3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:47 +00:00
f810014cb2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:04:46 +00:00
9d51ac3252 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:49 +00:00
a2028e5a97 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:48 +00:00
e9cadbe823 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:47 +00:00
3f799e5d6f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:46 +00:00
8722d4b1b2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:45 +00:00
e653e447c6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:44 +00:00
88c2a9003a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:43 +00:00
739c87dd5e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:42 +00:00
046eb43f56 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:41 +00:00
8c17569d52 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:40 +00:00
0f995e80a9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:39 +00:00
09696b9c10 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:38 +00:00
b6362d895d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:37 +00:00
fc6d7d9b29 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:36 +00:00
4bc1d5939e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:34 +00:00
fb1ec4b879 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:34 +00:00
d3a3fa3c77 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:33 +00:00
72e36fbf76 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:32 +00:00
558ed2306c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:31 +00:00
0e8fbce3ea Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:30 +00:00
b0cecf4b66 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:29 +00:00
314f12f0d4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:27 +00:00
df0d005724 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:26 +00:00
a487adf187 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:25 +00:00
5e40d1c63a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:24 +00:00
304c5172e6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:23 +00:00
23b7b1e80b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:22 +00:00
5bbab6fab5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:21 +00:00
1585c66a2d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:20 +00:00
f57d262d62 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:19 +00:00
69c4b974ef Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:18 +00:00
214433437e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:17 +00:00
de13dc5909 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:16 +00:00
ad34c1d6eb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:15 +00:00
ac1f5c0c8f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:14 +00:00
475273a71b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:12 +00:00
2f525cb95e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:11 +00:00
9874bd4e21 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:10 +00:00
0212b26047 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:09 +00:00
20da29605b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:08 +00:00
034eab9244 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:07 +00:00
60fc5bcdda Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:06 +00:00
6a80f28c52 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:05 +00:00
13bb0df929 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:04 +00:00
04b4210da2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:03 +00:00
7d03e32f51 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:02 +00:00
4fdbdaedc9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:03:01 +00:00
1cc392bfb2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:59 +00:00
c856fed428 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:58 +00:00
c4a447738e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:57 +00:00
d3b296b587 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:56 +00:00
c678877b59 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:55 +00:00
104f73b7d2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:54 +00:00
135ae7fcfe Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:53 +00:00
9d502807fb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:52 +00:00
c3581e1b69 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:51 +00:00
3ce9111fa7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:50 +00:00
516e985bd0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:49 +00:00
46c0d054a6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:48 +00:00
392984c5e8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:47 +00:00
2bc536263e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:46 +00:00
957fb6f312 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:45 +00:00
4dbea36b0f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:44 +00:00
3c67472a8c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:43 +00:00
f6e22cc063 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:42 +00:00
9b4f874307 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:41 +00:00
a054dc878a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:40 +00:00
48e48ecf03 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:39 +00:00
471c451c21 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:38 +00:00
a88793a6ad Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:37 +00:00
3e50cea188 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:36 +00:00
4da0dfbd51 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:35 +00:00
da5eab2f1f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:34 +00:00
78e1187722 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:32 +00:00
bfda8e2e56 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:31 +00:00
5014234817 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:30 +00:00
a185c34d65 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:29 +00:00
8fc4975a9e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:28 +00:00
7414a836e7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:27 +00:00
122f31f4b6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:26 +00:00
b59d8cee90 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:25 +00:00
1db27642af Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:24 +00:00
6f5be6eae9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:23 +00:00
2a7103cdd3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:22 +00:00
af722d6184 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:20 +00:00
2bf0695e9d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:19 +00:00
faf1b4fff1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:18 +00:00
bd59526535 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:17 +00:00
f404e0ef31 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:16 +00:00
a11b547cdc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:02:15 +00:00
1507d10fe5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:26 +00:00
84cc63a038 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:25 +00:00
cbb5244320 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:25 +00:00
809e33bd93 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:24 +00:00
b8f6c82010 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:23 +00:00
4be3583e7b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:22 +00:00
de10442135 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:21 +00:00
44ec4115ea Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:21 +00:00
eb27193b6e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:20 +00:00
cead209507 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:19 +00:00
f2122a7706 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:19 +00:00
88f970547f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:18 +00:00
fc7bfaa089 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:17 +00:00
234aee88e2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:17 +00:00
33a761413c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:16 +00:00
db76be28e3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:15 +00:00
62a64bcadc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:15 +00:00
3d69d5b814 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:14 +00:00
850d9bb6c5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:13 +00:00
89f02eeda5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:12 +00:00
cba2ad4052 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:12 +00:00
f566b64a4e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:11 +00:00
9e5070e4c7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:10 +00:00
d95c52e27a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:10 +00:00
051e01b1d5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:09 +00:00
4261ec5ed0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:08 +00:00
98bc5f8027 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:08 +00:00
ccdc19f576 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:07 +00:00
84e6416ca1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:06 +00:00
368561c08a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:06 +00:00
3de397f595 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:05 +00:00
700d367666 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:04 +00:00
a384996942 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:04 +00:00
81f0c9411c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:03 +00:00
cbdea4a66a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:02 +00:00
752c1ebe59 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:01 +00:00
f77ab77c0b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:00 +00:00
71d223259b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:01:00 +00:00
a69b413c5b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:59 +00:00
daafc7c705 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:58 +00:00
52b130631c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:57 +00:00
01594ba32b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:57 +00:00
9015195fb9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:56 +00:00
6d07bc4c06 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:55 +00:00
acf12b508b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:55 +00:00
b7a2c4ea2f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:54 +00:00
4399a06c37 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:54 +00:00
5ee5d4f5cd Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:53 +00:00
256c5679c7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:52 +00:00
45bfc361f7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:52 +00:00
0ecbc9213c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:51 +00:00
63dc434824 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:50 +00:00
3e1a94eaed Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:50 +00:00
68ce996d7b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:49 +00:00
89d5846a15 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:48 +00:00
7f76c2d7e7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:47 +00:00
1326d21dfa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:47 +00:00
b37e8a0f7a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:46 +00:00
8f629981ce Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:45 +00:00
beace16323 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:45 +00:00
e19464e2a8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:44 +00:00
008ca2b8a7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:43 +00:00
6e56dc2437 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:43 +00:00
a09d856ddb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:42 +00:00
9a69aa7709 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:41 +00:00
0254cac7fc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:40 +00:00
bc629b414c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:40 +00:00
8f69ab2665 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:39 +00:00
01514c86c9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:39 +00:00
ca1f963db1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:38 +00:00
5fd44a1ae0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:37 +00:00
284e2939f5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:37 +00:00
70d43d34ca Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:36 +00:00
8425e51ca4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:35 +00:00
4994547440 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:34 +00:00
2fbf03f179 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:34 +00:00
2e1a1fbe03 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:33 +00:00
dd5c59c645 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:32 +00:00
3e88c5783b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:32 +00:00
0a63809482 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:31 +00:00
743b002387 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:30 +00:00
79fe584847 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:30 +00:00
63d6a65f65 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:29 +00:00
d20f9916f3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:29 +00:00
285397f44b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:28 +00:00
5d7a1983f9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:27 +00:00
8cb7ce7d65 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:26 +00:00
2b78518c30 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:26 +00:00
b6be57e58f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:25 +00:00
e225ff2780 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:24 +00:00
b98f7b4769 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:24 +00:00
795d6e7d9d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:23 +00:00
100198bd1f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:22 +00:00
23a254a556 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:21 +00:00
12edb222fe Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:21 +00:00
e8ba8fa737 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:20 +00:00
b4322a0037 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:19 +00:00
4ad5be42ac Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:19 +00:00
36edebf085 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:18 +00:00
fed7367831 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:17 +00:00
f2733d5329 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:16 +00:00
bb090554de Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:15 +00:00
42a7238060 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:14 +00:00
f9df23afbd Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:13 +00:00
81e9e5a672 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:12 +00:00
c7d69b60ae Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:11 +00:00
a2f9f9b7c5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:10 +00:00
32431d413c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:09 +00:00
cfa3b92440 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:09 +00:00
d84b02bbd3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:08 +00:00
55c7f73cf0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:07 +00:00
0ca0df7b4d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:06 +00:00
57d989b675 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:05 +00:00
de9e8736a2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:04 +00:00
c404a4666b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:02 +00:00
25707d453f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:01 +00:00
39099edaf2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 15:00:00 +00:00
284678d009 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:59 +00:00
30c49600c4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:59 +00:00
a3dbb7e17b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:58 +00:00
6008b2dc05 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:57 +00:00
829ad64e09 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:56 +00:00
25043fe102 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:56 +00:00
88686ff2c3 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:55 +00:00
12abc05f7d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:54 +00:00
aefa4babca Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:54 +00:00
c62fd5ecfa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:53 +00:00
7eb7f7ea8e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:52 +00:00
d9fe73f32b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:52 +00:00
e7824591a7 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:51 +00:00
b05fc21d36 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:51 +00:00
d93328bf86 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:50 +00:00
f4ccfb22ac Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:49 +00:00
0113a319f4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:48 +00:00
0ee6d65498 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:48 +00:00
86de2d1eb9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:47 +00:00
db3ab398e9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:47 +00:00
cc3bcd9819 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:46 +00:00
7230f7788f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:45 +00:00
bb433c6f46 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:44 +00:00
b3883d85d5 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:44 +00:00
65e8752498 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:43 +00:00
6327aefce2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:42 +00:00
be499f838f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:41 +00:00
5a4e74189a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:41 +00:00
071b72038c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:40 +00:00
43e08cbef1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:39 +00:00
3acbb098bc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:39 +00:00
01ea86d870 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:38 +00:00
022900429a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:37 +00:00
f468d2f4fd Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:37 +00:00
7e8d4e2128 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:36 +00:00
c5bd3349f8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:35 +00:00
1185f5ea8b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:35 +00:00
eeb9acdef1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:34 +00:00
71924556d6 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:33 +00:00
437bf1914f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:33 +00:00
2247a2b2aa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:32 +00:00
142ef0ddd2 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:31 +00:00
a6ef031a85 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:31 +00:00
1201b65d40 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:30 +00:00
0e58a32f10 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:30 +00:00
1ac96b414e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:29 +00:00
1f2b9fbed1 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:28 +00:00
d491d1fc0b Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:28 +00:00
2475691c4e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:27 +00:00
87d4426b3d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:26 +00:00
0ecc6a5a18 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:26 +00:00
844bc6c148 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:25 +00:00
66dfe1c5e4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:24 +00:00
150ecdf120 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:23 +00:00
aad3d4bd50 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:23 +00:00
6cc75e639c Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:22 +00:00
a628c83a90 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:20 +00:00
40dfe0fccc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:19 +00:00
215e5b1677 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:18 +00:00
cd5eda9df9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:17 +00:00
cad8f0a19d Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:16 +00:00
8563621d65 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:15 +00:00
86fce55c97 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:14 +00:00
86d61ed1a0 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:12 +00:00
fbacd499dc Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:11 +00:00
ccb8b3d268 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:10 +00:00
1773ce4444 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:10 +00:00
c2517abff9 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:09 +00:00
e87775affa Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:08 +00:00
cc5c04d5da Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:07 +00:00
a18cbbf49a Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:07 +00:00
ee3e8edfa8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:06 +00:00
79b3210bda Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:05 +00:00
9580b1f29e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:04 +00:00
04a7bbbda8 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:04 +00:00
c6857b850e Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:03 +00:00
48a025302f Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:02 +00:00
38246e90ee Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:01 +00:00
7e1e94d165 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:59:00 +00:00
aa83112e71 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:59 +00:00
044fec0d50 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:58 +00:00
e32318b2ad Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:58 +00:00
e742c77d12 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:57 +00:00
20ed701fec Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:56 +00:00
e1e2481bbb Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:55 +00:00
015696e7c4 Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:54 +00:00
d318a5a9ec Tower: upload laundry_management 19.0.19.0.4 (via marketplace) 2026-05-01 14:58:54 +00:00
d802d63444 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:27:00 +00:00
7decda722d Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:59 +00:00
4a8b911529 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:58 +00:00
435650436c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:57 +00:00
7eb8ce5eba Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:57 +00:00
45ab562a84 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:56 +00:00
1cd28a15b5 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:55 +00:00
be15835a67 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:54 +00:00
0043ea605b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:53 +00:00
a9d9132707 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:53 +00:00
968f881838 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:52 +00:00
6e5863b6f0 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:50 +00:00
f096aad827 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:50 +00:00
75afd665b8 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:49 +00:00
2f8a31ca87 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:48 +00:00
02c94565a6 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:45 +00:00
2eaabfca59 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:44 +00:00
c300658c45 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:43 +00:00
ffd3540da7 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:41 +00:00
af57c49e0c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:40 +00:00
3cb908e43a Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:39 +00:00
b9763d1cab Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:38 +00:00
3081b1727e Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:36 +00:00
795a79e2de Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:36 +00:00
622d469902 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:35 +00:00
0d3d9fd659 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:34 +00:00
0624e3329d Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:34 +00:00
d37effb75e Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:33 +00:00
5d6cbe8712 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:32 +00:00
7da2508ad5 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:32 +00:00
e84b5fc21c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:31 +00:00
302b4e2086 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:30 +00:00
8979652c30 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:29 +00:00
cd5e49cad6 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:28 +00:00
aceab86bd0 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:28 +00:00
ac0562565d Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:27 +00:00
dea9410bff Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:26 +00:00
321300cb96 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:25 +00:00
fe85d4a831 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:25 +00:00
d96900edcd Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:24 +00:00
506f0f11c4 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:23 +00:00
ae32f463b7 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:22 +00:00
4df8ed7aca Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:22 +00:00
4319b86036 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:21 +00:00
af9f7335fe Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:19 +00:00
6dd9422d13 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:16 +00:00
c161cf6911 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:16 +00:00
55a84c282b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:15 +00:00
5ef1cc0aed Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:14 +00:00
c774c5b9ed Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:13 +00:00
03914adbd3 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:13 +00:00
f944dc402e Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:12 +00:00
41a2fb8e87 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:11 +00:00
d98c59977c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:10 +00:00
5635da77ea Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:09 +00:00
f566e6ebc0 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:09 +00:00
7ac4617819 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:08 +00:00
e5a427556a Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:07 +00:00
eadaacc266 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:06 +00:00
94c1637d10 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:05 +00:00
3f9d72c4fe Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:05 +00:00
00bdf183bb Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:04 +00:00
0b5452b9ef Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:03 +00:00
603e4a5a7a Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:02 +00:00
2682634640 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:02 +00:00
5bac29e608 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:01 +00:00
0b34ccfe0b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:26:00 +00:00
4d9163364b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:59 +00:00
64aa4f4cd8 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:59 +00:00
10755bf441 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:58 +00:00
95cb5d6eb5 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:57 +00:00
bd05b6f737 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:57 +00:00
90bfd7cd31 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:56 +00:00
6aebd3a480 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:54 +00:00
fa5a12e8b7 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:52 +00:00
a4f1c765d0 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:52 +00:00
6809c236c3 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:51 +00:00
53dd954217 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:50 +00:00
525f4160a7 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:49 +00:00
f9f462b129 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:49 +00:00
1f55211ecd Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:48 +00:00
db8ff1ab76 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:47 +00:00
98fb9f325e Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:47 +00:00
0811265bc1 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:46 +00:00
4357f1842a Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:45 +00:00
3a54c60b51 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:45 +00:00
c79cf90797 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:44 +00:00
11573d49d4 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:43 +00:00
15fc7bb78b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:42 +00:00
e92df10e2b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:41 +00:00
b49af826f3 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:41 +00:00
a95c2da928 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:40 +00:00
c04db924cc Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:39 +00:00
c2ff38fd10 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:38 +00:00
3b51b7e059 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:38 +00:00
473f603fee Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:37 +00:00
03136df83e Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:36 +00:00
7c3e66de7c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:36 +00:00
50b4c9349a Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:35 +00:00
dab6e99136 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:34 +00:00
2a74c6981c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:34 +00:00
bbf0e0ede6 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:33 +00:00
107b28dd6b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:32 +00:00
45d4562015 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:32 +00:00
0305486fc0 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:31 +00:00
92ce1ad080 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:30 +00:00
465f081a01 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:29 +00:00
a7bfa330d5 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:29 +00:00
c4116b0001 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:28 +00:00
d724678c31 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:26 +00:00
7b6bc7c32c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:26 +00:00
14006bf8b6 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:25 +00:00
16ffcf64c9 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:24 +00:00
16c7dfa4c7 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:23 +00:00
a5bc3df00b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:22 +00:00
c3697aa775 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:21 +00:00
0545a5e27f Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:20 +00:00
d4140f6046 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:20 +00:00
4e83a19734 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:19 +00:00
90d255d5b7 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:18 +00:00
fde08f1a51 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:17 +00:00
367c39cd19 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:16 +00:00
1fe1b890f7 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:16 +00:00
039f0b9679 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:14 +00:00
b59bf760fc Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:13 +00:00
254a5edeb1 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:12 +00:00
e1cd635b2b Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:11 +00:00
fd488f3611 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:11 +00:00
936141a47c Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:10 +00:00
00ad28e7e5 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:09 +00:00
133d01f1bc Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:08 +00:00
2bc961a7f6 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:08 +00:00
2763df5b85 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:07 +00:00
c70fc08d8a Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:06 +00:00
fb315401ed Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:04 +00:00
7426af0136 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:03 +00:00
7d4ce79754 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:02 +00:00
76c842ad49 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:01 +00:00
c5b643b175 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:00 +00:00
7be3e6fc46 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:25:00 +00:00
3768d57ef1 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:24:59 +00:00
79a1fc6302 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:24:58 +00:00
cfae5c1e6e Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:24:57 +00:00
ee86816a05 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:24:57 +00:00
3e5625c49a Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:24:56 +00:00
aad046ad01 Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:24:56 +00:00
8b0ce6857d Tower: upload base_accounting_kit 19.0.2.3.1 (via marketplace) 2026-05-01 14:24:55 +00:00
0a00df7d8f Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:27 +00:00
0b04773828 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:26 +00:00
0bfcfc8e5e Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:26 +00:00
6569b22484 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:25 +00:00
6c99447b98 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:25 +00:00
6c2975aa2c Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:24 +00:00
8a323969dd Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:23 +00:00
6adfee8522 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:22 +00:00
94e5c2bc26 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:21 +00:00
a730c30313 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:21 +00:00
0f9842a4e4 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:19 +00:00
d48cd0eb4f Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:19 +00:00
d051b7eb77 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:18 +00:00
229fb1c9d1 Tower: upload om_account_budget 1.0.1 (via marketplace) 2026-05-01 14:21:17 +00:00
391 changed files with 63374 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
Odoo 19 Full Accounting Kit for Community
=========================================
Full accounting kit for Odoo 19 community editions
Configuration
=============
No configuration
Company
-------
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
License
-------
General Public License, Version 3 (LGPL v3).
(http://www.gnu.org/licenses/lgpl-3.0-standalone.html)
Credits
-------
Developer: (V19) MohammedIrfan T, Ashik MA, Contact: odoo@cybrosys.com
Contacts
--------
* Mail Contact : odoo@cybrosys.com
* Website : https://cybrosys.com
Bug Tracker
-----------
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
Maintainer
==========
.. image:: https://cybrosys.com/images/logo.png
:target: https://cybrosys.com
This module is maintained by Cybrosys Technologies.
For support and more information, please visit `Our Website <https://cybrosys.com/>`__
Further information
===================
HTML Description: `<static/description/index.html>`__

View File

@@ -0,0 +1,25 @@
# -*- # -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import models
from . import report
from . import wizard
from . import controllers

View File

@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
{
'name': 'Odoo 19 Full Accounting Kit for Community',
'version': '19.0.2.3.1',
'category': 'Accounting',
'live_test_url': 'https://kit.easyinstance.com/web/login?redirect=/odoo/accounting',
'summary': """Odoo 19 Accounting, Odoo 19 Accounting Reports, Odoo18 Accounting, Odoo Accounting, Odoo19 Financial Reports, Odoo19 Asset, Odoo19 Profit and Loss, PDC, Followups, Odoo19, Accounting, Odoo Apps, Reports""",
'description': """ Odoo 19 Accounting, The module used to manage the Full
Account Features that can manage the Account Reports,Journals Asset and
Budget Management, Accounting Reports, PDC, Credit Limit,
Follow Ups, Day-Bank-Cash book report, Odoo 18 Accounting, odoo apps""",
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': "https://www.cybrosys.com",
'depends': ['account', 'sale', 'account_check_printing', 'analytic',
'base_account_budget', 'contacts'],
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'data/account_financial_report_data.xml',
'data/cash_flow_data.xml',
'data/followup_levels.xml',
'data/multiple_invoice_data.xml',
'data/recurring_entry_cron.xml',
'data/account_pdc_data.xml',
'views/reports_config_view.xml',
'views/accounting_menu.xml',
'views/account_group.xml',
'views/credit_limit_view.xml',
'views/account_configuration.xml',
'views/res_config_settings_views.xml',
'views/account_followup.xml',
'views/followup_line_views.xml',
'views/followup_report.xml',
'wizard/asset_depreciation_confirmation_views.xml',
'wizard/asset_modify_views.xml',
'views/account_asset_asset_views.xml',
'views/account_asset_category_views.xml',
'views/account_move_views.xml',
'views/product_template_views.xml',
'views/multiple_invoice_layout_view.xml',
'views/multiple_invoice_form.xml',
'views/account_journal_views.xml',
'views/res_partner_views.xml',
'wizard/financial_report_views.xml',
'wizard/account_report_general_ledger_views.xml',
'wizard/account_report_partner_ledger_views.xml',
'wizard/kit_account_tax_report_views.xml',
'wizard/account_balance_report_views.xml',
'wizard/account_aged_trial_balance_views.xml',
'wizard/account_print_journal_views.xml',
'wizard/cash_flow_report_views.xml',
'wizard/account_bank_book_report_views.xml',
'wizard/account_cash_book_report_views.xml',
'wizard/account_day_book_report_views.xml',
'report/report_financial_template.xml',
'report/general_ledger_report_template.xml',
'report/report_journal_audit_template.xml',
'report/report_aged_partner_template.xml',
'report/report_trial_balance_template.xml',
'report/report_tax_template.xml',
'report/report_partner_ledger_template.xml',
'report/cash_flow_report_template.xml',
'report/account_bank_book_template.xml',
'report/account_cash_book_template.xml',
'report/account_day_book_template.xml',
'report/account_asset_report_views.xml',
'report/report.xml',
'report/multiple_invoice_layouts.xml',
'report/multiple_invoice_report_template.xml',
'report/res_partner_reports.xml',
'report/res_partner_templates.xml',
'views/account_recurring_payments_view.xml',
'views/account_move_line_views.xml',
'views/account_bank_statement_views.xml',
'views/account_bank_statement_line_views.xml',
'views/account_payment_view.xml',
'wizard/account_lock_date_views.xml',
'wizard/import_bank_statement_views.xml',
],
'external_dependencies': {
'python': ['openpyxl', 'ofxparse', 'qifparse']
},
'assets': {
'web.assets_backend': [
'base_accounting_kit/static/src/scss/style.scss',
'base_accounting_kit/static/src/scss/bank_rec_widget.css',
'base_accounting_kit/static/src/js/bank_reconcile_form_list_widget.js',
'base_accounting_kit/static/src/js/KanbanController.js',
'base_accounting_kit/static/src/js/ListController.js',
'base_accounting_kit/static/src/js/bank_reconcile_form_lines_widget.js',
'base_accounting_kit/static/src/js/action_manager.js',
'base_accounting_kit/static/src/xml/bank_rec_widget.xml',
'base_accounting_kit/static/src/xml/bank_reconcile_widget.xml',
]
},
'license': 'LGPL-3',
'images': ['static/description/banner.gif'],
'installable': True,
'auto_install': False,
'application': True,
}

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import statement_report

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import json
from odoo import http
from odoo.http import content_disposition, request
from odoo.tools import html_escape
class XLSXReportController(http.Controller):
""" Controller for xlsx report """
@http.route('/xlsx_report', type='http', auth='user', methods=['POST'],
csrf=False)
def get_report_xlsx(self, model, data, output_format, report_name,
report_action=None, options=None, **kwargs):
""" Get xlsx report data """
report_obj = request.env[model].sudo()
try:
if output_format == 'xlsx':
response = request.make_response(
None, headers=[
('Content-Type', 'application/vnd.ms-excel'),
('Content-Disposition', content_disposition(
report_name + '.xlsx'))])
report_obj.get_xlsx_report(data, response, report_name, report_action)
response.set_cookie('fileToken', 'dummy token')
return response
except Exception as event:
serialize = http.serialize_exception(event)
error = {
'code': 200,
'message': 'Odoo Server Error',
'data': serialize
}
return request.make_response(html_escape(json.dumps(error)))

View File

@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<!-- Financial Reports data records in the model account.financial.report-->
<record id="account_financial_report_profitandloss0"
model="account.financial.report">
<field name="name">Profit and Loss</field>
<field name="sign" eval="'-1'"/>
<field name="type">sum</field>
</record>
<!-- Income report -->
<record id="account_financial_report_income0"
model="account.financial.report">
<field name="name">Income</field>
<field name="sign" eval="'-1'"/>
<field name="sequence">1</field>
<field name="parent_id"
ref="account_financial_report_profitandloss0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">sum</field>
</record>
<!-- Other Income -->
<record id="account_financial_report_other_income0"
model="account.financial.report">
<field name="name">Other Income</field>
<field name="sequence">10</field>
<field name="parent_id"
ref="account_financial_report_income0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">account_type</field>
<field name="account_type_ids">income_other</field>
</record>
<!-- Gross profit -->
<record id="financial_report_gross_profit"
model="account.financial.report">
<field name="name">Gross Profit</field>
<field name="parent_id"
ref="account_financial_report_income0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">sum</field>
<field name="sequence">3</field>
</record>
<!-- Cost of revenue -->
<record id="financial_report_cost_of_revenue"
model="account.financial.report">
<field name="name">Cost of Revenue</field>
<field name="sequence">10</field>
<field name="parent_id"
ref="financial_report_gross_profit"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">account_type</field>
<field name="account_type_ids">expense_direct_cost</field>
</record>
<!-- Operating Income -->
<record id="account_financial_report_operating_income0"
model="account.financial.report">
<field name="name">Operating Income</field>
<field name="sequence">1</field>
<field name="parent_id"
ref="financial_report_gross_profit"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">account_type</field>
<field name="account_type_ids">income</field>
</record>
<!-- Expense -->
<record id="account_financial_report_expense0"
model="account.financial.report">
<field name="name">Expense</field>
<field name="sign" eval="'-1'"/>
<field name="sequence">2</field>
<field name="parent_id"
ref="account_financial_report_profitandloss0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">account_type</field>
<field name="account_type_ids">expense</field>
</record>
<!-- Balance Sheet -->
<record id="account_financial_report_balancesheet0"
model="account.financial.report">
<field name="name">Balance Sheet</field>
<field name="type">sum</field>
</record>
<!-- Assets -->
<record id="account_financial_report_assets0"
model="account.financial.report">
<field name="name">Assets</field>
<field name="parent_id"
ref="account_financial_report_balancesheet0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">account_type</field>
<field name="account_type_ids">income_other</field>
</record>
<!-- Liability -->
<record id="account_financial_report_liabilitysum0"
model="account.financial.report">
<field name="name">Liability</field>
<field name="sequence">1</field>
<field name="parent_id"
ref="account_financial_report_balancesheet0"/>
<field name="display_detail">no_detail</field>
<field name="type">sum</field>
</record>
<!-- Liability -->
<record id="account_financial_report_liability0"
model="account.financial.report">
<field name="name">Liability</field>
<field name="parent_id"
ref="account_financial_report_liabilitysum0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">account_type</field>
<field name="account_type_ids">income_other</field>
</record>
<!-- Profit (Loss) to report -->
<record id="account_financial_report_profitloss_toreport0"
model="account.financial.report">
<field name="name">Profit (Loss) to report</field>
<field name="parent_id"
ref="account_financial_report_liabilitysum0"/>
<field name="display_detail">no_detail</field>
<field name="type">account_report</field>
<field name="account_report_id"
ref="account_financial_report_profitandloss0"/>
</record>
<!-- Common Report -->
<record id="account_report_view_form" model="ir.ui.view">
<field name="name">account.report.view.form</field>
<field name="model">account.report</field>
<field name="arch" type="xml">
<form string="Report Options">
<group col="4">
<field name="target_move" widget="radio"/>
<field name="date_from"/>
<field name="date_to"/>
</group>
<group>
<field name="journal_ids" widget="many2many_tags"
options="{'no_create': True}"/>
<field name="company_id" invisible="1"/>
</group>
<footer>
<button name="check_report" string="Print"
type="object" default_focus="1"
class="oe_highlight"
data-hotkey="q"/>
<button string="Cancel" class="btn btn-secondary"
special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<!-- Account payment method Inbound pdc-->
<record id="account_payment_method_pdc_in"
model="account.payment.method">
<field name="name">PDC</field>
<field name="code">pdc</field>
<field name="payment_type">inbound</field>
</record>
<!-- Outbound pdc -->
<record id="account_payment_method_pdc_out"
model="account.payment.method">
<field name="name">PDC</field>
<field name="code">pdc</field>
<field name="payment_type">outbound</field>
</record>
<!-- Decimal precision for account -->
<record forcecreate="True" id="decimal_account"
model="decimal.precision">
<field name="name">Account</field>
<field name="digits" eval="3"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<!-- Records for the account.financial.report model-->
<record id="account_financial_report_cash_flow0" model="account.financial.report">
<field name="name">Cash Flow Statement</field>
<field name="type">sum</field>
</record>
<!-- Defines a financial report record for operations -->
<record id="account_financial_report_operation0"
model="account.financial.report">
<field name="name">Operations</field>
<field name="sequence">1</field>
<field name="parent_id" ref="account_financial_report_cash_flow0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">sum</field>
</record>
<!-- Cash in operation -->
<record id="cash_in_from_operation0" model="account.financial.report">
<field name="name">Cash In</field>
<field name="sequence">1</field>
<field name="parent_id" ref="account_financial_report_operation0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">accounts</field>
</record>
<!-- Cash out operation -->
<record id="cash_out_operation1" model="account.financial.report">
<field name="name">Cash Out</field>
<field name="sequence">2</field>
<field name="parent_id" ref="account_financial_report_operation0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">accounts</field>
</record>
<!-- Defines a financial report record for Investing Activities -->
<record id="account_financial_report_investing_activity0" model="account.financial.report">
<field name="name">Investing Activities</field>
<field name="sequence">2</field>
<field name="parent_id" ref="account_financial_report_cash_flow0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">sum</field>
</record>
<!-- Cash in Investing Activities -->
<record id="cash_in_investing0" model="account.financial.report">
<field name="name">Cash In</field>
<field name="parent_id" ref="account_financial_report_investing_activity0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">accounts</field>
</record>
<!-- Cash out Investing Activities -->
<record id="cash_out_investing1" model="account.financial.report">
<field name="name">Cash Out</field>
<field name="parent_id" ref="account_financial_report_investing_activity0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">accounts</field>
</record>
<!-- Defines a financial report record for Financing Activities -->
<record id="account_financial_report_financing_activity1" model="account.financial.report">
<field name="name">Financing Activities</field>
<field name="sequence">3</field>
<field name="parent_id" ref="account_financial_report_cash_flow0"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">sum</field>
</record>
<!-- Cash in Financing Activities -->
<record id="cash_in_financial0" model="account.financial.report">
<field name="name">Cash In</field>
<field name="parent_id" ref="account_financial_report_financing_activity1"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">accounts</field>
</record>
<!-- Cash out Financing Activities -->
<record id="cash_out_financial1" model="account.financial.report">
<field name="name">Cash Out</field>
<field name="parent_id" ref="account_financial_report_financing_activity1"/>
<field name="display_detail">detail_with_hierarchy</field>
<field name="type">accounts</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<!-- Data file for the follow-up lines -->
<record model="followup.line" id="followup_line_id" >
<field name="name">Reminder</field>
<field name="delay">5</field>
</record>
<record model="account.followup" id="followup">
<field name="followup_line_ids" eval="[(6,0,[ref('followup_line_id')])]"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<!-- Data file for the Multiple Invoice-->
<record id="multiple_invoice_sample_name" model="multiple.invoice">
<field name="copy_name">Sample Name</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding='UTF-8'?>
<odoo>
<data noupdate="1">
<!-- The schedular action for Recurring Entries -->
<record id="recurring_template_cron" model="ir.cron">
<field name="name">Generate Recurring Entries</field>
<field name="model_id" ref="model_account_recurring_payments"/>
<field name="state">code</field>
<field name="code">model._cron_generate_entries()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,26 @@
## Module <base_accounting_kit>
#### 17.09.2025
#### Version 19.0.1.0.0
#### ADD
- Initial commit for Odoo 19 Full Accounting Kit for Community
#### 21.10.2025
#### Version 19.0.2.0.0
#### ADD
- Added Customer Statement feature.
#### 09.01.2026
#### Version 19.0.2.1.0
#### ADD
- Fixed the issue in the bank statement import.
#### 09.02.2026
#### Version 19.0.2.2.0
#### UPDT
- Fixed the issues in the bank statement import of csv and ofx files.
-
#### 25.03.2026
#### Version 19.0.2.3.0
#### UPDT
- Fixed the issue in fiscal year dates.

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import account_account
from . import account_asset_asset
from . import account_asset_category
from . import account_asset_depreciation_line
from . import account_bank_statement_line
from . import account_followup
from . import account_journal
from . import account_move
from . import account_move_line
from . import account_payment
from . import account_payment_method
from . import account_recurring_entries_line
from . import account_report
from . import followup_line
from . import multiple_invoice
from . import multiple_invoice_layout
from . import product_template
from . import recurring_payments
from . import res_company
from . import res_config_settings
from . import res_partner
from . import sale_order

View File

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models
from odoo.tools.misc import get_lang
class CashFlow(models.Model):
"""Inherits the account.account model to add additional functionality and
fields to the account"""
_inherit = 'account.account'
def get_cash_flow_ids(self):
"""Returns a list of cashflows for the account"""
cash_flow_id = self.env.ref('base_accounting_kit.account_financial_report_cash_flow0')
if cash_flow_id:
return [('parent_id.id', '=', cash_flow_id.id)]
cash_flow_type = fields.Many2one('account.financial.report',
string="Cash Flow type",
domain=get_cash_flow_ids)
@api.onchange('cash_flow_type')
def onchange_cash_flow_type(self):
"""Onchange the cash flow type of the account that will be updating
the account_ids values"""
for rec in self.cash_flow_type:
# update new record
rec.write({
'account_ids': [(4, self._origin.id)]
})
if self._origin.cash_flow_type.ids:
for rec in self._origin.cash_flow_type:
# remove old record
rec.write({'account_ids': [(3, self._origin.id)]})
class AccountCommonJournalReport(models.TransientModel):
"""Model used for creating the common journal report"""
_name = 'account.common.journal.report'
_description = 'Common Journal Report'
_inherit = "account.report"
section_main_report_ids = fields.Many2many(string="Section Of",
comodel_name='account.report',
relation="account_common_journal_report_section_rel",
column1="sub_report_id",
column2="main_report_id")
section_report_ids = fields.Many2many(string="Sections",
comodel_name='account.report',
relation="account_common_journal_report_section_rel",
column1="main_report_id",
column2="sub_report_id")
amount_currency = fields.Boolean(
'With Currency',
help="Print Report with the currency column if the currency differs "
"from the company currency.")
company_id = fields.Many2one('res.company', string='Company',
required=True, readonly=True,
default=lambda self: self.env.company)
date_from = fields.Date(string='Start Date')
date_to = fields.Date(string='End Date')
target_move = fields.Selection([('posted', 'All Posted Entries'),
('all', 'All Entries'),
], string='Target Moves',
required=True, default='posted')
def pre_print_report(self, data):
"""Pre-print the given data and that updates the amount
amount_currency value"""
data['form'].update({'amount_currency': self.amount_currency})
return data
def check_report(self):
"""Function to check if the report comes active models and related values"""
self.ensure_one()
data = {}
data['ids'] = self.env.context.get('active_ids', [])
data['model'] = self.env.context.get('active_model', 'ir.ui.menu')
data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move', 'company_id'])[0]
used_context = self._build_contexts(data)
data['form']['used_context'] = dict(used_context, lang=get_lang(self.env).code)
return self.with_context(discard_logo_check=True)._print_report(data)
def _build_contexts(self, data):
"""Builds the context information for the given data"""
result = {}
result['journal_ids'] = 'journal_ids' in data['form'] and data['form']['journal_ids'] or False
result['state'] = 'target_move' in data['form'] and data['form']['target_move'] or ''
result['date_from'] = data['form']['date_from'] or False
result['date_to'] = data['form']['date_to'] or False
result['strict_range'] = True if result['date_from'] else False
result['company_id'] = data['form']['company_id'][0] or False
return result

View File

@@ -0,0 +1,623 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import calendar
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.fields import Date
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF, float_is_zero
from odoo.exceptions import UserError, ValidationError
class AccountAssetAsset(models.Model):
"""
Model for managing assets with depreciation functionality
"""
_name = 'account.asset.asset'
_description = 'Asset/Revenue Recognition'
_inherit = ['mail.thread']
entry_count = fields.Integer(compute='_entry_count',
string='# Asset Entries')
name = fields.Char(string='Asset Name', required=True)
code = fields.Char(string='Reference', size=32)
value = fields.Float(string='Gross Value', required=True,
digits=0)
currency_id = fields.Many2one('res.currency', string='Currency',
required=True,
default=lambda self: self.env.company.currency_id.id)
company_id = fields.Many2one('res.company', string='Company',
required=True,
default=lambda self: self.env.company)
note = fields.Text()
category_id = fields.Many2one('account.asset.category', string='Asset Model',
required=False, change_default=True
)
date = fields.Date(string='Date', required=True,
default=fields.Date.context_today)
state = fields.Selection(
[('draft', 'Draft'), ('open', 'Running'), ('close', 'Close'),('cancelled','Cancelled')],
'Status', required=True, copy=False, default='draft',
help="When an asset is created, the status is 'Draft'.\n"
"If the asset is confirmed, the status goes in 'Running' and the depreciation lines can be posted in the accounting.\n"
"You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status.")
active = fields.Boolean(default=True)
partner_id = fields.Many2one('res.partner', string='Partner')
method = fields.Selection(
[('linear', 'Straight Line'), ('degressive', 'Declining')],
string='Computation Method', required=True,default='linear',
help="Choose the method to use to compute the amount of depreciation lines.\n * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
method_number = fields.Integer(string='Number of Depreciations',
default=5,
help="The number of depreciation's needed to depreciate your asset")
method_period = fields.Integer(string='Number of Months in a Period',
required=True, default=12,
help="The amount of time between two depreciation's, in months")
method_end = fields.Date(string='Ending Date')
method_progress_factor = fields.Float(string='Degressive Factor',
default=0.3,)
value_residual = fields.Float(compute='_amount_residual',
digits=0, string='Residual Value')
method_time = fields.Selection(
[('number', 'Number of Entries'), ('end', 'Ending Date')],
string='Time Method', required=True, default='number',
help="Choose the method to use to compute the dates and number of entries.\n"
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
prorata = fields.Boolean(string='Prorata Temporis',
help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first January / Start date of fiscal year')
depreciation_line_ids = fields.One2many('account.asset.depreciation.line',
'asset_id',
string='Depreciation Lines',
)
salvage_value = fields.Float(string='Salvage Value', digits=0,
help="It is the amount you plan to have that you cannot depreciate.")
invoice_id = fields.Many2one('account.move', string='Invoice',
copy=False)
type = fields.Selection([('sale', 'Sale: Revenue Recognition'),
('purchase', 'Purchase: Asset')], required=True, index=True, default='purchase')
#asset category
account_analytic_id = fields.Many2one('account.analytic.account',
string='Analytic Account',
domain="[('company_id', '=', company_id)]")
account_asset_id = fields.Many2one('account.account',
string='Asset Account', required=True,
domain="[('account_type', '!=', 'asset_receivable'),('account_type', '!=', 'liability_payable'),('account_type', '!=', 'asset_cash'),('account_type', '!=', 'liability_credit_card'),('active', '=', True)]",
help="Account used to record the purchase of the asset at its original price.")
account_depreciation_id = fields.Many2one(
'account.account', string='Depreciation Account',
required=True,
domain="[('account_type', '!=', 'asset_receivable'),('account_type', '!=', 'liability_payable'),('account_type', '!=', 'asset_cash'),('account_type', '!=', 'liability_credit_card'),('active', '=', True)]",
help="Account used in the depreciation entries, to decrease the asset value.")
account_depreciation_expense_id = fields.Many2one(
'account.account', string='Expense Account',
required=True,
domain="[('account_type', '!=', 'asset_receivable'),('account_type', '!=','liability_payable'),('account_type', '!=', 'asset_cash'),('account_type', '!=','liability_credit_card'),('active', '=', True)]",
help="Account used in the periodical entries, to record a part of the asset as expense.")
journal_id = fields.Many2one('account.journal', string='Journal',
required=True)
open_asset = fields.Boolean(string='Auto-confirm Assets',
help="Check this if you want to automatically confirm the assets of this category when created by invoices.")
group_entries = fields.Boolean(string='Group Journal Entries',
help="Check this if you want to group the generated entries by categories.")
def unlink(self):
""" Prevents deletion of assets in 'open' or 'close' state or with posted depreciation entries."""
for asset in self:
if asset.state in ['open', 'close']:
raise UserError(
_('You cannot delete a document is in %s state.') % (
asset.state,))
for depreciation_line in asset.depreciation_line_ids:
if depreciation_line.move_id:
raise UserError(_(
'You cannot delete a document that contains posted entries.'))
return super(AccountAssetAsset, self).unlink()
def _get_last_depreciation_date(self):
"""
@param id: ids of a account.asset.asset objects
@return: Returns a dictionary of the effective dates of the last depreciation entry made for given asset ids. If there isn't any, return the purchase date of this asset
"""
self.env.cr.execute("""
SELECT a.id as id, COALESCE(MAX(m.date),a.date) AS date
FROM account_asset_asset a
LEFT JOIN account_asset_depreciation_line rel ON (rel.asset_id = a.id)
LEFT JOIN account_move m ON (rel.move_id = m.id)
WHERE a.id IN %s
GROUP BY a.id, m.date """, (tuple(self.ids),))
result = dict(self.env.cr.fetchall())
return result
@api.onchange('category_id')
def gross_value(self):
"""Update the 'value' field based on the 'price' of the selected 'category_id'."""
self.value = self.category_id.price
@api.onchange('method')
def onchange_method(self):
if self.depreciation_line_ids:
self.depreciation_line_ids = [(fields.Command.clear())]
@api.model
def compute_generated_entries(self, date, asset_type=None):
"""Compute generated entries for assets based on the provided date and asset type."""
# Entries generated : one by grouped category and one by asset from ungrouped category
created_move_ids = []
type_domain = []
if asset_type:
type_domain = [('type', '=', asset_type)]
ungrouped_assets = self.env['account.asset.asset'].search(
type_domain + [('state', '=', 'open'),
('category_id.group_entries', '=', False)])
created_move_ids += ungrouped_assets._compute_entries(date,
group_entries=False)
for grouped_category in self.env['account.asset.category'].search(
type_domain + [('group_entries', '=', True)]):
assets = self.env['account.asset.asset'].search(
[('state', '=', 'open'),
('category_id', '=', grouped_category.id)])
created_move_ids += assets._compute_entries(date,
group_entries=True)
return created_move_ids
def _compute_board_amount(self, sequence, residual_amount, amount_to_depr,
undone_dotation_number,
posted_depreciation_line_ids, total_days,
depreciation_date):
"""Compute the depreciation amount for a specific sequence in the asset's depreciation schedule."""
amount = 0
if sequence == undone_dotation_number:
amount = residual_amount
else:
if self.method == 'linear':
amount = amount_to_depr / (undone_dotation_number - len(
posted_depreciation_line_ids))
if self.prorata:
amount = amount_to_depr / self.method_number
if sequence == 1:
if self.method_period % 12 != 0:
date = datetime.strptime(str(self.date),
'%Y-%m-%d')
month_days = \
calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
amount = (
amount_to_depr / self.method_number) / month_days * days
else:
days = (self.company_id.compute_fiscalyear_dates(
depreciation_date)[
'date_to'] - depreciation_date).days + 1
amount = (
amount_to_depr / self.method_number) / total_days * days
elif self.method == 'degressive':
amount = residual_amount * self.method_progress_factor
if self.prorata:
if sequence == 1:
if self.method_period % 12 != 0:
date = datetime.strptime(str(self.date),
'%Y-%m-%d')
month_days = \
calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
amount = (
residual_amount * self.method_progress_factor) / month_days * days
else:
days = (self.company_id.compute_fiscalyear_dates(
depreciation_date)[
'date_to'] - depreciation_date).days + 1
amount = (
residual_amount * self.method_progress_factor) / total_days * days
return amount
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
"""Compute the number of remaining depreciations for an asset based on the depreciation date and total days."""
undone_dotation_number = self.method_number
if self.method_time == 'end':
end_date = datetime.strptime(str(self.method_end), DF).date()
undone_dotation_number = 0
while depreciation_date <= end_date:
depreciation_date = date(depreciation_date.year,
depreciation_date.month,
depreciation_date.day) + relativedelta(
months=+self.method_period)
undone_dotation_number += 1
if self.prorata:
undone_dotation_number += 1
return undone_dotation_number
def compute_depreciation_board(self):
"""
Compute the depreciation schedule for the asset based on its current state and parameters.
This method calculates the depreciation amount for each period and generates depreciation entries accordingly.
"""
self.ensure_one()
posted_depreciation_line_ids = self.depreciation_line_ids.filtered(
lambda x: x.move_check).sorted(key=lambda l: l.depreciation_date)
unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(
lambda x: not x.move_check)
# Remove old unposted depreciation lines. We cannot use unlink() with One2many field
commands = [(2, line_id.id, False) for line_id in
unposted_depreciation_line_ids]
if self.value_residual != 0.0:
amount_to_depr = residual_amount = self.value_residual
if self.prorata:
# if we already have some previous validated entries, starting date is last entry + method perio
if posted_depreciation_line_ids and \
posted_depreciation_line_ids[-1].depreciation_date:
last_depreciation_date = datetime.strptime(
posted_depreciation_line_ids[-1].depreciation_date,
DF).date()
depreciation_date = last_depreciation_date + relativedelta(
months=+self.method_period)
else:
depreciation_date = datetime.strptime(
str(self._get_last_depreciation_date()[self.id]),
DF).date()
else:
# depreciation_date = 1st of January of purchase year if annual valuation, 1st of
# purchase month in other cases
if self.method_period >= 12:
if self.company_id.fiscalyear_last_month:
asset_date = date(year=int(self.date.year),
month=int(
self.company_id.fiscalyear_last_month),
day=int(
self.company_id.fiscalyear_last_day)) + relativedelta(
days=1) + \
relativedelta(year=int(
self.date.year)) # e.g. 2018-12-31 +1 -> 2019
else:
asset_date = datetime.strptime(
str(self.date)[:4] + '-01-01', DF).date()
else:
asset_date = datetime.strptime(str(self.date)[:7] + '-01',
DF).date()
# if we already have some previous validated entries, starting date isn't 1st January but last entry + method period
if posted_depreciation_line_ids and \
posted_depreciation_line_ids[-1].depreciation_date:
last_depreciation_date = datetime.strptime(str(
posted_depreciation_line_ids[-1].depreciation_date),
DF).date()
depreciation_date = last_depreciation_date + relativedelta(
months=+self.method_period)
else:
depreciation_date = asset_date
day = depreciation_date.day
month = depreciation_date.month
year = depreciation_date.year
total_days = (year % 4) and 365 or 366
undone_dotation_number = self._compute_board_undone_dotation_nb(
depreciation_date, total_days)
for x in range(len(posted_depreciation_line_ids),
undone_dotation_number):
sequence = x + 1
amount = self._compute_board_amount(sequence, residual_amount,
amount_to_depr,
undone_dotation_number,
posted_depreciation_line_ids,
total_days,
depreciation_date)
amount = self.currency_id.round(amount)
if float_is_zero(amount,
precision_rounding=self.currency_id.rounding):
continue
residual_amount -= amount
vals = {
'amount': amount,
'asset_id': self.id,
'sequence': sequence,
'name': (self.code or '') + '/' + str(sequence),
'remaining_value': residual_amount if residual_amount >= 0 else 0.0,
'depreciated_value': self.value - (
self.salvage_value + residual_amount),
'depreciation_date': depreciation_date.strftime(DF),
}
commands.append((0, False, vals))
# Considering Depr. Period as months
depreciation_date = date(year, month, day) + relativedelta(
months=+self.method_period)
day = depreciation_date.day
month = depreciation_date.month
year = depreciation_date.year
self.write({'depreciation_line_ids': commands})
last_depr_date = None
if self.depreciation_line_ids:
last_depr_date = max(self.depreciation_line_ids.mapped('depreciation_date'))
if last_depr_date:
self._compute_entries(date=last_depr_date)
return True
def validate(self):
"""Update the state to 'open' and track specific fields based on the asset's method."""
self.write({'state': 'open'})
field = [
'method',
'method_number',
'method_period',
'method_end',
'method_progress_factor',
'method_time',
'salvage_value',
'invoice_id',
]
ref_tracked_fields = self.env['account.asset.asset'].fields_get(field)
if not self.depreciation_line_ids:
self.compute_depreciation_board()
for asset in self:
tracked_fields = ref_tracked_fields.copy()
if asset.method == 'linear':
del (tracked_fields['method_progress_factor'])
if asset.method_time != 'end':
del (tracked_fields['method_end'])
else:
del (tracked_fields['method_number'])
dummy, tracking_value_ids = asset._mail_track(tracked_fields,
dict.fromkeys(
field))
asset.message_post(subject=_('Asset created'),
tracking_value_ids=tracking_value_ids)
today_date = fields.Date.context_today(self)
# Split lines based on depreciation_date
draft_lines = asset.depreciation_line_ids.filtered(lambda l: l.move_id and l.move_id.state == 'draft')
#Post only entries before today
lines_to_post_now = draft_lines.filtered(lambda l: l.depreciation_date < today_date)
moves_to_post_now = lines_to_post_now.mapped('move_id')
if moves_to_post_now:
moves_to_post_now._post()
#Set auto_post='at_date' for entries today or later
future_lines = draft_lines.filtered(lambda l: l.depreciation_date >= today_date)
future_moves = future_lines.mapped('move_id')
if future_moves:
future_moves.write({'auto_post': 'at_date'})
return True
def _get_disposal_moves(self):
"""Get the disposal moves for the asset."""
move_ids = []
for asset in self:
unposted_depreciation_line_ids = asset.depreciation_line_ids.filtered(
lambda x: not x.move_check)
if unposted_depreciation_line_ids:
old_values = {
'method_end': asset.method_end,
'method_number': asset.method_number,
}
# Remove all unposted depr. lines
commands = [(2, line_id.id, False) for line_id in
unposted_depreciation_line_ids]
# Create a new depr. line with the residual amount and post it
sequence = len(asset.depreciation_line_ids) - len(
unposted_depreciation_line_ids) + 1
today = datetime.today().strftime(DF)
vals = {
'amount': asset.value_residual,
'asset_id': asset.id,
'sequence': sequence,
'name': (asset.code or '') + '/' + str(sequence),
'remaining_value': 0,
'depreciated_value': asset.value - asset.salvage_value,
# the asset is completely depreciated
'depreciation_date': today,
}
commands.append((0, False, vals))
asset.write(
{'depreciation_line_ids': commands, 'method_end': today,
'method_number': sequence})
tracked_fields = self.env['account.asset.asset'].fields_get(
['method_number', 'method_end'])
changes, tracking_value_ids = asset._mail_track(
tracked_fields, old_values)
if changes:
asset.message_post(subject=_(
'Asset sold or disposed. Accounting entry awaiting for validation.'),
tracking_value_ids=tracking_value_ids)
move_ids += asset.depreciation_line_ids[-1].create_move(
post_move=False)
return move_ids
def set_to_close(self):
"""Set the asset to close state by creating disposal moves and returning an action window to view the move(s)."""
move_ids = self._get_disposal_moves()
if move_ids:
name = _('Disposal Move')
view_mode = 'form'
if len(move_ids) > 1:
name = _('Disposal Moves')
view_mode = 'list,form'
return {
'name': name,
'view_mode': view_mode,
'res_model': 'account.move',
'type': 'ir.actions.act_window',
'target': 'current',
'res_id': move_ids[0],
}
# Fallback, as if we just clicked on the smartbutton
return self.open_entries()
def set_to_draft(self):
"""Set the asset's state to 'draft'."""
self.write({'state': 'draft'})
@api.depends('value', 'salvage_value', 'depreciation_line_ids.move_check',
'depreciation_line_ids.amount')
def _amount_residual(self):
"""Compute the residual value of the asset based on the total depreciation amount."""
for record in self:
total_amount = 0.0
for line in record.depreciation_line_ids:
if line.move_check:
total_amount += line.amount
record.value_residual = record.value - total_amount - record.salvage_value
@api.onchange('company_id')
def onchange_company_id(self):
"""Update the 'currency_id' field based on the selected 'company_id'."""
self.currency_id = self.company_id.currency_id.id
@api.depends('depreciation_line_ids.move_id')
def _entry_count(self):
"""Compute the number of entries related to the asset based on the depreciation lines."""
for asset in self:
res = self.env['account.asset.depreciation.line'].search_count(
[('asset_id', '=', asset.id), ('move_id', '!=', False)])
asset.entry_count = res or 0
@api.constrains('prorata', 'method_time')
def _check_prorata(self):
"""Check if prorata temporis can be applied for the given asset based on the 'prorata' and 'method_time' fields."""
if self.prorata and self.method_time != 'number':
raise ValidationError(_(
'Prorata temporis can be applied only for time method "number of depreciations".'))
@api.onchange('category_id')
def onchange_category_id(self):
"""Update the fields of the asset based on the selected 'category_id'."""
vals = self.onchange_category_id_values(self.category_id.id)
# We cannot use 'write' on an object that doesn't exist yet
if vals:
for k, v in vals['value'].items():
setattr(self, k, v)
def onchange_category_id_values(self, category_id):
"""Update the fields of the asset based on the selected 'category_id'."""
if category_id:
category = self.env['account.asset.category'].browse(category_id)
return {
'value': {
'method': category.method,
'method_number': category.method_number,
'method_time': category.method_time,
'method_period': category.method_period,
'method_progress_factor': category.method_progress_factor,
'method_end': category.method_end,
'prorata': category.prorata,
'journal_id':category.journal_id.id,
'account_asset_id':category.account_asset_id.id,
'account_depreciation_id':category.account_depreciation_id.id,
'account_depreciation_expense_id':category.account_depreciation_expense_id.id,
'account_analytic_id':category.account_analytic_id.id
}
}
@api.onchange('method_time')
def onchange_method_time(self):
"""Update the 'prorata' field based on the selected 'method_time' value."""
if self.method_time != 'number':
self.prorata = False
def copy_data(self, default=None):
"""Copies the data of the current record with the option to override default values."""
if default is None:
default = {}
default['name'] = self.name + _(' (copy)')
return super(AccountAssetAsset, self).copy_data(default)
def _compute_entries(self, date, group_entries=False):
"""Compute depreciation entries for the given date."""
depreciation_ids = self.env['account.asset.depreciation.line'].search([
('asset_id', 'in', self.ids), ('depreciation_date', '<=', date),
('move_check', '=', False)])
if group_entries:
return depreciation_ids.create_grouped_move()
return depreciation_ids.create_move()
def open_entries(self):
"""Return a dictionary to open journal entries related to the asset."""
move_ids = []
for asset in self:
for depreciation_line in asset.depreciation_line_ids:
if depreciation_line.move_id:
move_ids.append(depreciation_line.move_id.id)
return {
'name': _('Journal Entries'),
'view_mode': 'list,form',
'res_model': 'account.move',
'views': [(self.env.ref('account.view_move_tree').id, 'list'), (False, 'form')],
'view_id': False,
'type': 'ir.actions.act_window',
'domain': [('id', 'in', move_ids)],
}
def action_save_model(self):
return{
'type': 'ir.actions.act_window',
'name': _('Asset Model'),
'res_model': 'account.asset.category',
'view_mode': 'form',
'target': 'current',
'context': {'default_price': self.value,
'default_method_time':self.method_time,
'default_method_end':self.method_end,
'default_method_number':self.method_number,
'default_method_period':self.method_period,
'default_method':self.method,
'default_company_id':self.company_id.id,
'default_method_progress_factor':self.method_progress_factor,
'default_prorata':self.prorata,
'default_group_entries':self.group_entries,
'default_open_asset':self.open_asset,
'default_account_analytic_id':self.account_analytic_id.id,
'default_account_depreciation_expense_id':self.account_depreciation_expense_id.id,
'default_account_depreciation_id':self.account_depreciation_id.id,
'default_account_asset_id':self.account_asset_id.id,
'default_journal_id':self.journal_id.id,
'default_asset_id': self.id,
}
}
def action_cancel_assets(self):
for asset in self:
for move in asset.depreciation_line_ids.mapped('move_id'):
if move.state == 'posted':
# Force to draft
move.button_draft() # or move.state = 'draft' if button_draft is restricted
move.unlink()
# Delete all depreciation lines
asset.depreciation_line_ids.unlink()
# Reset state
asset.state = 'cancelled'

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models
class AccountAssetCategory(models.Model):
_name = 'account.asset.category'
_description = 'Asset category'
active = fields.Boolean(default=True)
name = fields.Char(required=True, index=True, string="Asset Type")
company_id = fields.Many2one('res.company', string='Company',
required=True,
default=lambda self: self.env.company)
price = fields.Monetary(string='Price', required=True)
currency_id = fields.Many2one("res.currency",
default=lambda self: self.env[
'res.currency'].search(
[('name', '=', 'USD')]).id,
readonly=True, hide=True)
account_analytic_id = fields.Many2one('account.analytic.account',
string='Analytic Account',
domain="[('company_id', '=', company_id)]")
account_asset_id = fields.Many2one('account.account',
string='Asset Account', required=True,
domain="[('account_type', '!=', 'asset_receivable'),('account_type', '!=', 'liability_payable'),('account_type', '!=', 'asset_cash'),('account_type', '!=', 'liability_credit_card'),('active', '=', True)]",
help="Account used to record the purchase of the asset at its original price.")
account_depreciation_id = fields.Many2one(
'account.account', string='Depreciation Account',
required=True,
domain="[('account_type', '!=', 'asset_receivable'),('account_type', '!=', 'liability_payable'),('account_type', '!=', 'asset_cash'),('account_type', '!=', 'liability_credit_card'),('active', '=', True)]",
help="Account used in the depreciation entries, to decrease the asset value.")
account_depreciation_expense_id = fields.Many2one(
'account.account', string='Expense Account',
required=True,
domain="[('account_type', '!=', 'asset_receivable'),('account_type', '!=','liability_payable'),('account_type', '!=', 'asset_cash'),('account_type', '!=','liability_credit_card'),('active', '=', True)]",
help="Account used in the periodical entries, to record a part of the asset as expense.")
journal_id = fields.Many2one('account.journal', string='Journal',
required=True)
method = fields.Selection(
[('linear', 'Straight Line'), ('degressive', 'Declining')],
string='Computation Method', required=True, default='linear',
help="Choose the method to use to compute the amount of depreciation lines.\n"
" * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
method_number = fields.Integer(string='Number of Depreciations', default=5,
help="The number of depreciations needed to depreciate your asset")
method_period = fields.Integer(string='Period Length', default=1,
help="State here the time between 2 depreciations, in months",
required=True)
method_progress_factor = fields.Float('Degressive Factor', default=0.3)
method_time = fields.Selection(
[('number', 'Number of Entries'), ('end', 'Ending Date')],
string='Time Method', required=True, default='number',
help="Choose the method to use to compute the dates and number of entries.\n"
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
method_end = fields.Date('Ending date')
prorata = fields.Boolean(string='Prorata Temporis',
help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first of January')
open_asset = fields.Boolean(string='Auto-confirm Assets',
help="Check this if you want to automatically confirm the assets of this category when created by invoices.")
group_entries = fields.Boolean(string='Group Journal Entries',
help="Check this if you want to group the generated entries by categories.")
type = fields.Selection([('sale', 'Sale: Revenue Recognition'),
('purchase', 'Purchase: Asset')], required=True,
index=True, default='purchase')
@api.onchange('account_asset_id')
def onchange_account_asset(self):
"""Onchange method triggered when the 'account_asset_id' field is modified.
Updates 'account_depreciation_id' or 'account_depreciation_expense_id' based on the 'type' field value."""
if self.type == "purchase":
self.account_depreciation_id = self.account_asset_id
elif self.type == "sale":
self.account_depreciation_expense_id = self.account_asset_id
@api.onchange('type')
def onchange_type(self):
"""Update the 'prorata' and 'method_period' fields based on the value of the 'type' field."""
if self.type == 'sale':
self.prorata = True
self.method_period = 1
else:
self.method_period = 12
@api.onchange('method_time')
def _onchange_method_time(self):
"""Update the 'prorata' field based on the value of the 'method_time' field.
Set 'prorata' to False if 'method_time' is not equal to 'number'."""
if self.method_time != 'number':
self.prorata = False
@api.model
def create(self, vals):
record = super().create(vals)
asset_id = self.env.context.get('default_asset_id')
if asset_id:
asset = self.env['account.asset.asset'].browse(asset_id)
asset.category_id = record.id
return record

View File

@@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare
class AccountAssetDepreciationLine(models.Model):
"""Model for managing asset depreciation lines in the accounting system."""
_name = 'account.asset.depreciation.line'
_description = 'Asset depreciation line'
name = fields.Char(string='Depreciation Name', required=True, index=True)
sequence = fields.Integer(required=True)
asset_id = fields.Many2one('account.asset.asset', string='Asset',
required=True, ondelete='cascade')
parent_state = fields.Selection(related='asset_id.state',
string='State of Asset')
amount = fields.Float(string='Current Depreciation',
required=True)
remaining_value = fields.Float(string='Next Period Depreciation',
required=True)
depreciated_value = fields.Float(string='Cumulative Depreciation',
required=True)
depreciation_date = fields.Date('Depreciation Date', index=True)
move_id = fields.Many2one('account.move', string='Depreciation Entry')
move_check = fields.Boolean(compute='_get_move_check', string='Linked',
store=True)
move_posted_check = fields.Boolean(compute='_get_move_posted_check',
string='Posted', store=True)
@api.depends('move_id')
def _get_move_check(self):
"""Compute the 'move_check' field based on the presence of 'move_id'
for each record in the 'AccountAssetDepreciationLine' class."""
for line in self:
line.move_check = bool(line.move_id)
@api.depends('move_id.state')
def _get_move_posted_check(self):
"""Compute the 'move_posted_check' field based on the state of 'move_id'
for each record in the 'AccountAssetDepreciationLine' class."""
for line in self:
line.move_posted_check = True if line.move_id and line.move_id.state == 'posted' else False
def create_move(self, post_move=True):
"""Create accounting moves for asset depreciation lines."""
created_moves = self.env['account.move']
prec = self.env['decimal.precision'].precision_get('Account')
if self.mapped('move_id'):
raise UserError(_(
'This depreciation is already linked to a journal entry! Please post or delete it.'))
for line in self:
asset_id = line.asset_id
depreciation_date = self.env.context.get(
'depreciation_date') or line.depreciation_date or fields.Date.context_today(
self)
company_currency = asset_id.company_id.currency_id
current_currency = asset_id.currency_id
amount = current_currency._convert(line.amount, company_currency,
line.asset_id.company_id,
depreciation_date)
asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids))
partner = self.env['res.partner']._find_accounting_partner(line.asset_id.partner_id)
move_line_1 = {
'name': asset_name,
'account_id': asset_id.account_depreciation_id.id,
'debit': 0.0 if float_compare(amount, 0.0,
precision_digits=prec) > 0 else -amount,
'credit': amount if float_compare(amount, 0.0,
precision_digits=prec) > 0 else 0.0,
'journal_id': asset_id.journal_id.id,
'partner_id': partner.id,
'currency_id': company_currency != current_currency and current_currency.id or company_currency.id,
'amount_currency': company_currency != current_currency and - 1.0 * line.amount or 0.0,
}
move_line_2 = {
'name': asset_name,
'account_id': asset_id.account_depreciation_expense_id.id,
'credit': 0.0 if float_compare(amount, 0.0,
precision_digits=prec) > 0 else -amount,
'debit': amount if float_compare(amount, 0.0,
precision_digits=prec) > 0 else 0.0,
'journal_id': asset_id.journal_id.id,
'partner_id': partner.id,
'currency_id': company_currency != current_currency and current_currency.id or company_currency.id,
'amount_currency': company_currency != current_currency and line.amount or 0.0,
}
line_ids = [(0, 0, {
'account_id': asset_id.account_depreciation_id.id,
'partner_id': partner.id,
'credit': amount if float_compare(amount, 0.0,
precision_digits=prec) > 0 else 0.0,
}), (0, 0, {
'account_id': asset_id.account_depreciation_expense_id.id,
'partner_id': partner.id,
'debit': amount if float_compare(amount, 0.0,
precision_digits=prec) > 0 else 0.0,
})]
move = self.env['account.move'].create({
'ref': line.asset_id.code,
'date': depreciation_date or False,
'journal_id': asset_id.journal_id.id,
'line_ids': line_ids,
})
for move_line in move.line_ids:
if move_line.account_id.id == move_line_1['account_id']:
move_line.write({'credit': move_line_1['credit'],
'debit': move_line_1['debit']})
elif move_line.account_id.id == move_line_2['account_id']:
move_line.write({'debit': move_line_2['debit'],
'credit': move_line_2['credit']})
if move.line_ids.filtered(
lambda x: x.name == 'Automatic Balancing Line'):
move.line_ids.filtered(
lambda x: x.name == 'Automatic Balancing Line').unlink()
line.write({'move_id': move.id, 'move_check': True})
created_moves |= move
if post_move and created_moves:
created_moves.filtered(lambda m: any(
m.asset_depreciation_ids.mapped(
'asset_id.open_asset'))).post()
return [x.id for x in created_moves]
def create_grouped_move(self, post_move=True):
"""Create a grouped accounting move for asset depreciation lines."""
if not self.exists():
return []
created_moves = self.env['account.move']
category_id = self[
0].asset_id.category_id # we can suppose that all lines have the same category
depreciation_date = self.env.context.get(
'depreciation_date') or fields.Date.context_today(self)
amount = 0.0
for line in self:
# Sum amount of all depreciation lines
company_currency = line.asset_id.company_id.currency_id
current_currency = line.asset_id.currency_id
amount += current_currency.compute(line.amount, company_currency)
name = category_id.name + _(' (grouped)')
move_line_1 = {
'name': name,
'account_id': category_id.account_depreciation_id.id,
'debit': 0.0,
'credit': amount,
'journal_id': category_id.journal_id.id,
'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False,
}
move_line_2 = {
'name': name,
'account_id': category_id.account_depreciation_expense_id.id,
'credit': 0.0,
'debit': amount,
'journal_id': category_id.journal_id.id,
'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False,
}
move_vals = {
'ref': category_id.name,
'date': depreciation_date or False,
'journal_id': category_id.journal_id.id,
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
}
move = self.env['account.move'].create(move_vals)
self.write({'move_id': move.id, 'move_check': True})
created_moves |= move
if post_move and created_moves:
self.post_lines_and_close_asset()
created_moves.post()
return [x.id for x in created_moves]
def post_lines_and_close_asset(self):
# we re-evaluate the assets to determine whether we can close them
# `message_post` invalidates the (whole) cache
# preprocess the assets and lines in which a message should be posted,
# and then post in batch will prevent the re-fetch of the same data over and over.
assets_to_close = self.env['account.asset.asset']
for line in self:
asset = line.asset_id
if asset.currency_id.is_zero(asset.value_residual):
assets_to_close |= asset
self.log_message_when_posted()
assets_to_close.write({'state': 'close'})
for asset in assets_to_close:
asset.message_post(body=_("Document closed."))
def log_message_when_posted(self):
"""Format and post messages for asset depreciation lines that are posted."""
def _format_message(message_description, tracked_values):
message = ''
if message_description:
message = '<span>%s</span>' % message_description
for name, values in tracked_values.items():
message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % name
message += '%s</div>' % values
return message
# `message_post` invalidates the (whole) cache
# preprocess the assets in which messages should be posted,
# and then post in batch will prevent the re-fetch of the same data over and over.
assets_to_post = {}
for line in self:
if line.move_id and line.move_id.state == 'draft':
partner_name = line.asset_id.partner_id.name
currency_name = line.asset_id.currency_id.name
msg_values = {_('Currency'): currency_name,
_('Amount'): line.amount}
if partner_name:
msg_values[_('Partner')] = partner_name
msg = _format_message(_('Depreciation line posted.'),
msg_values)
assets_to_post.setdefault(line.asset_id, []).append(msg)
for asset, messages in assets_to_post.items():
for msg in messages:
asset.message_post(body=msg)
# def unlink(self):
# """Check if the depreciation line is linked to a posted move before deletion."""
# for record in self:
# if record.move_check:
# if record.asset_id.category_id.type == 'purchase':
# msg = _("You cannot delete posted depreciation lines.")
# else:
# msg = _("You cannot delete posted installment lines.")
# raise UserError(msg)
# return super(AccountAssetDepreciationLine, self).unlink()

View File

@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models
from odoo.http import request
class AccountBankStatementLine(models.Model):
"""Update the 'rowdata' field for the specified record."""
_name = 'account.bank.statement.line'
_inherit = ['account.bank.statement.line', 'mail.thread',
'mail.activity.mixin', 'analytic.mixin']
lines_widget = fields.Char(string="Lines Widget")
account_id = fields.Many2one('account.account', string='Account')
tax_ids = fields.Many2many('account.tax')
form_name = fields.Char()
form_balance = fields.Monetary(currency_field='currency_id')
rowdata = fields.Json(string="RowData")
matchRowdata = fields.Json(string="MatchRowData")
record_id = fields.Integer()
company_currency_id = fields.Many2one(
related='company_id.currency_id', readonly=True,
)
bank_state = fields.Selection(selection=[('invalid', 'Invalid'),
('valid', 'Valid'),
('reconciled', 'Reconciled')],
compute='_compute_state', store=True)
reconcile_models_widget = fields.Char()
lines_widget_json = fields.Json(store=True)
@api.model
def update_rowdata(self, record_id):
"""Update the 'rowdata' field for the specified record."""
request.session['record_id'] = record_id
@api.model
def update_match_row_data(self, resId):
"""Update the match row data for a specific record identified by the given resId."""
request.session['resId'] = resId
move_record = self.env['account.move.line'].browse(resId)
move_record_values = {
'id': move_record.id,
'account_id': move_record.account_id.id,
'account_name': move_record.account_id.name,
'account_code': move_record.account_id.code,
'partner_id': move_record.partner_id,
'partner_name': move_record.partner_id.name,
'date': move_record.date,
'move_id': move_record.move_id,
'move_name': move_record.move_id.name,
'name': move_record.name,
'amount_residual_currency': move_record.amount_residual_currency,
'amount_residual': move_record.amount_residual,
'currency_id': move_record.currency_id.id,
'currency_symbol': move_record.currency_id.symbol
}
return move_record_values
def button_validation(self, async_action=False):
"""Ensure the current recordset holds a single record and mark it as reconciled."""
self.ensure_one()
self.is_reconciled = True
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def button_reset(self):
"""Reset the current bank statement line if it is in a 'reconciled' state."""
self.ensure_one()
if self.bank_state == 'reconciled':
self.action_undo_reconciliation()
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def button_to_check(self, async_action=True):
"""Ensure the current recordset holds a single record, validate the bank
state, and mark the move as 'to check'."""
self.ensure_one()
if self.bank_state == 'valid':
self.button_validation(async_action=async_action)
self.move_id.to_check = True
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def button_set_as_checked(self):
"""Mark the associated move as 'not to check' by setting 'to_check' to False."""
self.ensure_one()
self.move_id.to_check = False
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
@api.model
def get_statement_line(self, record_id):
"""Retrieve and format bank statement line details based on the provided record ID."""
statement_line_records = self.env[
'account.bank.statement.line'].search_read([('id', '=', record_id)])
result_list = []
for record in statement_line_records:
move_id = record.get('move_id', False)
partner_id = record.get('partner_id', False)
date = record.get('date', False)
amount = record.get('amount', False)
currency_id = record.get('currency_id', False)
payment_ref = record.get("payment_ref", False)
bank_state = record.get("bank_state", False)
id = record.get("id", False)
if move_id:
move_record = self.env['account.move.line'].search(
[('move_id', '=', move_id[0])], limit=1)
currency_symbol = self.env['res.currency'].browse(
currency_id[0])
account_id = move_record.account_id
date_str = date.strftime('%Y-%m-%d') if date else None
result_list.append({
'id': id,
'move_id': move_id,
'partner_id': partner_id,
'account_id': account_id.id,
'account_name': account_id.name,
'account_code': account_id.code,
'date': date_str,
'amount': amount,
'currency_symbol': currency_symbol.symbol,
'payment_ref': payment_ref,
'bank_state': bank_state,
})
# Update the account_id for the current record
self.env['account.bank.statement.line'].browse(
record['id']).write({'account_id': account_id.id})
return result_list
@api.depends('account_id')
def _compute_state(self):
"""Compute the state of bank transactions based on the account's
reconciliation status and journal settings."""
for record in self:
if record.is_reconciled:
record.bank_state = 'reconciled'
else:
suspense_account = record.journal_id.suspense_account_id
if suspense_account in record.account_id:
record.bank_state = 'invalid'
else:
record.bank_state = 'valid'

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
class Followup(models.Model):
"""Model for managing account follow-ups."""
_name = 'account.followup'
_description = 'Account Follow-up'
_rec_name = 'name'
followup_line_ids = fields.One2many('followup.line', 'followup_id',
'Follow-up', copy=True)
company_id = fields.Many2one('res.company', 'Company',
default=lambda self: self.env.company)
name = fields.Char(related='company_id.name', readonly=True)

View File

@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models, _
class AccountJournal(models.Model):
"""Module inherited for adding the reconcile method in the account
journal"""
_inherit = "account.journal"
multiple_invoice_ids = fields.One2many('multiple.invoice',
'journal_id',
string='Multiple Invoice')
multiple_invoice_type = fields.Selection(
[('text', 'Text'), ('watermark', 'Watermark')], required=True,
default='text', string="Display Type")
text_position = fields.Selection([
('header', 'Header'),
('footer', 'Footer'),
('body', 'Document Body')
], required=True, default='header', string='Text Position')
body_text_position = fields.Selection([
('tl', 'Top Left'),
('tr', 'Top Right'),
('bl', 'Bottom Left'),
('br', 'Bottom Right'),
], default='tl', string='Body Text Position')
text_align = fields.Selection([
('right', 'Right'),
('left', 'Left'),
('center', 'Center'),
], default='right', string='Center Align Text Position')
layout = fields.Char(string="Layout",
related="company_id.external_report_layout_id.key")
def action_open_reconcile(self):
"""Open the reconciliation view based on the type of the account journal."""
self.ensure_one()
if self.type in ('bank', 'cash'):
views = [
(self.env.ref(
'base_accounting_kit.account_bank_statement_line_view_kanban').id,
'kanban'),
(self.env.ref(
'base_accounting_kit.account_bank_statement_line_view_tree').id,
'list'), # Include tree view
]
context = {
'default_journal_id': self.id,
'search_default_journal_id': self.id,
}
kanban_first = True
name = None
extra_domain = None
return {
'name': name or _("Bank Reconciliation"),
'type': 'ir.actions.act_window',
'res_model': 'account.bank.statement.line',
'context': context,
'search_view_id': [
self.env.ref(
'base_accounting_kit.account_bank_statement_line_view_search').id,
'search'],
'view_mode': 'kanban,list' if kanban_first else 'list,kanban',
'views': views if kanban_first else views[::-1],
'domain': [('state', '!=', 'cancel')] + (extra_domain or []),
'help': _("""
<p class="o_view_nocontent_smiling_face">
Nothing to do here!
</p>
<p>
No transactions matching your filters were found.
</p>
"""),
}
else:
# Open reconciliation view for customers/suppliers
action_context = {'show_mode_selector': False,
'company_ids': self.mapped('company_id').ids}
if self.type == 'sale':
action_context.update({'mode': 'customers'})
elif self.type == 'purchase':
action_context.update({'mode': 'suppliers'})
return {
'type': 'ir.actions.client',
'tag': 'manual_reconciliation_view',
'context': action_context,
}
def action_import_wizard(self):
"""Function to open wizard"""
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'import.bank.statement',
'target': 'new',
'context': {
'default_journal_id': self.id,
}
}

View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class AccountMove(models.Model):
"""Inherits from the account.move model for adding the depreciation
field to the account"""
_inherit = 'account.move'
has_due = fields.Boolean(string='Has due')
is_warning = fields.Boolean(string='Is warning')
due_amount = fields.Float(string="Due Amount",
related='partner_id.due_amount')
recurring_ref = fields.Char(string='Recurring Ref')
asset_depreciation_ids = fields.One2many('account.asset.depreciation.line',
'move_id',
string='Assets Depreciation Lines')
to_check = fields.Boolean(string='To Check', tracking=True,
help="If this checkbox is ticked, it means that the user was not sure of all the related "
"information at the time of the creation of the move and that the move needs to be "
"checked again.",
)
def button_cancel(self):
"""Button action to cancel the transfer"""
for move in self:
for line in move.asset_depreciation_ids:
line.move_posted_check = False
return super(AccountMove, self).button_cancel()
def post(self):
"""Supering the post method to mapped the asset depreciation records"""
self.mapped('asset_depreciation_ids').post_lines_and_close_asset()
return super(AccountMove, self).action_post()
@api.model
def _refund_cleanup_lines(self, lines):
"""Supering the refund cleanup lines to check the asset category """
result = super(AccountMove, self)._refund_cleanup_lines(lines)
for i, line in enumerate(lines):
for name, field in line._fields.items():
if name == 'asset_category_id':
result[i][2][name] = False
break
return result
def action_cancel(self):
"""Action perform to cancel the asset record"""
res = super(AccountMove, self).action_cancel()
self.env['account.asset.asset'].sudo().search(
[('invoice_id', 'in', self.ids)]).write({'active': False})
return res
def action_post(self):
"""To check the selected customers due amount is exceed than blocking stage"""
pay_type = ['out_invoice', 'out_refund', 'out_receipt']
for rec in self:
if rec.partner_id.active_limit and rec.move_type in pay_type \
and rec.partner_id.enable_credit_limit:
if rec.due_amount >= rec.partner_id.blocking_stage and rec.partner_id.blocking_stage != 0:
raise UserError(_(
"%s is in Blocking Stage and "
"has a due amount of %s %s to pay") % (
rec.partner_id.name, rec.due_amount,
rec.currency_id.symbol))
result = super(AccountMove, self).action_post()
for inv in self:
context = dict(self.env.context)
# Within the context of an invoice,
# this default value is for the type of the invoice, not the type
# of the asset. This has to be cleaned from the context before
# creating the asset,otherwise it tries to create the asset with
# the type of the invoice.
context.pop('default_type', None)
inv.invoice_line_ids.with_context(context).asset_create()
return result
@api.onchange('partner_id')
def check_due(self):
"""To show the due amount and warning stage"""
if self.partner_id and self.partner_id.due_amount > 0 \
and self.partner_id.active_limit \
and self.partner_id.enable_credit_limit:
self.has_due = True
else:
self.has_due = False
if self.partner_id and self.partner_id.active_limit \
and self.partner_id.enable_credit_limit:
if self.due_amount >= self.partner_id.warning_stage:
if self.partner_id.warning_stage != 0:
self.is_warning = True
else:
self.is_warning = False

View File

@@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import ast
from datetime import datetime
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from dateutil.relativedelta import relativedelta
class AccountInvoiceLine(models.Model):
"""Define a model for account invoice lines with fields related to assets and their management."""
_inherit = 'account.move.line'
asset_category_id = fields.Many2one('account.asset.category',
string='Asset Category')
asset_start_date = fields.Date(string='Asset Start Date',
compute='_get_asset_date', readonly=True,
store=True)
asset_end_date = fields.Date(string='Asset End Date',
compute='_get_asset_date', readonly=True,
store=True)
asset_mrr = fields.Float(string='Monthly Recurring Revenue',
compute='_get_asset_date',
readonly=True, digits='Account',
store=True)
@api.depends('asset_category_id', 'move_id.invoice_date')
def _get_asset_date(self):
"""Returns the asset_start_date and the asset_end_date of the Asset"""
for record in self:
record.asset_mrr = 0
record.asset_start_date = False
record.asset_end_date = False
cat = record.asset_category_id
if cat:
if cat.method_number == 0 or cat.method_period == 0:
raise UserError(_(
'The number of depreciations or the period length of '
'your asset category cannot be null.'))
months = cat.method_number * cat.method_period
if record.move_id in ['out_invoice', 'out_refund']:
record.asset_mrr = record.price_subtotal_signed / months
if record.move_id.invoice_date:
start_date = datetime.strptime(
str(record.move_id.invoice_date), DF).replace(day=1)
end_date = (start_date + relativedelta(months=months,
days=-1))
record.asset_start_date = start_date.strftime(DF)
record.asset_end_date = end_date.strftime(DF)
def asset_create(self):
"""Create function for the asset and its associated properties"""
for record in self:
if record.asset_category_id:
vals = {
'name': record.name,
'code': record.move_id.name or False,
'category_id': record.asset_category_id.id,
'value': record.price_subtotal,
'partner_id': record.partner_id.id,
'company_id': record.move_id.company_id.id,
'currency_id': record.move_id.company_currency_id.id,
'date': record.move_id.invoice_date,
'invoice_id': record.move_id.id,
}
changed_vals = record.env[
'account.asset.asset'].onchange_category_id_values(
vals['category_id'])
vals.update(changed_vals['value'])
asset = record.env['account.asset.asset'].create(vals)
if record.asset_category_id.open_asset:
asset.validate()
return True
@api.depends('asset_category_id')
def onchange_asset_category_id(self):
"""On change function based on the category and its updates the
account status"""
if self.move_id.move_type == 'out_invoice' and self.asset_category_id:
self.account_id = self.asset_category_id.account_asset_id.id
elif self.move_id.move_type == 'in_invoice' and self.asset_category_id:
self.account_id = self.asset_category_id.account_asset_id.id
@api.onchange('product_id')
def _onchange_uom_id(self):
"""Onchange function for product that's call the UOM compute function
and the asset category function"""
result = super(AccountInvoiceLine, self)._compute_product_uom_id()
self.onchange_asset_category_id()
return result
@api.depends('product_id')
def _onchange_product_id(self):
"""Onchange product values and it's associated with the move types"""
vals = super(AccountInvoiceLine, self)._compute_price_unit()
if self.product_id:
if self.move_id.move_type == 'out_invoice':
self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id
elif self.move_id.move_type == 'in_invoice':
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id
return vals
def _set_additional_fields(self, invoice):
"""The function adds additional fields that based on the invoice
move types"""
if not self.asset_category_id:
if invoice.type == 'out_invoice':
self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id.id
elif invoice.type == 'in_invoice':
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
self.onchange_asset_category_id()
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
def get_invoice_line_account(self, type, product, fpos, company):
""""It returns the invoice line and callback"""
return product.asset_category_id.account_asset_id or super(
AccountInvoiceLine, self).get_invoice_line_account(type, product,
fpos, company)
@api.model
def _query_get(self, domain=None):
"""Used to add domain constraints to the query"""
self.check_access_rights('read')
context = dict(self._context or {})
domain = domain or []
if not isinstance(domain, (list, tuple)):
domain = ast.literal_eval(domain)
date_field = 'date'
if context.get('aged_balance'):
date_field = 'date_maturity'
if context.get('date_to'):
domain += [(date_field, '<=', context['date_to'])]
if context.get('date_from'):
if not context.get('strict_range'):
domain += ['|', (date_field, '>=', context['date_from']),
('account_id.include_initial_balance', '=', True)]
elif context.get('initial_bal'):
domain += [(date_field, '<', context['date_from'])]
else:
domain += [(date_field, '>=', context['date_from'])]
if context.get('journal_ids'):
domain += [('journal_id', 'in', context['journal_ids'])]
state = context.get('state')
if state and state.lower() != 'all':
domain += [('parent_state', '=', state)]
if context.get('company_id'):
domain += [('company_id', '=', context['company_id'])]
elif context.get('allowed_company_ids'):
domain += [('company_id', 'in', self.env.companies.ids)]
else:
domain += [('company_id', '=', self.env.company.id)]
if context.get('reconcile_date'):
domain += ['|', ('reconciled', '=', False), '|',
('matched_debit_ids.max_date', '>', context['reconcile_date']),
('matched_credit_ids.max_date', '>', context['reconcile_date'])]
if context.get('account_tag_ids'):
domain += [('account_id.tag_ids', 'in', context['account_tag_ids'].ids)]
if context.get('account_ids'):
domain += [('account_id', 'in', context['account_ids'].ids)]
if context.get('analytic_tag_ids'):
domain += [('analytic_tag_ids', 'in', context['analytic_tag_ids'].ids)]
if context.get('analytic_account_ids'):
domain += [('analytic_account_id', 'in', context['analytic_account_ids'].ids)]
if context.get('partner_ids'):
domain += [('partner_id', 'in', context['partner_ids'].ids)]
if context.get('partner_categories'):
domain += [('partner_id.category_id', 'in', context['partner_categories'].ids)]
where_clause = ""
where_clause_params = []
tables = ''
if domain:
domain.append(('display_type', 'not in', ('line_section', 'line_note')))
domain.append(('parent_state', '!=', 'cancel'))
query = self._search(domain, bypass_access=True)
tables, from_params = query.from_clause
where_clause, where_params = query.where_clause
where_clause_params = from_params + where_params
return tables, where_clause, where_clause_params

View File

@@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models, _
from odoo.exceptions import UserError
class AccountRegisterPayments(models.TransientModel):
"""Inherits the account.payment.register model to add the new
fields and functions"""
_inherit = "account.payment.register"
bank_reference = fields.Char(string="Bank Reference", copy=False)
cheque_reference = fields.Char(string="Cheque Reference", copy=False)
effective_date = fields.Date('Effective Date',
help='Effective date of PDC', copy=False,
default=False)
def _prepare_payment_vals(self, invoices):
"""Its prepare the payment values for the invoice and update
the MultiPayment"""
res = super(AccountRegisterPayments, self)._prepare_payment_vals(
invoices)
# Check payment method is Check or PDC
check_pdc_ids = self.env['account.payment.method'].search(
[('code', 'in', ['pdc', 'check_printing'])])
if self.payment_method_id.id in check_pdc_ids.ids:
currency_id = self.env['res.currency'].browse(res['currency_id'])
journal_id = self.env['account.journal'].browse(res['journal_id'])
# Updating values in case of Multi payments
res.update({
'bank_reference': self.bank_reference,
'cheque_reference': self.cheque_reference,
'check_manual_sequencing': journal_id.check_manual_sequencing,
'effective_date': self.effective_date,
'check_amount_in_words': currency_id.amount_to_text(
res['amount']),
})
return res
def _create_payment_vals_from_wizard(self, batch_result):
"""It super the wizard action of the create payment values and update
the bank and cheque values"""
res = super(AccountRegisterPayments,
self)._create_payment_vals_from_wizard(
batch_result)
if self.effective_date:
res.update({
'bank_reference': self.bank_reference,
'cheque_reference': self.cheque_reference,
'effective_date': self.effective_date,
})
return res
def _create_payment_vals_from_batch(self, batch_result):
"""It super the batch action of the create payment values and update
the bank and cheque values"""
res = super(AccountRegisterPayments,
self)._create_payment_vals_from_batch(
batch_result)
if self.effective_date:
res.update({
'bank_reference': self.bank_reference,
'cheque_reference': self.cheque_reference,
'effective_date': self.effective_date,
})
return res
def _create_payments(self):
"""USed to create a list of payments and update the bank and
cheque reference"""
payments = super(AccountRegisterPayments, self)._create_payments()
for payment in payments:
payment.write({
'bank_reference': self.bank_reference,
'cheque_reference': self.cheque_reference
})
return payments
class AccountPayment(models.Model):
"""It inherits the account.payment model for adding new fields
and functions"""
_inherit = "account.payment"
bank_reference = fields.Char(string="Bank Reference", copy=False)
cheque_reference = fields.Char(string="Cheque Reference",copy=False)
effective_date = fields.Date('Effective Date',
help='Effective date of PDC', copy=False,
default=False)
def open_payment_matching_screen(self):
"""Open reconciliation view for customers/suppliers"""
move_line_id = False
for move_line in self.line_ids:
if move_line.account_id.reconcile:
move_line_id = move_line.id
break
if not self.partner_id:
raise UserError(_("Payments without a customer can't be matched"))
action_context = {'company_ids': [self.company_id.id], 'partner_ids': [
self.partner_id.commercial_partner_id.id]}
if self.partner_type == 'customer':
action_context.update({'mode': 'customers'})
elif self.partner_type == 'supplier':
action_context.update({'mode': 'suppliers'})
if move_line_id:
action_context.update({'move_line_id': move_line_id})
return {
'type': 'ir.actions.client',
'tag': 'manual_reconciliation_view',
'context': action_context,
}
def print_checks(self):
""" Check that the recordset is valid, set the payments state to
sent and call print_checks() """
# Since this method can be called via a client_action_multi, we
# need to make sure the received records are what we expect
selfs = self.filtered(lambda r:
r.payment_method_id.code
in ['check_printing', 'pdc']
and r.state != 'reconciled')
if len(selfs) == 0:
raise UserError(_(
"Payments to print as a checks must have 'Check' "
"or 'PDC' selected as payment method and "
"not have already been reconciled"))
if any(payment.journal_id != selfs[0].journal_id for payment in selfs):
raise UserError(_(
"In order to print multiple checks at once, they "
"must belong to the same bank journal."))
if not selfs[0].journal_id.check_manual_sequencing:
# The wizard asks for the number printed on the first
# pre-printed check so payments are attributed the
# number of the check the'll be printed on.
last_printed_check = selfs.search([
('journal_id', '=', selfs[0].journal_id.id),
('check_number', '!=', "0")], order="check_number desc",
limit=1)
next_check_number = last_printed_check and int(
last_printed_check.check_number) + 1 or 1
return {
'name': _('Print Pre-numbered Checks'),
'type': 'ir.actions.act_window',
'res_model': 'print.prenumbered.checks',
'view_mode': 'form',
'target': 'new',
'context': {
'payment_ids': self.ids,
'default_next_check_number': next_check_number,
}
}
else:
self.filtered(lambda r: r.state == 'draft').post()
self.write({'state': 'sent'})
return self.do_print_checks()
def _prepare_payment_moves(self):
""" supered function to set effective date """
res = super(AccountPayment, self)._prepare_payment_moves()
inbound_pdc_id = self.env.ref(
'base_accounting_kit.account_payment_method_pdc_in').id
outbound_pdc_id = self.env.ref(
'base_accounting_kit.account_payment_method_pdc_out').id
if self.payment_method_id.id == inbound_pdc_id or \
self.payment_method_id.id == outbound_pdc_id \
and self.effective_date:
res[0]['date'] = self.effective_date
for line in res[0]['line_ids']:
line[2]['date_maturity'] = self.effective_date
return res
def mark_as_sent(self):
"""Updates the is_move_sent value of the payment model"""
self.write({'is_sent': True})
def unmark_as_sent(self):
"""Updates the is_move_sent value of the payment model"""
self.write({'is_sent': False})

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, models
class AccountPaymentMethod(models.Model):
"""The class inherits the account payment method for supering the
_get_payment_method_information function"""
_inherit = "account.payment.method"
@api.model
def _get_payment_method_information(self):
"""Super the function to update the pdc values"""
res = super()._get_payment_method_information()
res['pdc'] = {'mode': 'multi', 'domain': [('type', '=', 'bank')]}
return res

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
class GetAllRecurringEntries(models.TransientModel):
"""Model for managing account recurring entries lines."""
_name = 'account.recurring.entries.line'
_description = 'Account Recurring Entries Line'
date = fields.Date('Date')
template_name = fields.Char('Name')
amount = fields.Float('Amount')
tmpl_id = fields.Many2one('account.recurring.payments', string='id')

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models
from odoo.tools import get_lang
class AccountCommonReport(models.Model):
"""Inherits the Account report model to add special fields and functions"""
_inherit = "account.report"
_description = "Account Common Report"
company_id = fields.Many2one('res.company', string='Company',
required=True, readonly=True,
default=lambda self: self.env.company)
journal_ids = fields.Many2many(
comodel_name='account.journal',
string='Journals',
required=True,
default=lambda self: self.env['account.journal'].search([('company_id', '=', self.company_id.id)]),
domain="[('company_id', '=', company_id)]")
date_from = fields.Date(string='Start Date')
date_to = fields.Date(string='End Date')
target_move = fields.Selection([('posted', 'All Posted Entries'),
('all', 'All Entries'),
], string='Target Moves',
required=True, default='posted')
@api.onchange('company_id')
def _onchange_company_id(self):
"""Onchange function based on the company and updated the journals"""
if self.company_id:
self.journal_ids = self.env['account.journal'].search(
[('company_id', '=', self.company_id.id)])
else:
self.journal_ids = self.env['account.journal'].search([])
def _build_contexts(self, data):
"""Builds the context information for the given data"""
result = {}
result['journal_ids'] = 'journal_ids' in data['form'] and data['form']['journal_ids'] or False
result['state'] = 'target_move' in data['form'] and data['form']['target_move'] or ''
result['date_from'] = data['form']['date_from'] or False
result['date_to'] = data['form']['date_to'] or False
result['strict_range'] = True if result['date_from'] else False
result['company_id'] = data['form']['company_id'][0] or False
return result
def _print_report(self, data):
"""Raise an error if the report comes checked """
raise NotImplementedError()
def check_report(self):
"""Function to check if the report comes active models and related
values"""
self.ensure_one()
data = {}
data['ids'] = self.env.context.get('active_ids', [])
data['model'] = self.env.context.get('active_model', 'ir.ui.menu')
data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move', 'company_id'])[0]
used_context = self._build_contexts(data)
data['form']['used_context'] = dict(used_context, lang=get_lang(self.env).code)
return self.with_context(discard_logo_check=True)._print_report(data)

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
class FollowupLine(models.Model):
"""Model for defining follow-up criteria including the action name, sequence order, due days, and related follow-ups."""
_name = 'followup.line'
_description = 'Follow-up Criteria'
_order = 'delay'
name = fields.Char('Follow-Up Action', required=True, translate=True)
sequence = fields.Integer(
help="Gives the sequence order when displaying a list of follow-up lines.")
delay = fields.Integer('Due Days', required=True,
help="The number of days after the due date of the invoice"
" to wait before sending the reminder."
" Could be negative if you want to send a polite alert beforehand.")
followup_id = fields.Many2one('account.followup', 'Follow Ups',
ondelete="cascade")

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
class MultipleInvoice(models.Model):
"""Multiple Invoice Model"""
_name = "multiple.invoice"
_description = 'Multiple Invoice'
_order = "sequence"
sequence = fields.Integer(string='Sequence No')
copy_name = fields.Char(string='Invoice Copy Name')
journal_id = fields.Many2one('account.journal', string="Journal")

View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models
from odoo.tools.misc import file_path
try:
import sass as libsass
except ImportError:
libsass = None
class MultipleInvoiceLayout(models.TransientModel):
"""
Customise the invoice copy document layout and display a live preview
"""
_name = 'multiple.invoice.layout'
_description = 'Multiple Invoice Document Layout'
def _get_default_journal(self):
"""The default function to return the journal for the invoice"""
return self.env['account.journal'].search(
[('id', '=', self.env.context.get('active_id'))]).id
company_id = fields.Many2one(
'res.company', default=lambda self: self.env.company, required=True)
layout = fields.Char(related="company_id.external_report_layout_id.key")
journal_id = fields.Many2one('account.journal', string='Journal',
required=True, default=_get_default_journal)
multiple_invoice_type = fields.Selection(
related='journal_id.multiple_invoice_type', readonly=False,
required=True)
text_position = fields.Selection(related='journal_id.text_position',
readonly=False, required=True,
default='header')
body_text_position = fields.Selection(
related='journal_id.body_text_position',
readonly=False)
text_align = fields.Selection(
related='journal_id.text_align',
readonly=False)
preview = fields.Html(compute='_compute_preview',
sanitize=False,
sanitize_tags=False,
sanitize_attributes=False,
sanitize_style=False,
sanitize_form=False,
strip_style=False,
strip_classes=False)
@api.depends('multiple_invoice_type', 'text_position', 'body_text_position',
'text_align')
def _compute_preview(self):
""" compute a qweb based preview to display on the wizard """
styles = self._get_asset_style()
for wizard in self:
if wizard.company_id:
preview_css = self._get_css_for_preview(styles, wizard.id)
layout = self._get_layout_for_preview()
ir_ui_view = wizard.env['ir.ui.view']
wizard.preview = ir_ui_view._render_template(
'base_accounting_kit.multiple_invoice_wizard_preview',
{'company': wizard.company_id, 'preview_css': preview_css,
'layout': layout,
'mi_type': self.multiple_invoice_type,
'txt_position': self.text_position,
'body_txt_position': self.body_text_position,
'txt_align': self.text_align,
'mi': self.env.ref(
'base_accounting_kit.multiple_invoice_sample_name')
})
else:
wizard.preview = False
def _get_asset_style(self):
"""Used to set the asset style"""
company_styles = self.env['ir.qweb']._render(
'web.styles_company_report', {
'company_ids': self.company_id,
}, raise_if_not_found=False)
return company_styles
@api.model
def _get_css_for_preview(self, scss, new_id):
"""
Compile the scss into css.
"""
css_code = self._compile_scss(scss)
return css_code
@api.model
def _compile_scss(self, scss_source):
"""
This code will compile valid scss into css.
Parameters are the same from odoo/addons/base/models/assetsbundle.py
Simply copied and adapted slightly
"""
# No scss ? still valid, returns empty css
if not scss_source.strip():
return ""
precision = 8
output_style = 'expanded'
bootstrap_path = file_path('web', 'static', 'lib', 'bootstrap',
'scss')
try:
return libsass.compile(
string=scss_source,
include_paths=[
bootstrap_path,
],
output_style=output_style,
precision=precision,
)
except libsass.CompileError as e:
raise libsass.CompileError(e.args[0])
def _get_layout_for_preview(self):
"""Returns the layout Preview for the accounting module"""
if self.layout == 'web.external_layout_boxed':
new_layout = 'base_accounting_kit.boxed'
elif self.layout == 'web.external_layout_bold':
new_layout = 'base_accounting_kit.bold'
elif self.layout == 'web.external_layout_striped':
new_layout = 'base_accounting_kit.striped'
else:
new_layout = 'base_accounting_kit.standard'
return new_layout
def document_layout_save(self):
"""meant to be overridden document_layout_save"""
return self.env.context.get('report_action') or {
'type': 'ir.actions.act_window_close'}

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
class ProductTemplate(models.Model):
"""Inherited the model for adding new fields and functions"""
_inherit = 'product.template'
asset_category_id = fields.Many2one('account.asset.category',
string='Asset Type',
company_dependent=True,
ondelete="restrict")
deferred_revenue_category_id = fields.Many2one('account.asset.category',
string='Deferred Revenue Type',
company_dependent=True,
ondelete="restrict")
def _get_asset_accounts(self):
"""Override method to customize asset accounts based on asset and deferred revenue categories."""
res = super(ProductTemplate, self)._get_asset_accounts()
if self.asset_category_id:
res['stock_input'] = self.property_account_expense_id
if self.deferred_revenue_category_id:
res['stock_output'] = self.property_account_income_id
return res

View File

@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
from odoo import api, models, fields
class RecurringPayments(models.Model):
"""Created the module for recurring payments"""
_name = 'account.recurring.payments'
_description = 'Accounting Recurring Payment'
def _get_next_schedule(self):
"""Function for adding the schedule process"""
if self.date:
recurr_dates = []
today = datetime.today()
start_date = datetime.strptime(str(self.date), '%Y-%m-%d')
while start_date <= today:
recurr_dates.append(str(start_date.date()))
if self.recurring_period == 'days':
start_date += relativedelta(days=self.recurring_interval)
elif self.recurring_period == 'weeks':
start_date += relativedelta(weeks=self.recurring_interval)
elif self.recurring_period == 'months':
start_date += relativedelta(months=self.recurring_interval)
else:
start_date += relativedelta(years=self.recurring_interval)
self.next_date = start_date.date()
name = fields.Char(string='Name')
debit_account = fields.Many2one('account.account', 'Debit Account',
required=True)
credit_account = fields.Many2one('account.account', 'Credit Account',
required=True)
journal_id = fields.Many2one('account.journal', 'Journal', required=True)
analytic_account_id = fields.Many2one('account.analytic.account',
'Analytic Account')
date = fields.Date('Starting Date', required=True, default=date.today())
next_date = fields.Date('Next Schedule', compute=_get_next_schedule,
readonly=True, copy=False)
recurring_period = fields.Selection(selection=[('days', 'Days'),
('weeks', 'Weeks'),
('months', 'Months'),
('years', 'Years')],
store=True, required=True)
amount = fields.Float('Amount')
description = fields.Text('Description')
state = fields.Selection(selection=[('draft', 'Draft'),
('running', 'Running')],
default='draft', string='Status')
journal_state = fields.Selection(selection=[('draft', 'Unposted'),
('posted', 'Posted')],
required=True, default='draft',
string='Generate Journal As')
recurring_interval = fields.Integer('Recurring Interval', default=1)
partner_id = fields.Many2one('res.partner', 'Partner')
pay_time = fields.Selection(selection=[('pay_now', 'Pay Directly'),
('pay_later', 'Pay Later')],
store=True, required=True)
company_id = fields.Many2one('res.company',
default=lambda l: l.env.company.id)
recurring_lines = fields.One2many('account.recurring.entries.line', 'tmpl_id')
@api.onchange('partner_id')
def onchange_partner_id(self):
"""Onchange partner field for updating the credit account value"""
if self.partner_id.property_account_receivable_id:
self.credit_account = self.partner_id.property_account_payable_id
@api.model
def _cron_generate_entries(self):
"""Generate recurring entries based on the defined schedule
and create corresponding accounting moves."""
data = self.env['account.recurring.payments'].search(
[('state', '=', 'running')])
entries = self.env['account.move'].search(
[('recurring_ref', '!=', False)])
journal_dates = []
journal_codes = []
remaining_dates = []
for entry in entries:
journal_dates.append(str(entry.date))
if entry.recurring_ref:
journal_codes.append(str(entry.recurring_ref))
today = datetime.today()
for line in data:
if line.date:
recurr_dates = []
start_date = datetime.strptime(str(line.date), '%Y-%m-%d')
while start_date <= today:
recurr_dates.append(str(start_date.date()))
if line.recurring_period == 'days':
start_date += relativedelta(
days=line.recurring_interval)
elif line.recurring_period == 'weeks':
start_date += relativedelta(
weeks=line.recurring_interval)
elif line.recurring_period == 'months':
start_date += relativedelta(
months=line.recurring_interval)
else:
start_date += relativedelta(
years=line.recurring_interval)
for rec in recurr_dates:
recurr_code = str(line.id) + '/' + str(rec)
if recurr_code not in journal_codes:
remaining_dates.append({
'date': rec,
'template_name': line.name,
'amount': line.amount,
'tmpl_id': line.id,
})
child_ids = self.recurring_lines.create(remaining_dates)
for line in child_ids:
tmpl_id = line.tmpl_id
recurr_code = str(tmpl_id.id) + '/' + str(line.date)
line_ids = [(0, 0, {
'account_id': tmpl_id.credit_account.id,
'partner_id': tmpl_id.partner_id.id,
'credit': line.amount,
# 'analytic_account_id': tmpl_id.analytic_account_id.id,
}), (0, 0, {
'account_id': tmpl_id.debit_account.id,
'partner_id': tmpl_id.partner_id.id,
'debit': line.amount,
# 'analytic_account_id': tmpl_id.analytic_account_id.id,
})]
vals = {
'date': line.date,
'recurring_ref': recurr_code,
'company_id': self.env.company.id,
'journal_id': tmpl_id.journal_id.id,
'ref': line.template_name,
'narration': 'Recurring entry',
'line_ids': line_ids
}
move_id = self.env['account.move'].create(vals)
if tmpl_id.journal_state == 'posted':
move_id.post()

View File

@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from datetime import datetime
import calendar
from odoo import models, api, _
from odoo.exceptions import RedirectWarning
class ResCompany(models.Model):
"""Model for inheriting res_company."""
_inherit = "res.company"
@api.model_create_multi
def create(self, vals_list):
"""Ensure fiscal year day does not exceed the maximum valid day for the selected month during record creation."""
for vals in vals_list:
if 'fiscalyear_last_month' in vals and 'fiscalyear_last_day' in vals:
month = vals.get('fiscalyear_last_month')
day = vals.get('fiscalyear_last_day')
if month and day:
if vals.account_opening_date:
year = vals.account_opening_date.year
else:
year = datetime.now().year
max_day = calendar.monthrange(year, int(month))[1]
if int(day) > max_day:
vals['fiscalyear_last_day'] = max_day
return super(ResCompany, self).create(vals_list)
def write(self, vals):
"""Auto-correct fiscal year day to a valid value when month or day is updated to prevent invalid calendar dates."""
if 'fiscalyear_last_month' in vals or 'fiscalyear_last_day' in vals:
month = vals.get('fiscalyear_last_month')
day = vals.get('fiscalyear_last_day')
if month:
if self.account_opening_date:
year = self.account_opening_date.year
else:
year = datetime.now().year
max_day = calendar.monthrange(year, int(month))[1]
if not day:
if any(company.fiscalyear_last_day > max_day for company in self):
vals['fiscalyear_last_day'] = max_day
elif int(day) > max_day:
vals['fiscalyear_last_day'] = max_day
return super(ResCompany, self).write(vals)
def _validate_locks(self, values):
"""Validate the hard lock date by checking for unposted entries and unreconciled bank statement lines."""
if values.get('hard_lock_date'):
draft_entries = self.env['account.move'].search([
('company_id', 'in', self.ids),
('state', '=', 'draft'),
('date', '<=', values['hard_lock_date'])])
if draft_entries:
error_msg = _('There are still unposted entries in the '
'period you want to lock. You should either post '
'or delete them.')
action_error = {
'view_mode': 'list',
'name': 'Unposted Entries',
'res_model': 'account.move',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', draft_entries.ids)],
'search_view_id': [self.env.ref('account.view_account_move_filter').id, 'search'],
'views': [[self.env.ref('account.view_move_tree').id, 'list'], [self.env.ref('account.view_move_form').id, 'form']],
}
raise RedirectWarning(error_msg, action_error, _('Show unposted entries'))
unreconciled_statement_lines = self.env['account.bank.statement.line'].search([
('company_id', 'in', self.ids),
('is_reconciled', '=', False),
('date', '<=', values['hard_lock_date']),
('move_id.state', 'in', ('draft', 'posted')),
])
if unreconciled_statement_lines:
error_msg = _("There are still unreconciled bank statement lines in the period you want to lock."
"You should either reconcile or delete them.")
action_error = {
'view_mode': 'kanban',
'name': 'Unreconciled Transactions',
'res_model': 'account.bank.statement.line',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', unreconciled_statement_lines.ids)],
'views': [[self.env.ref(
'base_accounting_kit.account_bank_statement_line_view_kanban').id,
'kanban']]
}
raise RedirectWarning(error_msg, action_error, _('Show Unreconciled Bank Statement Lines'))

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import models, fields, api
class ResConfigSettings(models.TransientModel):
"""Defines a model for configuration settings with additional fields for
managing customer credit limit and Anglo-Saxon accounting settings."""
_inherit = 'res.config.settings'
customer_credit_limit = fields.Boolean(string="Customer Credit Limit")
use_anglo_saxon_accounting = fields.Boolean(string="Use Anglo-Saxon accounting", readonly=False,
related='company_id.anglo_saxon_accounting')
fiscalyear_last_day = fields.Integer(
related='company_id.fiscalyear_last_day', readonly=False
)
fiscalyear_last_month = fields.Selection(
related='company_id.fiscalyear_last_month', readonly=False
)
@api.model
def get_values(self):
"""Retrieve the values for configuration settings including the
customer credit limit from the database parameters. """
res = super(ResConfigSettings, self).get_values()
params = self.env['ir.config_parameter'].sudo()
customer_credit_limit = params.get_param('customer_credit_limit',
default=False)
res.update(customer_credit_limit=customer_credit_limit)
return res
def set_values(self):
"""Set the customer credit limit value in the database parameters using superuser access."""
super(ResConfigSettings, self).set_values()
self.env['ir.config_parameter'].sudo().set_param(
"customer_credit_limit",
self.customer_credit_limit)
@api.model
def get_view_id(self):
"""Retrieve the ID of the view for bank reconciliation widget form."""
view_id = self.env['ir.model.data']._xmlid_to_res_id(
'base_accounting_kit.view_bank_reconcile_widget_form')
return view_id

View File

@@ -0,0 +1,528 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from datetime import date, timedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
import base64
import io
import json
import xlsxwriter
from odoo.exceptions import ValidationError, UserError
from odoo.tools.json import json_default
class ResPartner(models.Model):
"""Inheriting res.partner"""
_inherit = "res.partner"
invoice_list = fields.One2many('account.move', 'partner_id',
string="Invoice Details",
readonly=True,
domain=(
[('payment_state', '=', 'not_paid'),
('move_type', '=', 'out_invoice')]))
total_due = fields.Monetary(compute='_compute_for_followup', store=False,
readonly=True)
next_reminder_date = fields.Date(compute='_compute_for_followup',
store=False, readonly=True)
total_overdue = fields.Monetary(compute='_compute_for_followup',
store=False, readonly=True)
followup_status = fields.Selection(
[('in_need_of_action', 'In need of action'),
('with_overdue_invoices', 'With overdue invoices'),
('no_action_needed', 'No action needed')],
string='Followup status',
)
warning_stage = fields.Float(string='Warning Amount',
help="A warning message will appear once the "
"selected customer is crossed warning "
"amount. Set its value to 0.00 to"
" disable this feature")
blocking_stage = fields.Float(string='Blocking Amount',
help="Cannot make sales once the selected "
"customer is crossed blocking amount."
"Set its value to 0.00 to disable "
"this feature")
due_amount = fields.Float(string="Total Sale",
compute="compute_due_amount")
active_limit = fields.Boolean("Active Credit Limit", default=False)
enable_credit_limit = fields.Boolean(string="Credit Limit Enabled",
compute="_compute_enable_credit_limit")
def _compute_for_followup(self):
"""
Compute the fields 'total_due', 'total_overdue' , 'next_reminder_date' and 'followup_status'
"""
for record in self:
total_due = 0
total_overdue = 0
today = fields.Date.today()
for am in record.invoice_list:
if am.company_id == self.env.company:
amount = am.amount_residual
total_due += amount
is_overdue = today > am.invoice_date_due if am.invoice_date_due else today > am.date
if is_overdue:
total_overdue += amount or 0
min_date = record.get_min_date()
action = record.action_after()
if min_date:
date_reminder = min_date + timedelta(days=action)
if date_reminder:
record.next_reminder_date = date_reminder
else:
date_reminder = today
record.next_reminder_date = date_reminder
if total_overdue > 0 and date_reminder > today:
followup_status = "with_overdue_invoices"
elif total_due > 0 and date_reminder <= today:
followup_status = "in_need_of_action"
else:
followup_status = "no_action_needed"
record.total_due = total_due
record.total_overdue = total_overdue
record.followup_status = followup_status
def get_min_date(self):
"""Get the minimum invoice due date from the partner's invoice list."""
today = date.today()
for this in self:
if this.invoice_list:
min_list = this.invoice_list.mapped('invoice_date_due')
while False in min_list:
min_list.remove(False)
return min(min_list)
else:
return today
def get_delay(self):
"""Retrieve the delay information for follow-up lines associated with the company."""
delay = """SELECT fl.id, fl.delay
FROM followup_line fl
JOIN account_followup af ON fl.followup_id = af.id
WHERE af.company_id = %s
ORDER BY fl.delay;
"""
self._cr.execute(delay, [self.env.company.id])
record = self._cr.dictfetchall()
return record
def action_after(self):
"""Retrieve the delay information for follow-up lines associated with the company and return the delay value if found."""
lines = self.env['followup.line'].search([(
'followup_id.company_id', '=', self.env.company.id)])
if lines:
record = self.get_delay()
for i in record:
return i['delay']
def compute_due_amount(self):
"""Compute function to compute the due amount with the
credit and debit amount"""
for rec in self:
if not rec.id:
continue
rec.due_amount = rec.credit - rec.debit
def _compute_enable_credit_limit(self):
""" Check credit limit is enabled in account settings """
params = self.env['ir.config_parameter'].sudo()
customer_credit_limit = params.get_param('customer_credit_limit',
default=False)
for rec in self:
rec.enable_credit_limit = True if customer_credit_limit else False
@api.constrains('warning_stage', 'blocking_stage')
def constrains_warning_stage(self):
"""Constrains functionality used to indicate or raise an
UserError"""
if self.active_limit and self.enable_credit_limit:
if self.warning_stage >= self.blocking_stage:
if self.blocking_stage > 0:
raise UserError(_(
"Warning amount should be less than Blocking amount"))
# customer statement
customer_report_ids = fields.Many2many(
'account.move',
compute='_compute_customer_report_ids',
help='Partner Invoices related to Customer')
vendor_statement_ids = fields.Many2many(
'account.move',
compute='_compute_vendor_statement_ids',
help='Partner Bills related to Vendor')
currency_id = fields.Many2one(
'res.currency',
default=lambda self: self.env.company.currency_id.id,
help="currency related to Customer or Vendor")
def _compute_customer_report_ids(self):
""" For computing 'invoices' of partner """
for rec in self:
inv_ids = self.env['account.move'].search(
[('partner_id', '=', rec.id),
('move_type', '=', 'out_invoice'),
('payment_state', '!=', 'paid'),
('state', '=', 'posted')])
rec.customer_report_ids = inv_ids
def _compute_vendor_statement_ids(self):
""" For computing 'bills' of partner """
for rec in self:
bills = self.env['account.move'].search(
[('partner_id', '=', rec.id),
('move_type', '=', 'in_invoice'),
('payment_state', '!=', 'paid'),
('state', '=', 'posted')])
rec.vendor_statement_ids = bills
def main_query(self):
""" Return select query """
query = """SELECT name , invoice_date, invoice_date_due,
amount_total_signed AS sub_total,
amount_residual_signed AS amount_due ,
amount_residual AS balance
FROM account_move WHERE payment_state != 'paid'
AND state ='posted' AND partner_id= '%s'
AND company_id = '%s' """ % (self.id, self.env.company.id)
return query
def amount_query(self):
""" Return query for calculating total amount """
amount_query = """ SELECT SUM(amount_total_signed) AS total,
SUM(amount_residual) AS balance
FROM account_move WHERE payment_state != 'paid'
AND state ='posted' AND partner_id= '%s'
AND company_id = '%s' """ % (self.id, self.env.company.id)
return amount_query
def action_share_pdf(self):
""" Action for sharing customer pdf report """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
report = self.env['ir.actions.report'].sudo()._render_qweb_pdf(
'base_accounting_kit.res_partner_action', self, data=data)
data_record = base64.b64encode(report[0])
ir_values = {
'name': 'Statement Report',
'type': 'binary',
'datas': data_record,
'mimetype': 'application/pdf',
'res_model': 'res.partner'
}
attachment = self.env['ir.attachment'].sudo().create(ir_values)
email_values = {
'email_to': self.email,
'subject': 'Payment Statement Report',
'body_html': '<p>Dear <strong> Mr/Miss. ' + self.name +
'</strong> </p> <p> We have attached your '
'payment statement. Please check </p> '
'<p>Best regards, </p> <p> ' + self.env.user.name,
'attachment_ids': [attachment.id],
}
mail = self.env['mail.mail'].sudo().create(email_values)
mail.send()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': 'Email Sent Successfully',
'type': 'success',
'sticky': False
}
}
else:
raise ValidationError('There is no statement to send')
def action_print_pdf(self):
""" Action for printing pdf report """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
return self.env.ref('base_accounting_kit.res_partner_action'
).report_action(self, data=data)
else:
raise ValidationError('There is no statement to print')
def action_print_xlsx(self):
""" Action for printing xlsx report of customers """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
return {
'type': 'ir.actions.report',
'data': {
'model': 'res.partner',
'options': json.dumps(data,
default=json_default),
'output_format': 'xlsx',
'report_name': 'Payment Statement Report'
},
'report_type': 'xlsx',
}
else:
raise ValidationError('There is no statement to print')
def get_xlsx_report(self, data, response):
""" Get xlsx report data """
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet()
cell_format_with_color = workbook.add_format({
'font_size': '14px', 'bold': True,
'bg_color': 'yellow', 'border': 1})
cell_format = workbook.add_format({'font_size': '14px', 'bold': True})
txt = workbook.add_format({'font_size': '13px'})
txt_border = workbook.add_format({'font_size': '13px', 'border': 1})
head = workbook.add_format({'align': 'center', 'bold': True,
'font_size': '22px'})
sheet.merge_range('B2:Q4', 'Payment Statement Report', head)
if data['customer']:
sheet.merge_range('B7:D7', 'Customer/Supplier : ', cell_format)
sheet.merge_range('E7:H7', data['customer'], txt)
sheet.merge_range('B9:C9', 'Address : ', cell_format)
if data['street']:
sheet.merge_range('D9:F9', data['street'], txt)
if data['street2']:
sheet.merge_range('D10:F10', data['street2'], txt)
if data['city']:
sheet.merge_range('D11:F11', data['city'], txt)
if data['state']:
sheet.merge_range('D12:F12', data['state'], )
if data['zip']:
sheet.merge_range('D13:F13', data['zip'], txt)
sheet.merge_range('B15:C15', 'Date', cell_format_with_color)
sheet.merge_range('D15:G15', 'Invoice/Bill Number',
cell_format_with_color)
sheet.merge_range('H15:I15', 'Due Date', cell_format_with_color)
sheet.merge_range('J15:L15', 'Invoices/Debit', cell_format_with_color)
sheet.merge_range('M15:O15', 'Amount Due', cell_format_with_color)
sheet.merge_range('P15:R15', 'Balance Due', cell_format_with_color)
row = 15
column = 0
for record in data['my_data']:
sub_total = data['currency'] + str(record['sub_total'])
amount_due = data['currency'] + str(record['amount_due'])
balance = data['currency'] + str(record['balance'])
total = data['currency'] + str(data['total'])
remain_balance = data['currency'] + str(data['balance'])
sheet.merge_range(row, column + 1, row, column + 2,
record['invoice_date'], txt_border)
sheet.merge_range(row, column + 3, row, column + 6,
record['name'], txt_border)
sheet.merge_range(row, column + 7, row, column + 8,
record['invoice_date_due'], txt_border)
sheet.merge_range(row, column + 9, row, column + 11,
sub_total, txt_border)
sheet.merge_range(row, column + 12, row, column + 14,
amount_due, txt_border)
sheet.merge_range(row, column + 15, row, column + 17,
balance, txt_border)
row = row + 1
sheet.write(row + 2, column + 1, 'Total Amount: ', cell_format)
sheet.merge_range(row + 2, column + 3, row + 2, column + 4,
total, txt)
sheet.write(row + 4, column + 1, 'Balance Due: ', cell_format)
sheet.merge_range(row + 4, column + 3, row + 4, column + 4,
remain_balance, txt)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def action_share_xlsx(self):
""" Action for sharing xlsx report via email """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet()
cell_format = workbook.add_format({
'font_size': '14px', 'bold': True})
txt = workbook.add_format({'font_size': '13px'})
head = workbook.add_format(
{'align': 'center', 'bold': True, 'font_size': '22px'})
sheet.merge_range('B2:P4', 'Payment Statement Report', head)
date_style = workbook.add_format(
{'text_wrap': True, 'align': 'center',
'num_format': 'yyyy-mm-dd'})
if data['customer']:
sheet.write('B7:C7', 'Customer : ', cell_format)
sheet.merge_range('D7:G7', data['customer'], txt)
sheet.write('B9:C7', 'Address : ', cell_format)
if data['street']:
sheet.merge_range('D9:F9', data['street'], txt)
if data['street2']:
sheet.merge_range('D10:F10', data['street2'], txt)
if data['city']:
sheet.merge_range('D11:F11', data['city'], txt)
if data['state']:
sheet.merge_range('D12:F12', data['state'], txt)
if data['zip']:
sheet.merge_range('D13:F13', data['zip'], txt)
sheet.write('B15', 'Date', cell_format)
sheet.write('D15', 'Invoice/Bill Number', cell_format)
sheet.write('H15', 'Due Date', cell_format)
sheet.write('J15', 'Invoices/Debit', cell_format)
sheet.write('M15', 'Amount Due', cell_format)
sheet.write('P15', 'Balance Due', cell_format)
row = 16
column = 0
for record in data['my_data']:
sub_total = data['currency'] + str(record['sub_total'])
amount_due = data['currency'] + str(record['amount_due'])
balance = data['currency'] + str(record['balance'])
total = data['currency'] + str(data['total'])
remain_balance = data['currency'] + str(data['balance'])
sheet.merge_range(row, column + 1, row, column + 2,
record['invoice_date'], date_style)
sheet.merge_range(row, column + 3, row, column + 5,
record['name'], txt)
sheet.merge_range(row, column + 7, row, column + 8,
record['invoice_date_due'], date_style)
sheet.merge_range(row, column + 9, row, column + 10,
sub_total, txt)
sheet.merge_range(row, column + 12, row, column + 13,
amount_due, txt)
sheet.merge_range(row, column + 15, row, column + 16,
balance, txt)
row = row + 1
sheet.write(row + 2, column + 1, 'Total Amount : ', cell_format)
sheet.merge_range(row + 2, column + 4, row + 2, column + 5,
total, txt)
sheet.write(row + 4, column + 1, 'Balance Due : ', cell_format)
sheet.merge_range(row + 4, column + 4, row + 4, column + 5,
remain_balance, txt)
workbook.close()
output.seek(0)
xlsx = base64.b64encode(output.read())
output.close()
ir_values = {
'name': "Statement Report.xlsx",
'type': 'binary',
'datas': xlsx,
'store_fname': xlsx,
}
attachment = self.env['ir.attachment'].sudo().create(ir_values)
email_values = {
'email_to': self.email,
'subject': 'Payment Statement Report',
'body_html': '<p>Dear <strong> Mr/Miss. ' + self.name +
'</strong> </p> <p> We have attached your'
' payment statement. Please check </p> '
'<p>Best regards, </p> <p> ' + self.env.user.name,
'attachment_ids': [attachment.id],
}
mail = self.env['mail.mail'].sudo().create(email_values)
mail.send()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': 'Email Sent Successfully',
'type': 'success',
'sticky': False
}
}
else:
raise ValidationError('There is no statement to send')

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools.translate import _
class SaleOrder(models.Model):
"""The Class inherits the sale.order model for adding the new
fields and functions"""
_inherit = 'sale.order'
has_due = fields.Boolean(string='Has due')
is_warning = fields.Boolean(string='Is warning')
due_amount = fields.Float(string='Due Amount',
related='partner_id.due_amount')
def _action_confirm(self):
"""To check the selected customers due amount is exceed than
blocking stage"""
if self.partner_id.active_limit \
and self.partner_id.enable_credit_limit:
if self.due_amount >= self.partner_id.blocking_stage:
if self.partner_id.blocking_stage != 0:
raise UserError(_(
"%s is in Blocking Stage and "
"has a due amount of %s %s to pay") % (
self.partner_id.name, self.due_amount,
self.currency_id.symbol))
return super(SaleOrder, self)._action_confirm()
@api.onchange('partner_id')
def check_due(self):
"""To show the due amount and warning stage"""
if self.partner_id and self.partner_id.due_amount > 0 \
and self.partner_id.active_limit \
and self.partner_id.enable_credit_limit:
self.has_due = True
else:
self.has_due = False
if self.partner_id and self.partner_id.active_limit\
and self.partner_id.enable_credit_limit:
if self.due_amount >= self.partner_id.warning_stage:
if self.partner_id.warning_stage != 0:
self.is_warning = True
else:
self.is_warning = False

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import account_asset_report
from . import account_bank_book
from . import account_cash_book
from . import account_day_book
from . import account_report_common_account
from . import cash_flow_report
from . import general_ledger_report
from . import multiple_invoice_report
from . import report_aged_partner
from . import report_financial
from . import report_journal_audit
from . import report_partner_ledger
from . import report_tax
from . import report_trial_balance

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models, tools
class AssetAssetReport(models.Model):
_name = "asset.asset.report"
_description = "Assets Analysis"
_auto = False
name = fields.Char(string='Year', required=False, readonly=True)
date = fields.Date(readonly=True)
depreciation_date = fields.Date(string='Depreciation Date', readonly=True)
asset_id = fields.Many2one('account.asset.asset', string='Asset', readonly=True)
asset_category_id = fields.Many2one('account.asset.category', string='Asset category', readonly=True)
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], string='Status', readonly=True)
depreciation_value = fields.Float(string='Amount of Depreciation Lines', readonly=True)
installment_value = fields.Float(string='Amount of Installment Lines', readonly=True)
move_check = fields.Boolean(string='Posted', readonly=True)
installment_nbr = fields.Integer(string='# of Installment Lines', readonly=True)
depreciation_nbr = fields.Integer(string='# of Depreciation Lines', readonly=True)
gross_value = fields.Float(string='Gross Amount', readonly=True)
posted_value = fields.Float(string='Posted Amount', readonly=True)
unposted_value = fields.Float(string='Unposted Amount', readonly=True)
company_id = fields.Many2one('res.company', string='Company', readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, 'asset_asset_report')
self._cr.execute("""
create or replace view asset_asset_report as (
select
min(dl.id) as id,
dl.name as name,
dl.depreciation_date as depreciation_date,
a.date as date,
(CASE WHEN dlmin.id = min(dl.id)
THEN a.value
ELSE 0
END) as gross_value,
dl.amount as depreciation_value,
dl.amount as installment_value,
(CASE WHEN dl.move_check
THEN dl.amount
ELSE 0
END) as posted_value,
(CASE WHEN NOT dl.move_check
THEN dl.amount
ELSE 0
END) as unposted_value,
dl.asset_id as asset_id,
dl.move_check as move_check,
a.category_id as asset_category_id,
a.partner_id as partner_id,
a.state as state,
count(dl.*) as installment_nbr,
count(dl.*) as depreciation_nbr,
a.company_id as company_id
from account_asset_depreciation_line dl
left join account_asset_asset a on (dl.asset_id=a.id)
left join (select min(d.id) as id,ac.id as ac_id from
account_asset_depreciation_line as d inner join
account_asset_asset as ac ON (ac.id=d.asset_id) group by
ac_id) as dlmin on dlmin.ac_id=a.id
where a.active is true
group by
dl.amount,dl.asset_id,dl.depreciation_date,dl.name,
a.date, dl.move_check, a.state, a.category_id,
a.partner_id, a.company_id,
a.value, a.id, a.salvage_value, dlmin.id
)""")

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Asset Report Pivot View -->
<record id="asset_asset_report_view_pivot" model="ir.ui.view">
<field name="name">asset.asset.report.view.pivot</field>
<field name="model">asset.asset.report</field>
<field name="arch" type="xml">
<pivot string="Assets Analysis" disable_linking="True">
<field name="asset_category_id" type="row"/>
<field name="gross_value" type="measure"/>
<field name="unposted_value" type="measure"/>
</pivot>
</field>
</record>
<!-- Asset Report Graph View -->
<record id="asset_asset_report_view_graph" model="ir.ui.view">
<field name="name">asset.asset.report.view.graph</field>
<field name="model">asset.asset.report</field>
<field name="arch" type="xml">
<graph string="Assets Analysis">
<field name="asset_category_id" type="row"/>
<field name="gross_value" type="measure"/>
<field name="unposted_value" type="measure"/>
</graph>
</field>
</record>
<!-- Asset Report Search View -->
<record id="asset_asset_report_view_search" model="ir.ui.view">
<field name="name">asset.asset.report.view.search</field>
<field name="model">asset.asset.report</field>
<field name="arch" type="xml">
<search string="Assets Analysis">
<field name="date"/>
<field name="depreciation_date"/>
<filter string="Draft" name="draft" domain="[('state','=','draft')]" help="Assets in draft state"/>
<filter string="Running" name="running" domain="[('state','=','open')]" help="Assets in running state"/>
<filter string="Not archived" name="only_active" domain="[('asset_id.active','=', True)]"/>
<separator/>
<filter string="Posted" name="posted" domain="[('move_check','=',True)]" help="Posted depreciation lines" context="{'unposted_value_visible': 0}"/>
<field name="asset_id"/>
<field name="asset_category_id"/>
<group>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<filter string="Asset" name="asset" context="{'group_by':'asset_id'}"/>
<filter string="Asset Category" name="asset_category" context="{'group_by':'asset_category_id'}"/>
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<separator/>
<filter string="Purchase Month" name="purchase_month" help="Date of asset purchase"
context="{'group_by':'date:month'}"/>
<filter string="Depreciation Month" name="deprecation_month" help="Date of depreciation"
context="{'group_by':'depreciation_date:month'}"/>
</group>
</search>
</field>
</record>
<!--Asset Report Action-->
<record id="action_asset_asset_report" model="ir.actions.act_window">
<field name="name">Assets Analysis</field>
<field name="res_model">asset.asset.report</field>
<field name="view_mode">graph,pivot</field>
<field name="search_view_id" ref="asset_asset_report_view_search"/>
<field name="domain">[('asset_category_id.type', '=', 'purchase')]</field>
<field name="context">{'search_default_only_active': 1}</field>
<field name="help" type="html">
<p>
From this report, you can have an overview on all depreciation's. The
search bar can also be used to personalize your assets depreciation reporting.
</p>
</field>
</record>
<!--Assets Menuitem-->
<menuitem name="Assets" action="action_asset_asset_report"
id="menu_action_asset_asset_report"
parent="account.account_reports_management_menu" sequence="21"/>
</odoo>

View File

@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from datetime import time
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportBankBook(models.AbstractModel):
_name = 'report.base_accounting_kit.report_bank_book'
_description = 'Bank Book Report'
def _get_account_move_entry(self, accounts, init_balance, sortby,
display_account):
cr = self.env.cr
move_line = self.env['account.move.line']
move_lines = {x: [] for x in accounts.ids}
# Prepare initial sql query and Get the initial move lines
if init_balance:
init_tables, init_where_clause, init_where_params = move_line.with_context(
date_from=self.env.context.get('date_from'), date_to=False,
initial_bal=True)._query_get()
init_wheres = [""]
if init_where_clause.strip():
init_wheres.append(init_where_clause.strip())
init_filters = " AND ".join(init_wheres)
filters = init_filters.replace('account_move_line__move_id',
'm').replace('account_move_line',
'l')
sql = ("""SELECT 0 AS lid, l.account_id AS account_id, \
'' AS ldate, '' AS lcode, 0.0 AS amount_currency, \
'' AS lref, 'Initial Balance' AS lname, \
COALESCE(SUM(l.debit),0.0) AS debit, \
COALESCE(SUM(l.credit),0.0) AS credit, \
COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance, \
'' AS lpartner_id,\
'' AS move_name, '' AS mmove_id, '' AS currency_code,\
NULL AS currency_id,\
'' AS invoice_id, '' AS invoice_type, '' AS invoice_number,\
'' AS partner_name\
FROM account_move_line l\
LEFT JOIN account_move m ON (l.move_id=m.id)\
LEFT JOIN res_currency c ON (l.currency_id=c.id)\
LEFT JOIN res_partner p ON (l.partner_id=p.id)\
JOIN account_journal j ON (l.journal_id=j.id)\
WHERE l.account_id IN %s""" + filters + ' GROUP BY l.account_id')
params = (tuple(accounts.ids),) + tuple(init_where_params)
cr.execute(sql, params)
for row in cr.dictfetchall():
move_lines[row.pop('account_id')].append(row)
sql_sort = 'l.date, l.move_id'
if sortby == 'sort_journal_partner':
sql_sort = 'j.code, p.name, l.move_id'
# Prepare sql query base on selected parameters from wizard
tables, where_clause, where_params = move_line._query_get()
wheres = [""]
if where_clause.strip():
wheres.append(where_clause.strip())
filters = " AND ".join(wheres)
filters = filters.replace('account_move_line__move_id', 'm').replace(
'account_move_line', 'l')
# Get move lines base on sql query and Calculate the total
# balance of move lines
sql = ('''SELECT l.id AS lid, l.account_id \
AS account_id, l.date AS ldate, j.code AS lcode,\
l.currency_id, l.amount_currency, l.ref AS lref, l.name AS lname,\
COALESCE(l.debit,0) AS debit, \
COALESCE(l.credit,0) AS credit, \
COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS balance,\
m.name AS move_name, c.symbol AS \
currency_code, p.name AS partner_name\
FROM account_move_line l\
JOIN account_move m ON (l.move_id=m.id)\
LEFT JOIN res_currency c ON (l.currency_id=c.id)\
LEFT JOIN res_partner p ON (l.partner_id=p.id)\
JOIN account_journal j ON (l.journal_id=j.id)\
JOIN account_account acc ON (l.account_id = acc.id) \
WHERE l.account_id IN %s ''' + filters + ''' GROUP BY \
l.id, l.account_id, l.date, j.code, l.currency_id, \
l.amount_currency, l.ref, l.name, m.name, \
c.symbol, p.name ORDER BY ''' + sql_sort)
params = (tuple(accounts.ids),) + tuple(where_params)
cr.execute(sql, params)
for row in cr.dictfetchall():
balance = 0
for line in move_lines.get(row['account_id']):
balance += line['debit'] - line['credit']
row['balance'] += balance
move_lines[row.pop('account_id')].append(row)
# Calculate the debit, credit and balance for Accounts
account_res = []
for account in accounts:
account_company = self.env.company
currency = account.currency_id and \
account.currency_id or account_company.currency_id
res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance'])
res['code'] = account.code
res['name'] = account.name
res['move_lines'] = move_lines[account.id]
for line in res.get('move_lines'):
res['debit'] += line['debit']
res['credit'] += line['credit']
res['balance'] = line['balance']
if display_account == 'all':
account_res.append(res)
if display_account == 'movement' and res.get('move_lines'):
account_res.append(res)
if display_account == 'not_zero' and not currency.is_zero(
res['balance']):
account_res.append(res)
return account_res
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(self.env.context.get('active_ids', []))
init_balance = data['form'].get('initial_balance', True)
sortby = data['form'].get('sortby', 'sort_date')
display_account = 'movement'
codes = []
if data['form'].get('journal_ids', False):
codes = [journal.code for journal in
self.env['account.journal'].search(
[('id', 'in', data['form']['journal_ids'])])]
account_ids = data['form']['account_ids']
accounts = self.env['account.account'].search(
[('id', 'in', account_ids)])
if not accounts:
journals = self.env['account.journal'].search([('type', '=', 'bank')])
accounts = []
for journal in journals:
accounts.append(journal.default_account_id.id)
accounts = self.env['account.account'].search([('id', 'in', accounts)])
accounts_res = self.with_context(data['form'].get('used_context', {}))._get_account_move_entry(
accounts,
init_balance,
sortby,
display_account)
return {
'doc_ids': docids,
'doc_model': model,
'data': data['form'],
'docs': docs,
'time': time,
'Accounts': accounts_res,
'print_journal': codes,
}

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_bank_book">
<t t-call="web.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-call="web.internal_layout">
<div class="page"><br/>
<div class="row" style="top:60px;">
<h2><span t-esc="env.company.name"/>: Bank Book Report</h2>
</div>
<div class="row">
<div class="col-xs-4" style="width:40%;">
<strong>Journals:</strong>
<p t-esc="', '.join([ lt or '' for lt in print_journal ])"/>
</div>
<div class="col-xs-4" style="width:30%;">
<strong>Display Account</strong>
<p>
<span t-if="data['display_account'] == 'all'">All accounts'</span>
<span t-if="data['display_account'] == 'movement'">With movements</span>
<span t-if="data['display_account'] == 'not_zero'">With balance not equal to zero</span>
</p>
</div>
<div class="col-xs-4" style="width:30%;">
<strong>Target Moves:</strong>
<p t-if="data['target_move'] == 'all'">All Entries</p>
<p t-if="data['target_move'] == 'posted'">All Posted Entries</p>
</div>
</div>
<br/>
<div class="row">
<div style="width:70%;">
<strong>Sorted By:</strong>
<p t-if="data['sortby'] == 'sort_date'">Date</p>
<p t-if="data['sortby'] == 'sort_journal_partner'">Journal and Partner</p>
</div>
<div style="width:30%;">
<t t-if="data['date_from']">
<strong>Date from :</strong>
<span t-esc="data['date_from']"/>
<br/>
</t>
<t t-if="data['date_to']">
<strong>Date to :</strong>
<span t-esc="data['date_to']"/>
</t>
</div>
</div>
<br/>
<table class="table table-condensed">
<thead>
<tr class="text-center">
<th>Date</th>
<th>JRNL</th>
<th>Partner</th>
<th>Ref</th>
<th>Move</th>
<th>Entry Label</th>
<th>Debit</th>
<th>Credit</th>
<th>Balance</th>
<th groups="base.group_multi_currency">Currency</th>
</tr>
</thead>
<tbody>
<t t-foreach="Accounts" t-as="account">
<tr style="font-weight: bold;">
<td colspan="6">
<span style="color: white;" t-esc="'..'"/>
<span t-esc="account['code']"/>
<span t-esc="account['name']"/>
</td>
<td class="text-right">
<span t-esc="account['debit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="account['credit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="account['balance']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td groups="base.group_multi_currency"/>
</tr>
<tr t-foreach="account['move_lines']" t-as="line">
<td>
<span t-esc="line['ldate']"/>
</td>
<td>
<span t-esc="line['lcode']"/>
</td>
<td>
<span t-esc="line['partner_name']"/>
</td>
<td>
<span t-if="line['lref']" t-esc="line['lref']"/>
</td>
<td>
<span t-esc="line['move_name']"/>
</td>
<td>
<span t-esc="line['lname']"/>
</td>
<td class="text-right">
<span t-esc="line['debit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="line['credit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="line['balance']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td t-if="line['amount_currency']" class="text-end" groups="base.group_multi_currency">
<span t-esc="line['amount_currency'] if line['amount_currency'] > 0.00 else ''"/>
<span t-esc="line['currency_code'] if line['amount_currency'] > 0.00 else ''"/>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from datetime import time
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportCashBook(models.AbstractModel):
_name = 'report.base_accounting_kit.report_cash_book'
_description = 'Cash Book Report'
def _get_account_move_entry(self, accounts, init_balance, sortby, display_account):
cr = self.env.cr
move_line = self.env['account.move.line']
move_lines = {x: [] for x in accounts.ids}
# ------------------------------
# 1. Initial Balance
# ------------------------------
if init_balance:
init_tables, init_where_clause, init_where_params = move_line.with_context(
date_from=self.env.context.get('date_from'),
date_to=False,
initial_bal=True,
)._query_get()
init_wheres = [""]
if init_where_clause.strip():
init_wheres.append(init_where_clause.strip())
init_filters = " AND ".join(init_wheres)
init_filters = init_filters.replace('account_move_line__move_id', 'm').replace(
'account_move_line', 'l'
)
sql = """
SELECT 0 AS lid, l.account_id AS account_id, '' AS ldate,
'' AS lcode, 0.0 AS amount_currency, '' AS lref,
'Initial Balance' AS lname,
COALESCE(SUM(l.debit),0.0) AS debit,
COALESCE(SUM(l.credit),0.0) AS credit,
COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS balance,
'' AS lpartner_id, '' AS move_name, '' AS mmove_id,
'' AS currency_code, NULL AS currency_id,
'' AS invoice_id, '' AS invoice_type, '' AS invoice_number,
'' AS partner_name
FROM account_move_line l
LEFT JOIN account_move m ON (l.move_id=m.id)
LEFT JOIN res_currency c ON (l.currency_id=c.id)
LEFT JOIN res_partner p ON (l.partner_id=p.id)
JOIN account_journal j ON (l.journal_id=j.id)
WHERE l.account_id IN %s
""" + init_filters + """
GROUP BY l.account_id
"""
params = (tuple(accounts.ids) or (0,),) + tuple(init_where_params)
cr.execute(sql, params)
for row in cr.dictfetchall():
move_lines[row.pop('account_id')].append(row)
# ------------------------------
# 2. Sorting
# ------------------------------
sql_sort = 'l.date, l.move_id'
if sortby == 'sort_journal_partner':
sql_sort = 'j.code, p.name, l.move_id'
# ------------------------------
# 3. Prepare SQL filters
# ------------------------------
tables, where_clause, where_params = move_line._query_get()
wheres = [""]
if where_clause.strip():
wheres.append(where_clause.strip())
filters = " AND ".join(wheres)
filters = filters.replace('account_move_line__move_id', 'm').replace(
'account_move_line', 'l'
)
# ------------------------------
# 4. Accounts fallback
# ------------------------------
if not accounts:
# fallback: take receivable/payable accounts if none passed
accounts = self.env['account.account'].search([
('account_type', 'in', ['asset_receivable', 'liability_payable'])
])
if not accounts.ids:
return [] # no accounts, no results
account_ids = tuple(accounts.ids) or (0,)
params = (account_ids,) + tuple(where_params)
# ------------------------------
# 5. Main SQL query
# ------------------------------
sql = """
SELECT l.id AS lid, l.account_id AS account_id, l.date AS ldate,
j.code AS lcode, l.currency_id, l.amount_currency, l.ref AS lref,
l.name AS lname, COALESCE(l.debit,0) AS debit,
COALESCE(l.credit,0) AS credit,
COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS balance,
m.name AS move_name, c.symbol AS currency_code, p.name AS partner_name
FROM account_move_line l
JOIN account_move m ON (l.move_id=m.id)
LEFT JOIN res_currency c ON (l.currency_id=c.id)
LEFT JOIN res_partner p ON (l.partner_id=p.id)
JOIN account_journal j ON (l.journal_id=j.id)
JOIN account_account acc ON (l.account_id = acc.id)
WHERE l.account_id IN %s
""" + filters + """
GROUP BY l.id, l.account_id, l.date, j.code, l.currency_id,
l.amount_currency, l.ref, l.name, m.name, c.symbol, p.name
ORDER BY """ + sql_sort
cr.execute(sql, params)
# ------------------------------
# 6. Attach lines to accounts
# ------------------------------
for row in cr.dictfetchall():
balance = 0
for line in move_lines.get(row['account_id'], []): # ✅ safe fallback
balance += line['debit'] - line['credit']
row['balance'] += balance
acc_id = row.pop('account_id')
if acc_id not in move_lines:
move_lines[acc_id] = [] # ✅ ensure list exists
move_lines[acc_id].append(row)
# ------------------------------
# 7. Build account results
# ------------------------------
account_res = []
for account in accounts:
account_company = self.env.company
currency = account.currency_id or account_company.currency_id
res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance'])
res['code'] = account.code
res['name'] = account.name
res['move_lines'] = move_lines.get(account.id, []) # ✅ safe lookup
for line in res['move_lines']:
res['debit'] += line['debit']
res['credit'] += line['credit']
res['balance'] = line['balance']
if display_account == 'all':
account_res.append(res)
elif display_account == 'movement' and res.get('move_lines'):
account_res.append(res)
elif display_account == 'not_zero' and not currency.is_zero(res['balance']):
account_res.append(res)
return account_res
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(
self.env.context.get('active_ids', []))
init_balance = data['form'].get('initial_balance', True)
sortby = data['form'].get('sortby', 'sort_date')
display_account = 'movement'
codes = []
if data['form'].get('journal_ids', False):
codes = [journal.code for journal in
self.env['account.journal'].search(
[('id', 'in', data['form']['journal_ids'])])]
account_ids = data['form']['account_ids']
accounts = self.env['account.account'].search(
[('id', 'in', account_ids)])
if not accounts:
journals = self.env['account.journal'].search(
[('type', '=', 'cash')])
accounts = []
for journal in journals:
accounts.append(
journal.default_account_id.id)
accounts = self.env['account.account'].search(
[('id', 'in', accounts)])
accounts_res = self.with_context(
data['form'].get('used_context', {}))._get_account_move_entry(
accounts,
init_balance,
sortby,
display_account)
return {
'doc_ids': docids,
'doc_model': model,
'data': data['form'],
'docs': docs,
'time': time,
'Accounts': accounts_res,
'print_journal': codes,
}

View File

@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_cash_book">
<t t-call="web.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-call="web.internal_layout">
<div class="page"><br/>
<h2><span t-esc="env.company.name"/>: Cash Book Report
</h2>
<div class="row">
<div class="col-xs-4" style="width:40%;">
<strong>Journals:</strong>
<p t-esc="', '.join([ lt or '' for lt in
print_journal ])"/>
</div>
<div class="col-xs-4" style="width:30%;">
<strong>Display Account</strong>
<p>
<span t-if="data['display_account'] == 'all'">
All accounts'
</span>
<span t-if="data['display_account'] ==
'movement'">
With movements
</span>
<span t-if="data['display_account'] ==
'not_zero'">
With balance not equal to zero
</span>
</p>
</div>
<div class="col-xs-4" style="width:30%;">
<strong>Target Moves:</strong>
<p t-if="data['target_move'] == 'all'">All Entries
</p>
<p t-if="data['target_move'] == 'posted'">All Posted
Entries
</p>
</div>
</div>
<br/>
<div class="row">
<div style="width:70%;">
<strong>Sorted By:</strong>
<p t-if="data['sortby'] == 'sort_date'">Date</p>
<p t-if="data['sortby'] == 'sort_journal_partner'">
Journal and Partner
</p>
</div>
<div style="width:30%;">
<t t-if="data['date_from']">
<strong>Date from :</strong>
<span t-esc="data['date_from']"/>
<br/>
</t>
<t t-if="data['date_to']">
<strong>Date to :</strong>
<span t-esc="data['date_to']"/>
</t>
</div>
</div>
<br/>
<table class="table table-condensed">
<thead>
<tr class="text-centre">
<th>Date</th>
<th>JRNL</th>
<th>Partner</th>
<th>Ref</th>
<th>Move</th>
<th>Entry Label</th>
<th>Debit</th>
<th>Credit</th>
<th>Balance</th>
<th groups="base.group_multi_currency">
Currency
</th>
</tr>
</thead>
<tbody>
<t t-foreach="Accounts" t-as="account">
<tr style="font-weight: bold;">
<td colspan="6">
<span style="color: white;"
t-esc="'..'"/>
<span t-esc="account['code']"/>
<span t-esc="account['name']"/>
</td>
<td class="text-end">
<span t-esc="account['debit']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="account['credit']"
t-options="{'widget':
'monetary', 'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="account['balance']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td groups="base.group_multi_currency"/>
</tr>
<tr t-foreach="account['move_lines']"
t-as="line">
<td>
<span t-esc="line['ldate']"/>
</td>
<td>
<span t-esc="line['lcode']"/>
</td>
<td>
<span t-esc="line['partner_name']"/>
</td>
<td>
<span t-if="line['lref']"
t-esc="line['lref']"/>
</td>
<td>
<span t-esc="line['move_name']"/>
</td>
<td>
<span t-esc="line['lname']"/>
</td>
<td class="text-end">
<span t-esc="line['debit']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="line['credit']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="line['balance']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td t-if="line['amount_currency']"
class="text-end"
groups="base.group_multi_currency">
<span t-esc="line['amount_currency']
if line['amount_currency'] > 0.00 else
''"/>
<span t-esc="line['currency_code'] if
line['amount_currency'] > 0.00 else ''"/>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import time
from datetime import timedelta, datetime
from odoo import api, models, _
from odoo.exceptions import UserError
class DayBookPdfReport(models.AbstractModel):
_name = 'report.base_accounting_kit.day_book_report_template'
_description = 'Day Book Report'
def _get_account_move_entry(self, accounts, form_data, pass_date):
cr = self.env.cr
move_line = self.env['account.move.line']
tables, where_clause, where_params = move_line._query_get()
wheres = [""]
if where_clause.strip():
wheres.append(where_clause.strip())
if form_data['target_move'] == 'posted':
target_move = "AND m.state = 'posted'"
else:
target_move = ''
sql = ('''
SELECT l.id AS lid, acc.name as accname, l.account_id AS
account_id, l.date AS ldate, j.code AS lcode, l.currency_id,
l.amount_currency, l.ref AS lref, l.name AS lname,
COALESCE(l.debit,0) AS debit, COALESCE(l.credit,0) AS credit,
COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS
balance,
m.name AS move_name, c.symbol AS currency_code, p.name
AS partner_name
FROM account_move_line l
JOIN account_move m ON (l.move_id=m.id)
LEFT JOIN res_currency c ON (l.currency_id=c.id)
LEFT JOIN res_partner p ON (l.partner_id=p.id)
JOIN account_journal j ON (l.journal_id=j.id)
JOIN account_account acc ON (l.account_id = acc.id)
WHERE l.account_id IN %s AND l.journal_id IN %s '''
+ target_move + ''' AND l.date = %s
GROUP BY l.id, l.account_id, l.date,
j.code, l.currency_id, l.amount_currency, l.ref,
l.name, m.name, c.symbol, p.name , acc.name
ORDER BY l.date DESC
''')
params = (
tuple(accounts.ids), tuple(form_data['journal_ids']), pass_date)
cr.execute(sql, params)
data = cr.dictfetchall()
res = {}
debit = credit = balance = 0.00
for line in data:
debit += line['debit']
credit += line['credit']
balance += line['balance']
res['debit'] = debit
res['credit'] = credit
res['balance'] = balance
res['lines'] = data
return res
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(
self.env.context.get('active_ids', []))
form_data = data['form']
codes = []
if data['form'].get('journal_ids', False):
codes = [journal.code for journal in
self.env['account.journal'].search(
[('id', 'in', data['form']['journal_ids'])])]
active_acc = data['form']['account_ids']
accounts = self.env['account.account'].search(
[('id', 'in', active_acc)]) if data['form']['account_ids'] else \
self.env['account.account'].search([])
date_start = datetime.strptime(form_data['date_from'],
'%Y-%m-%d').date()
date_end = datetime.strptime(form_data['date_to'], '%Y-%m-%d').date()
days = date_end - date_start
dates = []
record = []
for i in range(days.days + 1):
dates.append(date_start + timedelta(days=i))
for head in dates:
pass_date = str(head)
accounts_res = self.with_context(
data['form'].get('used_context', {}))._get_account_move_entry(
accounts, form_data, pass_date)
if accounts_res['lines']:
record.append({
'date': head,
'debit': accounts_res['debit'],
'credit': accounts_res['credit'],
'balance': accounts_res['balance'],
'child_lines': accounts_res['lines']
})
return {
'doc_ids': docids,
'doc_model': model,
'data': data['form'],
'docs': docs,
'time': time,
'Accounts': record,
'print_journal': codes,
}

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="day_book_report_template">
<t t-call="web.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-call="web.internal_layout">
<div class="page"><br/>
<h2><span t-esc="env.company.name"/>: Day Book Report
</h2>
<div class="row mt32" style="margin-bottom:3%;">
<div class="col-7">
<strong>Journals:</strong>
<p t-esc="', '.join([ lt or '' for lt in print_journal ])"/>
</div>
<div class="col-2">
<strong>Target Moves:</strong>
<p t-if="data['target_move'] == 'all'">All Entries</p>
<p t-if="data['target_move'] == 'posted'">All Posted Entries</p>
</div>
<div class="col-3">
<t t-if="data['date_from']">
<strong>Date from :</strong>
<span t-esc="data['date_from']"/>
<br/>
</t>
<t t-if="data['date_to']">
<strong>Date to :</strong>
<span t-esc="data['date_to']"/>
</t>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr class="text-center">
<th>Date</th>
<th>JRNL</th>
<th>Partner</th>
<th>Ref</th>
<th>Move</th>
<th>Entry Label</th>
<th>Debit</th>
<th>Credit</th>
<th>Balance</th>
<th groups="base.group_multi_currency">Currency</th>
</tr>
</thead>
<tbody>
<t t-foreach="Accounts" t-as="account">
<tr style="font-weight: bold;background: #ededed;">
<td colspan="6">
<span style="color: white;" t-esc="'..'"/>
<span t-esc="account['date']"/>
</td>
<td class="text-right">
<span t-esc="account['debit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="account['credit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="account['balance']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td groups="base.group_multi_currency"/>
</tr>
<tr t-foreach="account['child_lines']" t-as="line">
<td>
<span t-esc="line['ldate']"/>
</td>
<td>
<span t-esc="line['lcode']"/>
</td>
<td>
<span t-esc="line['partner_name']"/>
</td>
<td>
<span t-if="line['lref']" t-esc="line['lref']"/>
</td>
<td>
<span t-esc="line['move_name']"/>
</td>
<td>
<span t-esc="line['lname']"/>
</td>
<td class="text-right">
<span t-esc="line['debit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="line['credit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right">
<span t-esc="line['balance']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td t-if="line['amount_currency']" class="text-end" groups="base.group_multi_currency">
<span t-esc="line['amount_currency'] if line['amount_currency'] > 0.00 else ''"/>
<span t-esc="line['currency_code'] if line['amount_currency'] > 0.00 else ''"/>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
from odoo.tools.misc import get_lang
class AccountCommonAccountReport(models.TransientModel):
_name = 'account.common.account.report'
_description = 'Account Common Account Report'
_inherit = "account.report"
section_main_report_ids = fields.Many2many(string="Section Of",
comodel_name='account.report',
relation="account_common_report_section_rel",
column1="sub_report_id",
column2="main_report_id")
section_report_ids = fields.Many2many(string="Sections",
comodel_name='account.report',
relation="account_common_report_section_rel",
column1="main_report_id",
column2="sub_report_id")
display_account = fields.Selection(
[('all', 'All'), ('movement', 'With movements'),
('not_zero', 'With balance is not equal to 0')],
string='Display Accounts', required=True, default='movement')
target_move = fields.Selection([('posted', 'All Posted Entries'),
('all', 'All Entries'),
], string='Target Moves', required=True, default='posted')
date_from = fields.Date(string='Start Date')
date_to = fields.Date(string='End Date')
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True,
default=lambda self: self.env.company)
def _build_contexts(self, data):
result = {}
result['journal_ids'] = 'journal_ids' in data['form'] and data['form']['journal_ids'] or False
result['state'] = 'target_move' in data['form'] and data['form']['target_move'] or ''
result['date_from'] = data['form']['date_from'] or False
result['date_to'] = data['form']['date_to'] or False
result['strict_range'] = True if result['date_from'] else False
result['company_id'] = data['form']['company_id'][0] or False
return result
def _print_report(self, data):
raise NotImplementedError()
def check_report(self):
self.ensure_one()
data = {}
data['ids'] = self.env.context.get('active_ids', [])
data['model'] = self.env.context.get('active_model', 'ir.ui.menu')
data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move', 'company_id'])[0]
used_context = self._build_contexts(data)
data['form']['used_context'] = dict(used_context, lang=get_lang(self.env).code)
return self.with_context(discard_logo_check=True)._print_report(data)
def pre_print_report(self, data):
data['form'].update(self.read(['display_account'])[0])
return data

View File

@@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import time
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportFinancial(models.AbstractModel):
_name = 'report.base_accounting_kit.report_cash_flow'
_description = 'Cash Flow Report'
def _compute_account_balance(self, accounts):
mapping = {
'balance': "COALESCE(SUM(debit),0) - COALESCE(SUM(credit), 0) as balance",
'debit': "COALESCE(SUM(debit), 0) as debit",
'credit': "COALESCE(SUM(credit), 0) as credit",
}
res = {}
for account in accounts:
res[account.id] = dict.fromkeys(mapping, 0.0)
if accounts:
tables, where_clause, where_params = self.env[
'account.move.line']._query_get()
tables = tables.replace('"', '') if tables else "account_move_line"
wheres = [""]
if where_clause.strip():
wheres.append(where_clause.strip())
filters = " AND ".join(wheres)
request = "SELECT account_id as id, " + ', '.join(
mapping.values()) + \
" FROM " + tables + \
" WHERE account_id IN %s " \
+ filters + \
" GROUP BY account_id"
params = (tuple(accounts._ids),) + tuple(where_params)
self.env.cr.execute(request, params)
for row in self.env.cr.dictfetchall():
res[row['id']] = row
return res
def _compute_report_balance(self, reports):
res = {}
fields = ['credit', 'debit', 'balance']
for report in reports:
if report.id in res:
continue
res[report.id] = dict((fn, 0.0) for fn in fields)
if report.type == 'accounts':
# it's the sum of credit or debit
res2 = self._compute_report_balance(report.parent_id)
for key, value in res2.items():
cash_in_operation = self.env.ref(
'base_accounting_kit.cash_in_from_operation0')
cash_out_operation = self.env.ref(
'base_accounting_kit.cash_out_operation1')
cash_in_financial = self.env.ref(
'base_accounting_kit.cash_in_financial0')
cash_out_financial = self.env.ref(
'base_accounting_kit.cash_out_financial1')
cash_in_investing = self.env.ref(
'base_accounting_kit.cash_in_investing0')
cash_out_investing = self.env.ref(
'base_accounting_kit.cash_out_investing1')
if report == cash_in_operation or report == cash_in_financial or report == cash_in_investing:
res[report.id]['debit'] += value['debit']
res[report.id]['balance'] += value['debit']
elif report == cash_out_operation or report == cash_out_financial or report == cash_out_investing:
res[report.id]['credit'] += value['credit']
res[report.id]['balance'] += -(value['credit'])
elif report.type == 'account_type':
# it's the sum the leaf accounts with such an account type
accounts = self.env['account.account'].search(
[('account_type', 'in', report.account_type_ids)])
res[report.id]['account'] = self._compute_account_balance(
accounts)
for value in res[report.id]['account'].values():
for field in fields:
res[report.id][field] += value.get(field)
elif report.type == 'account_report' and report.account_report_id:
# it's the amount of the linked
res[report.id]['account'] = self._compute_account_balance(
report.account_ids)
for value in res[report.id]['account'].values():
for field in fields:
res[report.id][field] += value.get(field)
elif report.type == 'sum':
# it's the sum of the linked accounts
res[report.id]['account'] = self._compute_account_balance(
report.account_ids)
for values in res[report.id]['account'].values():
for field in fields:
res[report.id][field] += values.get(field)
return res
def get_account_lines(self, data):
lines = []
account_report = self.env['account.financial.report'].search(
[('id', '=', data['account_report_id'][0])])
child_reports = account_report._get_children_by_order()
res = self.with_context(
data.get('used_context'))._compute_report_balance(child_reports)
if data['enable_filter']:
comparison_res = self.with_context(
data.get('comparison_context'))._compute_report_balance(
child_reports)
for report_id, value in comparison_res.items():
res[report_id]['comp_bal'] = value['balance']
report_acc = res[report_id].get('account')
if report_acc:
for account_id, val in comparison_res[report_id].get(
'account').items():
report_acc[account_id]['comp_bal'] = val['balance']
for report in child_reports:
vals = {
'name': report.name,
'balance': res[report.id]['balance'] * int(report.sign),
'type': 'report',
'level': bool(report.style_overwrite) and int(
report.style_overwrite) or report.level,
'account_type': report.type or False,
# used to underline the financial report balances
}
if data['debit_credit']:
vals['debit'] = res[report.id]['debit']
vals['credit'] = res[report.id]['credit']
if data['enable_filter']:
vals['balance_cmp'] = res[report.id]['comp_bal'] * int(
report.sign)
lines.append(vals)
if report.display_detail == 'no_detail':
# the rest of the loop is used to display the details of the financial report, so it's not needed here.
continue
if res[report.id].get('account'):
# if res[report.id].get('debit'):
sub_lines = []
for account_id, value in res[report.id]['account'].items():
# if there are accounts to display, we add them to the
# lines with a level equals to their level in
# the COA + 1 (to avoid having them with a too low level
# that would conflicts with the level of data
# financial reports for Assets, liabilities...)
flag = False
account = self.env['account.account'].browse(account_id)
vals = {
'name': account.code + ' ' + account.name,
'balance': value['balance'] * int(report.sign) or 0.0,
'type': 'account',
'level': report.display_detail == 'detail_with_hierarchy' and 4,
'account_type': account.internal_type,
}
if data['debit_credit']:
vals['debit'] = value['debit']
vals['credit'] = value['credit']
if not account.company_id.currency_id.is_zero(
vals[
'debit']) or not account.company_id.currency_id.is_zero(
vals['credit']):
flag = True
if not account.company_id.currency_id.is_zero(
vals['balance']):
flag = True
if data['enable_filter']:
vals['balance_cmp'] = value['comp_bal'] * int(
report.sign)
if not account.company_id.currency_id.is_zero(
vals['balance_cmp']):
flag = True
if flag:
sub_lines.append(vals)
lines += sorted(sub_lines,
key=lambda sub_line: sub_line['name'])
return lines
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get(
'active_model') or not self.env.context.get('active_id'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(self.env.context.get('active_id'))
report_lines = self.get_account_lines(data.get('form'))
return {
'doc_ids': self.ids,
'doc_model': model,
'data': data['form'],
'docs': docs,
'time': time,
'get_account_lines': report_lines,
}

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_cash_flow">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="web.internal_layout">
<div class="page">
<h2 t-esc="data['account_report_id'][1]"/>
<div class="row mt32 mb32">
<div class="col-4">
<strong>Target Moves:</strong>
<p>
<span t-if="data['target_move'] == 'all'">All Entries</span>
<span t-if="data['target_move'] == 'posted'">All Posted Entries</span>
</p>
</div>
<div class="col-4">
<p>
<strong>Date from :</strong>
<span t-esc="data['date_from']"/>
<br/>
<strong>Date to :</strong>
<span t-esc="data['date_to']"/>
</p>
</div>
</div>
<table class="table table-sm table-reports">
<thead>
<tr>
<th>
<strong>Name</strong>
</th>
<th class="text-right" t-if="data['debit_credit']">
<strong>Debit</strong>
</th>
<th class="text-right" t-if="data['debit_credit']">
<strong>Credit</strong>
</th>
<th class="text-right">
<strong>Balance</strong>
</th>
<th class="text-right" t-if="data['enable_filter']">
<strong t-esc="data['label_filter']"/>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="get_account_lines" t-as="a">
<t t-if="a['level'] != 0">
<t t-if="a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: normal;'"/>
</t>
<t t-if="not a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: bold;'"/>
</t>
<td>
<span style="color: white;" t-esc="'..' * a.get('level', 0)"/>
<span t-att-style="style" t-esc="a.get('name')"/>
</td>
<td t-if="data['debit_credit']" class="text-right" style="white-space: text-nowrap;">
<span t-att-style="style" t-esc="a.get('debit')" t-if="data['debit_credit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td t-if="data['debit_credit']" class="text-right" style="white-space: text-nowrap;">
<span t-att-style="style" t-esc="a.get('credit')"
t-if="data['debit_credit']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right" style="white-space: text-nowrap;">
<span t-att-style="style" t-esc="a.get('balance')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right" t-if="data['enable_filter']">
<span t-att-style="style" t-esc="a.get('balance_cmp')"
t-if="data['enable_filter']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</t>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,183 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import time
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportGeneralLedger(models.AbstractModel):
_name = 'report.base_accounting_kit.report_general_ledger'
_description = 'General Ledger Report'
def _get_account_move_entry(self, accounts, init_balance, sortby,
display_account):
"""
:param:
accounts: the recordset of accounts
init_balance: boolean value of initial_balance
sortby: sorting by date or partner and journal
display_account: type of account(receivable, payable and both)
Returns a dictionary of accounts with following key and value {
'code': account code,
'name': account name,
'debit': sum of total debit amount,
'credit': sum of total credit amount,
'balance': total balance,
'amount_currency': sum of amount_currency,
'move_lines': list of move line
}
"""
cr = self.env.cr
MoveLine = self.env['account.move.line']
move_lines = {x: [] for x in accounts.ids}
# Prepare initial sql query and Get the initial move lines
if init_balance:
init_tables, init_where_clause, init_where_params = MoveLine.with_context(
date_from=self.env.context.get('date_from'), date_to=False,
initial_bal=True)._query_get()
init_wheres = [""]
if init_where_clause.strip():
init_wheres.append(init_where_clause.strip())
init_filters = " AND ".join(init_wheres)
filters = init_filters.replace('account_move_line__move_id',
'm').replace('account_move_line',
'l')
sql = ("""SELECT 0 AS lid, l.account_id AS account_id, ''
AS ldate, '' AS lcode, 0.0 AS amount_currency, '' AS lref,
'Initial Balance' AS lname, COALESCE(SUM(l.debit),0.0) AS debit,
COALESCE(SUM(l.credit),0.0) AS credit, COALESCE(SUM(l.debit),0)
- COALESCE(SUM(l.credit), 0) as balance, '' AS lpartner_id,\
'' AS move_name, '' AS mmove_id, '' AS currency_code,\
NULL AS currency_id,\
'' AS invoice_id, '' AS invoice_type, '' AS invoice_number,\
'' AS partner_name\
FROM account_move_line l\
LEFT JOIN account_move m ON (l.move_id=m.id)\
LEFT JOIN res_currency c ON (l.currency_id=c.id)\
LEFT JOIN res_partner p ON (l.partner_id=p.id)\
LEFT JOIN account_move i ON (m.id =i.id)\
JOIN account_journal j ON (l.journal_id=j.id)\
WHERE l.account_id IN %s""" + filters +
' GROUP BY l.account_id')
params = (tuple(accounts.ids),) + tuple(init_where_params)
cr.execute(sql, params)
for row in cr.dictfetchall():
move_lines[row.pop('account_id')].append(row)
sql_sort = 'l.date, l.move_id'
if sortby == 'sort_journal_partner':
sql_sort = 'j.code, p.name, l.move_id'
# Prepare sql query base on selected parameters from wizard
tables, where_clause, where_params = MoveLine._query_get()
wheres = [""]
if where_clause.strip():
wheres.append(where_clause.strip())
filters = " AND ".join(wheres)
filters = filters.replace('account_move_line__move_id', 'm').replace(
'account_move_line', 'l')
# Get move lines base on sql query and Calculate the total balance of move lines
sql = ('''SELECT l.id AS lid, l.account_id AS account_id,
l.date AS ldate, j.code AS lcode, l.currency_id, l.amount_currency,
l.ref AS lref, l.name AS lname, COALESCE(l.debit,0) AS debit,
COALESCE(l.credit,0) AS credit, COALESCE(SUM(l.debit),0) -
COALESCE(SUM(l.credit), 0) AS balance,\
m.name AS move_name, c.symbol AS currency_code, p.name AS
partner_name\
FROM account_move_line l\
JOIN account_move m ON (l.move_id=m.id)\
LEFT JOIN res_currency c ON (l.currency_id=c.id)\
LEFT JOIN res_partner p ON (l.partner_id=p.id)\
JOIN account_journal j ON (l.journal_id=j.id)\
JOIN account_account acc ON (l.account_id = acc.id) \
WHERE l.account_id IN %s ''' + filters + ''' GROUP BY l.id,
l.account_id, l.date, j.code, l.currency_id, l.amount_currency,
l.ref, l.name, m.name, c.symbol, p.name ORDER BY ''' + sql_sort)
params = (tuple(accounts.ids),) + tuple(where_params)
cr.execute(sql, params)
for row in cr.dictfetchall():
balance = 0
for line in move_lines.get(row['account_id']):
balance += line['debit'] - line['credit']
row['balance'] += balance
move_lines[row.pop('account_id')].append(row)
# Calculate the debit, credit and balance for Accounts
account_res = []
for account in accounts:
account_company = self.env.company
currency = account.currency_id and account.currency_id or account_company.currency_id
res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance'])
res['code'] = account.code
res['name'] = account.name
res['move_lines'] = move_lines[account.id]
for line in res.get('move_lines'):
res['debit'] += line['debit']
res['credit'] += line['credit']
res['balance'] = line['balance']
if display_account == 'all':
account_res.append(res)
if display_account == 'movement' and res.get('move_lines'):
account_res.append(res)
if display_account == 'not_zero' and not currency.is_zero(
res['balance']):
account_res.append(res)
return account_res
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(
self.env.context.get('active_ids', []))
init_balance = data['form'].get('initial_balance', True)
sortby = data['form'].get('sortby', 'sort_date')
display_account = data['form']['display_account']
codes = []
if data['form'].get('journal_ids', False):
codes = [journal.code for journal in
self.env['account.journal'].search(
[('id', 'in', data['form']['journal_ids'])])]
accounts = docs if model == 'account.account' else self.env[
'account.account'].search([])
accounts_res = self.with_context(
data['form'].get('used_context', {}))._get_account_move_entry(
accounts, init_balance, sortby, display_account)
return {
'doc_ids': docids,
'doc_model': model,
'data': data['form'],
'docs': docs,
'time': time,
'Accounts': accounts_res,
'print_journal': codes,
}

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_general_ledger">
<t t-call="web.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-call="web.internal_layout">
<div class="page"><br/>
<h2><span t-esc="env.company.name"/>: General ledger</h2>
<div class="row mt32">
<div class="col-4">
<strong>Journals:</strong>
<p t-esc="', '.join([ lt or '' for lt in print_journal ])"/>
</div>
<div class="col-4">
<strong>Display Account</strong>
<p>
<span t-if="data['display_account'] == 'all'">All accounts'</span>
<span t-if="data['display_account'] == 'movement'">With movements</span>
<span t-if="data['display_account'] == 'not_zero'">With balance not equal to zero</span>
</p>
</div>
<div class="col-4">
<strong>Target Moves:</strong>
<p t-if="data['target_move'] == 'all'">All Entries</p>
<p t-if="data['target_move'] == 'posted'">All Posted Entries</p>
</div>
</div>
<div class="row mb32">
<div class="col-4">
<strong>Sorted By:</strong>
<p t-if="data['sortby'] == 'sort_date'">Date</p>
<p t-if="data['sortby'] == 'sort_journal_partner'">Journal and Partner</p>
</div>
<div class="col-4">
<t t-if="data['date_from']"><strong>Date from :</strong> <span t-esc="data['date_from']"/><br/></t>
<t t-if="data['date_to']"><strong>Date to :</strong> <span t-esc="data['date_to']"/></t>
</div>
</div>
<table class="table table-sm table-reports">
<thead>
<tr class="text-center">
<th>Date</th>
<th>JRNL</th>
<th>Partner</th>
<th>Ref</th>
<th>Move</th>
<th>Entry Label</th>
<th>Debit</th>
<th>Credit</th>
<th>Balance</th>
<th groups="base.group_multi_currency">Currency</th>
</tr>
</thead>
<tbody>
<t t-foreach="Accounts" t-as="account">
<tr style="font-weight: bold;">
<td colspan="6">
<span style="color: white;" t-esc="'..'"/>
<span t-esc="account['code']"/>
<span t-esc="account['name']"/>
</td>
<td class="text-end">
<span t-esc="account['debit']" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="account['credit']" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="account['balance']" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td groups="base.group_multi_currency"/>
</tr>
<tr t-foreach="account['move_lines']" t-as="line">
<td><span t-esc="line['ldate']"/></td>
<td><span t-esc="line['lcode']"/></td>
<td><span t-esc="line['partner_name']"/></td>
<td><span t-if="line['lref']" t-esc="line['lref']"/></td>
<td><span t-esc="line['move_name']"/></td>
<td><span t-esc="line['lname']"/></td>
<td class="text-right">
<span t-esc="line['debit']" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="line['credit']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="line['balance']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<t t-if="line['amount_currency']">
<td class="text-end"
groups="base.group_multi_currency">
<span t-esc="line['amount_currency']
if line['amount_currency'] > 0.00
else ''"/>
<span t-esc="line['currency_code']
if line['amount_currency'] > 0.00
else ''"/>
</td>
</t>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,645 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="base_accounting_kit.standard">
<div t-attf-class="header o_company_#{company.id}_layout"
t-att-style="report_header_style">
<div class="row">
<div class="col-3 mb4">
<img t-if="company.logo"
t-att-src="image_data_uri(company.logo)"
style="max-height: 45px;" alt="Logo"/>
<!--Header-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'header'">
<div class="row">
<div t-if="txt_align == 'left'"
class="text-left">
<span t-esc="mi.copy_name"
style="font-size: 20px; padding-left:25px; white-space:nowrap;"/>
</div>
<div t-if="txt_align == 'center'"
class="text-center">
<span t-esc="mi.copy_name" style="font-size: 20px;
margin-left:340px; margin-right:340px; white-space:nowrap;"/>
</div>
</div>
</t>
</t>
</div>
<div class="col-9 text-right" style="margin-top:22px;"
t-field="company.report_header" name="moto"/>
</div>
<!--Header-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'header'">
<div t-if="txt_align == 'right'" class="text-right">
<span t-esc="mi.copy_name" style="font-size: 20px;"/>
</div>
</t>
</t>
<div t-if="company.logo or company.report_header"
class="row zero_min_height">
<div class="col-12">
<div style="border-bottom: 1px solid black;"/>
</div>
</div>
<div class="row">
<div class="col-6" name="company_address">
<span t-if="company.company_details"
t-field="company.company_details"></span>
</div>
</div>
<!--Watermark-->
<t t-if="mi_type =='watermark'">
<div style="opacity:0.15; font-size:100px; width:85%; text-align:center;top:500px; right:100px; position: fixed; z-index:99; -webkit-transform: rotate(-30deg);">
<t t-esc="mi.copy_name"/>
</div>
</t>
</div>
<div t-attf-class="article o_report_layout_standard o_company_#{company.id}_layout {{ 'o_layout_background' if company.layout_background in ['Geometric', 'Custom'] else '' }}"
t-attf-style="background-image: url({{ 'data:image/png;base64,%s' % company.layout_background_image.decode('utf-8') if company.layout_background_image and company.layout_background == 'Custom' else '/base/static/img/bg_background_template.jpg' if company.layout_background == 'Geometric' else ''}});"
t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id"
t-att-data-oe-lang="o and o.env.context.get('lang')">
<div class="pt-5">
<!-- This div ensures that the address is not cropped by the header. -->
<t t-call="web.address_layout"/>
</div>
<t t-out="0"/>
</div>
<div t-attf-class="footer o_standard_footer o_company_#{company.id}_layout">
<div class="text-center" style="border-top: 1px solid black;">
<ul class="list-inline mb4">
<div t-field="company.report_footer"/>
<!--Footer-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'footer'">
<div t-if="txt_align == 'right'" class="text-right">
<span t-esc="mi.copy_name"
style="font-size: 15px;"/>
</div>
<div t-if="txt_align == 'left'" class="text-left">
<span t-esc="mi.copy_name"
style="font-size: 15px;"/>
</div>
<div t-if="txt_align == 'center'"
class="text-center;">
<span t-esc="mi.copy_name"
style="font-size: 15px;"/>
</div>
</t>
</t>
</ul>
<div t-if="report_type == 'pdf'" class="text-muted">
Page:
<span class="page"/>
/
<span class="topage"/>
</div>
</div>
</div>
</template>
<template id="base_accounting_kit.boxed">
<div t-attf-class="header o_company_#{company.id}_layout"
t-att-style="report_header_style">
<div class="o_boxed_header">
<div class="row mb8">
<div class="col-6">
<img t-if="company.logo"
t-att-src="image_data_uri(company.logo)"
alt="Logo"/>
<!--Header-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'header'">
<div t-if="txt_align == 'left'">
<span t-esc="mi.copy_name"
style="font-size: 25px; white-space:nowrap;"/>
</div>
<div t-if="txt_align == 'center'"
class="text-align: center">
<span t-esc="mi.copy_name" style="font-size: 25px;
margin-left:340px; margin-right:340px; white-space:nowrap;"/>
</div>
</t>
</t>
</div>
<div class="col-6 text-right mb4">
<h4 class="mt0" t-field="company.report_header"/>
<div name="company_address" class="float-right mb4">
<span t-if="company.company_details"
t-field="company.company_details"/>
<!--Header-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'header'">
<div t-if="txt_align == 'right'"
class="float-right mb4">
<span t-esc="mi.copy_name"
style="font-size: 25px;"/>
</div>
</t>
</t>
<br/>
</div>
</div>
</div>
</div>
<!--Watermark-->
<t t-if="mi_type =='watermark'">
<div style="opacity:0.15; font-size:100px; width:85%; text-align:center;top:500px; right:100px; position: fixed; z-index:99; -webkit-transform: rotate(-30deg);">
<t t-esc="mi.copy_name"/>
</div>
</t>
</div>
<div t-attf-class="article o_report_layout_boxed o_company_#{company.id}_layout {{ 'o_layout_background' if company.layout_background in ['Geometric', 'Custom'] else '' }}"
t-attf-style="background-image: url({{ 'data:image/png;base64,%s' % company.layout_background_image.decode('utf-8') if company.layout_background_image and company.layout_background == 'Custom' else '/base/static/img/bg_background_template.jpg' }});"
t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id"
t-att-data-oe-lang="o and o.env.context.get('lang')">
<div class="pt-5">
<!-- This div ensures that the address is not cropped by the header. -->
<t t-call="web.address_layout"/>
</div>
<t t-out="0"/>
</div>
<div t-attf-class="footer o_boxed_footer o_company_#{company.id}_layout">
<div class="text-center">
<div t-field="company.report_footer"/>
<!--Footer-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'footer'">
<div t-if="txt_align == 'right'" class="text-right">
<span t-esc="mi.copy_name"
style="font-size: 20px;"/>
</div>
<div t-if="txt_align == 'left'" class="text-left">
<span t-esc="mi.copy_name"
style="font-size: 20px;"/>
</div>
<div t-if="txt_align == 'center'" class="text-center;">
<span t-esc="mi.copy_name"
style="font-size: 20px;"/>
</div>
</t>
</t>
<div t-if="report_type == 'pdf'">
Page:
<span class="page"/>
/
<span class="topage"/>
</div>
</div>
</div>
</template>
<template id="base_accounting_kit.bold">
<div t-attf-class="header o_company_#{company.id}_layout"
t-att-style="report_header_style">
<div class="o_clean_header">
<div class="row">
<div class="col-6">
<img t-if="company.logo"
t-att-src="image_data_uri(company.logo)"
alt="Logo"/>
<!--Header-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'header'">
<div t-if="txt_align == 'left'">
<br/>
<span t-esc="mi.copy_name"
style="font-size: 20px; padding-left:25px; white-space:nowrap;"/>
</div>
<div t-if="txt_align == 'center'"
class="text-align: center">
<br/>
<span t-esc="mi.copy_name" style="font-size: 20px;
margin-left:280px; margin-right:280px; white-space:nowrap;"/>
</div>
</t>
</t>
</div>
<div class="col-5 offset-1" name="company_address">
<ul class="list-unstyled">
<strong>
<li t-if="company.name">
<span t-field="company.name"/>
</li>
</strong>
<li t-if="forced_vat or company.vat">
<t t-esc="company.country_id.vat_label or 'Tax ID'"/>
:
<span t-if="forced_vat" t-esc="forced_vat"/>
<span t-else="" t-field="company.vat"/>
</li>
<li t-if="company.phone">Tel:
<span class="o_force_ltr"
t-field="company.phone"/>
</li>
<li t-if="company.email">
<span t-field="company.email"/>
</li>
<li t-if="company.website">
<span t-field="company.website"/>
</li>
<!--Header-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'header'">
<div t-if="txt_align == 'right'">
<span t-esc="mi.copy_name"
style="font-size: 20px;"/>
</div>
</t>
</t>
</ul>
</div>
</div>
</div>
<!--Watermark-->
<t t-if="mi_type =='watermark'">
<div style="opacity:0.15; font-size:100px; width:85%; text-align:center;top:500px; right:100px; position: fixed; z-index:99; -webkit-transform: rotate(-30deg);">
<t t-esc="mi.copy_name"/>
</div>
</t>
</div>
<div t-attf-class="article o_report_layout_bold o_company_#{company.id}_layout {{ 'o_layout_background' if company.layout_background in ['Geometric', 'Custom'] else '' }}"
t-attf-style="background-image: url({{ 'data:image/png;base64,%s' % company.layout_background_image.decode('utf-8') if company.layout_background_image and company.layout_background == 'Custom' else '/base/static/img/bg_background_template.jpg' }});"
t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id"
t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-call="web.address_layout"/>
<t t-out="0"/>
</div>
<div t-attf-class="footer o_clean_footer o_company_#{company.id}_layout">
<div class="row">
<div class="col-4">
<span t-field="company.report_footer"/>
<!--Footer Left-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'footer'">
<div t-if="txt_align == 'left'" class="text-left">
<span t-esc="mi.copy_name"
style="font-size: 18px; padding-left:25px; white-space:nowrap;"/>
</div>
</t>
</t>
</div>
<div class="col-4">
<span t-if="company.company_details"
t-field="company.company_details"/>
</div>
<div class="col-3">
<h5 class="mt0 mb0" t-field="company.report_header"/>
<!--Footer-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'footer'">
<div t-if="txt_align == 'right'" class="text-right">
<span t-esc="mi.copy_name"
style="font-size: 18px;"/>
</div>
<div t-if="txt_align == 'center'"
class="text-center;">
<span t-esc="mi.copy_name"
style="font-size: 18px;"/>
</div>
</t>
</t>
</div>
<div class="col-1">
<ul t-if="report_type == 'pdf'"
class="list-inline pagenumber float-right text-center">
<li class="list-inline-item">
<strong>
<span class="page"/>
</strong>
</li>
</ul>
</div>
</div>
</div>
</template>
<template id="base_accounting_kit.striped">
<div t-attf-class="o_company_#{company.id}_layout header"
t-att-style="report_header_style">
<div class="o_background_header">
<div class="float-right">
<h3 class="mt0 text-right" t-field="company.report_header"/>
</div>
<img t-if="company.logo"
t-att-src="image_data_uri(company.logo)" class="float-left"
alt="Logo"/>
<div class="float-left company_address">
<span t-if="company.company_details"
t-field="company.company_details"></span>
</div>
<!--Header-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'header'">
<div t-if="txt_align == 'right'" class="text-right"
style="position: relative; top: 50px;">
<span t-esc="mi.copy_name"
style="font-size: 20px;"/>
</div>
<div t-if="txt_align == 'center'" class="text-center">
<br/>
<span t-esc="mi.copy_name" style="font-size: 20px;
margin-left:280px; margin-right:280px; white-space:nowrap;"/>
</div>
<div t-if="txt_align == 'left'" class="text-left"
style="position: fixed; top: 70px; left:20px;">
<br/>
<span t-esc="mi.copy_name"
style="font-size: 20px; white-space:nowrap;"/>
</div>
</t>
</t>
<div class="clearfix mb8"/>
</div>
<!--Watermark-->
<t t-if="mi_type =='watermark'">
<div style="opacity:0.15; font-size:100px; width:85%; text-align:center;top:500px; right:100px; position: fixed; z-index:99; -webkit-transform: rotate(-30deg);">
<t t-esc="mi.copy_name"/>
</div>
</t>
</div>
<div t-attf-class="o_company_#{company.id}_layout article o_report_layout_striped {{ 'o_layout_background' if company.layout_background in ['Geometric', 'Custom'] else '' }}"
t-attf-style="background-image: url({{ 'data:image/png;base64,%s' % company.layout_background_image.decode('utf-8') if company.layout_background_image and company.layout_background == 'Custom' else '/base/static/img/bg_background_template.jpg' }});"
t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id"
t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-call="web.address_layout"/>
<t t-out="0"/>
</div>
<div t-attf-class="o_company_#{company.id}_layout footer o_background_footer">
<div class="text-center">
<ul class="list-inline">
<div t-field="company.report_footer"/>
</ul>
<!--Footer-->
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'footer'">
<div t-if="txt_align == 'right'" class="text-right">
<span t-esc="mi.copy_name"
style="font-size: 15px;"/>
</div>
<div t-if="txt_align == 'left'" class="text-left">
<span t-esc="mi.copy_name"
style="font-size: 15px;"/>
</div>
<div t-if="txt_align == 'center'" class="text-center;">
<span t-esc="mi.copy_name"
style="font-size: 15px;"/>
</div>
</t>
</t>
<div t-if="report_type == 'pdf'" class="text-muted">
Page:
<span class="page"/>
of
<span class="topage"/>
</div>
</div>
</div>
</template>
<template id="multiple_invoice_wizard_preview">
<t t-call="web.html_preview_container">
<t t-call="base_accounting_kit.new_external_layout">
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'body'">
<div t-if="body_txt_position == 'tr'"
style="font-size:25px; text-align:right;">
<span>Sample Name</span>
</div>
<div t-if="body_txt_position == 'tl'"
style="font-size:25px; text-align:left;">
<span>Sample Name</span>
</div>
</t>
</t>
<div class="pt-5">
<div class="address row">
<div name="address" class="col-md-5 ml-auto">
<address>
<address class="mb-0" itemscope="itemscope"
itemtype="http://schema.org/Organization">
<div>
<span itemprop="name">Deco Addict</span>
</div>
<div itemprop="address"
itemscope="itemscope"
itemtype="http://schema.org/PostalAddress">
<div class="d-flex align-items-baseline">
<span class="w-100 o_force_ltr"
itemprop="streetAddress">77
Santa Barbara
Rd<br/>Pleasant Hill CA 94523
<br/>United States
</span>
</div>
</div>
</address>
</address>
</div>
</div>
</div>
<div class="page">
<h2>
<span>Invoice</span>
<span>INV/2020/07/0003</span>
</h2>
<div id="informations" class="row mt32 mb32">
<div class="col-auto mw-100 mb-2" name="invoice_date">
<strong>Invoice Date:</strong>
<p class="m-0">07/08/2020</p>
</div>
<div class="col-auto mw-100 mb-2" name="due_date">
<strong>Due Date:</strong>
<p class="m-0">08/07/2020</p>
</div>
</div>
<table class="table table-sm o_main_table"
name="invoice_line_table">
<thead>
<tr>
<th name="th_description" class="text-left">
<span>Description</span>
</th>
<th name="th_quantity" class="text-right">
<span>Quantity</span>
</th>
<th name="th_priceunit"
class="text-right d-none d-md-table-cell">
<span>Unit Price</span>
</th>
<th name="th_taxes"
class="text-left d-none d-md-table-cell">
<span>Taxes</span>
</th>
<th name="th_subtotal" class="text-right">
<span>Amount</span>
</th>
</tr>
</thead>
<tbody class="invoice_tbody">
<tr>
<td name="account_invoice_line_name">
<span>[FURN_8999] Three-Seat Sofa
<br/>
Three Seater Sofa with Lounger in Steel
Grey Colour
</span>
</td>
<td class="text-right">
<span>5.000</span>
</td>
<td class="text-right d-none d-md-table-cell">
<span class="text-nowrap">1,500.00</span>
</td>
<td class="text-left d-none d-md-table-cell">
<span id="line_tax_ids">15.00%</span>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap">$
<span class="oe_currency_value">
7,500.00
</span>
</span>
</td>
</tr>
<tr>
<td name="account_invoice_line_name">
<span>[FURN_8220] Four Person Desk
<br/>
Four person modern office workstation
</span>
</td>
<td class="text-right">
<span>5.000</span>
</td>
<td class="text-right d-none d-md-table-cell">
<span class="text-nowrap">23,500.00</span>
</td>
<td class="text-left d-none d-md-table-cell">
<span id="line_tax_ids">15.00%</span>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap">$
<span class="oe_currency_value">
117,500.00
</span>
</span>
</td>
</tr>
</tbody>
</table>
<div class="clearfix">
<div id="total" class="row">
<div class="col-sm-7 col-md-6 ml-auto">
<table class="table table-sm"
style="page-break-inside: avoid;">
<tbody>
<tr class="border-black o_subtotal"
style="">
<td>
<strong>Subtotal</strong>
</td>
<td class="text-right">
<span>$
<span class="oe_currency_value">
125,000.00
</span>
</span>
</td>
</tr>
<tr style="">
<td>
<span class="text-nowrap">Tax
15%
</span>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap">$
18,750.00
</span>
</td>
</tr>
<tr class="border-black o_total">
<td>
<strong>Total</strong>
</td>
<td class="text-right">
<span class="text-nowrap">$
<span class="oe_currency_value">
143,750.00
</span>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>
Please use the following communication for your payment
:
<b>
<span>
INV/2020/07/0003
</span>
</b>
</p>
<p name="payment_term">
<span>Payment terms: 300 Days</span>
</p>
<t t-if="mi_type == 'text'">
<t t-if="txt_position == 'body'">
<div t-if="body_txt_position == 'br'"
style="font-size:25px; text-align:right;">
<span>Sample Name</span>
</div>
<div t-if="body_txt_position == 'bl'"
style="font-size:25px; text-align:left;">
<span>Sample Name</span>
</div>
</t>
</t>
</div>
</t>
</t>
</template>
<template id="new_external_layout">
<t t-if="not o" t-set="o" t-value="doc"/>
<t t-if="not company">
<!-- Multicompany -->
<t t-if="company_id">
<t t-set="company" t-value="company_id"/>
</t>
<t t-elif="o and 'company_id' in o">
<t t-set="company" t-value="o.company_id.sudo()"/>
</t>
<t t-else="else">
<t t-set="company" t-value="res_company"/>
</t>
</t>
<t t-if="layout" t-call="{{layout}}">
<t t-raw="0"/>
</t>
<t t-else="else" t-call="base_accounting_kit.standard">
<t t-raw="0"/>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, models
class ReportInvoiceMultiple(models.AbstractModel):
_name = 'report.base_accounting_kit.report_multiple_invoice'
_inherit = 'report.account.report_invoice'
_description = 'Report Invoice Multiple'
@api.model
def _get_report_values(self, docids, data=None):
rslt = super()._get_report_values(docids, data)
inv = rslt['docs']
layout = inv.journal_id.company_id.external_report_layout_id.key
if layout == 'web.external_layout_boxed':
new_layout = 'base_accounting_kit.boxed'
elif layout == 'web.external_layout_bold':
new_layout = 'base_accounting_kit.bold'
elif layout == 'web.external_layout_striped':
new_layout = 'base_accounting_kit.striped'
else:
new_layout = 'base_accounting_kit.standard'
rslt['mi_type'] = inv.journal_id.multiple_invoice_type
rslt['mi_ids'] = inv.journal_id.multiple_invoice_ids
rslt['txt_position'] = inv.journal_id.text_position
rslt['body_txt_position'] = inv.journal_id.body_text_position
rslt['txt_align'] = inv.journal_id.text_align
rslt['layout'] = new_layout
rslt['report_type'] = data.get('report_type') if data else ''
return rslt

View File

@@ -0,0 +1,307 @@
<odoo>
<template id="report_multiple_invoice_new">
<t t-call="base_accounting_kit.new_external_layout">
<t t-set="o" t-value="o.with_context(lang=lang)"/>
<t t-set="address">
<address t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'/>
<div t-if="o.partner_id.vat" class="mt16">
<t t-if="o.company_id.country_id.vat_label"
t-esc="o.company_id.country_id.vat_label"
id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>:
<span t-field="o.partner_id.vat"/>
</div>
</t>
<div class="page">
<t t-set="txt_style"
t-value="'font-size:25px; text-align:center;top:0px; left:15px; position:absolute; z-index:99;'"/>
<t t-if="body_txt_position == 'tr'">
<t t-set="txt_style"
t-value="'font-size:25px; text-align:center;top:0px; right:15px; position:absolute; z-index:99;'"/>
</t>
<t t-if="body_txt_position == 'br'">
<t t-set="txt_style"
t-value="'font-size:25px; text-align:right;'"/>
</t>
<t t-if="body_txt_position == 'bl'">
<t t-set="txt_style"
t-value="'font-size:25px; text-align:left;'"/>
</t>
<h2>
<span t-if="o.move_type == 'out_invoice' and o.state == 'posted'">
Invoice
</span>
<span t-if="o.move_type == 'out_invoice' and o.state == 'draft'">
Draft Invoice
</span>
<span t-if="o.move_type == 'out_invoice' and o.state == 'cancel'">
Cancelled Invoice
</span>
<span t-if="o.move_type == 'out_refund'">Credit Note</span>
<span t-if="o.move_type == 'in_refund'">Vendor Credit Note
</span>
<span t-if="o.move_type == 'in_invoice'">Vendor Bill</span>
<span t-if="o.name != '/'" t-field="o.name"/>
</h2>
<div id="informations" class="row mt32 mb32">
<div class="col-auto col-3 mw-100 mb-2"
t-if="o.invoice_date" name="invoice_date">
<strong>Invoice Date:</strong>
<p class="m-0" t-field="o.invoice_date"/>
</div>
<div class="col-auto col-3 mw-100 mb-2"
t-if="o.invoice_date_due and o.move_type == 'out_invoice' and o.state == 'posted'"
name="due_date">
<strong>Due Date:</strong>
<p class="m-0" t-field="o.invoice_date_due"/>
</div>
<div class="col-auto col-3 mw-100 mb-2"
t-if="o.invoice_origin" name="origin">
<strong>Source:</strong>
<p class="m-0" t-field="o.invoice_origin"/>
</div>
<div class="col-auto col-3 mw-100 mb-2"
t-if="o.partner_id.ref" name="customer_code">
<strong>Customer Code:</strong>
<p class="m-0" t-field="o.partner_id.ref"/>
</div>
<div class="col-auto col-3 mw-100 mb-2" t-if="o.ref"
name="reference">
<strong>Reference:</strong>
<p class="m-0" t-field="o.ref"/>
</div>
</div>
<t t-set="display_discount"
t-value="any(l.discount for l in o.invoice_line_ids)"/>
<table class="table table-sm o_main_table"
name="invoice_line_table">
<thead>
<tr>
<th name="th_description" class="text-left">
<span>Description</span>
</th>
<th name="th_quantity" class="text-right">
<span>Quantity</span>
</th>
<th name="th_priceunit"
t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span>Unit Price</span>
</th>
<th name="th_price_unit" t-if="display_discount"
t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span>Disc.%</span>
</th>
<th name="th_taxes"
t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span>Taxes</span>
</th>
<th name="th_subtotal" class="text-right">
<span>
Amount
</span>
</th>
</tr>
</thead>
<tbody class="invoice_tbody">
<t t-set="current_subtotal" t-value="0"/>
<t t-set="lines"
t-value="o.invoice_line_ids.sorted(key=lambda l: (-l.sequence, l.date, l.move_name, -l.id), reverse=True)"/>
<t t-foreach="lines" t-as="line">
<t t-set="current_subtotal"
t-value="current_subtotal + line.price_subtotal"
groups="account.group_show_line_subtotals_tax_excluded"/>
<t t-set="current_subtotal"
t-value="current_subtotal + line.price_total"
groups="account.group_show_line_subtotals_tax_included"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="line.display_type not in ('line_section', 'line_note')"
name="account_invoice_line_accountable">
<td name="account_invoice_line_name">
<span t-field="line.name"
t-options="{'widget': 'text'}"/>
</td>
<td class="text-right">
<span t-field="line.quantity"/>
<span t-field="line.product_uom_id"
groups="uom.group_uom"/>
</td>
<td t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="text-nowrap"
t-field="line.price_unit"/>
</td>
<td t-if="display_discount"
t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="text-nowrap"
t-field="line.discount"/>
</td>
<td t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_ids))"
id="line_tax_ids"/>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap"
t-field="line.price_subtotal"
/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
<td colspan="99">
<span t-field="line.name"
t-options="{'widget': 'text'}"/>
</td>
<t t-set="current_section" t-value="line"/>
<t t-set="current_subtotal" t-value="0"/>
</t>
<t t-if="line.display_type == 'line_note'">
<td colspan="99">
<span t-field="line.name"
t-options="{'widget': 'text'}"/>
</td>
</t>
</tr>
<t t-if="current_section and (line_last or lines[line_index+1].display_type == 'line_section')">
<tr class="is-subtotal text-right">
<td colspan="99">
<strong class="mr16">Subtotal</strong>
<span
t-esc="current_subtotal"
t-options='{"widget":
"monetary", "display_currency":
o.currency_id}'
/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<div class="clearfix">
<div id="right-elements" t-attf-class="#{'col-5' if report_type != 'html' else 'col-12 col-md-5'} ms-5 d-inline-block float-end">
<div id="total" class="clearfix row mt-n3">
<div class="ms-auto">
<table class="o_total_table table table-borderless avoid-page-break-inside">
<!-- Tax totals summary (invoice currency) -->
<t t-if="o.tax_totals" t-call="account.document_tax_totals">
<t t-set="tax_totals" t-value="o.tax_totals"/>
<t t-set="currency" t-value="o.currency_id"/>
</t>
<!--Payments-->
<t t-if="print_with_payments">
<t t-if="o.payment_state != 'invoicing_legacy'">
<t t-set="payments_vals" t-value="o.sudo().invoice_payments_widget and o.sudo().invoice_payments_widget['content'] or []"/>
<t t-foreach="payments_vals" t-as="payment_vals">
<tr t-if="payment_vals['is_exchange'] == 0">
<td>
<i class="oe_form_field text-end oe_payment_label">Paid on <t t-out="payment_vals['date']" t-options='{"widget": "date"}'>2021-09-19</t></i>
</td>
<td class="text-end">
<span t-out="payment_vals['amount']" t-options='{"widget": "monetary", "display_currency": o.currency_id}'>20.00</span>
</td>
</tr>
</t>
<t t-if="len(payments_vals) > 0">
<tr class="fw-bold">
<td>Amount Due</td>
<td class="text-end">
<span t-field="o.amount_residual">11.05</span>
</td>
</tr>
</t>
</t>
</t>
</table>
</div>
</div>
<div class="mb-2">
<p class="text-end lh-sm" t-if="o.company_id.display_invoice_amount_total_words">
Total amount in words: <br/>
<small class="text-muted lh-sm"><span t-field="o.amount_total_words">Thirty one dollar and Five cents</span></small>
</p>
</div>
<!-- Tax totals summary (company currency) -->
<t t-if="o.tax_totals.get('display_in_company_currency')">
<t t-set="tax_totals" t-value="o.tax_totals"/>
<t t-call="account.document_tax_totals_company_currency_template"/>
</t>
<t t-else="">
<div class="oe_structure"/>
</t>
</div>
</div>
<p t-if="o.move_type in ('out_invoice', 'in_refund') and o.payment_reference"
name="payment_communication">
Please use the following communication for your payment :
<b>
<span t-field="o.payment_reference"/>
</b>
</p>
<p t-if="o.invoice_payment_term_id" name="payment_term">
<span t-field="o.invoice_payment_term_id.note"/>
</p>
<p t-if="o.narration" name="comment">
<span t-field="o.narration"/>
</p>
<p t-if="o.fiscal_position_id.note" name="note">
<span t-field="o.fiscal_position_id.note"/>
</p>
<p t-if="o.invoice_incoterm_id" name="incoterm">
<strong>Incoterm:</strong>
<span t-field="o.invoice_incoterm_id.code"/>
-
<span t-field="o.invoice_incoterm_id.name"/>
</p>
<div id="qrcode" t-if="o.display_qr_code">
<p t-if="qr_code_urls.get(o.id)">
<strong class="text-center">Scan me with your banking
app.
</strong>
<br/>
<br/>
<img class="border border-dark rounded"
t-att-src="qr_code_urls[o.id]"/>
</p>
</div>
<t t-if="mi_type == 'text'">
<div t-if="txt_position == 'body'" t-att-style="txt_style">
<span t-esc="mi.copy_name"/>
</div>
</t>
</div>
</t>
</template>
<template id="report_multiple_invoice">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-set="lang"
t-value="o.invoice_user_id.sudo().lang if o.move_type in ('in_invoice', 'in_refund') else o.partner_id.lang"/>
<t t-set="print_with_payments" t-value="True"/>
<t t-if="o._get_name_invoice_report() == 'account.report_invoice_document'"
t-call="account.report_invoice_document" t-lang="lang"/>
<t t-foreach="mi_ids" t-as="mi">
<t t-call="base_accounting_kit.report_multiple_invoice_new"
t-lang="lang"/>
</t>
</t>
</t>
</template>
<record id="report_multiple_invoice_copies" model="ir.actions.report">
<field name="name">Multiple Invoice Copies</field>
<field name="model">account.move</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_multiple_invoice</field>
<field name="report_file">base_accounting_kit.report_multiple_invoice</field>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- # Financial report -->
<record id="financial_report_pdf" model="ir.actions.report">
<field name="name">Financial reports</field>
<field name="model">financial.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_financial</field>
<field name="report_file">base_accounting_kit.report_financial</field>
</record>
<!-- # General ledger report -->
<record id="action_report_general_ledger" model="ir.actions.report">
<field name="name">General Ledger</field>
<field name="model">account.report.general.ledger</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_general_ledger</field>
<field name="report_file">base_accounting_kit.report_general_ledger
</field>
</record>
<!-- # Partner ledger report -->
<record id="action_report_partnerledger" model="ir.actions.report">
<field name="name">Partner Ledger</field>
<field name="model">account.report.partner.ledger</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_partnerledger</field>
<field name="report_file">base_accounting_kit.report_partnerledger</field>
</record>
<!-- # Ageing report -->
<record id="action_report_aged_partner_balance" model="ir.actions.report">
<field name="name">Aged Partner Balance</field>
<field name="model">res.partner</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_agedpartnerbalance</field>
<field name="report_file">base_accounting_kit.report_agedpartnerbalance</field>
</record>
<!-- # Journal audit report -->
<record id="action_report_journal" model="ir.actions.report">
<field name="name">Journals Audit</field>
<field name="model">account.common.journal.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_journal_audit</field>
<field name="report_file">base_accounting_kit.report_journal_audit</field>
</record>
<!-- # Tax report -->
<record id="action_report_account_tax" model="ir.actions.report">
<field name="name">Tax Report</field>
<field name="model">kit.account.tax.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_tax</field>
<field name="report_file">base_accounting_kit.report_tax</field>
</record>
<!-- # Trial balance report -->
<record id="action_report_trial_balance" model="ir.actions.report">
<field name="name">Trial Balance</field>
<field name="model">account.balance.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_trial_balance</field>
<field name="report_file">base_accounting_kit.report_trial_balance</field>
</record>
<!-- # CAsh flow statements -->
<record id="action_report_cash_flow" model="ir.actions.report">
<field name="name">Cash Flow Statement</field>
<field name="model">account.financial.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_cash_flow</field>
<field name="report_file">base_accounting_kit.report_cash_flow</field>
</record>
<!-- # Accounting Bank Book Report -->
<record id="action_report_bank_book" model="ir.actions.report">
<field name="name">Bank Book Report</field>
<field name="model">account.bank.book.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_bank_book</field>
<field name="report_file">base_accounting_kit.report_bank_book</field>
<field name="attachment_use">False</field>
</record>
<!-- # Accounting Cash Book Report -->
<record id="action_report_cash_book" model="ir.actions.report">
<field name="name">Cash Book Report</field>
<field name="model">account.cash.book.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.report_cash_book</field>
<field name="report_file">base_accounting_kit.report_cash_book</field>
<field name="attachment_use">False</field>
</record>
<!-- # Accounting Day Book Report -->
<record id="day_book_pdf_report" model="ir.actions.report">
<field name="name">Day Book PDF Report</field>
<field name="model">account.day.book.report</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.day_book_report_template</field>
<field name="report_file">base_accounting_kit.day_book_report_template</field>
<field name="attachment_use">True</field>
</record>
</odoo>

View File

@@ -0,0 +1,297 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import api, models, _
from odoo.exceptions import UserError
from odoo.tools import float_is_zero
class ReportAgedPartnerBalance(models.AbstractModel):
_name = 'report.base_accounting_kit.report_agedpartnerbalance'
_description = 'Aged Partner Balance Report'
def _get_partner_move_lines(self, account_type, date_from, target_move,
period_length):
# This method can receive the context key 'include_nullified_amount' {Boolean}
# Do an invoice and a payment and unreconcile. The amount will be nullified
# By default, the partner wouldn't appear in this report.
# The context key allow it to appear
# In case of a period_length of 30 days as of 2019-02-08, we want the following periods:
# Name Stop Start
# 1 - 30 : 2019-02-07 - 2019-01-09
# 31 - 60 : 2019-01-08 - 2018-12-10
# 61 - 90 : 2018-12-09 - 2018-11-10
# 91 - 120 : 2018-11-09 - 2018-10-11
# +120 : 2018-10-10
periods = {}
start = datetime.strptime(date_from, "%Y-%m-%d")
date_from = datetime.strptime(date_from, "%Y-%m-%d").date()
for i in range(5)[::-1]:
stop = start - relativedelta(days=period_length)
period_name = str((5 - (i + 1)) * period_length + 1) + '-' + str(
(5 - i) * period_length)
period_stop = (start - relativedelta(days=1)).strftime('%Y-%m-%d')
if i == 0:
period_name = '+' + str(4 * period_length)
periods[str(i)] = {
'name': period_name,
'stop': period_stop,
'start': (i != 0 and stop.strftime('%Y-%m-%d') or False),
}
start = stop
res = []
total = []
cr = self.env.cr
user_company = self.env.company
user_currency = user_company.currency_id
ResCurrency = self.env['res.currency'].with_context(date=date_from)
company_ids = self._context.get('company_ids') or [user_company.id]
move_state = ['draft', 'posted']
if target_move == 'posted':
move_state = ['posted']
arg_list = (tuple(move_state), tuple(account_type))
# build the reconciliation clause to see what partner needs to be printed
reconciliation_clause = '(l.reconciled IS FALSE)'
cr.execute(
'SELECT debit_move_id, credit_move_id FROM account_partial_reconcile where max_date > %s',
(date_from,))
reconciled_after_date = []
for row in cr.fetchall():
reconciled_after_date += [row[0], row[1]]
if reconciled_after_date:
reconciliation_clause = '(l.reconciled IS FALSE OR l.id IN %s)'
arg_list += (tuple(reconciled_after_date),)
arg_list += (date_from, tuple(company_ids))
query = '''
SELECT DISTINCT l.partner_id, UPPER(res_partner.name)
FROM account_move_line AS l left join res_partner on l.partner_id = res_partner.id, account_account, account_move am
WHERE (l.account_id = account_account.id)
AND (l.move_id = am.id)
AND (am.state IN %s)
AND (account_account.account_type IN %s)
AND ''' + reconciliation_clause + '''
AND (l.date <= %s)
AND l.company_id IN %s
ORDER BY UPPER(res_partner.name)'''
cr.execute(query, arg_list)
partners = cr.dictfetchall()
# put a total of 0
for i in range(7):
total.append(0)
# Build a string like (1,2,3) for easy use in SQL query
partner_ids = [partner['partner_id'] for partner in partners if
partner['partner_id']]
lines = dict(
(partner['partner_id'] or False, []) for partner in partners)
if not partner_ids:
return [], [], {}
# This dictionary will store the not due amount of all partners
undue_amounts = {}
query = '''SELECT l.id
FROM account_move_line AS l, account_account, account_move am
WHERE (l.account_id = account_account.id) AND (l.move_id = am.id)
AND (am.state IN %s)
AND (account_account.account_type IN %s)
AND (COALESCE(l.date_maturity,l.date) >= %s)\
AND ((l.partner_id IN %s) OR (l.partner_id IS NULL))
AND (l.date <= %s)
AND l.company_id IN %s'''
cr.execute(query, (
tuple(move_state), tuple(account_type), date_from,
tuple(partner_ids), date_from, tuple(company_ids)))
aml_ids = cr.fetchall()
aml_ids = aml_ids and [x[0] for x in aml_ids] or []
for line in self.env['account.move.line'].browse(aml_ids):
partner_id = line.partner_id.id or False
if partner_id not in undue_amounts:
undue_amounts[partner_id] = 0.0
line_amount = ResCurrency._get_conversion_rate(line.company_id.currency_id,
user_currency, line.balance)
if user_currency.is_zero(line_amount):
continue
for partial_line in line.matched_debit_ids:
if partial_line.max_date <= date_from:
line_amount += ResCurrency._get_conversion_rate(
partial_line.company_id.currency_id, user_currency,
partial_line.amount)
for partial_line in line.matched_credit_ids:
if partial_line.max_date <= date_from:
line_amount -= ResCurrency._get_conversion_rate(
partial_line.company_id.currency_id, user_currency,
partial_line.amount)
if not self.env.company.currency_id.is_zero(line_amount):
undue_amounts[partner_id] += line_amount
lines[partner_id].append({
'line': line,
'amount': line_amount,
'period': 6,
})
# Use one query per period and store results in history (a list variable)
# Each history will contain: history[1] = {'<partner_id>': <partner_debit-credit>}
history = []
for i in range(5):
args_list = (
tuple(move_state), tuple(account_type), tuple(partner_ids),)
dates_query = '(COALESCE(l.date_maturity,l.date)'
if periods[str(i)]['start'] and periods[str(i)]['stop']:
dates_query += ' BETWEEN %s AND %s)'
args_list += (
periods[str(i)]['start'], periods[str(i)]['stop'])
elif periods[str(i)]['start']:
dates_query += ' >= %s)'
args_list += (periods[str(i)]['start'],)
else:
dates_query += ' <= %s)'
args_list += (periods[str(i)]['stop'],)
args_list += (date_from, tuple(company_ids))
query = '''SELECT l.id
FROM account_move_line AS l, account_account, account_move am
WHERE (l.account_id = account_account.id) AND (l.move_id = am.id)
AND (am.state IN %s)
AND (account_account.account_type IN %s)
AND ((l.partner_id IN %s) OR (l.partner_id IS NULL))
AND ''' + dates_query + '''
AND (l.date <= %s)
AND l.company_id IN %s'''
cr.execute(query, args_list)
partners_amount = {}
aml_ids = cr.fetchall()
aml_ids = aml_ids and [x[0] for x in aml_ids] or []
for line in self.env['account.move.line'].browse(aml_ids):
partner_id = line.partner_id.id or False
if partner_id not in partners_amount:
partners_amount[partner_id] = 0.0
line_amount = ResCurrency._get_conversion_rate(line.company_id.currency_id,
user_currency, line.balance)
if user_currency.is_zero(line_amount):
continue
for partial_line in line.matched_debit_ids:
if partial_line.max_date <= date_from:
line_amount += ResCurrency._get_conversion_rate(
partial_line.company_id.currency_id, user_currency,
partial_line.amount)
for partial_line in line.matched_credit_ids:
if partial_line.max_date <= date_from:
line_amount -= ResCurrency._get_conversion_rate(
partial_line.company_id.currency_id, user_currency,
partial_line.amount)
if not self.env.company.currency_id.is_zero(
line_amount):
partners_amount[partner_id] += line_amount
lines[partner_id].append({
'line': line,
'amount': line_amount,
'period': i + 1,
})
history.append(partners_amount)
for partner in partners:
if partner['partner_id'] is None:
partner['partner_id'] = False
at_least_one_amount = False
values = {}
undue_amt = 0.0
if partner[
'partner_id'] in undue_amounts: # Making sure this partner actually was found by the query
undue_amt = undue_amounts[partner['partner_id']]
total[6] = total[6] + undue_amt
values['direction'] = undue_amt
if not float_is_zero(values['direction'],
precision_rounding=self.env.company.currency_id.rounding):
at_least_one_amount = True
for i in range(5):
during = False
if partner['partner_id'] in history[i]:
during = [history[i][partner['partner_id']]]
# Adding counter
total[(i)] = total[(i)] + (during and during[0] or 0)
values[str(i)] = during and during[0] or 0.0
if not float_is_zero(values[str(i)],
precision_rounding=self.env.company.currency_id.rounding):
at_least_one_amount = True
values['total'] = sum(
[values['direction']] + [values[str(i)] for i in range(5)])
## Add for total
total[(i + 1)] += values['total']
values['partner_id'] = partner['partner_id']
if partner['partner_id']:
browsed_partner = self.env['res.partner'].browse(
partner['partner_id'])
values['name'] = browsed_partner.name and len(
browsed_partner.name) >= 45 and browsed_partner.name[
0:40] + '...' or browsed_partner.name
values['trust'] = browsed_partner.trust
else:
values['name'] = _('Unknown Partner')
values['trust'] = False
if at_least_one_amount or (
self._context.get('include_nullified_amount') and lines[
partner['partner_id']]):
res.append(values)
return res, total, lines
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get(
'active_model') or not self.env.context.get('active_id'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
total = []
model = self.env.context.get('active_model')
docs = self.env[model].browse(self.env.context.get('active_id'))
target_move = data['form'].get('target_move', 'all')
date_from = data['form'].get('date_from', time.strftime('%Y-%m-%d'))
if data['form']['result_selection'] == 'customer':
account_type = ['asset_receivable']
elif data['form']['result_selection'] == 'supplier':
account_type = ['liability_payable']
else:
account_type = ['liability_payable', 'asset_receivable']
movelines, total, dummy = self._get_partner_move_lines(account_type,
date_from,
target_move,
data['form']['period_length'])
return {
'doc_ids': self.ids,
'doc_model': model,
'data': data['form'],
'docs': docs,
'time': time,
'get_partner_lines': movelines,
'get_direction': total,
}

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_agedpartnerbalance">
<t t-call="web.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-call="web.internal_layout">
<div class="page"><br/>
<h2>Aged Partner Balance</h2>
<div class="row mt32">
<div class="col-3">
<strong>Start Date:</strong>
<p t-esc="data['date_from']"/>
</div>
<div class="col-3">
<strong>Period Length (days)</strong>
<p t-esc="data['period_length']"/>
</div>
</div>
<div class="row mb32">
<div class="col-3">
<strong>Partner's:</strong>
<p>
<span t-if="data['result_selection'] == 'customer'">Receivable Accounts</span>
<span t-if="data['result_selection'] == 'supplier'">Payable Accounts</span>
<span t-if="data['result_selection'] == 'customer_supplier'">Receivable and Payable Accounts</span>
</p>
</div>
<div class="col-3">
<strong>Target Moves:</strong>
<p>
<span t-if="data['target_move'] == 'all'">All Entries</span>
<span t-if="data['target_move'] == 'posted'">All Posted Entries</span>
</p>
</div>
</div>
<table class="table table-sm table-reports">
<thead>
<tr>
<th>Partners</th>
<th class="text-right">
<span>Not due</span>
</th>
<th class="text-right"><span t-esc="data['4']['name']"/></th>
<th class="text-right"><span t-esc="data['3']['name']"/></th>
<th class="text-right"><span t-esc="data['2']['name']"/></th>
<th class="text-right"><span t-esc="data['1']['name']"/></th>
<th class="text-right"><span t-esc="data['0']['name']"/></th>
<th class="text-right">Total</th>
</tr>
<tr t-if="get_partner_lines">
<th>Account Total</th>
<th class="text-right"><span t-esc="get_direction[6]" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/></th>
<th class="text-right"><span t-esc="get_direction[4]" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/></th>
<th class="text-right"><span t-esc="get_direction[3]" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/></th>
<th class="text-right"><span t-esc="get_direction[2]" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/></th>
<th class="text-right"><span t-esc="get_direction[1]" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/></th>
<th class="text-right"><span t-esc="get_direction[0]" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/></th>
<th class="text-right"><span t-esc="get_direction[5]" t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/></th>
</tr>
</thead>
<tbody>
<tr t-foreach="get_partner_lines" t-as="partner">
<td>
<span t-esc="partner['name']"/>
</td>
<td class="text-start">
<span t-esc="partner['direction']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-start">
<span t-esc="partner['4']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-start">
<span t-esc="partner['3']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-start">
<span t-esc="partner['2']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-start">
<span t-esc="partner['1']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-start">
<span t-esc="partner['0']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-start">
<span t-esc="partner['total']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import api, fields, models
# ---------------------------------------------------------
# Account Financial Report
# ---------------------------------------------------------
class AccountTypes(models.Model):
_name = "account.account.type"
name = fields.Char(string='Account Type', required=True, translate=True)
type = fields.Selection([
('other', 'Regular'),
('receivable', 'Receivable'),
('payable', 'Payable'),
('liquidity', 'Liquidity'),
], required=True, default='other',
help="The 'Internal Type' is used for features available on "
"different types of accounts: liquidity type is for cash or "
"bank accounts" \
", payable/receivable is for vendor/customer accounts.")
class AccountFinancialReport(models.Model):
_name = "account.financial.report"
_description = "Account Report"
_rec_name = 'name'
@api.depends('parent_id', 'parent_id.level')
def _get_level(self):
"""Returns a dictionary with key=the ID of a record and
value = the level of this
record in the tree structure."""
for report in self:
level = 0
if report.parent_id:
level = report.parent_id.level + 1
report.level = level
def _get_children_by_order(self):
"""returns a recordset of all the children computed recursively,
and sorted by sequence. Ready for the printing"""
res = self
children = self.search([('parent_id', 'in', self.ids)],
order='sequence ASC')
if children:
for child in children:
res += child._get_children_by_order()
return res
name = fields.Char('Report Name', required=True, translate=True)
parent_id = fields.Many2one('account.financial.report', 'Parent')
children_ids = fields.One2many(
'account.financial.report',
'parent_id',
'Account Report')
sequence = fields.Integer('Sequence')
level = fields.Integer(compute='_get_level', string='Level', store=True, recursive=True)
type = fields.Selection(
[('sum', 'View'),
('accounts', 'Accounts'),
('account_type', 'Account Type'),
('account_report', 'Report Value')],
'Type',
default='sum')
account_ids = fields.Many2many(
'account.account',
'account_account_financial_report',
'report_line_id',
'account_id',
'Accounts')
account_report_id = fields.Many2one(
'account.financial.report',
'Report Value')
# account_type_ids = fields.Many2many(
# 'account.account.type',
# 'Account Types')
account_type_ids = fields.Selection(
selection=[
("asset_receivable", "Receivable"),
("asset_cash", "Bank and Cash"),
("asset_current", "Current Assets"),
("asset_non_current", "Non-current Assets"),
("asset_prepayments", "Prepayments"),
("asset_fixed", "Fixed Assets"),
("liability_payable", "Payable"),
("liability_credit_card", "Credit Card"),
("liability_current", "Current Liabilities"),
("liability_non_current", "Non-current Liabilities"),
("equity", "Equity"),
("equity_unaffected", "Current Year Earnings"),
("income", "Income"),
("income_other", "Other Income"),
("expense", "Expenses"),
("expense_depreciation", "Depreciation"),
("expense_direct_cost", "Cost of Revenue"),
("off_balance", "Off-Balance Sheet"),
],
string="Type",
help="These types are defined according to your country. The type contains more information " \
"about the account and its specificities."
)
sign = fields.Selection(
[("-1", 'Reverse balance sign'), ("1", 'Preserve balance sign')],
'Sign on Reports', required=True, default="1",
help='For accounts that are typically more'
' debited than credited and that you'
' would like to print as negative'
' amounts in your reports, you should'
' reverse the sign of the balance;'
' e.g.: Expense account. The same applies'
' for accounts that are typically more'
' credited than debited and that you would'
' like to print as positive amounts in'
' your reports; e.g.: Income account.')
display_detail = fields.Selection(
[('no_detail', 'No detail'),
('detail_flat', 'Display children flat'),
('detail_with_hierarchy', 'Display children with hierarchy')],
'Display details',
default='detail_flat')
style_overwrite = fields.Selection(
[('0', 'Automatic formatting'),
('1', 'Main Title 1 (bold, underlined)'),
('2', 'Title 2 (bold)'),
('3', 'Title 3 (bold, smaller)'),
('4', 'Normal Text'),
('5', 'Italic Text (smaller)'),
('6', 'Smallest Text')],
'Financial Report Style',
default='0',
help="You can set up here the format you want this"
" record to be displayed. If you leave the"
" automatic formatting, it will be computed"
" based on the financial reports hierarchy "
"(auto-computed field 'level').")

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="report_financial">
<t t-call="web.html_container">
<t t-call="web.internal_layout">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<div class="page">
<h2 t-esc="data['form']['account_report_id'][1]"/>
<div class="row mt32 mb32">
<div class="col-4">
<strong>Target Moves:</strong>
<p>
<span t-if="data['form']['target_move'] == 'all'">All Entries</span>
<span t-if="data['form']['target_move'] == 'posted'">All Posted Entries</span>
</p>
</div>
<div class="col-4">
<p>
<t t-if="data['form']['date_from']">
<strong>Date from :</strong>
<span t-esc="data['form']['date_from']"/>
<br/>
</t>
<t t-if="data['form']['date_to']">
<strong>Date to :</strong>
<span t-esc="data['form']['date_to']"/>
</t>
</p>
</div>
</div>
<table class="table table-sm table-reports" t-if="data['form']['debit_credit'] == 1">
<thead>
<tr>
<th>Name</th>
<th class="text-right">Debit</th>
<th class="text-right">Credit</th>
<th class="text-right">Balance</th>
</tr>
</thead>
<tbody>
<tr t-foreach="report_lines" t-as="a">
<t t-if="a['level'] != 0">
<t t-if="a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: normal;'"/>
</t>
<t t-if="not a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: bold;'"/>
</t>
<td>
<span style="color: white;" t-esc="'..' * a.get('level', 0)"/>
<span t-att-style="style" t-esc="a.get('name')"/>
</td>
<td class="text-right" style="white-space: text-nowrap;">
<span t-att-style="style" t-esc="a.get('debit')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right" style="white-space: text-nowrap;">
<span t-att-style="style" t-esc="a.get('credit')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-right" style="white-space: text-nowrap;">
<span t-att-style="style" t-esc="a.get('balance')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</t>
</tr>
</tbody>
</table>
<table class="table table-sm table-reports"
t-if="not data['form']['enable_filter'] and not data['form']['debit_credit']">
<thead>
<tr>
<th>Name</th>
<th class="text-right">Balance</th>
</tr>
</thead>
<tbody>
<tr t-foreach="report_lines" t-as="a">
<t t-if="a['level'] != 0">
<t t-if="a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: normal;'"/>
</t>
<t t-if="not a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: bold;'"/>
</t>
<td>
<span style="color: white;" t-esc="'..' * a.get('level', 0)"/>
<span t-att-style="style" t-esc="a.get('name')"/>
</td>
<td class="text-right">
<span t-att-style="style" t-esc="a.get('balance')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</t>
</tr>
</tbody>
</table>
<table class="table table-sm table-reports"
t-if="data['form']['enable_filter'] == 1 and not data['form']['debit_credit']">
<thead>
<tr>
<th>Name</th>
<th class="text-right">Balance</th>
<th class="text-right">
<span>Comp</span>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="report_lines" t-as="a">
<t t-if="a['level'] != 0">
<t t-if="a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: normal;'"/>
</t>
<t t-if="not a.get('level') &gt; 3">
<t t-set="style" t-value="'font-weight: bold;'"/>
</t>
<td>
<span style="color: white;" t-esc="'..'"/>
<span t-att-style="style" t-esc="a.get('name')"/>
</td>
<td class="text-end">
<span t-att-style="style"
t-esc="a.get('balance')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-att-style="style"
t-esc="a.get('balance_cmp')"/>
</td>
</t>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import time
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportJournal(models.AbstractModel):
_name = 'report.base_accounting_kit.report_journal_audit'
_description = 'Journal Report'
def lines(self, target_move, journal_ids, sort_selection, data):
if isinstance(journal_ids, int):
journal_ids = [journal_ids]
move_state = ['draft', 'posted']
if target_move == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_ids)] + query_get_clause[2]
query = 'SELECT "account_move_line".id FROM ' + query_get_clause[
0] + (', account_move am, account_account acc WHERE '
'"account_move_line".account_id = acc.id AND '
'"account_move_line".move_id=am.id AND am.state IN %s AND '
'"account_move_line".journal_id IN %s AND ') + \
query_get_clause[1] + ' ORDER BY '
if sort_selection == 'date':
query += '"account_move_line".date'
else:
query += 'am.name'
query += ', "account_move_line".move_id'
self.env.cr.execute(query, tuple(params))
ids = (x[0] for x in self.env.cr.fetchall())
return self.env['account.move.line'].browse(ids)
def _sum_debit(self, data, journal_id):
move_state = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[
2]
self.env.cr.execute('SELECT SUM(debit) FROM ' + query_get_clause[
0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s'
' AND "account_move_line".journal_id IN %s AND ' +
query_get_clause[1] + ' ',
tuple(params))
return self.env.cr.fetchone()[0] or 0.0
def _sum_credit(self, data, journal_id):
move_state = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[
2]
self.env.cr.execute('SELECT SUM(credit) FROM ' + query_get_clause[
0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' +
query_get_clause[1] + ' ',
tuple(params))
return self.env.cr.fetchone()[0] or 0.0
def _get_taxes(self, data, journal_id):
move_state = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
move_state = ['posted']
query_get_clause = self._get_query_get_clause(data)
params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[
2]
query = """
SELECT rel.account_tax_id, SUM("account_move_line".balance) AS base_amount
FROM account_move_line_account_tax_rel rel, """ + query_get_clause[
0] + """
LEFT JOIN account_move am ON "account_move_line".move_id = am.id
WHERE "account_move_line".id = rel.account_move_line_id
AND am.state IN %s
AND "account_move_line".journal_id IN %s
AND """ + query_get_clause[1] + """
GROUP BY rel.account_tax_id"""
self.env.cr.execute(query, tuple(params))
ids = []
base_amounts = {}
for row in self.env.cr.fetchall():
ids.append(row[0])
base_amounts[row[0]] = row[1]
res = {}
for tax in self.env['account.tax'].browse(ids):
self.env.cr.execute(
'SELECT sum(debit - credit) FROM ' + query_get_clause[
0] + ', account_move am '
'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' +
query_get_clause[1] + ' AND tax_line_id = %s',
tuple(params + [tax.id]))
res[tax] = {
'base_amount': base_amounts[tax.id],
'tax_amount': self.env.cr.fetchone()[0] or 0.0,
}
if journal_id.type == 'sale':
# sales operation are credits
res[tax]['base_amount'] = res[tax]['base_amount'] * -1
res[tax]['tax_amount'] = res[tax]['tax_amount'] * -1
return res
def _get_query_get_clause(self, data):
return self.env['account.move.line'].with_context(
data['form'].get('used_context', {}))._query_get()
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
target_move = data['form'].get('target_move', 'all')
sort_selection = data['form'].get('sort_selection', 'date')
res = {}
for journal in data['form']['journal_ids']:
res[journal] = self.with_context(
data['form'].get('used_context', {})).lines(target_move,
journal,
sort_selection,
data)
return {
'doc_ids': data['form']['journal_ids'],
'doc_model': self.env['account.journal'],
'data': data,
'docs': self.env['account.journal'].browse(
data['form']['journal_ids']),
'time': time,
'lines': res,
'sum_credit': self._sum_credit,
'sum_debit': self._sum_debit,
'get_taxes': self._get_taxes,
}

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_journal_audit">
<t t-call="web.html_container">
<t t-call="web.internal_layout">
<t t-foreach="docs" t-as="o">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<div class="page">
<span t-esc="context_timestamp(datetime.datetime.now()).strftime('%Y-%m-%d %H:%M')"/>
<h2>
<t t-esc="o.name"/>
Journal
</h2>
<div class="row mt32">
<div class="col-3">
<strong>Company:</strong>
<p t-esc="env.company.name"/>
</div>
<div class="col-3">
<strong>Journal:</strong>
<p t-esc="o.name"/>
</div>
<div class="col-3">
<strong>Entries Sorted By:</strong>
<p t-if="data['form'].get('sort_selection') != 'l.date'">Journal Entry Number</p>
<p t-if="data['form'].get('sort_selection') == 'l.date'">Date</p>
</div>
<div class="col-3">
<strong>Target Moves:</strong>
<p t-if="data['form']['target_move'] == 'all'">All Entries</p>
<p t-if="data['form']['target_move'] == 'posted'">All Posted Entries</p>
</div>
</div>
<table class="table table-sm">
<thead>
<tr>
<th>Move</th>
<th>Date</th>
<th>Account</th>
<th>Partner</th>
<th>Label</th>
<th>Debit</th>
<th>Credit</th>
<th t-if="data['form']['amount_currency']">Currency</th>
</tr>
</thead>
<tbody>
<tr t-foreach="lines[o.id]" t-as="aml">
<td>
<span t-esc="aml.move_id.name != '/' and aml.move_id.name or ('*'+str(aml.move_id.id))"/>
</td>
<td>
<span t-field="aml.date"/>
</td>
<td>
<span t-field="aml.account_id.code"/>
</td>
<td>
<span t-esc="aml.sudo().partner_id and aml.sudo().partner_id.name and aml.sudo().partner_id.name[:23] or ''"/>
</td>
<td>
<span t-esc="aml.name and aml.name[:35]"/>
</td>
<td>
<span t-esc="aml.debit"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td>
<span t-esc="aml.credit"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td t-if="data['form']['amount_currency'] and aml.amount_currency">
<span t-esc="aml.amount_currency"
t-options="{'widget': 'monetary', 'display_currency': aml.currency_id}"/>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-4 pull-right">
<table class="table table-sm">
<tr>
<td>
<strong>Total</strong>
</td>
<td>
<span t-esc="sum_debit(data, o)"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td>
<span t-esc="sum_credit(data, o)"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</tr>
</table>
</div>
</div>
<div class="row">
<div class="col-4">
<table class="table table-sm table-reports">
<thead>
<tr>
<th colspan="3">Tax Declaration</th>
</tr>
<tr>
<th>Name</th>
<th>Base Amount</th>
<th>Tax Amount</th>
</tr>
</thead>
<tbody>
<t t-set="taxes" t-value="get_taxes(data, o)"/>
<tr t-foreach="taxes" t-as="tax">
<td>
<span t-esc="tax.name"/>
</td>
<td>
<span t-esc="taxes[tax]['base_amount']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td>
<span t-esc="taxes[tax]['tax_amount']"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<p style="page-break-after: always;"/>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import time
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportPartnerLedger(models.AbstractModel):
_name = 'report.base_accounting_kit.report_partnerledger'
_description = 'Partner Ledger Report'
def _lines(self, data, partner):
full_account = []
currency = self.env['res.currency']
query_get_data = self.env['account.move.line'].with_context(
data['form'].get('used_context', {}))._query_get()
reconcile_clause = "" if data['form'][
'reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL '
params = [partner.id, tuple(data['computed']['move_state']),
tuple(data['computed']['account_ids'])] + \
query_get_data[2]
query = """
SELECT "account_move_line".id, "account_move_line".date, j.code,
acc.name as a_name, "account_move_line".ref,
m.name as move_name, "account_move_line".name,
"account_move_line".debit, "account_move_line".credit,
"account_move_line".amount_currency,
"account_move_line".currency_id, c.symbol AS currency_code
FROM """ + query_get_data[0] + """
LEFT JOIN account_journal j ON ("account_move_line".journal_id = j.id)
LEFT JOIN account_account acc ON ("account_move_line".account_id = acc.id)
LEFT JOIN res_currency c ON ("account_move_line".currency_id=c.id)
LEFT JOIN account_move m ON (m.id="account_move_line".move_id)
WHERE "account_move_line".partner_id = %s
AND m.state IN %s
AND "account_move_line".account_id IN %s AND """ + \
query_get_data[1] + reconcile_clause + """
ORDER BY "account_move_line".date"""
self.env.cr.execute(query, tuple(params))
res = self.env.cr.dictfetchall()
sum = 0.0
lang_code = self.env.context.get('lang') or 'en_US'
lang = self.env['res.lang']
lang_id = lang._lang_get(lang_code)
date_format = lang_id.date_format
for r in res:
r['date'] = r['date']
r['displayed_name'] = '-'.join(
r[field_name] for field_name in ('move_name', 'ref', 'name')
if r[field_name] not in (None, '', '/')
)
sum += r['debit'] - r['credit']
r['progress'] = sum
r['currency_id'] = currency.browse(r.get('currency_id'))
full_account.append(r)
return full_account
def _sum_partner(self, data, partner, field):
if field not in ['debit', 'credit', 'debit - credit']:
return
result = 0.0
query_get_data = self.env['account.move.line'].with_context(
data['form'].get('used_context', {}))._query_get()
reconcile_clause = "" if data['form'][
'reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL '
params = [partner.id, tuple(data['computed']['move_state']),
tuple(data['computed']['account_ids'])] + \
query_get_data[2]
query = """SELECT sum(""" + field + """)
FROM """ + query_get_data[0] + """, account_move AS m
WHERE "account_move_line".partner_id = %s
AND m.id = "account_move_line".move_id
AND m.state IN %s
AND account_id IN %s
AND """ + query_get_data[1] + reconcile_clause
self.env.cr.execute(query, tuple(params))
contemp = self.env.cr.fetchone()
if contemp is not None:
result = contemp[0] or 0.0
return result
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(_("Form content is missing, this report cannot be printed."))
data['computed'] = {}
obj_partner = self.env['res.partner']
query_get_data = self.env['account.move.line'].with_context(
data['form'].get('used_context', {}))._query_get()
# move state
data['computed']['move_state'] = ['draft', 'posted']
if data['form'].get('target_move', 'all') == 'posted':
data['computed']['move_state'] = ['posted']
# account types
result_selection = data['form'].get('result_selection', 'customer')
if result_selection == 'supplier':
data['computed']['ACCOUNT_TYPE'] = ['liability_payable']
elif result_selection == 'customer':
data['computed']['ACCOUNT_TYPE'] = ['asset_receivable']
else:
data['computed']['ACCOUNT_TYPE'] = ['liability_payable', 'asset_receivable']
# fetch account ids
self.env.cr.execute("""
SELECT a.id
FROM account_account a
WHERE a.account_type IN %s
AND a.active""", # ✅ changed here
(tuple(data['computed']['ACCOUNT_TYPE']),)
)
data['computed']['account_ids'] = [a for (a,) in self.env.cr.fetchall()]
# prevent empty tuple issue
account_ids = tuple(data['computed']['account_ids']) or (0,)
params = [tuple(data['computed']['move_state']), account_ids] + query_get_data[2]
reconcile_clause = "" if data['form']['reconciled'] else \
' AND "account_move_line".full_reconcile_id IS NULL '
query = """
SELECT DISTINCT "account_move_line".partner_id
FROM """ + query_get_data[0] + """, account_account AS account, account_move AS am
WHERE "account_move_line".partner_id IS NOT NULL
AND "account_move_line".account_id = account.id
AND am.id = "account_move_line".move_id
AND am.state IN %s
AND "account_move_line".account_id IN %s
AND account.active
AND """ + query_get_data[1] + reconcile_clause # ✅ changed here
self.env.cr.execute(query, tuple(params))
partner_ids = [res['partner_id'] for res in self.env.cr.dictfetchall()]
partners = obj_partner.browse(partner_ids)
partners = sorted(partners, key=lambda x: (x.ref or '', x.name or ''))
return {
'doc_ids': partner_ids,
'doc_model': self.env['res.partner'],
'data': data,
'docs': partners,
'time': time,
'lines': self._lines,
'sum_partner': self._sum_partner,
}

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_partnerledger">
<t t-call="web.html_container">
<t t-call="web.internal_layout">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<div class="page">
<h2>Partner Ledger</h2>
<div class="row">
<div class="col-3">
<strong>Company:</strong>
<p t-esc="env.company.name"/>
</div>
<div class="col-3">
<t t-if="data['form']['date_from']">
<strong>Date from :</strong>
<span t-esc="data['form']['date_from']"/>
<br/>
</t>
<t t-if="data['form']['date_to']">
<strong>Date to :</strong>
<span t-esc="data['form']['date_to']"/>
</t>
</div>
<div class="col-3">
<strong>Target Moves:</strong>
<p t-if="data['form']['target_move'] == 'all'">All Entries</p>
<p t-if="data['form']['target_move'] == 'posted'">All Posted Entries</p>
</div>
</div>
<table class="table table-sm table-reports">
<thead>
<tr>
<th>Date</th>
<th>JRNL</th>
<th>Ref</th>
<th>Debit</th>
<th>Credit</th>
<th>Balance</th>
<th t-if="data['form']['amount_currency']">Currency</th>
</tr>
</thead>
<t t-foreach="docs" t-as="o">
<tbody>
<tr>
<td colspan="3">
<strong t-esc="o.ref"/>
-
<strong t-esc="o.name"/>
</td>
<td class="text-right">
<strong t-esc="sum_partner(data, o, 'debit')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<strong t-esc="sum_partner(data, o, 'credit')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<strong t-esc="sum_partner(data, o, 'debit - credit')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</tr>
<tr t-foreach="lines(data, o)" t-as="line">
<td>
<span t-esc="line['date']"/>
</td>
<td>
<span t-esc="line['code']"/>
</td>
<td>
<span t-esc="line['displayed_name']"/>
</td>
<td class="text-end">
<span t-esc="line['debit']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="line['credit']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-esc="line['progress']"
t-options="{'widget': 'monetary',
'display_currency':
env.company.currency_id}"/>
</td>
<td class="text-end"
t-if="data['form']['amount_currency']">
<t t-if="line['currency_id']">
<span t-esc="line['amount_currency']"
t-options="{'widget': 'monetary',
'display_currency': line['currency_id']}"/>
</t>
</td>
</tr>
</tbody>
</t>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from _datetime import datetime
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportTax(models.AbstractModel):
_name = 'report.base_accounting_kit.report_tax'
_description = 'Tax Report'
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
return {
'data': data['form'],
'lines': self.get_lines(data.get('form')),
}
def _sql_from_amls_one(self):
sql = """SELECT "account_move_line".tax_line_id, COALESCE(SUM("account_move_line".debit-"account_move_line".credit), 0)
FROM %s
WHERE %s GROUP BY "account_move_line".tax_line_id"""
return sql
def _sql_from_amls_two(self):
sql = """SELECT r.account_tax_id, COALESCE(SUM("account_move_line".debit-"account_move_line".credit), 0)
FROM %s
INNER JOIN account_move_line_account_tax_rel r ON ("account_move_line".id = r.account_move_line_id)
INNER JOIN account_tax t ON (r.account_tax_id = t.id)
WHERE %s GROUP BY r.account_tax_id"""
return sql
def _compute_from_amls(self, options, taxes):
# compute the tax amount
sql = self._sql_from_amls_one()
tables, where_clause, where_params = self.env[
'account.move.line']._query_get()
query = sql % (tables, where_clause)
self.env.cr.execute(query, where_params)
results = self.env.cr.fetchall()
for result in results:
if result[0] in taxes:
taxes[result[0]]['tax'] = abs(result[1])
# compute the net amount
sql2 = self._sql_from_amls_two()
query = sql2 % (tables, where_clause)
self.env.cr.execute(query, where_params)
results = self.env.cr.fetchall()
for result in results:
if result[0] in taxes:
taxes[result[0]]['net'] = abs(result[1])
@api.model
def get_lines(self, options):
taxes = {}
for tax in self.env['account.tax'].search(
[('type_tax_use', '!=', 'none')]):
if tax.children_tax_ids:
for child in tax.children_tax_ids:
if child.type_tax_use != 'none':
continue
taxes[child.id] = {'tax': 0, 'net': 0, 'name': child.name,
'type': tax.type_tax_use}
else:
taxes[tax.id] = {'tax': 0, 'net': 0, 'name': tax.name,
'type': tax.type_tax_use}
if options['date_from'] and not options['date_to']:
self.with_context(date_from=options['date_from'],
strict_range=True)._compute_from_amls(options,
taxes)
elif options['date_to'] and not options['date_from']:
self.with_context(date_to=options['date_to'],
strict_range=True)._compute_from_amls(options,
taxes)
elif options['date_from'] and options['date_to']:
self.with_context(date_from=options['date_from'],
date_to=options['date_to'],
strict_range=True)._compute_from_amls(options,
taxes)
else:
date_to = str(datetime.today().date())
self.with_context(date_to=date_to,
strict_range=True)._compute_from_amls(options,
taxes)
groups = dict((tp, []) for tp in ['sale', 'purchase'])
for tax in taxes.values():
if tax['tax']:
groups[tax['type']].append(tax)
return groups

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_tax">
<t t-call="web.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-call="web.internal_layout">
<div class="page"><br/>
<h3>Tax Report</h3>
<div class="row">
<div class="col-3">
<strong>Company:</strong>
<p t-esc="env.company.name"/>
</div>
<div>
<t t-if="data['date_from']">
<strong>Date from :</strong>
<span t-esc="data['date_from']"/>
</t>
<br/>
<t t-if="data['date_to']">
<strong>Date to :</strong>
<span t-esc="data['date_to']"/>
</t>
</div>
</div>
<table class="table table-sm table-reports">
<thead>
<tr class="text-centre">
<th>Sale</th>
<th>Net</th>
<th>Tax</th>
</tr>
</thead>
<tr align="left" t-foreach="lines['sale']" t-as="line">
<td>
<span t-esc="line.get('name')"/>
</td>
<td>
<span t-att-style="style" t-esc="line.get('net')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td>
<span t-att-style="style" t-esc="line.get('tax')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</tr>
<br/>
<tr align="left">
<td>
<strong>Purchase</strong>
</td>
<td/>
<td/>
</tr>
<tr align="left" t-foreach="lines['purchase']" t-as="line">
<td>
<span t-esc="line.get('name')"/>
</td>
<td>
<span t-att-style="style" t-esc="line.get('net')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
<td>
<span t-att-style="style" t-esc="line.get('tax')"
t-options="{'widget': 'monetary', 'display_currency': env.company.currency_id}"/>
</td>
</tr>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import time
from odoo import api, models, _
from odoo.exceptions import UserError
class ReportTrialBalance(models.AbstractModel):
_name = 'report.base_accounting_kit.report_trial_balance'
_description = 'Trial Balance Report'
def _get_accounts(self, accounts, display_account):
""" compute the balance, debit and credit for the provided accounts
:Arguments:
`accounts`: list of accounts record,
`display_account`: it's used to display either all accounts or those accounts which balance is > 0
:Returns a list of dictionary of Accounts with following key and value
`name`: Account name,
`code`: Account code,
`credit`: total amount of credit,
`debit`: total amount of debit,
`balance`: total amount of balance,
"""
account_result = {}
# Prepare sql query base on selected parameters from wizard
tables, where_clause, where_params = self.env[
'account.move.line']._query_get()
tables = tables.replace('"', '')
if not tables:
tables = 'account_move_line'
wheres = [""]
if where_clause.strip():
wheres.append(where_clause.strip())
filters = " AND ".join(wheres)
# compute the balance, debit and credit for the provided accounts
request = (
"SELECT account_id AS id, SUM(debit) AS debit, "
"SUM(credit) AS credit, (SUM(debit) - SUM(credit)) "
"AS balance" +
" FROM " + tables + " WHERE account_id IN %s " +
filters + " GROUP BY account_id")
params = (tuple(accounts.ids),) + tuple(where_params)
self.env.cr.execute(request, params)
for row in self.env.cr.dictfetchall():
account_result[row.pop('id')] = row
account_res = []
for account in accounts:
res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance'])
account_company = self.env.company
currency = (account.currency_id and account.currency_id or
account_company.currency_id)
res['code'] = account.code
res['name'] = account.name
if account.id in account_result:
res['debit'] = account_result[account.id].get('debit')
res['credit'] = account_result[account.id].get('credit')
res['balance'] = account_result[account.id].get('balance')
if display_account == 'all':
account_res.append(res)
if display_account == 'not_zero' and not currency.is_zero(
res['balance']):
account_res.append(res)
if display_account == 'movement' and (
not currency.is_zero(res['debit']) or not currency.is_zero(
res['credit'])):
account_res.append(res)
return account_res
@api.model
def _get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model'):
raise UserError(
_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(
self.env.context.get('active_ids', []))
display_account = data['form'].get('display_account')
accounts = docs if model == 'account.account' else self.env[
'account.account'].search([])
account_res = self.with_context(
data['form'].get('used_context'))._get_accounts(accounts,
display_account)
return {
'doc_ids': self.ids,
'doc_model': model,
'data': data['form'],
'docs': docs,
'time': time,
'Accounts': account_res,
}

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_trial_balance">
<t t-call="web.html_container">
<t t-set="data_report_margin_top" t-value="12"/>
<t t-set="data_report_header_spacing" t-value="9"/>
<t t-set="data_report_dpi" t-value="110"/>
<t t-call="web.internal_layout">
<div class="page"><br/>
<h2><span t-esc="env.company.name"/>: Trial Balance</h2>
<div class="row mt32">
<div class="col-4">
<strong>Display Account:</strong>
<p>
<span t-if="data['display_account'] == 'all'">
All accounts
</span>
<span t-if="data['display_account'] == 'movement'">
With movements
</span>
<span t-if="data['display_account'] == 'not_zero'">
With balance not equal to zero
</span>
</p>
</div>
<div class="col-4">
<p>
<t t-if="data['date_from']">
<strong>Date from :</strong>
<span t-esc="data['date_from']"/>
<br/>
</t>
<t t-if="data['date_to']">
<strong>Date to :</strong>
<span t-esc="data['date_to']"/>
</t>
</p>
</div>
<div class="col-4">
<strong>Target Moves:</strong>
<p>
<span t-if="data['target_move'] == 'all'">All
Entries
</span>
<span t-if="data['target_move'] == 'posted'">All
Posted Entries
</span>
</p>
</div>
</div>
<table class="table table-sm table-reports">
<thead>
<tr class="text-centre">
<th>Code</th>
<th>Account</th>
<th class="text-right">Debit</th>
<th class="text-right">Credit</th>
<th class="text-right">Balance</th>
</tr>
</thead>
<tbody>
<tr t-foreach="Accounts" t-as="account">
<td>
<span t-att-style="style" t-esc="account['code']"/>
</td>
<td>
<span style="color: white;" t-esc="'..'"/>
<span t-att-style="style" t-esc="account['name']"/>
</td>
<td class="text-end">
<span t-att-style="style"
t-esc="account['debit']"
t-options="{'widget': 'monetary',
'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-att-style="style"
t-esc="account['credit']"
t-options="{'widget': 'monetary',
'display_currency': env.company.currency_id}"/>
</td>
<td class="text-end">
<span t-att-style="style"
t-esc="account['balance']"
t-options="{'widget': 'monetary',
'display_currency': env.company.currency_id}"/>
</td>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Action for statement report -->
<record id="res_partner_action" model="ir.actions.report">
<field name="name">Statement Report</field>
<field name="model">res.partner</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.res_partner_statement_report_template</field>
<field name="report_file">base_accounting_kit.res_partner_statement_report_template</field>
<field name="print_report_name">'Statement Report- %s' %(object.name)</field>
<field name="binding_model_id" ref="model_res_partner"/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Statement report template -->
<template id="res_partner_statement_report_template">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div page="page">
<h3>Payment Statement Report</h3>
</div><br/>
<table border="0">
<tr><t t-esc="customer"/></tr><br/><br/>
<tr><t t-if="street"> <t t-esc="street"/></t></tr><br/>
<tr><t t-if="street2"> <t t-esc="street2"/></t></tr><br/>
<tr><t t-if="city"> <t t-esc="city"/></t></tr><br/>
<tr><t t-if="state"> <t t-esc="state"/></t></tr><br/>
</table>
<br/><br/>
<table class="table" style="align-items: center;">
<thead>
<tr>
<th>Date</th>
<th>Invoice/Bill Number</th>
<th>Due Date</th>
<th>Invoices/Debit</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
<t t-foreach="my_data" t-as="line">
<tr>
<td align="center"><t t-esc="line['invoice_date']"/></td>
<td align="center"><t t-esc="line['name']"/></td>
<td align="center"><t t-esc="line['invoice_date_due']"/></td>
<td align="center">
<t t-esc="currency"/>
<t t-esc="line['sub_total']"/>
</td>
<td align="center">
<t t-esc="currency"/>
<t t-esc="line['balance']"/>
</td>
</tr>
</t>
</tbody>
</table>
<br/>
<t t-if="total">
<div class="clearfix" name="so_total_summary">
<div id="total" class="row" name="total">
<div t-attf-class="#{'col-6' if report_type != 'html' else 'col-sm-7 col-md-6'} ms-auto">
<table class="table table-sm">
<tbody>
<tr>
<td>Total Amount:</td>
<td>
<t t-esc="currency"/>
<t t-esc="total"/>
</td>
</tr>
<tr>
<td>Total Balance:</td>
<td>
<t t-esc="currency"/>
<t t-esc="balance"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,49 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_financial_report_user,account_fin_rep_name_user,model_account_financial_report,account.group_account_user,1,1,1,1
access_financial_report_manager,account_fin_rep_name_manager,model_account_financial_report,base_accounting_kit.group_account_chief,1,1,1,1
access_account_followup_manager,account.followup.manager,model_account_followup,base_accounting_kit.group_account_chief,1,1,1,1
access_account_followup_user,account.followup.user,model_account_followup,account.group_account_user,1,1,1,1
access_followup_line,followup.line,model_followup_line,base_accounting_kit.group_account_chief,1,1,1,1
access_account_followup_line_user,account.followup.line.user,model_followup_line,account.group_account_user,1,1,1,1
access_account_asset_category,account.asset.category,model_account_asset_category,account.group_account_user,1,0,0,0
access_asset_modify_user,access_asset_modify_user,model_asset_modify,account.group_account_user,1,0,0,0
access_asset_modify_manager,access_asset_modify_manager,model_asset_modify,base_accounting_kit.group_account_chief,1,1,1,1
access_account_asset_asset,account.asset.asset,model_account_asset_asset,account.group_account_user,1,0,0,0
access_account_asset_category_manager,account.asset.category,model_account_asset_category,base_accounting_kit.group_account_chief,1,1,1,1
access_account_asset_asset_manager,account.asset.asset,model_account_asset_asset,base_accounting_kit.group_account_chief,1,1,1,1
access_account_asset_depreciation_line,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_user,1,0,0,0
access_account_asset_depreciation_line_manager,account.asset.depreciation.line,model_account_asset_depreciation_line,base_accounting_kit.group_account_chief,1,1,1,1
access_asset_asset_report,asset.asset.report,model_asset_asset_report,account.group_account_user,1,0,0,0
access_asset_asset_report_manager,asset.asset.report,model_asset_asset_report,base_accounting_kit.group_account_chief,1,1,1,1
access_account_asset_category_invoicing_payment,account.asset.category,model_account_asset_category,account.group_account_invoice,1,0,0,0
access_account_asset_asset_invoicing_payment,account.asset.asset,model_account_asset_asset,account.group_account_invoice,1,0,1,0
access_account_asset_depreciation_line_invoicing_payment,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_invoice,1,0,1,0
access_account_aged_trial_balance,access.account.aged.trial.balance,model_account_aged_trial_balance,account.group_account_user,1,1,1,1
access_account_account_bank_book_report,access.account.bank.book.report,model_account_bank_book_report,account.group_account_user,1,1,1,1
access_account_cash_book_report,access.account.cash.book.report,model_account_cash_book_report,account.group_account_user,1,1,1,1
access_account_day_book_report,access.account.day.book.report,model_account_day_book_report,account.group_account_user,1,1,1,1
access_account_common_partner_report,access.account.common.partner.report,model_account_common_partner_report,account.group_account_user,1,1,1,1
access_asset_depreciation_confirmation,access.asset.depreciation.confirmation,model_asset_depreciation_confirmation,account.group_account_user,1,1,1,1
access_cash_flow_report,access.cash.flow.report,model_cash_flow_report,account.group_account_user,1,1,1,1
access_financial_report,access.financial.report,model_financial_report,account.group_account_user,1,1,1,1
access_report_base_accounting_kit_report_financial,access.report.base_accounting_kit.report_financial,model_report_base_accounting_kit_report_financial,account.group_account_user,1,1,1,1
access_account_report_general_ledger,access.account.report.general.ledger,model_account_report_general_ledger,account.group_account_user,1,1,1,1
access_account_print_journal,access.account.print.journal,model_account_print_journal,account.group_account_user,1,1,1,1
access_account_report_partner_ledger,access.account.report.partner.ledger,model_account_report_partner_ledger,account.group_account_user,1,1,1,1
access_account_common_account_report,access.account.common.account.report,model_account_common_account_report,account.group_account_user,1,1,1,1
access_kit_account_tax_report,access.kit.account.tax.report,model_kit_account_tax_report,account.group_account_user,1,1,1,1
access_account_balance_report,access.account.balance.report,model_account_balance_report,account.group_account_user,1,1,1,1
access_multiple_invoice,multiple_invoice,model_multiple_invoice,base_accounting_kit.group_account_chief,1,1,1,1
access_multiple_invoice_layout,multiple_invoice_layout,model_multiple_invoice_layout,base_accounting_kit.group_account_chief,1,1,1,1
access_account_common_journal_report,account.common.journal.report,model_account_common_journal_report,account.group_account_user,1,1,1,1
access_account_account_type,account.account.type,model_account_account_type,account.group_account_user,1,1,1,1
access_account_lock_date,access.account.lock.date,model_account_lock_date,account.group_account_user,1,1,1,1
access_account_recurring_entries_line,access.account.recurring.entries.line,model_account_recurring_entries_line,account.group_account_user,1,1,1,1
access_generate_recurring_entries,generate.recurring.entries.user,model_account_recurring_payments,account.group_account_user,1,1,1,1
access_import_bank_statement_user,access.import.bank.statement.user,model_import_bank_statement,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_financial_report_user account_fin_rep_name_user model_account_financial_report account.group_account_user 1 1 1 1
3 access_financial_report_manager account_fin_rep_name_manager model_account_financial_report base_accounting_kit.group_account_chief 1 1 1 1
4 access_account_followup_manager account.followup.manager model_account_followup base_accounting_kit.group_account_chief 1 1 1 1
5 access_account_followup_user account.followup.user model_account_followup account.group_account_user 1 1 1 1
6 access_followup_line followup.line model_followup_line base_accounting_kit.group_account_chief 1 1 1 1
7 access_account_followup_line_user account.followup.line.user model_followup_line account.group_account_user 1 1 1 1
8 access_account_asset_category account.asset.category model_account_asset_category account.group_account_user 1 0 0 0
9 access_asset_modify_user access_asset_modify_user model_asset_modify account.group_account_user 1 0 0 0
10 access_asset_modify_manager access_asset_modify_manager model_asset_modify base_accounting_kit.group_account_chief 1 1 1 1
11 access_account_asset_asset account.asset.asset model_account_asset_asset account.group_account_user 1 0 0 0
12 access_account_asset_category_manager account.asset.category model_account_asset_category base_accounting_kit.group_account_chief 1 1 1 1
13 access_account_asset_asset_manager account.asset.asset model_account_asset_asset base_accounting_kit.group_account_chief 1 1 1 1
14 access_account_asset_depreciation_line account.asset.depreciation.line model_account_asset_depreciation_line account.group_account_user 1 0 0 0
15 access_account_asset_depreciation_line_manager account.asset.depreciation.line model_account_asset_depreciation_line base_accounting_kit.group_account_chief 1 1 1 1
16 access_asset_asset_report asset.asset.report model_asset_asset_report account.group_account_user 1 0 0 0
17 access_asset_asset_report_manager asset.asset.report model_asset_asset_report base_accounting_kit.group_account_chief 1 1 1 1
18 access_account_asset_category_invoicing_payment account.asset.category model_account_asset_category account.group_account_invoice 1 0 0 0
19 access_account_asset_asset_invoicing_payment account.asset.asset model_account_asset_asset account.group_account_invoice 1 0 1 0
20 access_account_asset_depreciation_line_invoicing_payment account.asset.depreciation.line model_account_asset_depreciation_line account.group_account_invoice 1 0 1 0
21 access_account_aged_trial_balance access.account.aged.trial.balance model_account_aged_trial_balance account.group_account_user 1 1 1 1
22 access_account_account_bank_book_report access.account.bank.book.report model_account_bank_book_report account.group_account_user 1 1 1 1
23 access_account_cash_book_report access.account.cash.book.report model_account_cash_book_report account.group_account_user 1 1 1 1
24 access_account_day_book_report access.account.day.book.report model_account_day_book_report account.group_account_user 1 1 1 1
25 access_account_common_partner_report access.account.common.partner.report model_account_common_partner_report account.group_account_user 1 1 1 1
26 access_asset_depreciation_confirmation access.asset.depreciation.confirmation model_asset_depreciation_confirmation account.group_account_user 1 1 1 1
27 access_cash_flow_report access.cash.flow.report model_cash_flow_report account.group_account_user 1 1 1 1
28 access_financial_report access.financial.report model_financial_report account.group_account_user 1 1 1 1
29 access_report_base_accounting_kit_report_financial access.report.base_accounting_kit.report_financial model_report_base_accounting_kit_report_financial account.group_account_user 1 1 1 1
30 access_account_report_general_ledger access.account.report.general.ledger model_account_report_general_ledger account.group_account_user 1 1 1 1
31 access_account_print_journal access.account.print.journal model_account_print_journal account.group_account_user 1 1 1 1
32 access_account_report_partner_ledger access.account.report.partner.ledger model_account_report_partner_ledger account.group_account_user 1 1 1 1
33 access_account_common_account_report access.account.common.account.report model_account_common_account_report account.group_account_user 1 1 1 1
34 access_kit_account_tax_report access.kit.account.tax.report model_kit_account_tax_report account.group_account_user 1 1 1 1
35 access_account_balance_report access.account.balance.report model_account_balance_report account.group_account_user 1 1 1 1
36 access_multiple_invoice multiple_invoice model_multiple_invoice base_accounting_kit.group_account_chief 1 1 1 1
37 access_multiple_invoice_layout multiple_invoice_layout model_multiple_invoice_layout base_accounting_kit.group_account_chief 1 1 1 1
38 access_account_common_journal_report account.common.journal.report model_account_common_journal_report account.group_account_user 1 1 1 1
39 access_account_account_type account.account.type model_account_account_type account.group_account_user 1 1 1 1
40 access_account_lock_date access.account.lock.date model_account_lock_date account.group_account_user 1 1 1 1
41 access_account_recurring_entries_line access.account.recurring.entries.line model_account_recurring_entries_line account.group_account_user 1 1 1 1
42 access_generate_recurring_entries generate.recurring.entries.user model_account_recurring_payments account.group_account_user 1 1 1 1
43 access_import_bank_statement_user access.import.bank.statement.user model_import_bank_statement base.group_user 1 1 1 1

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record model="res.groups" id="group_account_dashboard">
<field name="name">Access to Accounting Dashboard</field>
</record>
<record id="account_asset_category_multi_company_rule" model="ir.rule">
<field name="name">Account Asset Category multi-company</field>
<field ref="model_account_asset_category" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
</field>
</record>
<record id="account_asset_asset_multi_company_rule" model="ir.rule">
<field name="name">Account Asset multi-company</field>
<field ref="model_account_asset_asset" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
</field>
</record>
<record id="account.group_account_invoice" model="res.groups">
<field name="name">Invoicing</field>
<field name="sequence">1</field>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="privilege_id" ref="account.res_groups_privilege_accounting"/>
</record>
<record id="account.group_account_user" model="res.groups">
<field name="name">Accountant</field>
<field name="sequence">2</field>
<field name="implied_ids"
eval="[(4, ref('account.group_account_invoice'))]"/>
<field name="privilege_id" ref="account.res_groups_privilege_accounting"/>
</record>
<record id="group_account_chief" model="res.groups">
<field name="name">Chief Accountant</field>
<field name="sequence">51</field>
<field name="implied_ids" eval="[(4, ref('account.group_account_invoice')), (4, ref('account.group_account_user')),(4, ref('account.group_account_manager'))]"/>
<field name="privilege_id" ref="account.res_groups_privilege_accounting"/>
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<record id="account.group_account_manager" model="res.groups">
<field name="name">Administrator</field>
<field name="sequence">52</field>
<field name="comment">Full access, including configuration rights.</field>
<field name="privilege_id" ref="account.res_groups_privilege_accounting"/>
<field name="implied_ids" eval="[(4, ref('account.group_account_invoice')),(4, ref('base_accounting_kit.group_account_chief'))]"/>
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Asset Form View -->
<record model="ir.ui.view" id="account_asset_asset_view_form">
<field name="name">account.asset.asset.view.form</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<form string="Asset">
<header>
<button name="validate" invisible="state != 'draft'" string="Confirm" type="object"
class="oe_highlight"/>
<button type="object" name="compute_depreciation_board" string="Compute Depreciation"
invisible="state != 'draft'"/>
<button name="set_to_close" invisible="state != 'open'" string="Sell or Dispose" type="object"
class="oe_highlight"/>
<button name="action_save_model" invisible="state != 'open' or category_id" string="Save as model" type="object"
/>
<button name="action_cancel_assets" string="Cancel Asset" type="object"
invisible="state != 'open'"/>
<button name="set_to_draft" string="Set to Draft" type="object"
invisible="state != 'cancelled'"/>
<button name="%(action_asset_modify)d" invisible="state != 'open'" string="Modify Depreciation"
type="action"/>
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="open_entries" type="object" icon="fa-pencil">
<field string="Items" name="entry_count" widget="statinfo"/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Laptop iBook"
readonly="state != 'draft'"/>
</h1>
</div>
<group>
<group>
<field name="category_id" domain="[('type', '=', 'purchase')]"
context="{'default_type': 'purchase'}"
help="Category of asset" readonly="state != 'draft'"/>
<field name="code" readonly="state != 'draft'"/>
<field name="date" help="Date of asset"/>
<field name="type" invisible="1"/>
</group>
<group>
<field name="currency_id" groups="base.group_multi_currency" readonly="state != 'draft'"/>
<field name="company_id" options="{'no_create': True}"
groups="base.group_multi_company" readonly="state != 'draft'"/>
<field name="value" widget="monetary"
options="{'currency_field': 'currency_id'}"
help="Gross value of asset"
readonly="state != 'draft'"/>
<field name="salvage_value" widget="monetary"
options="{'currency_field': 'currency_id'}"
readonly="state != 'draft'"
invisible="type in 'sale'"/>
<field name="value_residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="partner_id" string="Vendor" readonly="state != 'draft'"
domain="[('supplier_rank', '>', 0)]"/>
<field name="invoice_id" string="Invoice"
options="{'no_create': True}"
readonly="state != 'draft'"/>
</group>
</group>
<notebook colspan="4">
<page string="Assets">
<group>
<group>
<field name="type" invisible="1"/>
<field name="company_id" options="{'no_create': True}" readonly="state != 'draft'"/>
</group>
<group string="Journal Entries">
<field name="journal_id" readonly="state != 'draft'"/>
<div>
<label for="account_asset_id" invisible="type != 'purchase'"/>
<label for="account_asset_id" string="Deferred Revenue Account"
invisible="type != 'sale'"/>
</div>
<field name="account_asset_id" nolabel="1" invisible="not type" readonly="state != 'draft'"/>
<div>
<label for="account_depreciation_id" invisible="type != 'purchase'"/>
<label for="account_depreciation_id" string="Recognition Income Account"
invisible="type != 'sale'"/>
</div>
<field name="account_depreciation_id" nolabel="1" readonly="state != 'draft'"/>
<div>
<label for="account_depreciation_expense_id" invisible="type != 'purchase'"/>
<label for="account_depreciation_expense_id" string="Recognition Account"
invisible="type != 'sale'"/>
</div>
<field name="account_depreciation_expense_id" nolabel="1" readonly="state != 'draft'"/>
<field name="account_analytic_id" readonly="state != 'draft'"/>
</group>
<group string="Periodicity">
<field name="method_time" string="Time Method Based On" widget="radio"
invisible="type != 'purchase'" readonly="state != 'draft'"/>
<field name="method_number" string="Number of Entries"
invisible="method_time != 'number' and not type"
required="method_time in 'number'" readonly="state != 'draft'"/>
<label for="method_period" string="One Entry Every"/>
<div>
<field name="method_period" nolabel="1" invisible="not type" class="oe_inline" readonly="state != 'draft'"/>
months
</div>
<field name="method_end"
required="method_time in 'end'"
invisible="method_time != 'end'" readonly="state != 'draft'"/>
</group>
<group string="Additional Options">
<field name="open_asset" readonly="state != 'draft'"/>
<field name="group_entries" readonly="state != 'draft'"/>
</group>
<group invisible="type in 'sale'" string="Depreciation Method">
<field name="method" readonly="state != 'draft'"/>
<field name="method_progress_factor"
invisible="method in 'linear'"
required="method in 'degressive'" widget="percentage" readonly="state != 'draft'"/>
<field name="prorata"
invisible="method in 'end'" readonly="state != 'draft'"/>
</group>
</group>
</page>
<page string="Depreciation Board" invisible="not depreciation_line_ids">
<field name="depreciation_line_ids" mode="list"
readonly="state not in ['draft', 'open']"
options="{'reload_whole_on_button': true}">
<list string="Depreciation Lines" decoration-info="(move_check == False)" create="0"
editable="top">
<field name="depreciation_date" readonly="1"/>
<field name="depreciated_value" readonly="1"/>
<field name="amount" widget="monetary" string="Depreciation" readonly="1"/>
<field name="remaining_value" readonly="1" widget="monetary" string="Residual"/>
<field name="move_check" widget="deprec_lines_toggler"
invisible="parent_state != 'open'"/>
<field name="move_posted_check" invisible="1"/>
<field name="parent_state" invisible="1"/>
</list>
<form string="Depreciation Lines">
<group>
<group>
<field name="parent_state" invisible="1"/>
<field name="name"/>
<field name="sequence"/>
<field name="move_id"/>
<field name="move_check"/>
<field name="parent_state" invisible="1"/>
</group>
<group>
<field name="amount" widget="monetary"/>
<field name="depreciation_date"/>
<field name="depreciated_value"/>
<field name="remaining_value"/>
</group>
</group>
</form>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!--Asset Kanban View-->
<record model="ir.ui.view" id="account_asset_asset_view_kanban">
<field name="name">account.asset.asset.view.kanban</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="category_id"/>
<field name="date"/>
<field name="state"/>
<templates>
<t t-name="card">
<div t-attf-class="oe_kanban_global_click">
<div class="row mb4">
<div class="col-xs-6">
<strong>
<span>
<t t-esc="record.name.value"/>
</span>
</strong>
</div>
<div class="col-xs-6 pull-right text-right">
<strong>
<t t-esc="record.date.value"/>
</strong>
</div>
</div>
<div class="row">
<div class="col-xs-6 text-muted">
<span>
<t t-esc="record.category_id.value"/>
</span>
</div>
<div class="col-xs-6">
<span class="pull-right text-right">
<field name="state" widget="label_selection"
options="{'classes': {'draft': 'primary', 'open': 'success', 'close': 'default'}}"/>
</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!--Asset Tree View-->
<record model="ir.ui.view" id="account_asset_asset_view_tree">
<field name="name">account.asset.asset.view.tree</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<list string="Assets" decoration-info="(state == 'draft')" decoration-muted="(state == 'close')">
<field name="name"/>
<field name="category_id"/>
<field name="date"/>
<field name="partner_id" string="Vendor"/>
<field name="value"/>
<field name="value_residual" widget="monetary"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="state"/>
</list>
</field>
</record>
<!--Asset Search View-->
<record id="account_asset_asset_view_search" model="ir.ui.view">
<field name="name">account.asset.asset.view.search</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<search string="Asset Account">
<field name="name" string="Asset"/>
<field name="date"/>
<filter name="state" string="Current" domain="[('state','in', ('draft','open'))]"
help="Assets in draft and open states"/>
<filter name="state" string="Closed" domain="[('state','=', 'close')]" help="Assets in closed state"/>
<field name="category_id"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<group>
<filter name="date" string="Month" domain="[]" context="{'group_by':'date'}"/>
<filter name="category_id" string="Category" domain="[]" context="{'group_by':'category_id'}"/>
</group>
</search>
</field>
</record>
<!--Asset Action-->
<record model="ir.actions.act_window" id="action_account_asset_asset_form">
<field name="name">Assets</field>
<field name="res_model">account.asset.asset</field>
<field name="view_mode">list,kanban,form</field>
<field name="view_id" ref="account_asset_asset_view_tree"/>
<field name="domain">[('category_id.type', '=', 'purchase')]</field>
</record>
<!--Asset MenuItem-->
<menuitem parent="account.menu_finance_entries"
id="management_menu"
name="Management"
sequence="101" groups="account.group_account_user"/>
<menuitem parent="base_accounting_kit.management_menu"
id="menu_action_account_asset_asset_form"
action="action_account_asset_asset_form"
sequence="101" groups="account.group_account_user"/>
<!-- <menuitem parent="base_accounting_kit.management_menu"-->
<!-- id="menu_act_budget_view"-->
<!-- name="Budgets"-->
<!-- action="base_account_budget.act_budget_view" sequence="60"-->
<!-- groups="account.group_account_user"/>-->
<!-- Configuration -->
<menuitem id="menu_finance_config_assets"
name="Assets and Revenues"
parent="account.menu_finance_configuration"
sequence="25"/>
</odoo>

View File

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Asset Category Form View -->
<record model="ir.ui.view" id="account_asset_category_view_form">
<field name="name">account.asset.category.view.form</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<form string="Asset category">
<sheet>
<group>
<div class="oe_title">
<label for="name" string="Asset Type"
class="oe_edit_only"
invisible="type != 'purchase'"/>
<label for="name" string="Deferred Revenue Type"
class="oe_edit_only"
invisible="type in 'purchase'"/>
<h1>
<field name="name" placeholder="e.g. Computers"/>
</h1>
</div>
<group>
<field name="type" invisible="1"/>
<field name="company_id" options="{'no_create': True}"/>
<field name="price"/>
</group>
<group string="Journal Entries">
<field name="journal_id"/>
<div>
<label for="account_asset_id" invisible="type != 'purchase'"/>
<label for="account_asset_id" string="Deferred Revenue Account" invisible="type != 'sale'"/>
</div>
<field name="account_asset_id" nolabel="1" invisible="not type"/>
<div>
<label for="account_depreciation_id" invisible="type != 'purchase'"/>
<label for="account_depreciation_id" string="Recognition Income Account" invisible="type != 'sale'"/>
</div>
<field name="account_depreciation_id" nolabel="1" />
<div>
<label for="account_depreciation_expense_id" invisible="type != 'purchase'"/>
<label for="account_depreciation_expense_id" string="Recognition Account" invisible="type != 'sale'"/>
</div>
<field name="account_depreciation_expense_id" nolabel="1"/>
<field name="account_analytic_id"/>
</group>
<group string="Periodicity">
<field name="method_time" string="Time Method Based On" widget="radio" invisible="type != 'purchase'"/>
<field name="method_number" string="Number of Entries"
invisible="method_time != 'number' and not type"
required="method_time in 'number'"/>
<label for="method_period" string="One Entry Every"/>
<div>
<field name="method_period" nolabel="1" invisible="not type" class="oe_inline"/>
months
</div>
<field name="method_end"
required="method_time in 'end'"
invisible="method_time != 'end'"/>
</group>
<group string="Additional Options">
<field name="open_asset"/>
<field name="group_entries"/>
</group>
<group invisible="type in 'sale'" string="Depreciation Method">
<field name="method"/>
<field name="method_progress_factor"
invisible="method in 'linear'"
required="method in 'degressive'" widget="percentage"/>
<field name="prorata"
invisible="method in 'end'"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Asset Category Kanban View -->
<record id="account_asset_category_view_kanban" model="ir.ui.view">
<field name="name">account.asset.category.view.kanban</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="journal_id"/>
<field name="method"/>
<templates>
<t t-name="card">
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="row mb4">
<div class="col-xs-6">
<strong><span><t t-esc="record.name.value"/></span></strong>
</div>
<div class="col-xs-6 text-right">
<span class="badge"><strong><t t-esc="record.method.value"/></strong></span>
</div>
</div>
<div> <t t-esc="record.journal_id.value"/></div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Asset Category Tree View -->
<record model="ir.ui.view" id="account_asset_category_view_tree">
<field name="name">account.asset.category.view.tree</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<list string="Asset category">
<field name="name"/>
<field name="journal_id"/>
<field name="method"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</field>
</record>
<!-- Asset Category Search View -->
<record model="ir.ui.view" id="account_asset_category_view_search">
<field name="name">account.asset.category.view.search</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<search string="Search Asset Category">
<filter name="type" string="Sales" domain="[('type','=', 'sale')]" help="Deferred Revenues"/>
<filter name="type" string="Purchase" domain="[('type','=', 'purchase')]" help="Assets"/>
<field name="name" string="Category"/>
<field name="journal_id"/>
<group>
<filter name="type" string="Type" domain="[]" context="{'group_by':'type'}"/>
</group>
</search>
</field>
</record>
<!-- Asset Category Action -->
<record model="ir.actions.act_window" id="action_account_asset_asset_list_normal_purchase">
<field name="name">Asset Types</field>
<field name="res_model">account.asset.category</field>
<field name="domain">[('type', '=', 'purchase')]</field>
<field name="view_mode">list,kanban,form</field>
<field name="context">{'default_type': 'purchase'}</field>
</record>
<!-- Asset Category MenuItem -->
<menuitem parent="account.account_account_menu"
id="menu_action_account_asset_asset_list_normal_purchase"
action="action_account_asset_asset_list_normal_purchase"
sequence="6"/>
</odoo>

View File

@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Account Bank Statement Line Search View-->
<record id="account_bank_statement_line_view_search" model="ir.ui.view">
<field name="name">account.bank.statement.line.view.search</field>
<field name="model">account.bank.statement.line</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<search>
<field name="name" string="Transaction"/>
<field name="payment_ref"/>
<field name="date"/>
<field name="statement_id"/>
<field name="partner_id"/>
<field name="journal_id"/>
<field name="narration" string="Notes"/>
<field name="amount"/>
<field name="move_id" string="Journal Entry"/>
<separator/>
<filter name="deposits" string="Deposits"
domain="[('amount','>',0.0)]"/>
<filter name="payments" string="Payments"
domain="[('amount','&lt;',0.0)]"/>
<separator/>
<filter name="no_statement" string="No Statement"
domain="[('statement_id','=',False)]"/>
<filter name="invalid_statement" string="Invalid Statement"
domain="[('statement_complete','=',False)]"/>
<separator/>
<filter name="matched" string="Matched"
domain="[('is_reconciled','=',True)]"/>
<filter name="not_matched" string="Not Matched"
domain="[('is_reconciled','=',False)]"/>
<filter name="to_check" string="To Check"
domain="[('to_check','=',True)]"/>
<separator/>
<filter name="date" string="Date" date="date"/>
</search>
</field>
</record>
<!-- Kanban view on statement line injected inside the form (view_bank_reconcile_widget) -->
<record id="account_bank_statement_line_view_kanban" model="ir.ui.view">
<field name="name">account.bank.statement.line.view.kanban</field>
<field name="model">account.bank.statement.line</field>
<field name="arch" type="xml">
<kanban js_class="custom_kanban"
on_create="base_accounting_kit.action_bank_statement_line_form_bank_reconcile_widget">
<field name="id" invisible="1"/>
<field name="state" invisible="1"/>
<field name="statement_complete" invisible="1"/>
<field name="statement_valid" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="journal_id" invisible="1"/>
<field name="statement_id" invisible="1"/>
<field name="is_reconciled" invisible="1"/>
<field name="to_check" invisible="1"/>
<field name="partner_bank_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="foreign_currency_id" invisible="1"/>
<field name="statement_line_id" invisible="1"/>
<field name="lines_widget_json" invisible="1"/>
<templates>
<t t-name="card">
<div t-attf-class="oe_kanban_card oe_kanban_global_click o_bank_reconcile_st_line_kanban_card"
t-att-data-oe-id="record.id">
<t t-set="text_amount_class" t-value="record.amount &lt; 0.0 and 'text-danger'"/>
<!-- Statement Button -->
<div t-if="!record.statement_complete.raw_value" class="statement_button text-center mb-2">
<a role="button"
class="btn btn-secondary btn-sm statement"
tabindex="-1" type="action"
name="%(base_accounting_kit.action_bank_statement_create_form_bank_reconcile_widget)d">
Statement
</a>
</div>
<!-- Main Content -->
<div class="container-fluid p-0">
<!-- First Row -->
<div class="row g-0 align-items-center mb-1">
<div class="col-auto pe-2">
<strong class="bold_date">
<field name="date" class="d-inline"/>
</strong>
</div>
<div class="col text-truncate">
<em>
<field name="partner_id" class="d-inline"/>
</em>
</div>
<div t-if="record.statement_id.raw_value" class="col-auto ms-auto">
<div t-attf-class="text-truncate {{!(record.statement_complete.raw_value and record.statement_valid.raw_value) and 'text-danger' or ''}}">
<field name="statement_id" class="d-inline"/>
</div>
</div>
<div class="col-auto">
<strong t-att-class="text_amount_class">
<field name="amount" class="d-inline"/>
</strong>
</div>
</div>
<!-- Second Row -->
<div class="row g-0 align-items-center">
<div class="col text-truncate pe-2">
<field name="payment_ref" class="d-inline"/>
</div>
<div class="col-auto">
<div class="o_field_many2manytags o_field_widget d-flex flex-wrap gap-1">
<span t-if="record.to_check.raw_value"
class="badge text-bg-warning fw-normal to_check">
To Check
</span>
<div t-if="record.is_reconciled.raw_value"
class="badge text-bg-success fw-normal">
<i class="fa fa-check me-1"></i>
Matched
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Account Bank Statement Line Tree View -->
<record id="account_bank_statement_line_view_tree" model="ir.ui.view">
<field name="name">account.bank.statement.line.view.tree</field>
<field name="model">account.bank.statement.line</field>
<field name="arch" type="xml">
<list editable="top">
<field name="statement_complete" column_invisible="1"/>
<field name="statement_valid" column_invisible="1"/>
<field name="state" column_invisible="1"/>
<field name="is_reconciled" column_invisible="1"/>
<field name="to_check" column_invisible="1"/>
<field name="country_code" column_invisible="1"/>
<field name="currency_id" column_invisible="1"/>
<field name="sequence" widget="handle" column_invisible="1"/>
<field name="date"
readonly="is_reconciled == True and amount != 0"
options="{'datepicker':{'warn_future':true}}"/>
<field name="payment_ref" string="Label"/>
<field name="partner_id" string="Partner"/>
<field name="amount"
readonly="is_reconciled == True and amount != 0"/>
<field name="running_balance" string="Running Balance"/>
<field name="statement_id" string="Statement"/>
</list>
</field>
</record>
<!-- Account Bank Statement Line Form View -->
<record id="account_bank_statement_line_view_form"
model="ir.ui.view">
<field name="name">account.bank.statement.line.view.form</field>
<field name="model">account.bank.statement.line</field>
<field name="arch" type="xml">
<form string="Add a Transaction">
<field name="state" invisible="1"/>
<field name="statement_complete" invisible="1"/>
<field name="statement_valid" invisible="1"/>
<field name="is_reconciled" invisible="1"/>
<field name="suitable_journal_ids" invisible="1"/>
<field name="currency_id" invisible="1"/>
<group>
<group>
<field name="date"
readonly="is_reconciled == True and amount != 0"/>
<field name="payment_ref" required="1"
readonly="is_reconciled == True and amount != 0"/>
<field name="partner_id"
readonly="is_reconciled == True and amount != 0"/>
</group>
<group>
<field name="amount"
readonly="is_reconciled == True and amount != 0"/>
<field name="foreign_currency_id"
groups="base.group_multi_currency"
domain="[('id', '!=', currency_id)]"
options="{'no_open': True, 'no_create': True}"
readonly="is_reconciled == True and amount != 0"/>
<field name="amount_currency"
groups="base.group_multi_currency"
invisible="foreign_currency_id == False"
readonly="is_reconciled == True and amount != 0"/>
<field name="journal_id"
domain="[('type', 'in', ['bank', 'cash'])]"
readonly="context.get('default_journal_id') and is_reconciled == True and amount != 0"/>
</group>
</group>
</form>
</field>
</record>
<!-- Action Account Statement Line -->
<record id="action_bank_statement_line_form_bank_reconcile_widget"
model="ir.actions.act_window">
<field name="name">Add a Transaction</field>
<field name="res_model">account.bank.statement.line</field>
<field name="view_mode">form</field>
<field name="view_id"
ref="account_bank_statement_line_view_form"/>
<field name="target">new</field>
</record>
<!--Account Bank Statement Line Form View-->
<record id="view_bank_reconcile_widget_form" model="ir.ui.view">
<field name="name">account.bank.statement.line.view.form</field>
<field name="model">account.bank.statement.line</field>
<field name="arch" type="xml">
<form class="form_bank">
<field name="to_check" invisible="1"/>
<field name="state" invisible="1"/>
<field name="bank_state" invisible="1"/>
<div class="o_bank_reconcile_stats_buttons">
<div class="o_statusbar_buttons o_bank_reconcile_status_buttons_aside_left">
<button name="button_validation"
string="Validate" type="object"
class="btn btn-primary" id="validationButton"
invisible="bank_state != 'valid'"/>
<button string="Validate"
class="btn btn-secondary text-muted"
invisible="bank_state != 'invalid'"/>
<button name="button_reset"
invisible="bank_state != 'reconciled'"
string="Reset" type="object"/>
<button name="button_to_check"
string="To Check" type="object"
class="btn btn-secondary"
invisible="to_check == True"/>
<button name="button_set_as_checked"
string="Set as Checked"
type="object"
invisible="to_check == False"/>
</div>
<div class="bank_reconcile_models"
style="margin-left: 880px;margin-top: 10px;"
invisible="bank_state not in ('valid','invalid')">
</div>
</div>
<field name="lines_widget"
widget="bank_reconcile_widget_lines_widget"
class="w-100"/>
<notebook name="bank_reconcile_widget_notebook">
<page string="Match Existing Entries"
invisible="bank_state == 'reconciled'">
<widget name="form_list_view"
resModel="account.move.line"/>
</page>
<page name="manual_operations_tab"
string="Manual Operations"
invisible="bank_state == 'reconciled'">
<group>
<group>
<field name="partner_id"
string="Partner"/>
<field name='account_id' string="Account"/>
<field name="tax_ids" string="Taxes"
widget="many2many_tags"/>
<field name="analytic_distribution"
widget="analytic_distribution"/>
</group>
<group>
<field name="payment_ref" string="Label"
id="payment_form"/>
<field name="amount" string="Amount"/>
<div class="d-flex">
<span class="ml4 mr4">in</span>
<field name="company_currency_id"
style="width:120px"/>
</div>
</group>
</group>
</page>
<page name="discuss_page" string="Discuss">
<div class="bank_reconcile_widget_discuss_anchor">
<chatter/>
</div>
</page>
</notebook>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Account Bank Statement Form View -->
<record id="account_bank_statement_view_form"
model="ir.ui.view">
<field name="name">account.bank.statement.view.form</field>
<field name="model">account.bank.statement</field>
<field name="priority">100</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" required="1"/>
<field name="balance_start"/>
<field name="balance_end_real"/>
<field name="attachment_ids" widget="many2many_binary"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- Action Account Bank Statement -->
<record id="action_bank_statement_create_form_bank_reconcile_widget"
model="ir.actions.act_window">
<!-- Binding removed since the same can be achieved with multi-edit -->
<field name="name">Create Statement</field>
<field name="res_model">account.bank.statement</field>
<field name="view_mode">form</field>
<field name="view_id"
ref="account_bank_statement_view_form"/>
<field name="target">new</field>
</record>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!--Inheriting account form view-->
<record id="view_account_form" model="ir.ui.view">
<field name="name">account.account.view.form.inherit.base.accounting.kit</field>
<field name="model">account.account</field>
<field name="inherit_id" ref="account.view_account_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='active']" position="after">
<field name="cash_flow_type" widget="selection"/>
</xpath>
</data>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- FollowUp View Form -->
<record id="account_followup_view_form" model="ir.ui.view">
<field name="name">account.followup.view.form</field>
<field name="model">account.followup</field>
<field name="arch" type="xml">
<form string="Follow-up">
<h1><field name="name"/></h1>
<label for="company_id" groups="base.group_multi_company"/>
<field name="company_id" options="{'no_create': True}" class="oe_inline"
groups="base.group_multi_company"/>
<p class="oe_grey">
To remind customers of paying their invoices, you can
define different actions depending on how severely
overdue the customer is. These actions are bundled
into follow-up levels that are triggered when the due
date of an invoice has passed a certain
number of days. If there are other overdue invoices for the
same customer, the actions of the most
overdue invoice will be executed.
</p>
<field name="followup_line_ids"/>
</form>
</field>
</record>
<!-- FollowUp View Tree -->
<record id="account_followup_view_tree" model="ir.ui.view">
<field name="name">account.followup.view.tree</field>
<field name="model">account.followup</field>
<field name="arch" type="xml">
<list string="Follow-up">
<field name="company_id" />
</list>
</field>
</record>
<!-- FollowUp Search View -->
<record id="account_followup_view_search" model="ir.ui.view">
<field name="name">account.followup.view.search</field>
<field name="model">account.followup</field>
<field name="arch" type="xml">
<search string="Search Follow-up">
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
</record>
<!-- FollowUp Kanban View -->
<record id="account_followup_view_kanban" model="ir.ui.view">
<field name="name">account.followup.view.kanban</field>
<field name="model">account.followup</field>
<field name="arch" type="xml">
<kanban>
<field name="name"/>
<templates>
<t t-name="card">
<div t-attf-class="oe_kanban_global_click">
<div>
<strong><i class="fa fa-building" role="img" aria-label="Enterprise"/> <t t-esc="record.name.value"/></strong>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Action FollowUp -->
<record id="action_account_followup_definition_form" model="ir.actions.act_window">
<field name="name">Payment Follow-ups</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.followup</field>
<field name="search_view_id" ref="account_followup_view_search"/>
<field name="view_mode">list,kanban,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Define follow-up levels and their related actions
</p>
<p>
For each step, specify the actions to be taken and delay in days. It is
possible to use print and e-mail templates to send specific messages to
the customer.
</p>
</field>
</record>
<!-- FollowUp MenuItem -->
<menuitem action="action_account_followup_definition_form" id="account_followup_menu"
parent="account.account_account_menu" name="Follow-up Levels"
groups="base_accounting_kit.group_account_chief" sequence="2"/>
</odoo>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Account Group Form View -->
<record id="account_group_view_form" model="ir.ui.view">
<field name="name">account.group.view.form</field>
<field name="model">account.group</field>
<field name="arch" type="xml">
<form string="Account Groups">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="code_prefix_start"/>
<field name="code_prefix_end"/>
<field name="company_id"/>
</group>
</group>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Account Group Tree View -->
<record id="account_group_view_tree" model="ir.ui.view">
<field name="name">account.group.view.tree</field>
<field name="model">account.group</field>
<field name="arch" type="xml">
<list string="Account Groups">
<field name="name"/>
<field name="code_prefix_start"/>
<field name="code_prefix_end"/>
<field name="company_id"/>
</list>
</field>
</record>
<!--Account Groups Action-->
<record id="action_account_group" model="ir.actions.act_window">
<field name="name">Account Groups</field>
<field name="res_model">account.group</field>
<field name="view_mode">list,form</field>
</record>
<!--Account Groups MenuItem-->
<menuitem id="menu_account_group" name="Account Groups"
action="action_account_group" sequence="10"
parent="account.account_account_menu"/>
</odoo>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!--Inheriting Account Journal Kanban View-->
<record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
<field name="name">account.journal.view.kanban.inherit.base.accounting.kit</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.account_journal_dashboard_kanban_view" />
<field name="arch" type="xml">
<xpath
expr="//kanban/templates//div[@id='dashboard_bank_cash_left']"
position="inside">
<!-- <t t-if="dashboard.number_to_reconcile > 0">-->
<!-- <button class="btn btn-primary"-->
<!-- type="object"-->
<!-- name="action_open_reconcile">-->
<!-- <t t-esc="dashboard.number_to_reconcile"/> to Reconcile-->
<!-- </button>-->
<!-- </t>-->
<t t-if="journal_type == 'bank'">
<a name="action_import_wizard" type="object"
class="oe_inline"
groups="account.group_account_invoice">Import
</a>
</t>
</xpath>
</field>
</record>
</odoo>

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