Compare commits
1476 Commits
havari_ara
...
19.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f7f2e42a4 | |||
|
|
0bba5fcf42 | ||
|
|
5df745d1fa | ||
| d7bc4a4b88 | |||
| ee9b1958f1 | |||
| 48db592326 | |||
| 8f834373b7 | |||
| c6765e04f7 | |||
| fc97d80c2c | |||
| 46ec5c998d | |||
| f6f341c372 | |||
| 3d159a89eb | |||
|
|
a6a54b1f3f | ||
| 763268a1e9 | |||
| e2efba2971 | |||
| 7428a903a8 | |||
| 4a71eef134 | |||
| e9b6ac9bbb | |||
| 9304592ad6 | |||
| 26ae0e14df | |||
| 22162f9d47 | |||
| 7e07a27a97 | |||
| 0da904b92b | |||
| 9b4ce3b586 | |||
| 49420992f5 | |||
| 85d8f421c6 | |||
| 966b74e9a3 | |||
| e9d0895fc9 | |||
| 1ccd4e2f78 | |||
| e93e98f302 | |||
| 9e8b644dc6 | |||
| 8f624fee10 | |||
| efe8ee068a | |||
| 7ae04247ce | |||
| d05b1f4bab | |||
| 96eeaa2d51 | |||
| 69ae27892d | |||
| c9a8cb1792 | |||
| 120bff231c | |||
| 7f6aeebbe1 | |||
| 3f66de30c4 | |||
| 389ad788e3 | |||
| cc13e048b2 | |||
| 085380a411 | |||
| 1f3176a7b7 | |||
| 88f26e9397 | |||
| 2549ce8bb7 | |||
| 1e585a419d | |||
| 9d3ef18528 | |||
| df40adb5db | |||
| 9fc91ed47a | |||
| ef1ef68222 | |||
| aaeb29171b | |||
| 1a80cd8aa8 | |||
| a02c02b8e5 | |||
| 720966186a | |||
| d8924a234a | |||
| a5d21a8853 | |||
| cca846b84e | |||
| b88dcbfaa8 | |||
| 8c7bde04d7 | |||
| 52fcc8afa5 | |||
| e71b7a33c4 | |||
| 529d640d2c | |||
| db06b274c8 | |||
| d51e343b0e | |||
| 724e09d35b | |||
| f46eca6af8 | |||
| cc3d0f3a6f | |||
| 693cd33119 | |||
| 98c4ac4167 | |||
| 8392ae58d9 | |||
| a25f5df019 | |||
| d7983aab11 | |||
| 54ff49035e | |||
| a2172f4581 | |||
| 85147b59a1 | |||
| c5e332bf92 | |||
| fda43a256b | |||
| 2b34b6680a | |||
| 8af5977dc1 | |||
| 298759fdcd | |||
| 33b4abdfca | |||
| cee7dfbc8c | |||
| 4651545305 | |||
| 05e9d292ab | |||
| d3d43cecb8 | |||
| 227e71b14f | |||
| d10ef5216c | |||
| f1ed7c92f7 | |||
| 1cc27af9ac | |||
| a4b715534d | |||
| 16548c9204 | |||
| 7acc979f33 | |||
| f4a7d664f2 | |||
| b596416791 | |||
| 310de14ee8 | |||
| f626c90b77 | |||
| 23cede2cec | |||
| cc0e6493ae | |||
| 23cc52e9dc | |||
| 0f353f1df9 | |||
| 317cdd1618 | |||
| c25e179e67 | |||
| 59cdd6db51 | |||
| 998ab2b18a | |||
| 6c6e2a6d06 | |||
| cba963b090 | |||
| 273ca36a82 | |||
| cbcb69f040 | |||
| 01b304ad67 | |||
| a3235eb5e7 | |||
| 24f3e5b775 | |||
| 361c3708dc | |||
| 923f087b7a | |||
| 590c130efa | |||
| 23249091b1 | |||
| a255dc7aae | |||
| 66a9f804ff | |||
| c07af0a20f | |||
| 6b42a1a341 | |||
| 580d950eab | |||
| 08f8103f4f | |||
| 2c856313a5 | |||
| ce5240738c | |||
| 69804d0600 | |||
| 5e7f44f96e | |||
| 1325283047 | |||
| 097e327943 | |||
| 1e2d165c13 | |||
| 4b8cb108fa | |||
| b458358351 | |||
| 6d4b091bd0 | |||
| a473e89adb | |||
| 9035144daa | |||
| 8c11e98400 | |||
| 4c6a032214 | |||
| 89c01cde3a | |||
| 335ae7cc37 | |||
| aeb0dfa32b | |||
| ab0fcc907d | |||
| 488373ef1d | |||
| ed88e8b964 | |||
| c2fd100b77 | |||
| 6c358ca955 | |||
| 2f387e8a65 | |||
| efaeade746 | |||
| 599e09df61 | |||
| 52c65aa9b5 | |||
| 41c4ff034a | |||
| ced6377266 | |||
| fb04b47190 | |||
| cd6812f3b8 | |||
| 84183488f8 | |||
| 2c552e1c97 | |||
| b4810b9f98 | |||
| 5dbfced933 | |||
| afc1b45d6a | |||
| 5ff0a2841e | |||
| 59199e3702 | |||
| 93e36e7e29 | |||
| 4618b061c8 | |||
| 626ede148f | |||
| cad549b100 | |||
| 953b69ca28 | |||
| ed9fdb17f3 | |||
| 9ba4a97fe3 | |||
| c97dd79db1 | |||
| f2c47e8c13 | |||
| 0c09645e6d | |||
| 48aee47770 | |||
| cb3d25ce0f | |||
| b6660d899a | |||
| b22ccc147f | |||
| 5e1b2881db | |||
| 59397bbbaf | |||
| 43650a81e8 | |||
| e9114765c0 | |||
| f8e7bcad5d | |||
| d919f48451 | |||
| 81ce31a57a | |||
| f95eca7b2e | |||
| d353bd94ab | |||
| f6d3119fdc | |||
| 7903652796 | |||
| c0d4958555 | |||
| 4f21f98520 | |||
| dfcda13313 | |||
| 6b0de8a446 | |||
| 148c3a12d9 | |||
| 357c73e7d1 | |||
| 0553a3faf7 | |||
| 12c3d9dcc0 | |||
| 23b40d5922 | |||
| 3f63f8d209 | |||
| 6fd4de2cc8 | |||
| 794edc73a0 | |||
| bf822b8178 | |||
| 875db440fc | |||
| 76be4825f1 | |||
| 77ff5a54f2 | |||
| a33c2380a2 | |||
| c9d191cd7c | |||
| e5882ef4fc | |||
| 0afa863d76 | |||
| b6fe704976 | |||
| afe3108bac | |||
| 940cd44458 | |||
| 4982f777ca | |||
| f2794c7b4b | |||
| 0b1545593b | |||
| 0a026f7122 | |||
| d3b616b5e9 | |||
| 67d9c5a66d | |||
| 27973de8db | |||
| d21fc69654 | |||
| 8ef99a0351 | |||
| 15dba9eeac | |||
| 5b5bc26980 | |||
| 8f72d4c181 | |||
| 689a600475 | |||
| 125229bf32 | |||
| 311491585e | |||
| 89215de3cc | |||
| 3ed3987ff2 | |||
| bc62e0679e | |||
| 4540f89d56 | |||
| 76a9794601 | |||
| 2f74414402 | |||
| b7c000ec2b | |||
| 9bbea37ffa | |||
| bd51b90454 | |||
| 410f68fd85 | |||
| 6264906b96 | |||
| de1c0a7a97 | |||
| 5a4b57e071 | |||
| fa7ec32530 | |||
| 79c0e0c7df | |||
| 551c5ff078 | |||
| cee6b58491 | |||
| 71749fa98b | |||
| 0bf6239825 | |||
| b69300873e | |||
| 013f1ebf8a | |||
| 2392132751 | |||
| 3885674952 | |||
| 3933decc5d | |||
| 98e3afdfa6 | |||
| 25e374d814 | |||
| 9a9a0d093e | |||
| d8c641942e | |||
| a72d7b7497 | |||
| 699905af74 | |||
| 1dbf28c898 | |||
| 135cd35e8e | |||
| e7a07af65a | |||
| 8f92c36a90 | |||
| 1139606ba9 | |||
| 5e7dc1dace | |||
| 30500a1a79 | |||
| 49e35bb590 | |||
| 1de375ee02 | |||
| a35e329b22 | |||
| 7323f4e828 | |||
| e2cb07e990 | |||
| 98d315cef4 | |||
| 78b95aff58 | |||
| bf5b83a46d | |||
| 452a529dca | |||
| 7e3704ba35 | |||
| 6ad452e5af | |||
| 6911b4e06e | |||
| ff240d6e5f | |||
| 12125792f2 | |||
| bb945c2bbe | |||
| be8bec6163 | |||
| 2d85f731af | |||
| bfce835bee | |||
| a4a0184e3a | |||
| ba9d23abcc | |||
| 4c811eb71b | |||
| 3183ea091c | |||
| cb5a39181f | |||
| b80e594535 | |||
| 487da7eb79 | |||
| 55b006145a | |||
| 272e50202c | |||
| c7e08801e3 | |||
| 98c68049eb | |||
| 4837ebb77b | |||
| 2c6b22b221 | |||
| 040d040536 | |||
| 1c7b7877a3 | |||
| 25a626bdc8 | |||
| 08af20cc8d | |||
| f2d3140e01 | |||
| da0f9cbcf2 | |||
| eaa58f6116 | |||
| 74792b00dd | |||
| e8d03372a9 | |||
| b05e4dc5c2 | |||
| ed17b07946 | |||
| be48603ea5 | |||
| 065927abb9 | |||
| 46e8c1e977 | |||
| dca8863bbd | |||
| 55bdcfbdc6 | |||
| 10975b612e | |||
| 2bea9c692e | |||
| 29052e9644 | |||
| 3f30b37505 | |||
| 5d363428a2 | |||
| 9a14ce45c9 | |||
| 49198d27cc | |||
| 2002bd8dc2 | |||
| 7522e64961 | |||
| 7c8e4574e7 | |||
| c42184c3b2 | |||
| b7f1e8f324 | |||
| 966efb56c0 | |||
| 91a3aac647 | |||
| ec80e5f007 | |||
| 87df9231e4 | |||
| 1bd64417b8 | |||
| 0a800bf1aa | |||
| f0027eee3f | |||
| 0bca86c256 | |||
| 1e473a1677 | |||
| 1725eafcd9 | |||
| 1ad797d9f0 | |||
| 53be87e93c | |||
| 1ead3282cf | |||
| 2c142ccb76 | |||
| eda163a405 | |||
| adce085798 | |||
| 495fe47dfa | |||
| 037d1438e1 | |||
| 54e693e3f1 | |||
| 9d84acdc94 | |||
| 8132d571ac | |||
| 28567ff9e2 | |||
| ad566ac852 | |||
| e1d95717d6 | |||
| 824265c91a | |||
| 5c5d93f18f | |||
| 1ed351afcb | |||
| 37d902bbf1 | |||
| 4620f1f15d | |||
| 0919e928a2 | |||
| 6ccd6f551d | |||
| d9dbe67b8c | |||
| 00ecdb1eee | |||
| a32918ffab | |||
| 599cc43b87 | |||
| 3e951f4586 | |||
| 179eb40651 | |||
| 1ea0f65bad | |||
| 37a5973caa | |||
| 3bfd304755 | |||
| 422687b588 | |||
| e8760f7d0d | |||
| d7f5f98734 | |||
| e224f85ce5 | |||
| 9793d30a0b | |||
| 7ee8c79c11 | |||
| c5b1f87197 | |||
| 6d4b838700 | |||
| 32ebb495b2 | |||
| 1b0b3816fd | |||
| 461a147883 | |||
| 96a99e98a6 | |||
| 3f1e67983a | |||
| 480a145045 | |||
| 4d6d6e3a0b | |||
| 561595705c | |||
| 6fe848fe50 | |||
| 340836e114 | |||
| 8550a2c003 | |||
| b9cb1e1d6c | |||
| 058d098354 | |||
| 05e11743bc | |||
| 7bd3c34d30 | |||
| 365e38e9bd | |||
| da1339981b | |||
| 21e3832c7a | |||
| cdf57466e0 | |||
| 1dd7159ebf | |||
| a7aac0a6fc | |||
| 46ddc4e83d | |||
| f598856a85 | |||
| e4fab2bcd0 | |||
| 2b9c28b17d | |||
| aa240a5294 | |||
| 3a3f69f769 | |||
| e1550843f2 | |||
| 6e9bd26cc2 | |||
| 0274e8118a | |||
| fbee040fbe | |||
| cc39e82b47 | |||
| cc173aa77f | |||
| 49fd715c87 | |||
| be38f82271 | |||
| 3d6d13e7f6 | |||
| 057fe4b3d5 | |||
| e4b13e539e | |||
| 995b9e65ac | |||
| bc01210496 | |||
| cab4aebd03 | |||
| 7ecc317e64 | |||
| 30d46fbca0 | |||
| 9b2180deef | |||
| 9ad33e290f | |||
| 787efca2b6 | |||
| 16ced6a395 | |||
| 773c3cdd6e | |||
| 4629806e3e | |||
| 2129a6d505 | |||
| bf6096bf45 | |||
| e8bf47a6a9 | |||
| 7eb70e2623 | |||
| 89253bd00a | |||
| b55ae4fe9d | |||
| bb68ff0cc5 | |||
| 66add92264 | |||
| 2588c66508 | |||
| 5b4a3aab11 | |||
| 88118d9448 | |||
| d389ec4902 | |||
| 81bc19c9ca | |||
| 5859333ac1 | |||
| 403db72dc7 | |||
| 1e058fdb9a | |||
| 8c015d75be | |||
| 1ffc9fe53f | |||
| ea982c7dc5 | |||
| d6922f99a3 | |||
| a04bf4be8b | |||
| af2de3d58f | |||
| 485d66cb19 | |||
| 18e3ab1723 | |||
| 9abef0c065 | |||
| 8aff2d5df6 | |||
| 926071995c | |||
| 69fc239de1 | |||
| 08bfaa13cf | |||
| 43d06b9ead | |||
| 100f596f31 | |||
| 6b1e846f86 | |||
| 2a3bd8b770 | |||
| cad4fdef9e | |||
| f25a7de844 | |||
| da3727481e | |||
| 44c80d21ea | |||
| 0f815cd793 | |||
| 962f5792a3 | |||
| 9527852970 | |||
| b5aef11d8e | |||
| 90e3b774fe | |||
| d12ac3d874 | |||
| f5593b775d | |||
| 64268fad89 | |||
| 9e9b83e383 | |||
| b0959026a5 | |||
| c94d4b2692 | |||
| feb7eac8d0 | |||
| deb912fe74 | |||
| f48188cdc2 | |||
| ef2a582cbb | |||
| efa56c6aee | |||
| 3b45b08cd1 | |||
| 8ad2d11c45 | |||
| 35b29fa27c | |||
| 8f09fa4305 | |||
| b5b245876c | |||
| b16b052934 | |||
| b069c94742 | |||
| 03e8bb9e34 | |||
| bf629028b6 | |||
| 3395d3b27d | |||
| e14357c691 | |||
| 5b071517d5 | |||
| 83906f95f7 | |||
| 164d5e4e27 | |||
| 1575a75869 | |||
| 963b0d0dfa | |||
| f65e31fabc | |||
| d0bc1094b5 | |||
| e927eea9d2 | |||
| db23098f86 | |||
| 0acea56f70 | |||
| 3b1e6031ed | |||
| e600110516 | |||
| 52330d8399 | |||
| b0cc42047c | |||
| e94654c113 | |||
| 5ed65f685d | |||
| a997f2fa4b | |||
| 7ff298551b | |||
| 83dc76f9da | |||
| 603a8f1c72 | |||
| 2618f5bc15 | |||
| 53ed74871d | |||
| 1c2bbaf08e | |||
| e1001bc697 | |||
| 5e517adbef | |||
| 823f4a4078 | |||
| 40caeba39e | |||
| ee4a4afa94 | |||
| c7fc09061d | |||
| 3292953c6e | |||
| 0c6035f883 | |||
| d5dec2ce97 | |||
| db7810ce15 | |||
| b2657e9c92 | |||
| 48483a2e25 | |||
| cc2215e75f | |||
| baf34a96a9 | |||
| c412cb62b4 | |||
| 58abc5c6df | |||
| 0b69dbf4cc | |||
| 0c44c5f90d | |||
| c1886be4a6 | |||
| 832c6c58e0 | |||
| b918b2d54b | |||
| 731f8ef60c | |||
| 23c946dee2 | |||
| 4b70c0b1b8 | |||
| a80a309f6b | |||
| 76b546e0d2 | |||
| e12cbe8a1d | |||
| 53f96e1c12 | |||
| c6fcc52911 | |||
| 001f689a99 | |||
| dd63749c5f | |||
| d0a6a9ba6f | |||
| 3d9224af3c | |||
| 2547699573 | |||
| 12d9818809 | |||
| 019c26cbd2 | |||
| a2abda6b07 | |||
| 649d3a13dd | |||
| 96e8abf5de | |||
| 2989ad5a81 | |||
| dc5a6616a4 | |||
| b1e930eafb | |||
| c1521a6372 | |||
| 269beaf624 | |||
| 13e5e7cde8 | |||
| 4bbcae399e | |||
| 14899e48d3 | |||
| 0e5a2af28e | |||
| 83277217f9 | |||
| 4d226ec086 | |||
| 2d41262428 | |||
| 8dfd64f53a | |||
| ea73d93e8a | |||
| 1e4e023c1c | |||
| 41ecea74c6 | |||
| ac2ff0476b | |||
| 147c1e5050 | |||
| 2a6c7e3a87 | |||
| 5c2ae7b2c4 | |||
| 0fed636461 | |||
| d1772985ab | |||
| 697315c47f | |||
| ae802507a3 | |||
| cb69646f9f | |||
| 91a454851c | |||
| 7eb66dd5a4 | |||
| 67db32090b | |||
| 45091483a3 | |||
| b4f0e672b3 | |||
| 0603a3d6af | |||
| 0e403e40f7 | |||
| 8e857c5d16 | |||
| 27f1d35c41 | |||
| 25ab8eb3a5 | |||
| 5e11c33d38 | |||
| 8c450b3275 | |||
| fc8f57dc1e | |||
| 3eba6d66aa | |||
| 848aae224c | |||
| 5133ee78c9 | |||
| 083852cb99 | |||
| 53a7b6a318 | |||
| ae3a204c7e | |||
| 431b209881 | |||
| 2e2bbff9b0 | |||
| c666b76c2a | |||
| 92f51cc251 | |||
| a91d88866f | |||
| f55d2cb472 | |||
| 720276e095 | |||
| 5e3ad11d2a | |||
| 219f706143 | |||
| 9ca4e55cc9 | |||
| 254fb1d885 | |||
| c78fc717af | |||
| 2512b32b28 | |||
| b11ad8e295 | |||
| e9d4d08962 | |||
| 3f9f298838 | |||
| fe351faefe | |||
| e3aa3d8990 | |||
| 5253193596 | |||
| 3001e7c0b2 | |||
| ec436f6e9c | |||
| 7872aa608b | |||
| da801c6eaf | |||
| 2ebda56c3f | |||
| a4b2164f7c | |||
| 58bf5a2def | |||
| 7c33f0b44f | |||
| bdde4082d7 | |||
| 1b90a3e867 | |||
| d524c5e1d4 | |||
| f01f577792 | |||
| 27ce1c54a5 | |||
| 128525d282 | |||
| 32003002ac | |||
| 94e116c52d | |||
| c1ba536d03 | |||
| 9b7541ccc3 | |||
| 5f3f5baf37 | |||
| 8d12539329 | |||
| f5719810a4 | |||
| db0cb50deb | |||
| 2b65de6758 | |||
| 030be683b1 | |||
| 14d5ad7f29 | |||
| 531ad91474 | |||
| d4a490c757 | |||
| 9f15d65490 | |||
| a2a74e8744 | |||
| c21e46ddeb | |||
| da07d2e445 | |||
| 6dc553fde9 | |||
| f99799a954 | |||
| 9e23e1317c | |||
| 32d774fcd5 | |||
| e207f08083 | |||
| 47a23fed67 | |||
| c1a763bddc | |||
| 7d30801033 | |||
| 84e8400cb4 | |||
| 3e481ba6ab | |||
| e33e0077a1 | |||
| 75ba8a98cb | |||
| 0366582790 | |||
| f689083840 | |||
| 48703f32da | |||
| 2de2a5d904 | |||
| a785bff7bc | |||
| 545a23229d | |||
| 1a354517a3 | |||
| d99608dfe0 | |||
| 7fd8ff1230 | |||
| e4f3261e8a | |||
| f76ece3c19 | |||
| 61be87a6a0 | |||
| 32ef4a1135 | |||
| 105f6a4b1d | |||
| 76487f4782 | |||
| d854dde695 | |||
| 0d2ab5ef0f | |||
| eef53a3aaf | |||
| 5cd06f5379 | |||
| ba653ff7da | |||
| 8910346aa4 | |||
| d004eb3298 | |||
| 953d506294 | |||
| 2d1a474823 | |||
| 4aaf162b07 | |||
| 55919440aa | |||
| def3919e36 | |||
| 23729f436a | |||
| a498382b27 | |||
| ef2464809f | |||
| c5bc923362 | |||
| b921e79306 | |||
| 7d31a8c951 | |||
| cbd4217d7d | |||
| a9a25dbf23 | |||
| e9d49c9a58 | |||
| 0682aecf37 | |||
| 2637250db2 | |||
| d33628aebd | |||
| a80add2939 | |||
| 41de310ee1 | |||
| a6bebfd99c | |||
| 76e4d29876 | |||
| 8ade4a6e05 | |||
| a6f421a104 | |||
| 8aa7c35368 | |||
| 6c8caf18a8 | |||
| 66986764f8 | |||
| fa4f2d6714 | |||
| 8f445713f2 | |||
| 40fb8f2fa6 | |||
| 33d3610337 | |||
| 569ed5fa45 | |||
| ffb2695771 | |||
| 0b2dad4a11 | |||
| f2c0f1dbf1 | |||
| 1f4d827be0 | |||
| 77237f7463 | |||
| 275e79a729 | |||
| 589c24b30c | |||
| 9f7dc4f18b | |||
| 7243cebeb7 | |||
| 968080a32f | |||
| 711ae46103 | |||
| a2bbff1742 | |||
| 2cfc0fbb9e | |||
| 0f5db71fcc | |||
| ab3cdca6ac | |||
| b6b317cf39 | |||
| a572280a1b | |||
| 0bba7d8660 | |||
| b83c259bc9 | |||
| 275005173e | |||
| e894d52fb3 | |||
| d93f8decd8 | |||
| d3b0c242da | |||
| b198163958 | |||
| a947ad7169 | |||
| fb945d1f13 | |||
| b70d7ffd84 | |||
| 799550985c | |||
| 0507e609d0 | |||
| 6e6c57734f | |||
| 3a8001bbf5 | |||
| c5531c9622 | |||
| 5fc1508fbb | |||
| 5b2176f740 | |||
| 8e0f4bedc1 | |||
| d0a6262774 | |||
| 50c3b59309 | |||
| 382b052b20 | |||
| a942abb5d8 | |||
| 8d8980a351 | |||
| 9147825a27 | |||
| a5bfe360bc | |||
| 9b0de5b906 | |||
| 0a961e60f2 | |||
| b56eef73af | |||
| c01bf9817e | |||
| aad8160ddb | |||
| 01ad9545ef | |||
| 891226fb3a | |||
| 51df143937 | |||
| 4bd584cc1c | |||
| 2983906b42 | |||
| 9b503f6da4 | |||
| 3be92de4da | |||
| f2b145fbe3 | |||
| b441195cbf | |||
| c680ca48b9 | |||
| 3f3f72f1b2 | |||
| d24265e1f7 | |||
| a1debc4b51 | |||
| 3f87848fb0 | |||
| 6a5d7bd5c9 | |||
| 3e5e94610b | |||
| 8fef2569a6 | |||
| b67e36219b | |||
| 8b9bc53b4b | |||
| 7f0c5bb6d6 | |||
| a624a78e89 | |||
| 2d007bddd6 | |||
| 3b0fd1a653 | |||
| 6dfec1ece8 | |||
| f2f9f1356e | |||
| 8013a8466b | |||
| 1ea2132ae1 | |||
| f625656478 | |||
| 97c12ce152 | |||
| 8f4d684054 | |||
| 7345062f82 | |||
| 745334a16a | |||
| b2ede24091 | |||
| 1c8f09b64c | |||
| c1c520274f | |||
| d000fead4c | |||
| 0de0a6d5aa | |||
| 0e872ab117 | |||
| 528f191d56 | |||
| ce4ee639d3 | |||
| f810014cb2 | |||
| 9d51ac3252 | |||
| a2028e5a97 | |||
| e9cadbe823 | |||
| 3f799e5d6f | |||
| 8722d4b1b2 | |||
| e653e447c6 | |||
| 88c2a9003a | |||
| 739c87dd5e | |||
| 046eb43f56 | |||
| 8c17569d52 | |||
| 0f995e80a9 | |||
| 09696b9c10 | |||
| b6362d895d | |||
| fc6d7d9b29 | |||
| 4bc1d5939e | |||
| fb1ec4b879 | |||
| d3a3fa3c77 | |||
| 72e36fbf76 | |||
| 558ed2306c | |||
| 0e8fbce3ea | |||
| b0cecf4b66 | |||
| 314f12f0d4 | |||
| df0d005724 | |||
| a487adf187 | |||
| 5e40d1c63a | |||
| 304c5172e6 | |||
| 23b7b1e80b | |||
| 5bbab6fab5 | |||
| 1585c66a2d | |||
| f57d262d62 | |||
| 69c4b974ef | |||
| 214433437e | |||
| de13dc5909 | |||
| ad34c1d6eb | |||
| ac1f5c0c8f | |||
| 475273a71b | |||
| 2f525cb95e | |||
| 9874bd4e21 | |||
| 0212b26047 | |||
| 20da29605b | |||
| 034eab9244 | |||
| 60fc5bcdda | |||
| 6a80f28c52 | |||
| 13bb0df929 | |||
| 04b4210da2 | |||
| 7d03e32f51 | |||
| 4fdbdaedc9 | |||
| 1cc392bfb2 | |||
| c856fed428 | |||
| c4a447738e | |||
| d3b296b587 | |||
| c678877b59 | |||
| 104f73b7d2 | |||
| 135ae7fcfe | |||
| 9d502807fb | |||
| c3581e1b69 | |||
| 3ce9111fa7 | |||
| 516e985bd0 | |||
| 46c0d054a6 | |||
| 392984c5e8 | |||
| 2bc536263e | |||
| 957fb6f312 | |||
| 4dbea36b0f | |||
| 3c67472a8c | |||
| f6e22cc063 | |||
| 9b4f874307 | |||
| a054dc878a | |||
| 48e48ecf03 | |||
| 471c451c21 | |||
| a88793a6ad | |||
| 3e50cea188 | |||
| 4da0dfbd51 | |||
| da5eab2f1f | |||
| 78e1187722 | |||
| bfda8e2e56 | |||
| 5014234817 | |||
| a185c34d65 | |||
| 8fc4975a9e | |||
| 7414a836e7 | |||
| 122f31f4b6 | |||
| b59d8cee90 | |||
| 1db27642af | |||
| 6f5be6eae9 | |||
| 2a7103cdd3 | |||
| af722d6184 | |||
| 2bf0695e9d | |||
| faf1b4fff1 | |||
| bd59526535 | |||
| f404e0ef31 | |||
| a11b547cdc | |||
| 1507d10fe5 | |||
| 84cc63a038 | |||
| cbb5244320 | |||
| 809e33bd93 | |||
| b8f6c82010 | |||
| 4be3583e7b | |||
| de10442135 | |||
| 44ec4115ea | |||
| eb27193b6e | |||
| cead209507 | |||
| f2122a7706 | |||
| 88f970547f | |||
| fc7bfaa089 | |||
| 234aee88e2 | |||
| 33a761413c | |||
| db76be28e3 | |||
| 62a64bcadc | |||
| 3d69d5b814 | |||
| 850d9bb6c5 | |||
| 89f02eeda5 | |||
| cba2ad4052 | |||
| f566b64a4e | |||
| 9e5070e4c7 | |||
| d95c52e27a | |||
| 051e01b1d5 | |||
| 4261ec5ed0 | |||
| 98bc5f8027 | |||
| ccdc19f576 | |||
| 84e6416ca1 | |||
| 368561c08a | |||
| 3de397f595 | |||
| 700d367666 | |||
| a384996942 | |||
| 81f0c9411c | |||
| cbdea4a66a | |||
| 752c1ebe59 | |||
| f77ab77c0b | |||
| 71d223259b | |||
| a69b413c5b | |||
| daafc7c705 | |||
| 52b130631c | |||
| 01594ba32b | |||
| 9015195fb9 | |||
| 6d07bc4c06 | |||
| acf12b508b | |||
| b7a2c4ea2f | |||
| 4399a06c37 | |||
| 5ee5d4f5cd | |||
| 256c5679c7 | |||
| 45bfc361f7 | |||
| 0ecbc9213c | |||
| 63dc434824 | |||
| 3e1a94eaed | |||
| 68ce996d7b | |||
| 89d5846a15 | |||
| 7f76c2d7e7 | |||
| 1326d21dfa | |||
| b37e8a0f7a | |||
| 8f629981ce | |||
| beace16323 | |||
| e19464e2a8 | |||
| 008ca2b8a7 | |||
| 6e56dc2437 | |||
| a09d856ddb | |||
| 9a69aa7709 | |||
| 0254cac7fc | |||
| bc629b414c | |||
| 8f69ab2665 | |||
| 01514c86c9 | |||
| ca1f963db1 | |||
| 5fd44a1ae0 | |||
| 284e2939f5 | |||
| 70d43d34ca | |||
| 8425e51ca4 | |||
| 4994547440 | |||
| 2fbf03f179 | |||
| 2e1a1fbe03 | |||
| dd5c59c645 | |||
| 3e88c5783b | |||
| 0a63809482 | |||
| 743b002387 | |||
| 79fe584847 | |||
| 63d6a65f65 | |||
| d20f9916f3 | |||
| 285397f44b | |||
| 5d7a1983f9 | |||
| 8cb7ce7d65 | |||
| 2b78518c30 | |||
| b6be57e58f | |||
| e225ff2780 | |||
| b98f7b4769 | |||
| 795d6e7d9d | |||
| 100198bd1f | |||
| 23a254a556 | |||
| 12edb222fe | |||
| e8ba8fa737 | |||
| b4322a0037 | |||
| 4ad5be42ac | |||
| 36edebf085 | |||
| fed7367831 | |||
| f2733d5329 | |||
| bb090554de | |||
| 42a7238060 | |||
| f9df23afbd | |||
| 81e9e5a672 | |||
| c7d69b60ae | |||
| a2f9f9b7c5 | |||
| 32431d413c | |||
| cfa3b92440 | |||
| d84b02bbd3 | |||
| 55c7f73cf0 | |||
| 0ca0df7b4d | |||
| 57d989b675 | |||
| de9e8736a2 | |||
| c404a4666b | |||
| 25707d453f | |||
| 39099edaf2 | |||
| 284678d009 | |||
| 30c49600c4 | |||
| a3dbb7e17b | |||
| 6008b2dc05 | |||
| 829ad64e09 | |||
| 25043fe102 | |||
| 88686ff2c3 | |||
| 12abc05f7d | |||
| aefa4babca | |||
| c62fd5ecfa | |||
| 7eb7f7ea8e | |||
| d9fe73f32b | |||
| e7824591a7 | |||
| b05fc21d36 | |||
| d93328bf86 | |||
| f4ccfb22ac | |||
| 0113a319f4 | |||
| 0ee6d65498 | |||
| 86de2d1eb9 | |||
| db3ab398e9 | |||
| cc3bcd9819 | |||
| 7230f7788f | |||
| bb433c6f46 | |||
| b3883d85d5 | |||
| 65e8752498 | |||
| 6327aefce2 | |||
| be499f838f | |||
| 5a4e74189a | |||
| 071b72038c | |||
| 43e08cbef1 | |||
| 3acbb098bc | |||
| 01ea86d870 | |||
| 022900429a | |||
| f468d2f4fd | |||
| 7e8d4e2128 | |||
| c5bd3349f8 | |||
| 1185f5ea8b | |||
| eeb9acdef1 | |||
| 71924556d6 | |||
| 437bf1914f | |||
| 2247a2b2aa | |||
| 142ef0ddd2 | |||
| a6ef031a85 | |||
| 1201b65d40 | |||
| 0e58a32f10 | |||
| 1ac96b414e | |||
| 1f2b9fbed1 | |||
| d491d1fc0b | |||
| 2475691c4e | |||
| 87d4426b3d | |||
| 0ecc6a5a18 | |||
| 844bc6c148 | |||
| 66dfe1c5e4 | |||
| 150ecdf120 | |||
| aad3d4bd50 | |||
| 6cc75e639c | |||
| a628c83a90 | |||
| 40dfe0fccc | |||
| 215e5b1677 | |||
| cd5eda9df9 | |||
| cad8f0a19d | |||
| 8563621d65 | |||
| 86fce55c97 | |||
| 86d61ed1a0 | |||
| fbacd499dc | |||
| ccb8b3d268 | |||
| 1773ce4444 | |||
| c2517abff9 | |||
| e87775affa | |||
| cc5c04d5da | |||
| a18cbbf49a | |||
| ee3e8edfa8 | |||
| 79b3210bda | |||
| 9580b1f29e | |||
| 04a7bbbda8 | |||
| c6857b850e | |||
| 48a025302f | |||
| 38246e90ee | |||
| 7e1e94d165 | |||
| aa83112e71 | |||
| 044fec0d50 | |||
| e32318b2ad | |||
| e742c77d12 | |||
| 20ed701fec | |||
| e1e2481bbb | |||
| 015696e7c4 | |||
| d318a5a9ec | |||
| d802d63444 | |||
| 7decda722d | |||
| 4a8b911529 | |||
| 435650436c | |||
| 7eb8ce5eba | |||
| 45ab562a84 | |||
| 1cd28a15b5 | |||
| be15835a67 | |||
| 0043ea605b | |||
| a9d9132707 | |||
| 968f881838 | |||
| 6e5863b6f0 | |||
| f096aad827 | |||
| 75afd665b8 | |||
| 2f8a31ca87 | |||
| 02c94565a6 | |||
| 2eaabfca59 | |||
| c300658c45 | |||
| ffd3540da7 | |||
| af57c49e0c | |||
| 3cb908e43a | |||
| b9763d1cab | |||
| 3081b1727e | |||
| 795a79e2de | |||
| 622d469902 | |||
| 0d3d9fd659 | |||
| 0624e3329d | |||
| d37effb75e | |||
| 5d6cbe8712 | |||
| 7da2508ad5 | |||
| e84b5fc21c | |||
| 302b4e2086 | |||
| 8979652c30 | |||
| cd5e49cad6 | |||
| aceab86bd0 | |||
| ac0562565d | |||
| dea9410bff | |||
| 321300cb96 | |||
| fe85d4a831 | |||
| d96900edcd | |||
| 506f0f11c4 | |||
| ae32f463b7 | |||
| 4df8ed7aca | |||
| 4319b86036 | |||
| af9f7335fe | |||
| 6dd9422d13 | |||
| c161cf6911 | |||
| 55a84c282b | |||
| 5ef1cc0aed | |||
| c774c5b9ed | |||
| 03914adbd3 | |||
| f944dc402e | |||
| 41a2fb8e87 | |||
| d98c59977c | |||
| 5635da77ea | |||
| f566e6ebc0 | |||
| 7ac4617819 | |||
| e5a427556a | |||
| eadaacc266 | |||
| 94c1637d10 | |||
| 3f9d72c4fe | |||
| 00bdf183bb | |||
| 0b5452b9ef | |||
| 603e4a5a7a | |||
| 2682634640 | |||
| 5bac29e608 | |||
| 0b34ccfe0b | |||
| 4d9163364b | |||
| 64aa4f4cd8 | |||
| 10755bf441 | |||
| 95cb5d6eb5 | |||
| bd05b6f737 | |||
| 90bfd7cd31 | |||
| 6aebd3a480 | |||
| fa5a12e8b7 | |||
| a4f1c765d0 | |||
| 6809c236c3 | |||
| 53dd954217 | |||
| 525f4160a7 | |||
| f9f462b129 | |||
| 1f55211ecd | |||
| db8ff1ab76 | |||
| 98fb9f325e | |||
| 0811265bc1 | |||
| 4357f1842a | |||
| 3a54c60b51 | |||
| c79cf90797 | |||
| 11573d49d4 | |||
| 15fc7bb78b | |||
| e92df10e2b | |||
| b49af826f3 | |||
| a95c2da928 | |||
| c04db924cc | |||
| c2ff38fd10 | |||
| 3b51b7e059 | |||
| 473f603fee | |||
| 03136df83e | |||
| 7c3e66de7c | |||
| 50b4c9349a | |||
| dab6e99136 | |||
| 2a74c6981c | |||
| bbf0e0ede6 | |||
| 107b28dd6b | |||
| 45d4562015 | |||
| 0305486fc0 | |||
| 92ce1ad080 | |||
| 465f081a01 | |||
| a7bfa330d5 | |||
| c4116b0001 | |||
| d724678c31 | |||
| 7b6bc7c32c | |||
| 14006bf8b6 | |||
| 16ffcf64c9 | |||
| 16c7dfa4c7 | |||
| a5bc3df00b | |||
| c3697aa775 | |||
| 0545a5e27f | |||
| d4140f6046 | |||
| 4e83a19734 | |||
| 90d255d5b7 | |||
| fde08f1a51 | |||
| 367c39cd19 | |||
| 1fe1b890f7 | |||
| 039f0b9679 | |||
| b59bf760fc | |||
| 254a5edeb1 | |||
| e1cd635b2b | |||
| fd488f3611 | |||
| 936141a47c | |||
| 00ad28e7e5 | |||
| 133d01f1bc | |||
| 2bc961a7f6 | |||
| 2763df5b85 | |||
| c70fc08d8a | |||
| fb315401ed | |||
| 7426af0136 | |||
| 7d4ce79754 | |||
| 76c842ad49 | |||
| c5b643b175 | |||
| 7be3e6fc46 | |||
| 3768d57ef1 | |||
| 79a1fc6302 | |||
| cfae5c1e6e | |||
| ee86816a05 | |||
| 3e5625c49a | |||
| aad046ad01 | |||
| 8b0ce6857d | |||
| 0a00df7d8f | |||
| 0b04773828 | |||
| 0bfcfc8e5e | |||
| 6569b22484 | |||
| 6c99447b98 | |||
| 6c2975aa2c | |||
| 8a323969dd | |||
| 6adfee8522 | |||
| 94e5c2bc26 | |||
| a730c30313 | |||
| 0f9842a4e4 | |||
| d48cd0eb4f | |||
| d051b7eb77 | |||
| 229fb1c9d1 | |||
| 88db33f912 | |||
| ebe40db835 | |||
| 982454b2f5 | |||
| 9335bc4eda | |||
| b7b2b72379 | |||
| 68401a1cf2 | |||
| 01e8d981aa | |||
| c6b769d7eb | |||
| b0b87268fa | |||
| 434512a7c0 | |||
| 8fe8aa6bbf | |||
| 65e232de2b | |||
| 071bfcf31d | |||
| 568f9daaf1 | |||
| 127b7cb233 | |||
| 4142021d85 | |||
| 12d115d1fc | |||
| 7766e1a3a5 | |||
| 76fa65609b | |||
| 87315fbf30 | |||
| ed7fe289fc | |||
| 6540dbff99 | |||
| 60c245b407 | |||
| 8ffa385b1f | |||
| fd0a5a358a | |||
| 08dd515517 | |||
| 7cef065bde | |||
| 7ade9aae03 | |||
| 532efc8a07 | |||
| 10719628e3 | |||
| b8b2528b12 | |||
| aa4221b2fe | |||
| 261c16061f | |||
| 184c88df09 | |||
| 4072474ee3 | |||
| 65145b21fa | |||
| 6f4831e613 | |||
| dc4bf9e012 | |||
| ef5398f5fb | |||
| 791818fd64 | |||
| 3b08defcc3 | |||
| d3cf19c3f4 | |||
| 70f019ae37 | |||
| 630fb5358a | |||
| b5722ae417 | |||
| 8cd7052a63 | |||
| dbe0ea924c | |||
| 7953e4b50f | |||
| 4315e14697 | |||
| 6e032ba798 | |||
| c577f33a97 | |||
| b0327f442e | |||
| fbd39e10d6 | |||
| b18d61edc4 | |||
| f20f658a5f | |||
| 88f06ae627 | |||
| 5f076d37de | |||
| 054ec9151d | |||
| 7cbd9ab842 | |||
| 79943927dd | |||
| 6c9c36663e | |||
| 6b30fe12d0 | |||
| 9ef85d0aa9 | |||
| 7c3def72d6 | |||
| 481c83dce4 | |||
| 7210ca1ffd | |||
| d832e40e73 | |||
| e3db68618d | |||
| a031cefe86 | |||
| e57e734c40 | |||
| 17b7a9432c | |||
| 2dadde64d2 | |||
| 2d4e7b3ee2 | |||
| 617e1ddd42 | |||
| 926206c4a7 | |||
| 8b6041cd3d | |||
| 593267811f | |||
| 9728383428 | |||
| 194babdf41 | |||
| 6f78e2e524 | |||
| b04f780820 | |||
| fd396fe299 | |||
| 0670fe453d | |||
| fbfa62ad9a | |||
| f4d652059c | |||
| 72f3aa1ab6 | |||
| 13f6bc6f40 | |||
| 8408279160 | |||
| ef666a5685 | |||
| 0958b0a64c | |||
| d238e31685 | |||
| 1e5abc888e | |||
| 96a6b7bb1f | |||
| d4f016e8a0 | |||
| 99f0c26c35 | |||
| 06d20d0ff0 | |||
| faac65cddb | |||
| cbc4503466 | |||
| cf972bf291 | |||
| ebaf474567 | |||
| bb7f341d77 | |||
| 94189882f7 | |||
| 50a789be28 | |||
| 782012f925 | |||
| b5fe6ea753 | |||
| f212637b6a | |||
| 0957153883 | |||
| 4ddb63b195 | |||
| 8d07a37483 | |||
| 6eed23c19a | |||
| fd961a3017 | |||
| 9535a8ad6e | |||
| fd0e041e2b | |||
| 5732bcb409 | |||
| 2c0871abd1 | |||
| 37ffda207c | |||
| 369070c0fd | |||
| 822fbaad04 | |||
| f504f7cc7a | |||
| bdaef9f2e7 | |||
| 5ec6423f44 | |||
| d7e283d902 | |||
| 57ced67409 | |||
| 13723c9273 | |||
| 3be0ea0f7b | |||
| ca13d7d3f4 | |||
| be4975f356 | |||
| cb006a7a44 | |||
| a9dc3dcffa | |||
| 885bb255ae | |||
| fb7d82d2f8 | |||
| b35236833f | |||
| 188e732c27 | |||
| 85a037b7b0 | |||
| 6b4864d8f4 | |||
| c345f88fb7 | |||
| dd601d2041 | |||
| d528c4e8b0 | |||
| 37e7e78579 | |||
| 50ffe3acbf | |||
| fe054eb766 | |||
| d726ee0742 | |||
| 63a2854905 | |||
| 94c47c1d64 | |||
| 99decef40e | |||
| 2144d6510b | |||
| 3bc963d086 | |||
| b0208802cb | |||
| 7c70894890 | |||
| b34d911271 | |||
| afadba60cd | |||
| dc9bae18b5 | |||
| a9da9289b1 | |||
| 9ffa32624c | |||
| 0028b496e1 | |||
| 11d2704508 | |||
| f2f9e89a95 | |||
| cbf21b7ab8 | |||
| 73c3e87596 | |||
| 4597627bbf | |||
| 2a7f8d8700 | |||
| 976dbf28ec | |||
| 5be3f70d33 | |||
| 32afc8987c | |||
| 36eccf478e | |||
| afe157d626 | |||
| c1946aed03 | |||
| c4243349e5 | |||
| 50f5ca329f | |||
| 94acef514f | |||
| d64353f256 | |||
| 2c6e58c58b | |||
| 222c0cadf0 | |||
| 35e695d9c3 | |||
| b2efe86375 | |||
| 9d9b59c5d3 | |||
| e8ffe910ee | |||
| 727fc0e844 | |||
| f7982d1a0e | |||
| ee132a00a2 | |||
| 1861912af3 | |||
| b86139b373 | |||
| b8c54e3f8e | |||
| 1b57e46bb1 | |||
| dd948951db | |||
| 3bd6e77fa2 | |||
| 193600db34 | |||
| 3d2024ab9f | |||
| 2c163771d4 | |||
| 26f24c3022 | |||
| f61e5f7e77 | |||
| 3391a4c864 | |||
| 48ad67fc95 | |||
| cb0fcf1b06 | |||
| 2e42407d45 | |||
| 695188ae0f | |||
| 9df8e641fd | |||
| 20806189b9 | |||
| e85bcbd0a1 | |||
| a9ab2991d7 | |||
| 9dca096734 | |||
| 657f3e4fb4 | |||
| 80bbd23379 | |||
| 50ce4f41a2 | |||
| d57b15f9dd | |||
| db8cd58f31 | |||
| f0745cda51 | |||
| 6752ab966e | |||
| 8ddc02a7c8 | |||
| db033c9d8b | |||
| d97e5cecb2 | |||
| 46ab5fa005 | |||
| 147f237c6f | |||
| a488d64cb3 | |||
| 6b5ccf1604 | |||
| 516e1a3bb4 | |||
| 4c42f51869 | |||
| 9d8cbe6868 | |||
| 23dace1045 | |||
| c2cb157124 | |||
| 070f8187f1 | |||
| b4f4024174 | |||
| 91e6814c2f | |||
| ef39e07904 | |||
| 71d4b51e09 | |||
| 1a83db992c | |||
| 76ce02f7fa | |||
| 4de59a7674 | |||
| dc0417b263 | |||
| 5684de0fbc | |||
| 3fae6c3b2d | |||
| 9a89e53ec4 |
393
.gitea/qualify-addon.py
Normal file
@@ -0,0 +1,393 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
qualify-addon.py — Pillar 1 of the addon qualification gate.
|
||||
|
||||
Static checks against an Odoo addon source tree:
|
||||
manifest __manifest__.py parses, has 'name', 'version' starts with '<digit>.0.'
|
||||
pip-deps every non-stdlib import is declared in external_dependencies['python']
|
||||
app-name every <app> element in any view XML has a name= attribute
|
||||
menu-icon top-level <menuitem web_icon=> is set OR the addon ships
|
||||
static/description/icon.png
|
||||
hoot-import no JS file under static/src/ imports from '@odoo/hoot' or '@odoo/hoot-dom'
|
||||
webpack-name no JS file under static/lib/ uses self.webpackChunk_<unprefixed-name> —
|
||||
chunk array names must be addon-namespaced (e.g. webpackChunk_am5_<addon>)
|
||||
|
||||
Usage:
|
||||
python3 scripts/qualify-addon.py <addon-dir> [<addon-dir> ...]
|
||||
python3 scripts/qualify-addon.py --json <addon-dir>
|
||||
|
||||
Exit codes:
|
||||
0 all checks passed for every addon
|
||||
1 at least one addon failed at least one check
|
||||
2 bad usage / I/O error
|
||||
|
||||
Each finding is (severity, check, message). Severity:
|
||||
ERROR — the addon is broken-by-construction; refuse to admit to catalog
|
||||
WARN — likely problem but could be intentional; admit-with-warning posture
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
|
||||
# Python stdlib modules. Conservative — anything imported NOT in here AND not in
|
||||
# ODOO_BUILTINS is flagged as needing declaration. Better to false-positive (fixable
|
||||
# by adding to external_dependencies) than miss a real missing dep.
|
||||
STDLIB = frozenset({
|
||||
'abc', 'argparse', 'ast', 'asyncio', 'base64', 'binascii', 'bisect', 'calendar',
|
||||
'collections', 'configparser', 'contextlib', 'contextvars', 'copy', 'csv', 'ctypes',
|
||||
'dataclasses', 'datetime', 'decimal', 'difflib', 'dis', 'email', 'enum', 'errno',
|
||||
'fcntl', 'fnmatch', 'functools', 'gc', 'getpass', 'gettext', 'glob', 'gzip', 'hashlib',
|
||||
'heapq', 'hmac', 'html', 'http', 'imaplib', 'importlib', 'inspect', 'io', 'ipaddress',
|
||||
'itertools', 'json', 'keyword', 'locale', 'logging', 'math', 'mimetypes',
|
||||
'multiprocessing', 'numbers', 'operator', 'os', 'pathlib', 'pickle', 'pkgutil',
|
||||
'platform', 'pprint', 'queue', 'random', 're', 'select', 'selectors', 'shlex',
|
||||
'shutil', 'signal', 'smtplib', 'socket', 'sqlite3', 'ssl', 'stat', 'string', 'struct',
|
||||
'subprocess', 'sys', 'tempfile', 'textwrap', 'threading', 'time', 'timeit', 'token',
|
||||
'tokenize', 'traceback', 'types', 'typing', 'unicodedata', 'unittest', 'urllib',
|
||||
'uuid', 'warnings', 'weakref', 'xml', 'xmlrpc', 'zipfile', 'zlib', 'zoneinfo',
|
||||
'__future__',
|
||||
})
|
||||
|
||||
# Modules shipped by Odoo's base image. Never need declaration.
|
||||
ODOO_BUILTINS = frozenset({
|
||||
'odoo', 'psycopg2', 'lxml', 'PIL', 'requests', 'dateutil', 'pytz', 'passlib',
|
||||
'werkzeug', 'jinja2', 'markupsafe', 'docutils', 'reportlab', 'babel', 'xlsxwriter',
|
||||
'xlrd', 'xlwt', 'qrcode', 'vobject', 'polib', 'PyPDF2', 'cryptography', 'pyOpenSSL',
|
||||
'OpenSSL', 'suds', 'num2words', 'pyldap', 'ldap', 'xmltodict', 'zeep', 'gevent',
|
||||
'greenlet', 'libsass', 'idna', 'pyusb', 'serial', 'qrcode', 'mock', 'freezegun',
|
||||
'phonenumbers',
|
||||
})
|
||||
|
||||
# Bare-name webpackChunk arrays we know collide. Detector flags any
|
||||
# `self.webpackChunk_<name>` where <name> doesn't have an addon-derived suffix.
|
||||
# We allow the canonical chunk array names listed here ONLY if the addon's
|
||||
# directory name matches — i.e. we suggest namespacing.
|
||||
WEBPACK_CHUNK_RE = re.compile(r'self\.(webpackChunk[a-zA-Z0-9_]+)\b')
|
||||
|
||||
# JS imports of the hoot test framework that should never appear in production code.
|
||||
HOOT_IMPORT_RE = re.compile(
|
||||
r'''(?:from\s+['"]@odoo/hoot[a-z\-]*['"]|require\s*\(\s*['"]@odoo/hoot[a-z\-]*['"]\s*\))'''
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Finding:
|
||||
severity: str # 'ERROR' | 'WARN'
|
||||
check: str # short check id
|
||||
message: str # human-readable
|
||||
file: str | None = None # relative path, if applicable
|
||||
line: int | None = None # 1-indexed, if applicable
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Check 1 — manifest parses + has required keys
|
||||
# ---------------------------------------------------------------------------- #
|
||||
def check_manifest(addon_dir: Path) -> tuple[list[Finding], dict | None]:
|
||||
findings: list[Finding] = []
|
||||
mf_path = addon_dir / '__manifest__.py'
|
||||
if not mf_path.exists():
|
||||
findings.append(Finding('ERROR', 'manifest', 'no __manifest__.py'))
|
||||
return findings, None
|
||||
try:
|
||||
manifest = ast.literal_eval(mf_path.read_text())
|
||||
except (SyntaxError, ValueError) as e:
|
||||
findings.append(Finding('ERROR', 'manifest',
|
||||
f'__manifest__.py does not parse as Python literal: {e}',
|
||||
file='__manifest__.py'))
|
||||
return findings, None
|
||||
if not isinstance(manifest, dict):
|
||||
findings.append(Finding('ERROR', 'manifest',
|
||||
'__manifest__.py top-level is not a dict',
|
||||
file='__manifest__.py'))
|
||||
return findings, None
|
||||
if not manifest.get('name'):
|
||||
findings.append(Finding('ERROR', 'manifest',
|
||||
"missing 'name' key (Odoo refuses install)",
|
||||
file='__manifest__.py'))
|
||||
version = manifest.get('version', '')
|
||||
if not re.match(r'^\d+\.0\.\d+\.\d+\.\d+$', version):
|
||||
findings.append(Finding('WARN', 'manifest',
|
||||
f"version {version!r} is not in '<odoo_major>.0.x.y.z' form — "
|
||||
"Odoo will prepend the running Odoo major and may refuse install "
|
||||
"on a different major (incident #9)",
|
||||
file='__manifest__.py'))
|
||||
return findings, manifest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Check 2 — pip deps: every non-stdlib import is in external_dependencies
|
||||
# ---------------------------------------------------------------------------- #
|
||||
def check_pip_deps(addon_dir: Path, manifest: dict) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
declared = set(manifest.get('external_dependencies', {}).get('python', []))
|
||||
addon_name = addon_dir.name
|
||||
|
||||
# Pre-scan: collect this addon's submodule names so we don't flag intra-addon imports.
|
||||
own_submodules = {p.stem for p in addon_dir.rglob('*.py') if p.stem != '__init__'}
|
||||
own_submodules.add(addon_name)
|
||||
|
||||
seen_imports: set[tuple[str, str, int]] = set() # (toplevel, file, line)
|
||||
|
||||
for py_file in addon_dir.rglob('*.py'):
|
||||
if any(part.startswith('.') for part in py_file.parts):
|
||||
continue
|
||||
try:
|
||||
tree = ast.parse(py_file.read_text())
|
||||
except (SyntaxError, UnicodeDecodeError):
|
||||
continue
|
||||
rel = py_file.relative_to(addon_dir).as_posix()
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
seen_imports.add((alias.name.split('.')[0], rel, node.lineno))
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
if node.level: # relative import — intra-addon, skip
|
||||
continue
|
||||
if node.module:
|
||||
seen_imports.add((node.module.split('.')[0], rel, node.lineno))
|
||||
|
||||
for top, rel, lineno in sorted(seen_imports):
|
||||
if top in STDLIB or top in ODOO_BUILTINS or top in own_submodules:
|
||||
continue
|
||||
if top in declared:
|
||||
continue
|
||||
# PEP 8 names that are clearly local helpers (e.g. utils, models) — skip if
|
||||
# they look like a sibling module we missed in own_submodules.
|
||||
if (addon_dir / top).is_dir() or (addon_dir / f'{top}.py').exists():
|
||||
continue
|
||||
findings.append(Finding(
|
||||
'ERROR', 'pip-deps',
|
||||
f"imports '{top}' but it is not in external_dependencies['python'] "
|
||||
"(install will fail with ModuleNotFoundError — incident #5)",
|
||||
file=rel, line=lineno,
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Check 3 — every <app> element has a name= attribute
|
||||
# ---------------------------------------------------------------------------- #
|
||||
def check_app_name(addon_dir: Path) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
# XML files in views/ + data/ may contain res_config_settings <app> elements.
|
||||
for xml_file in list(addon_dir.rglob('views/*.xml')) + list(addon_dir.rglob('data/*.xml')):
|
||||
try:
|
||||
text = xml_file.read_text()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
rel = xml_file.relative_to(addon_dir).as_posix()
|
||||
# Multi-line tolerant regex: <app ... > with everything between.
|
||||
for m in re.finditer(r'<app\b[^>]*?>', text, re.DOTALL):
|
||||
tag = m.group()
|
||||
if 'name=' in tag:
|
||||
continue
|
||||
line = text[:m.start()].count('\n') + 1
|
||||
findings.append(Finding(
|
||||
'ERROR', 'app-name',
|
||||
"<app> element missing name= attribute. Odoo 18 SettingsFormCompiler "
|
||||
"calls toStringExpression(null) and crashes the entire Settings page "
|
||||
"(incident #7)",
|
||||
file=rel, line=line,
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Check 4 — top-level menus declare web_icon OR addon ships static/description/icon.png
|
||||
# ---------------------------------------------------------------------------- #
|
||||
def check_menu_icon(addon_dir: Path) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
has_default_icon = (addon_dir / 'static' / 'description' / 'icon.png').exists()
|
||||
for xml_file in addon_dir.rglob('*.xml'):
|
||||
try:
|
||||
text = xml_file.read_text()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
rel = xml_file.relative_to(addon_dir).as_posix()
|
||||
# Find <menuitem ... > whose XML has no parent= attribute (top-level menu).
|
||||
for m in re.finditer(r'<menuitem\b[^>]*?/?>', text, re.DOTALL):
|
||||
tag = m.group()
|
||||
if 'parent=' in tag:
|
||||
continue
|
||||
if 'web_icon=' in tag:
|
||||
continue
|
||||
if has_default_icon:
|
||||
# Odoo 18's auto-fallback path. Soft warning since it works for top-level
|
||||
# menus that get web_icon auto-populated from the module's icon.png.
|
||||
# But our incident #6 showed even with icon.png present, web_icon often
|
||||
# ends up empty in DB. So WARN, not ERROR.
|
||||
line = text[:m.start()].count('\n') + 1
|
||||
findings.append(Finding(
|
||||
'WARN', 'menu-icon',
|
||||
"top-level <menuitem> has no web_icon=. Will fall back to "
|
||||
"static/description/icon.png IF Odoo's auto-populate fires; "
|
||||
"if not, menu shows blank (incident #6). Set web_icon explicitly.",
|
||||
file=rel, line=line,
|
||||
))
|
||||
else:
|
||||
line = text[:m.start()].count('\n') + 1
|
||||
findings.append(Finding(
|
||||
'ERROR', 'menu-icon',
|
||||
"top-level <menuitem> has no web_icon= AND addon ships no "
|
||||
"static/description/icon.png — menu will render blank.",
|
||||
file=rel, line=line,
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Check 5 — no @odoo/hoot* imports in static/src/
|
||||
# ---------------------------------------------------------------------------- #
|
||||
def check_hoot_import(addon_dir: Path) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
src_dir = addon_dir / 'static' / 'src'
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
for js_file in src_dir.rglob('*.js'):
|
||||
try:
|
||||
text = js_file.read_text()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
rel = js_file.relative_to(addon_dir).as_posix()
|
||||
for m in HOOT_IMPORT_RE.finditer(text):
|
||||
line = text[:m.start()].count('\n') + 1
|
||||
findings.append(Finding(
|
||||
'ERROR', 'hoot-import',
|
||||
"imports from @odoo/hoot* in production code (static/src/). "
|
||||
"@odoo/hoot is the test framework; the production bundle does not "
|
||||
"register it. Page will white-screen (incident #3 class)",
|
||||
file=rel, line=line,
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Check 6 — webpack chunk arrays in static/lib/ must be addon-namespaced
|
||||
# ---------------------------------------------------------------------------- #
|
||||
def check_webpack_chunk(addon_dir: Path) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
lib_dir = addon_dir / 'static' / 'lib'
|
||||
if not lib_dir.exists():
|
||||
return findings
|
||||
addon_name = addon_dir.name
|
||||
seen: set[str] = set()
|
||||
for js_file in lib_dir.rglob('*.js'):
|
||||
try:
|
||||
text = js_file.read_text()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
rel = js_file.relative_to(addon_dir).as_posix()
|
||||
for m in WEBPACK_CHUNK_RE.finditer(text):
|
||||
chunk_name = m.group(1)
|
||||
if chunk_name in seen:
|
||||
continue
|
||||
seen.add(chunk_name)
|
||||
# Acceptable if chunk name contains: full addon name OR any 4+ char
|
||||
# sub-token of the addon name (e.g. 'ksdn' for 'ks_dashboard_ninja')
|
||||
# OR a known-namespaced suffix (anything past the standard library
|
||||
# prefix). We just need confidence the chunk array is unique-per-addon.
|
||||
addon_lower = addon_name.lower()
|
||||
chunk_lower = chunk_name.lower()
|
||||
tokens = [addon_lower.replace('_', '')] + [
|
||||
t for t in addon_lower.split('_') if len(t) >= 4
|
||||
]
|
||||
# Also accept any short 4+ char abbrev derived from initials of
|
||||
# underscore-separated parts (ks_dashboard_ninja -> ksdn)
|
||||
initials = ''.join(t[0] for t in addon_lower.split('_') if t)
|
||||
if len(initials) >= 3:
|
||||
tokens.append(initials)
|
||||
if any(t in chunk_lower for t in tokens):
|
||||
continue
|
||||
line = text[:m.start()].count('\n') + 1
|
||||
findings.append(Finding(
|
||||
'ERROR', 'webpack-chunk',
|
||||
f"uses bare webpack chunk array '{chunk_name}'. Two addons that ship "
|
||||
f"the same library (e.g. amCharts) collide on this global → bundle "
|
||||
f"execution aborts (incident #4). Rename to '{chunk_name}_{addon_name}' "
|
||||
"or similar.",
|
||||
file=rel, line=line,
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Runner
|
||||
# ---------------------------------------------------------------------------- #
|
||||
def qualify_addon(addon_dir: Path) -> dict:
|
||||
findings: list[Finding] = []
|
||||
|
||||
manifest_findings, manifest = check_manifest(addon_dir)
|
||||
findings.extend(manifest_findings)
|
||||
|
||||
if manifest is not None:
|
||||
findings.extend(check_pip_deps(addon_dir, manifest))
|
||||
|
||||
findings.extend(check_app_name(addon_dir))
|
||||
findings.extend(check_menu_icon(addon_dir))
|
||||
findings.extend(check_hoot_import(addon_dir))
|
||||
findings.extend(check_webpack_chunk(addon_dir))
|
||||
|
||||
errors = sum(1 for f in findings if f.severity == 'ERROR')
|
||||
warns = sum(1 for f in findings if f.severity == 'WARN')
|
||||
return {
|
||||
'addon': addon_dir.name,
|
||||
'path': str(addon_dir),
|
||||
'qualified': errors == 0,
|
||||
'errors': errors,
|
||||
'warns': warns,
|
||||
'findings': [asdict(f) for f in findings],
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
json_out = False
|
||||
args: list[str] = []
|
||||
for a in argv[1:]:
|
||||
if a == '--json':
|
||||
json_out = True
|
||||
elif a in ('-h', '--help'):
|
||||
print(__doc__)
|
||||
return 0
|
||||
else:
|
||||
args.append(a)
|
||||
if not args:
|
||||
print(__doc__, file=sys.stderr)
|
||||
return 2
|
||||
|
||||
results = []
|
||||
for path_str in args:
|
||||
path = Path(path_str).resolve()
|
||||
if not path.is_dir() or not (path / '__manifest__.py').exists():
|
||||
print(f'ERROR: {path} is not an Odoo addon directory '
|
||||
'(missing __manifest__.py)', file=sys.stderr)
|
||||
return 2
|
||||
results.append(qualify_addon(path))
|
||||
|
||||
if json_out:
|
||||
print(json.dumps(results, indent=2))
|
||||
else:
|
||||
for r in results:
|
||||
badge = '\033[32mQUALIFIED\033[0m' if r['qualified'] else '\033[31mFAILED\033[0m'
|
||||
print(f"\n{badge} {r['addon']} ({r['errors']} error(s), {r['warns']} warning(s))")
|
||||
if not r['findings']:
|
||||
continue
|
||||
for f in r['findings']:
|
||||
tag = '\033[31m' if f['severity'] == 'ERROR' else '\033[33m'
|
||||
loc = ''
|
||||
if f['file']:
|
||||
loc = f" [{f['file']}" + (f":{f['line']}" if f['line'] else '') + ']'
|
||||
print(f" {tag}{f['severity']:5}\033[0m {f['check']:<14} {f['message']}{loc}")
|
||||
|
||||
any_failed = any(not r['qualified'] for r in results)
|
||||
return 1 if any_failed else 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
40
.gitea/workflows/addon-qualify.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
# Pillar 1 of the addon-qualification proposal — runs on every push to any
|
||||
# branch and on every PR. Runs the vendored qualify-addon.py against every
|
||||
# addon directory in this repo.
|
||||
#
|
||||
# admit-with-warning posture: lint findings are reported but do NOT fail
|
||||
# the build (matches Pillar 3 informed-consent posture).
|
||||
#
|
||||
# To update the qualifier itself, edit scripts/qualify-addon.py in
|
||||
# odoo-tower/odooskyv3 then sync it here.
|
||||
name: addon-qualify
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
qualify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout addons repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run qualifier on every addon
|
||||
run: |
|
||||
set +e
|
||||
ADDONS=()
|
||||
for d in addons/*/; do
|
||||
[ -f "$d/__manifest__.py" ] && ADDONS+=("${d%/}")
|
||||
done
|
||||
if [ ${#ADDONS[@]} -eq 0 ]; then
|
||||
echo "No addons under addons/ — nothing to qualify"
|
||||
exit 0
|
||||
fi
|
||||
echo "Qualifying ${#ADDONS[@]} addons..."
|
||||
python3 .gitea/qualify-addon.py "${ADDONS[@]}"
|
||||
QUAL_RC=$?
|
||||
echo
|
||||
echo "::notice ::qualifier exit code $QUAL_RC (admit-with-warning — not failing build)"
|
||||
exit 0
|
||||
46
addons/accounting_pdf_reports/README.rst
Normal file
@@ -0,0 +1,46 @@
|
||||
====================================
|
||||
Odoo 19 Accounting Financial Reports
|
||||
====================================
|
||||
|
||||
This Module will provide all the financial reports for odoo 19
|
||||
community edition
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To install this module, you need to:
|
||||
|
||||
Download the module and add it to your Odoo addons folder. Afterward, log on to
|
||||
your Odoo server and go to the Apps menu. Trigger the debug mode and update the
|
||||
list by clicking on the "Update Apps List" link. Now install the module by
|
||||
clicking on the install button.
|
||||
|
||||
Upgrade
|
||||
============
|
||||
|
||||
To upgrade this module, you need to:
|
||||
|
||||
Download the module and add it to your Odoo addons folder. Restart the server
|
||||
and log on to your Odoo server. Select the Apps menu and upgrade the module by
|
||||
clicking on the upgrade button.
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
There is Nothing to Configure
|
||||
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Odoo Mates <odoomates@gmail.com>
|
||||
|
||||
|
||||
Author & Maintainer
|
||||
-------------------
|
||||
|
||||
This module is maintained by the Odoo Mates
|
||||
7
addons/accounting_pdf_reports/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from . import wizard
|
||||
from . import models
|
||||
from . import report
|
||||
|
||||
|
||||
def _pre_init_clean_m2m_models(env):
|
||||
env.cr.execute("""DROP TABLE IF EXISTS account_journal_account_report_partner_ledger_rel""")
|
||||
45
addons/accounting_pdf_reports/__manifest__.py
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
'name': 'Odoo 19 Accounting Financial Reports',
|
||||
'version': '19.0.1.0.2', # __odoosky_original_version__: '1.0.2'
|
||||
'category': 'Invoicing Management',
|
||||
'description': 'Accounting Reports For Odoo 19, Accounting Financial Reports, '
|
||||
'Odoo 19 Financial Reports',
|
||||
'summary': 'Accounting Reports For Odoo 19',
|
||||
'sequence': '1',
|
||||
'author': 'Odoo Mates, Odoo SA',
|
||||
'license': 'LGPL-3',
|
||||
'company': 'Odoo Mates',
|
||||
'maintainer': 'Odoo Mates',
|
||||
'support': 'odoomates@gmail.com',
|
||||
'website': 'https://www.youtube.com/watch?v=yA4NLwOLZms',
|
||||
'depends': ['account'],
|
||||
'live_test_url': 'https://www.youtube.com/watch?v=yA4NLwOLZms',
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/account_account_type.xml',
|
||||
'views/menu.xml',
|
||||
'views/ledger_menu.xml',
|
||||
'views/financial_report.xml',
|
||||
'views/settings.xml',
|
||||
'wizard/account_report_common_view.xml',
|
||||
'wizard/partner_ledger.xml',
|
||||
'wizard/general_ledger.xml',
|
||||
'wizard/trial_balance.xml',
|
||||
'wizard/balance_sheet.xml',
|
||||
'wizard/profit_and_loss.xml',
|
||||
'wizard/tax_report.xml',
|
||||
'wizard/aged_partner.xml',
|
||||
'wizard/journal_audit.xml',
|
||||
'report/report.xml',
|
||||
'report/report_partner_ledger.xml',
|
||||
'report/report_general_ledger.xml',
|
||||
'report/report_trial_balance.xml',
|
||||
'report/report_financial.xml',
|
||||
'report/report_tax.xml',
|
||||
'report/report_aged_partner.xml',
|
||||
'report/report_journal_audit.xml',
|
||||
'report/report_journal_entries.xml',
|
||||
],
|
||||
'pre_init_hook': '_pre_init_clean_m2m_models',
|
||||
'images': ['static/description/banner.gif'],
|
||||
}
|
||||
96
addons/accounting_pdf_reports/data/account_account_type.xml
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record model="account.account.type" id="data_account_type_receivable">
|
||||
<field name="name">Receivable</field>
|
||||
<field name="type">asset_receivable</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_payable">
|
||||
<field name="name">Payable</field>
|
||||
<field name="type">liability_payable</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_liquidity">
|
||||
<field name="name">Bank and Cash</field>
|
||||
<field name="type">asset_cash</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_credit_card">
|
||||
<field name="name">Credit Card</field>
|
||||
<field name="type">liability_credit_card</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_current_assets">
|
||||
<field name="name">Current Assets</field>
|
||||
<field name="type">asset_current</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_non_current_assets">
|
||||
<field name="name">Non-current Assets</field>
|
||||
<field name="type">asset_non_current</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_prepayments">
|
||||
<field name="name">Prepayments</field>
|
||||
<field name="type">asset_prepayments</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_fixed_assets">
|
||||
<field name="name">Fixed Assets</field>
|
||||
<field name="type">asset_fixed</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_current_liabilities">
|
||||
<field name="name">Current Liabilities</field>
|
||||
<field name="type">liability_current</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_non_current_liabilities">
|
||||
<field name="name">Non-current Liabilities</field>
|
||||
<field name="type">liability_non_current</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_equity">
|
||||
<field name="name">Equity</field>
|
||||
<field name="type">equity</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_unaffected_earnings">
|
||||
<field name="name">Current Year Earnings</field>
|
||||
<field name="type">equity_unaffected</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_revenue">
|
||||
<field name="name">Income</field>
|
||||
<field name="type">income</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_other_income">
|
||||
<field name="name">Other Income</field>
|
||||
<field name="type">income_other</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_expenses">
|
||||
<field name="name">Expenses</field>
|
||||
<field name="type">expense</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_depreciation">
|
||||
<field name="name">Depreciation</field>
|
||||
<field name="type">expense_depreciation</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_type_direct_costs">
|
||||
<field name="name">Cost of Revenue</field>
|
||||
<field name="type">expense_direct_cost</field>
|
||||
</record>
|
||||
|
||||
<record model="account.account.type" id="data_account_off_sheet">
|
||||
<field name="name">Off-Balance Sheet</field>
|
||||
<field name="type">off_balance</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
1101
addons/accounting_pdf_reports/i18n/ar.po
Normal file
1198
addons/accounting_pdf_reports/i18n/ar_001.po
Normal file
1192
addons/accounting_pdf_reports/i18n/ar_SY.po
Normal file
1038
addons/accounting_pdf_reports/i18n/de.po
Normal file
1201
addons/accounting_pdf_reports/i18n/es.po
Normal file
1349
addons/accounting_pdf_reports/i18n/es_AR.po
Normal file
1087
addons/accounting_pdf_reports/i18n/fr.po
Normal file
1173
addons/accounting_pdf_reports/i18n/tr.po
Normal file
1155
addons/accounting_pdf_reports/i18n/uk.po
Normal file
1391
addons/accounting_pdf_reports/i18n/zh_TW.po
Normal file
3
addons/accounting_pdf_reports/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import account_account_type
|
||||
from . import account_financial_report
|
||||
from . import account_move_line
|
||||
34
addons/accounting_pdf_reports/models/account_account_type.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from odoo import api, models, fields
|
||||
|
||||
|
||||
class AccountAccountType(models.Model):
|
||||
_name = "account.account.type"
|
||||
_description = "Account Account Type"
|
||||
|
||||
name = fields.Char('Name', required=True, translate=True)
|
||||
type = 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."
|
||||
)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
from odoo import api, models, fields
|
||||
|
||||
|
||||
class AccountFinancialReport(models.Model):
|
||||
_name = "account.financial.report"
|
||||
_description = "Account Report"
|
||||
|
||||
@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):
|
||||
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_account_financial_report_type',
|
||||
'report_id', 'account_type_id', 'Account Types'
|
||||
)
|
||||
report_domain = fields.Char(string="Report Domain")
|
||||
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').")
|
||||
children_ids = fields.One2many('account.financial.report', 'parent_id', string='Children')
|
||||
|
||||
117
addons/accounting_pdf_reports/models/account_move_line.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import ast
|
||||
from odoo.osv import expression
|
||||
from odoo import api, models, fields
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
@api.model
|
||||
def _where_calc(self, domain, active_test=True):
|
||||
"""Computes the WHERE clause needed to implement an OpenERP domain.
|
||||
|
||||
:param list domain: the domain to compute
|
||||
:param bool active_test: whether the default filtering of records with
|
||||
``active`` field set to ``False`` should be applied.
|
||||
:return: the query expressing the given domain as provided in domain
|
||||
:rtype: Query
|
||||
"""
|
||||
# if the object has an active field ('active', 'x_active'), filter out all
|
||||
# inactive records unless they were explicitly asked for
|
||||
if self._active_name and active_test and self.env.context.get('active_test', True):
|
||||
# the item[0] trick below works for domain items and '&'/'|'/'!'
|
||||
# operators too
|
||||
if not any(item[0] == self._active_name for item in domain):
|
||||
domain = [(self._active_name, '=', 1)] + domain
|
||||
|
||||
if domain:
|
||||
return expression.expression(domain, self).query
|
||||
else:
|
||||
return Query(self.env, self._table, self._table_sql)
|
||||
|
||||
@api.model
|
||||
def _apply_ir_rules(self, query, mode='read'):
|
||||
"""Add what's missing in ``query`` to implement all appropriate ir.rules
|
||||
(using the ``model_name``'s rules or the current model's rules if ``model_name`` is None)
|
||||
|
||||
:param query: the current query object
|
||||
"""
|
||||
if self.env.su:
|
||||
return
|
||||
|
||||
# apply main rules on the object
|
||||
Rule = self.env['ir.rule']
|
||||
domain = Rule._compute_domain(self._name, mode)
|
||||
if domain:
|
||||
expression.expression(domain, self.sudo(), self._table, query)
|
||||
|
||||
@api.model
|
||||
def _query_get(self, domain=None):
|
||||
self.check_access('read')
|
||||
|
||||
context = dict(self.env.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_distribution', '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._where_calc(domain)
|
||||
self._apply_ir_rules(query)
|
||||
from_string, from_params = query.from_clause
|
||||
where_string, where_params = query.where_clause
|
||||
tables, where_clause, where_clause_params = from_string, where_string, from_params + where_params
|
||||
return tables, where_clause, where_clause_params
|
||||
12
addons/accounting_pdf_reports/report/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from . import report_partner_ledger
|
||||
from . import report_general_ledger
|
||||
from . import report_trial_balance
|
||||
from . import report_tax
|
||||
from . import report_aged_partner
|
||||
from . import report_journal
|
||||
from . import report_financial
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
71
addons/accounting_pdf_reports/report/report.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<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">accounting_pdf_reports.report_general_ledger</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_general_ledger</field>
|
||||
</record>
|
||||
|
||||
<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">accounting_pdf_reports.report_partnerledger</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_partnerledger</field>
|
||||
</record>
|
||||
|
||||
|
||||
<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">accounting_pdf_reports.report_trialbalance</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_trialbalance</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_financial" model="ir.actions.report">
|
||||
<field name="name">Financial Report</field>
|
||||
<field name="model">account.financial.report</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">accounting_pdf_reports.report_financial</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_financial</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_account_tax" model="ir.actions.report">
|
||||
<field name="name">Tax Report</field>
|
||||
<field name="model">account.tax.report.wizard</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">accounting_pdf_reports.report_tax</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_tax</field>
|
||||
</record>
|
||||
|
||||
<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">accounting_pdf_reports.report_agedpartnerbalance</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_agedpartnerbalance</field>
|
||||
</record>
|
||||
|
||||
<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">accounting_pdf_reports.report_journal</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_journal</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_journal_entries" model="ir.actions.report">
|
||||
<field name="name">Journals Entries</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">accounting_pdf_reports.report_journal_entries</field>
|
||||
<field name="report_file">accounting_pdf_reports.report_journal_entries</field>
|
||||
<field name="binding_model_id" ref="account.model_account_move"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
263
addons/accounting_pdf_reports/report/report_aged_partner.py
Normal file
@@ -0,0 +1,263 @@
|
||||
import time
|
||||
from odoo import api, models, fields, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import float_is_zero
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class ReportAgedPartnerBalance(models.AbstractModel):
|
||||
_name = 'report.accounting_pdf_reports.report_agedpartnerbalance'
|
||||
_description = 'Aged Partner Balance Report'
|
||||
|
||||
def _get_partner_move_lines(self, account_type, partner_ids,
|
||||
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(str(date_from), "%Y-%m-%d")
|
||||
date_from = datetime.strptime(str(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.user.company_id
|
||||
user_currency = user_company.currency_id
|
||||
company_ids = self.env.context.get('company_ids') or [user_company.id]
|
||||
move_state = ['draft', 'posted']
|
||||
date = self.env.context.get('date') or fields.Date.today()
|
||||
company = self.env['res.company'].browse(self.env.context.get('company_id')) or self.env.company
|
||||
|
||||
if target_move == 'posted':
|
||||
move_state = ['posted']
|
||||
arg_list = (tuple(move_state), tuple(account_type))
|
||||
|
||||
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
|
||||
if not partner_ids:
|
||||
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 = line.company_id.currency_id._convert(line.balance,
|
||||
user_currency,
|
||||
company, date)
|
||||
if user_currency.is_zero(line_amount):
|
||||
continue
|
||||
for partial_line in line.matched_debit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_currency = partial_line.company_id.currency_id
|
||||
line_amount += line_currency._convert(partial_line.amount,
|
||||
user_currency,
|
||||
company, date)
|
||||
for partial_line in line.matched_credit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_currency = partial_line.company_id.currency_id
|
||||
line_amount -= line_currency._convert(partial_line.amount,
|
||||
user_currency,
|
||||
company, date)
|
||||
if not self.env.user.company_id.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_currency_id = line.company_id.currency_id
|
||||
line_amount = line_currency_id._convert(line.balance, user_currency, company, date)
|
||||
if user_currency.is_zero(line_amount):
|
||||
continue
|
||||
for partial_line in line.matched_debit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_currency_id = partial_line.company_id.currency_id
|
||||
line_amount += line_currency_id._convert(
|
||||
partial_line.amount, user_currency, company, date)
|
||||
for partial_line in line.matched_credit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_currency_id = partial_line.company_id.currency_id
|
||||
line_amount -= line_currency_id._convert(
|
||||
partial_line.amount, user_currency, company, date)
|
||||
if not self.env.user.company_id.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.user.company_id.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.user.company_id.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.env.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."))
|
||||
|
||||
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 = ['asset_receivable', 'liability_payable']
|
||||
partner_ids = data['form']['partner_ids']
|
||||
movelines, total, dummy = self._get_partner_move_lines(
|
||||
account_type, partner_ids, 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,
|
||||
}
|
||||
100
addons/accounting_pdf_reports/report/report_aged_partner.xml
Normal file
@@ -0,0 +1,100 @@
|
||||
<?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">
|
||||
<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-end">
|
||||
<span>Not due</span>
|
||||
</th>
|
||||
<th class="text-end"><span t-esc="data['4']['name']"/></th>
|
||||
<th class="text-end"><span t-esc="data['3']['name']"/></th>
|
||||
<th class="text-end"><span t-esc="data['2']['name']"/></th>
|
||||
<th class="text-end"><span t-esc="data['1']['name']"/></th>
|
||||
<th class="text-end"><span t-esc="data['0']['name']"/></th>
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
<tr t-if="get_partner_lines">
|
||||
<th>Account Total</th>
|
||||
<th class="text-end"><span t-esc="get_direction[6]" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></th>
|
||||
<th class="text-end"><span t-esc="get_direction[4]" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></th>
|
||||
<th class="text-end"><span t-esc="get_direction[3]" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></th>
|
||||
<th class="text-end"><span t-esc="get_direction[2]" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></th>
|
||||
<th class="text-end"><span t-esc="get_direction[1]" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></th>
|
||||
<th class="text-end"><span t-esc="get_direction[0]" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></th>
|
||||
<th class="text-end"><span t-esc="get_direction[5]" t-options="{'widget': 'monetary', 'display_currency': res_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-end">
|
||||
<span t-esc="partner['direction']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="partner['4']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="partner['3']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="partner['2']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="partner['1']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="partner['0']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="partner['total']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
163
addons/accounting_pdf_reports/report/report_financial.py
Normal file
@@ -0,0 +1,163 @@
|
||||
import time
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ReportFinancial(models.AbstractModel):
|
||||
_name = 'report.accounting_pdf_reports.report_financial'
|
||||
_description = 'Financial Reports'
|
||||
|
||||
def _compute_account_balance(self, accounts):
|
||||
""" compute the balance, debit and credit for the provided 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):
|
||||
'''returns a dictionary with key=the ID of a record and value=the credit, debit and balance amount
|
||||
computed for this record. If the record is of type :
|
||||
'accounts' : it's the sum of the linked accounts
|
||||
'account_type' : it's the sum of leaf accoutns with such an account_type
|
||||
'account_report' : it's the amount of the related report
|
||||
'sum' : it's the sum of the children of this record (aka a 'view' record)'''
|
||||
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 the linked accounts
|
||||
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 == '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.mapped('type'))])
|
||||
|
||||
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 report
|
||||
res2 = self._compute_report_balance(report.account_report_id)
|
||||
for key, value in res2.items():
|
||||
for field in fields:
|
||||
res[report.id][field] += value[field]
|
||||
elif report.type == 'sum':
|
||||
# it's the sum of the children of this account.report
|
||||
res2 = self._compute_report_balance(report.children_ids)
|
||||
for key, value in res2.items():
|
||||
for field in fields:
|
||||
res[report.id][field] += value[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'] * float(report.sign),
|
||||
'type': 'report',
|
||||
'level': bool(report.style_overwrite) and 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'] * float(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'):
|
||||
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'] * float(report.sign) or 0.0,
|
||||
'type': 'account',
|
||||
'level': report.display_detail == 'detail_with_hierarchy' and 4,
|
||||
'account_type': account.account_type,
|
||||
}
|
||||
if data['debit_credit']:
|
||||
vals['debit'] = value['debit']
|
||||
vals['credit'] = value['credit']
|
||||
if not self.env.company.currency_id.is_zero(vals['debit']) or not self.env.company.currency_id.is_zero(vals['credit']):
|
||||
flag = True
|
||||
if not self.env.company.currency_id.is_zero(vals['balance']):
|
||||
flag = True
|
||||
if data['enable_filter']:
|
||||
vals['balance_cmp'] = value['comp_bal'] * float(report.sign)
|
||||
if not self.env.company.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,
|
||||
}
|
||||
116
addons/accounting_pdf_reports/report/report_financial.xml
Normal file
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="report_financial">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<table class="table table-sm table-reports" t-if="data['debit_credit'] == 1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="text-end">Debit</th>
|
||||
<th class="text-end">Credit</th>
|
||||
<th class="text-end">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="get_account_lines" t-as="a">
|
||||
<t t-if="a['level'] != 0">
|
||||
<t t-if="int(a.get('level')) > 3"><t t-set="style" t-value="'font-weight: normal;'"/></t>
|
||||
<t t-if="not int(a.get('level')) > 3"><t t-set="style" t-value="'font-weight: bold;'"/></t>
|
||||
|
||||
<td>
|
||||
<span style="color: white;" t-esc="'..' * int(a.get('level', 0))"/>
|
||||
<span t-att-style="style" t-esc="a.get('name')"/>
|
||||
</td>
|
||||
<td class="text-end" style="white-space: text-nowrap;">
|
||||
<span t-att-style="style" t-esc="a.get('debit')" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end" style="white-space: text-nowrap;">
|
||||
<span t-att-style="style" t-esc="a.get('credit')" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end" style="white-space: text-nowrap;">
|
||||
<span t-att-style="style" t-esc="a.get('balance')" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="table table-sm table-reports" t-if="not data['enable_filter'] and not data['debit_credit']">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="text-end">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="get_account_lines" t-as="a">
|
||||
<t t-if="a['level'] != 0">
|
||||
<t t-if="int(a.get('level')) > 3"><t t-set="style" t-value="'font-weight: normal;'"/></t>
|
||||
<t t-if="not int(a.get('level')) > 3"><t t-set="style" t-value="'font-weight: bold;'"/></t>
|
||||
|
||||
<td>
|
||||
<span style="color: white;" t-esc="'..' * int(a.get('level', 0))"/>
|
||||
<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': res_company.currency_id}"/></td>
|
||||
</t>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="table table-sm table-reports" t-if="data['enable_filter'] == 1 and not data['debit_credit']">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="text-end">Balance</th>
|
||||
<th class="text-end"><span 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="int(a.get('level')) > 3"><t t-set="style" t-value="'font-weight: normal;'"/></t>
|
||||
<t t-if="not int(a.get('level')) > 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': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-att-style="style" t-esc="a.get('balance_cmp')" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
184
addons/accounting_pdf_reports/report/report_general_ledger.py
Normal file
@@ -0,0 +1,184 @@
|
||||
import time
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ReportGeneralLedger(models.AbstractModel):
|
||||
_name = 'report.accounting_pdf_reports.report_general_ledger'
|
||||
_description = 'General Ledger Report'
|
||||
|
||||
def _get_account_move_entry(self, accounts, analytic_account_ids,
|
||||
partner_ids, init_balance,
|
||||
sortby, display_account):
|
||||
"""
|
||||
:param:
|
||||
accounts: the recordset of accounts
|
||||
analytic_account_ids: the recordset of analytic 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:
|
||||
context = dict(self.env.context)
|
||||
context['date_from'] = self.env.context.get('date_from')
|
||||
context['date_to'] = False
|
||||
context['initial_bal'] = True
|
||||
if analytic_account_ids:
|
||||
context['analytic_account_ids'] = analytic_account_ids
|
||||
if partner_ids:
|
||||
context['partner_ids'] = partner_ids
|
||||
init_tables, init_where_clause, init_where_params = MoveLine.with_context(context)._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 analytic_account_id, '' 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 move_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
|
||||
context = dict(self.env.context)
|
||||
if analytic_account_ids:
|
||||
context['analytic_account_ids'] = analytic_account_ids
|
||||
if partner_ids:
|
||||
context['partner_ids'] = partner_ids
|
||||
tables, where_clause, where_params = MoveLine.with_context(context)._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, '' AS analytic_account_id,
|
||||
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:
|
||||
currency = account.currency_id and account.currency_id or self.env.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'])])]
|
||||
analytic_account_ids = False
|
||||
if data['form'].get('analytic_account_ids', False):
|
||||
analytic_account_ids = self.env['account.analytic.account'].search(
|
||||
[('id', 'in', data['form']['analytic_account_ids'])])
|
||||
partner_ids = False
|
||||
if data['form'].get('partner_ids', False):
|
||||
partner_ids = self.env['res.partner'].search(
|
||||
[('id', 'in', data['form']['partner_ids'])])
|
||||
if model == 'account.account':
|
||||
accounts = docs
|
||||
else:
|
||||
domain = []
|
||||
if data['form'].get('account_ids', False):
|
||||
domain.append(('id', 'in', data['form']['account_ids']))
|
||||
accounts = self.env['account.account'].search(domain)
|
||||
accounts_res = self.with_context(
|
||||
data['form'].get('used_context', {}))._get_account_move_entry(
|
||||
accounts,
|
||||
analytic_account_ids,
|
||||
partner_ids,
|
||||
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,
|
||||
'accounts': accounts,
|
||||
'partner_ids': partner_ids,
|
||||
'analytic_account_ids': analytic_account_ids,
|
||||
}
|
||||
124
addons/accounting_pdf_reports/report/report_general_ledger.xml
Normal file
@@ -0,0 +1,124 @@
|
||||
<?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">
|
||||
<h2><span t-esc="res_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>
|
||||
<t groups="analytic.group_analytic_accounting">
|
||||
<t t-if="analytic_account_ids">
|
||||
<div class="col-4">
|
||||
<strong>Analytic Accounts:</strong>
|
||||
<p t-esc="', '.join([aa.name or '' for aa in analytic_account_ids ])"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<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>
|
||||
<t groups="analytic.group_analytic_accounting">
|
||||
<th>Analytic Account</th>
|
||||
</t>
|
||||
<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>
|
||||
<t groups="analytic.group_analytic_accounting">
|
||||
<td></td>
|
||||
</t>
|
||||
<td class="text-end">
|
||||
<span t-esc="account['debit']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="account['credit']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="account['balance']" t-options="{'widget': 'monetary', 'display_currency': res_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>
|
||||
<t groups="analytic.group_analytic_accounting">
|
||||
<td><span t-esc="line['analytic_account_id']"/></td>
|
||||
</t>
|
||||
<td><span t-esc="line['lname']"/></td>
|
||||
<td class="text-end">
|
||||
<span t-esc="line['debit']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="line['credit']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="line['balance']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end" groups="base.group_multi_currency">
|
||||
<span t-esc="line['amount_currency'] if line['amount_currency'] and line['amount_currency'] > 0.00 else ''"/>
|
||||
<span t-esc="line['currency_code'] if line['amount_currency'] and line['amount_currency'] > 0.00 else ''"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
117
addons/accounting_pdf_reports/report/report_journal.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import time
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ReportJournal(models.AbstractModel):
|
||||
_name = 'report.accounting_pdf_reports.report_journal'
|
||||
_description = 'Journal Audit 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,
|
||||
}
|
||||
105
addons/accounting_pdf_reports/report/report_journal_audit.xml
Normal file
@@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="report_journal">
|
||||
<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-foreach="docs" t-as="o">
|
||||
<t t-call="web.internal_layout">
|
||||
<div class="page">
|
||||
<h2><t t-esc="o.name"/> Journal</h2>
|
||||
|
||||
<div class="row mt32">
|
||||
<div class="col-3">
|
||||
<strong>Company:</strong>
|
||||
<p t-esc="res_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 table-reports">
|
||||
<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': res_company.currency_id}"/></td>
|
||||
<td><span t-esc="aml.credit" t-options="{'widget': 'monetary', 'display_currency': res_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>
|
||||
<tr>
|
||||
<td><strong>Total</strong></td>
|
||||
<td><span t-esc="sum_debit(data, o)" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td>
|
||||
<td><span t-esc="sum_credit(data, o)" t-options="{'widget': 'monetary', 'display_currency': res_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': res_company.currency_id}"/></td>
|
||||
<td><span t-esc="taxes[tax]['tax_amount']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
108
addons/accounting_pdf_reports/report/report_journal_entries.xml
Normal file
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="report_journal_entries">
|
||||
<t t-call="web.html_container">
|
||||
<t t-call="web.external_layout">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<div class="page" style="font-size:15px;">
|
||||
<div>
|
||||
<h3>
|
||||
<span t-field="o.name"/>
|
||||
</h3>
|
||||
</div>
|
||||
<br></br>
|
||||
<div class="row">
|
||||
<table width="100%" class="table-bordered">
|
||||
|
||||
<tr>
|
||||
<td>Journal:
|
||||
<span t-field="o.journal_id.name"/>
|
||||
</td>
|
||||
<td>
|
||||
Date:
|
||||
<span t-field="o.date" t-options="{'widget': 'date'}"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Partner:
|
||||
<span t-field="o.partner_id.display_name"/>
|
||||
</td>
|
||||
<td>
|
||||
Reference:
|
||||
<span t-field="o.ref"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<br></br>
|
||||
<table width="100%" class="table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account</th>
|
||||
<th>Date</th>
|
||||
<th>Partner</th>
|
||||
<th>Label</th>
|
||||
<th>Analytic Account</th>
|
||||
<th>Debit</th>
|
||||
<th>Credit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-set="total_credit" t-value="0"/>
|
||||
<t t-set="total_debit" t-value="0"/>
|
||||
<t t-foreach="o.line_ids" t-as="line">
|
||||
<tr>
|
||||
<td>
|
||||
<span t-field="line.account_id.name"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="line.date" t-options="{'widget': 'date'}"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="line.partner_id.display_name"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="line.analytic_account_id.display_name"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="line.debit"
|
||||
t-options="{'widget': 'monetary', 'display_currency': line.currency_id}"/>
|
||||
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="line.credit"
|
||||
t-options="{'widget': 'monetary', 'display_currency': line.currency_id}"/>
|
||||
</td>
|
||||
<t t-set="total_credit" t-value="total_credit + line.credit"/>
|
||||
<t t-set="total_debit" t-value="total_debit + line.debit"/>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
<tfooter>
|
||||
<tr>
|
||||
<td colspan="5"></td>
|
||||
<td class="text-end">
|
||||
<span t-esc="total_debit"
|
||||
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="total_credit"
|
||||
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tfooter>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
122
addons/accounting_pdf_reports/report/report_partner_ledger.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import time
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ReportPartnerLedger(models.AbstractModel):
|
||||
_name = 'report.accounting_pdf_reports.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->>'en_US' 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()
|
||||
data['computed']['move_state'] = ['draft', 'posted']
|
||||
if data['form'].get('target_move', 'all') == 'posted':
|
||||
data['computed']['move_state'] = ['posted']
|
||||
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'] = ['asset_receivable', 'liability_payable']
|
||||
|
||||
self.env.cr.execute("""
|
||||
SELECT a.id
|
||||
FROM account_account a
|
||||
WHERE a.account_type IN %s
|
||||
AND a.active""", (tuple(data['computed']['ACCOUNT_TYPE']),))
|
||||
data['computed']['account_ids'] = [a for (a,) in self.env.cr.fetchall()]
|
||||
params = [tuple(data['computed']['move_state']), tuple(data['computed']['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
|
||||
self.env.cr.execute(query, tuple(params))
|
||||
if data['form']['partner_ids']:
|
||||
partner_ids = data['form']['partner_ids']
|
||||
else:
|
||||
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,
|
||||
}
|
||||
109
addons/accounting_pdf_reports/report/report_partner_ledger.xml
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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="res_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>Account</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="4">
|
||||
<strong t-esc="o.ref"/>
|
||||
-
|
||||
<strong t-esc="o.name"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<strong t-esc="sum_partner(data, o, 'debit')"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<strong t-esc="sum_partner(data, o, 'credit')"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<strong t-esc="sum_partner(data, o, 'debit - credit')"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_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['a_name']"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-esc="line['displayed_name']"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="line['debit']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="line['credit']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-esc="line['progress']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_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>
|
||||
70
addons/accounting_pdf_reports/report/report_tax.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ReportTax(models.AbstractModel):
|
||||
_name = 'report.accounting_pdf_reports.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}
|
||||
self.with_context(date_from=options['date_from'], date_to=options['date_to'],
|
||||
state=options['target_move'],
|
||||
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
|
||||
85
addons/accounting_pdf_reports/report/report_tax.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?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">
|
||||
<h3>Tax Report</h3>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<strong>Company:</strong>
|
||||
<p t-esc="res_company.name"/>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<t>
|
||||
<strong>Date from :</strong>
|
||||
<span t-esc="data['date_from']"/>
|
||||
</t>
|
||||
<br/>
|
||||
<t>
|
||||
<strong>Date to :</strong>
|
||||
<span t-esc="data['date_to']"/>
|
||||
</t>
|
||||
</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 align="left">
|
||||
<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': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-att-style="style" t-esc="line.get('tax')"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
<br/>
|
||||
<tr align="left">
|
||||
<td>
|
||||
<strong>Purchase</strong>
|
||||
</td>
|
||||
<td></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': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-att-style="style" t-esc="line.get('tax')"
|
||||
t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
90
addons/accounting_pdf_reports/report/report_trial_balance.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import time
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ReportTrialBalance(models.AbstractModel):
|
||||
_name = 'report.accounting_pdf_reports.report_trialbalance'
|
||||
_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'])
|
||||
currency = account.currency_id and account.currency_id or self.env.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([])
|
||||
context = data['form'].get('used_context')
|
||||
analytic_accounts = []
|
||||
if data['form'].get('analytic_account_ids'):
|
||||
analytic_account_ids = self.env['account.analytic.account'].browse(data['form'].get('analytic_account_ids'))
|
||||
context['analytic_account_ids'] = analytic_account_ids
|
||||
analytic_accounts = [account.name for account in analytic_account_ids]
|
||||
account_res = self.with_context(context)._get_accounts(accounts, 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'])])]
|
||||
return {
|
||||
'doc_ids': self.ids,
|
||||
'doc_model': model,
|
||||
'data': data['form'],
|
||||
'docs': docs,
|
||||
'print_journal': codes,
|
||||
'analytic_accounts': analytic_accounts,
|
||||
'time': time,
|
||||
'Accounts': account_res,
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="report_trialbalance">
|
||||
<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">
|
||||
<h2><span t-esc="res_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>
|
||||
<div class="row mt32">
|
||||
<div class="col-6">
|
||||
<strong>Journals:</strong>
|
||||
<p t-esc="', '.join([ lt or '' for lt in print_journal ])"/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<t t-if="analytic_accounts">
|
||||
<strong>Analytic Accounts:</strong>
|
||||
<p t-esc="', '.join([ analytic_account or '' for analytic_account in analytic_accounts ])"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-reports">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Account</th>
|
||||
<th class="text-end">Debit</th>
|
||||
<th class="text-end">Credit</th>
|
||||
<th class="text-end">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': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-att-style="style" t-esc="account['credit']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-att-style="style" t-esc="account['balance']" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
25
addons/accounting_pdf_reports/security/ir.model.access.csv
Normal file
@@ -0,0 +1,25 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_financial_report_accountant,access.account.financial.report.manager,model_account_financial_report,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_balance_report,access.account.balance.report,model_account_balance_report,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_invoice,1,1,1,1
|
||||
access_accounting_report,access.accounting.report,model_accounting_report,account.group_account_user,1,1,1,1
|
||||
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_tax_report,access.account.tax.report.wizard,model_account_tax_report_wizard,account.group_account_user,1,1,1,1
|
||||
|
||||
access_account_financial_report_accountant_bm,access.account.financial.report.bmanager,model_account_financial_report,account.group_account_manager,1,1,1,1
|
||||
access_account_report_general_ledger_bm,access.account.report.general.ledger.bmanager,model_account_report_general_ledger,account.group_account_manager,1,1,1,1
|
||||
access_account_balance_report_bm,access.account.balance.report.bmanager,model_account_balance_report,account.group_account_manager,1,1,1,1
|
||||
access_account_report_partner_ledger_bm,access.account.report.partner.ledger.bmanager,model_account_report_partner_ledger,account.group_account_manager,1,1,1,1
|
||||
access_accounting_report_bm,access.accounting.report.bmanager,model_accounting_report,account.group_account_manager,1,1,1,1
|
||||
access_account_aged_trial_balance_bm,access.account.aged.trial.balance.bmanager,model_account_aged_trial_balance,account.group_account_manager,1,1,1,1
|
||||
access_account_tax_report_bm,access.account.tax.report.wizard.bmanager,model_account_tax_report_wizard,account.group_account_manager,1,1,1,1
|
||||
access_account_print_journal_bm,access.account.account.print.journal.bmanager,model_account_print_journal,account.group_account_manager,1,1,1,1
|
||||
|
||||
access_account_common_journal_report,access.account.common.journal.report,model_account_common_journal_report,account.group_account_user,1,1,1,0
|
||||
access_account_print_journal,access.account.print.journal,model_account_print_journal,account.group_account_user,1,1,1,0
|
||||
|
||||
access_account_common_account_report,access_account_common_account_report,model_account_common_account_report,base.group_user,1,0,0,0
|
||||
access_account_common_partner_report,access_account_common_partner_report,model_account_common_partner_report,base.group_user,1,0,0,0
|
||||
access_account_common_report,access_account_common_report,accounting_pdf_reports.model_account_common_report,base.group_user,1,0,0,0
|
||||
access_account_account_type,access_account_account_type,accounting_pdf_reports.model_account_account_type,base.group_user,1,0,0,0
|
||||
|
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 34 KiB |
BIN
addons/accounting_pdf_reports/static/description/banner.gif
Normal file
|
After Width: | Height: | Size: 911 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 104 KiB |
BIN
addons/accounting_pdf_reports/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
155
addons/accounting_pdf_reports/static/description/index.html
Normal file
@@ -0,0 +1,155 @@
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="col-md-12">
|
||||
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Accounting Reports Odoo 18</b></h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div style="align:center;">
|
||||
<h1 style="text-align: center;">
|
||||
<span align="center" style="color:#148963;">
|
||||
<span class="fa fa-star fa-spin">
|
||||
</span>
|
||||
Added Financial Reports:</span>
|
||||
</h1>
|
||||
<div class="row" style="margin-top: 2rem;">
|
||||
<div class="col-lg-12">
|
||||
<div class="mt-3">
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">Partner Ledger Report.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">Journals Audit.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">General Ledger.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">Trial Balance.</span>
|
||||
</p><br/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12">
|
||||
<div class="mt-3">
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">Aged Partner Balance.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">Profit and Loss.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">Balance Sheet.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-check" style="color:green;font-size: 15px;">
|
||||
<span style="color:#000000;font-size: 15px;">Tax Report.</span>
|
||||
</p><br/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
</section>
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_centeralign oe_websiteonly">
|
||||
<h4 class="oe_slogan"><a href="https://www.youtube.com/watch?v=yA4NLwOLZms" target="_blank" style="color: #FFFFFF !important; border-radius: 0; background-color: #9c676e; border-color: #005ca7; padding: 15px; font-weight: bold;">
|
||||
<i class="fa fa-youtube">
|
||||
Watch on YouTube
|
||||
</i>
|
||||
</a></h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:olive;">Accounting Reports</h2>
|
||||
<h3 class="oe_slogan" style="color:#000066;font-size: 24px;">All in one financial reports for odoo community edition</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="account_reports.png" style="height:400px;">
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">General Ledger</h3>
|
||||
<h3 class="oe_slogan" style="color:#000066;font-size: 24px;">General ledger report with accounts, partners and analytic account filter</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="general_ledger_filter.png" style="height:400px;">
|
||||
</div>
|
||||
<br/>
|
||||
<h4 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Report</h4>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="general_ledger_report.png" style="height:400px;">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan" style="color:#1b1d26;">Partner Ledger</h3>
|
||||
<h3 class="oe_slogan" style="color:#000066;font-size: 24px;">Partner ledger report with partner filter.</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="partner_ledger_filter.png" style="height:400px;">
|
||||
</div>
|
||||
<br/>
|
||||
<h4 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Report</h4>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="partner_ledger_report.png" style="height:400px;">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan" style="color:#1b1d26;">Aged Partner Balance</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="aged_partner_balance_filter.png" style="height:400px;">
|
||||
</div>
|
||||
<br/>
|
||||
<h4 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Report</h4>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="aged_partner_balance_report.png" style="height:400px;">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<br/>
|
||||
|
||||
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
|
||||
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row ">
|
||||
<div class="oe_slogan text-center">
|
||||
<img src="odoo_mates.png"/>
|
||||
<div style="color:#269900;">
|
||||
<h3 style="color:#2C0091;font-size: 25px;">If you need any support or want more features, just contact us:</h3><br>
|
||||
<h3 style="color:#2C0091;font-size: 20px;">Email: <a href="odoomates@gmail.com">odoomates@gmail.com</a> <br></h3>
|
||||
</div>
|
||||
<div class="oe_slogan">
|
||||
<h2>
|
||||
<a target="_blank" href="https://www.facebook.com/odoomate/" target="new">
|
||||
<i class="fa fa-facebook-square" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a target="_blank" href="https://twitter.com/odoomates/" target="new">
|
||||
<i class="fa fa-twitter" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a href="#" target="_blank">
|
||||
<i class="fa fa-linkedin" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.youtube.com/channel/UCVKlUZP7HAhdQgs-9iTJklQ">
|
||||
<i class="fa fa-youtube-play" style="font-size:38px;"></i>
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
|
||||
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
|
||||
|
||||
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 147 KiB |
BIN
addons/accounting_pdf_reports/static/description/odoo_mates.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 87 KiB |
98
addons/accounting_pdf_reports/views/financial_report.xml
Normal file
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_financial_report_form" model="ir.ui.view">
|
||||
<field name="name">account.financial.report.form</field>
|
||||
<field name="model">account.financial.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Account Report">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type"/>
|
||||
<field name="sign"/>
|
||||
<field name="style_overwrite"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Report"
|
||||
invisible="type not in ['accounts', 'account_type', 'account_report']">
|
||||
<group>
|
||||
<field name="display_detail"
|
||||
invisible="type not in ['accounts', 'account_type', 'account_report']"/>
|
||||
<field name="account_report_id"
|
||||
invisible="type != 'account_report'"/>
|
||||
</group>
|
||||
<field name="account_ids" invisible="type != 'accounts'"/>
|
||||
<field name="account_type_ids" invisible="type != 'account_type'"/>
|
||||
</page>
|
||||
<page string="Childrens">
|
||||
<field name="children_ids" nolabel="1">
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_financial_report_tree" model="ir.ui.view">
|
||||
<field name="name">account.financial.report.list</field>
|
||||
<field name="model">account.financial.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Account Report">
|
||||
<field name="name"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
<field name="type"/>
|
||||
<field name="account_report_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_financial_report_search" model="ir.ui.view">
|
||||
<field name="name">account.financial.report.search</field>
|
||||
<field name="model">account.financial.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Account Report">
|
||||
<field name="name" string="Account Report"/>
|
||||
<field name="type"/>
|
||||
<field name="account_report_id"/>
|
||||
<filter string="Reports" name="filter_parent_id" domain="[('parent_id','=', False)]"/>
|
||||
<group>
|
||||
<filter name="parent_report" string="Parent Report"
|
||||
context="{'group_by':'parent_id'}"/>
|
||||
<filter name="report_type" string="Report Type" context="{'group_by':'type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_financial_report_tree" model="ir.actions.act_window">
|
||||
<field name="name">Financial Reports</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.financial.report</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_filter_parent_id': True}</field>
|
||||
<field name="search_view_id" ref="view_account_financial_report_search"/>
|
||||
<field name="view_id" ref="view_account_financial_report_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_finance_reports_settings"
|
||||
name="Financial Reports"
|
||||
sequence="9"
|
||||
groups="account.group_account_user,account.group_account_manager"
|
||||
parent="account.menu_finance_configuration"/>
|
||||
|
||||
<menuitem id="menu_account_reports"
|
||||
name="Account Reports"
|
||||
action="action_account_financial_report_tree"
|
||||
groups="account.group_account_user,account.group_account_manager"
|
||||
parent="menu_finance_reports_settings"/>
|
||||
|
||||
</odoo>
|
||||
|
||||
36
addons/accounting_pdf_reports/views/ledger_menu.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="action_account_moves_ledger_general" model="ir.actions.act_window">
|
||||
<field name="context">{'journal_type':'general', 'search_default_group_by_account': 1, 'search_default_posted':1}</field>
|
||||
<field name="name">General Ledger</field>
|
||||
<field name="res_model">account.move.line</field>
|
||||
<field name="domain">[('display_type', 'not in', ('line_section', 'line_note'))]</field>
|
||||
<field name="view_id" ref="account.view_move_line_tree_grouped_general"/>
|
||||
<field name="search_view_id" ref="account.view_account_move_line_filter"/>
|
||||
<field name="view_mode">list,pivot,graph</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_moves_ledger_partner" model="ir.actions.act_window">
|
||||
<field name="context">{'journal_type':'general', 'search_default_group_by_partner': 1,
|
||||
'search_default_posted':1, 'search_default_payable':1, 'search_default_receivable':1,
|
||||
'search_default_unreconciled':1}
|
||||
</field>
|
||||
<field name="name">Partner Ledger</field>
|
||||
<field name="res_model">account.move.line</field>
|
||||
<field name="domain">[('display_type', 'not in', ('line_section', 'line_note'))]</field>
|
||||
<field name="view_id" ref="account.view_move_line_tree_grouped_partner"/>
|
||||
<field name="search_view_id" ref="account.view_account_move_line_filter"/>
|
||||
<field name="view_mode">list,pivot,graph</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_finance_entries_accounting_ledgers" name="Ledgers" parent="account.menu_finance_entries"
|
||||
sequence="3">
|
||||
<menuitem id="menu_action_account_moves_ledger_general" action="action_account_moves_ledger_general"
|
||||
groups="account.group_account_readonly" sequence="1"/>
|
||||
<menuitem id="menu_action_account_moves_ledger_partner" action="action_account_moves_ledger_partner"
|
||||
groups="account.group_account_readonly" sequence="2"/>
|
||||
</menuitem>
|
||||
|
||||
</odoo>
|
||||
|
||||
24
addons/accounting_pdf_reports/views/menu.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<menuitem id="menu_finance_legal_statement"
|
||||
name="Financial Reports"
|
||||
sequence="10"
|
||||
parent="account.menu_finance_reports"/>
|
||||
|
||||
<menuitem id="menu_finance_partner_reports"
|
||||
name="Partner Reports"
|
||||
sequence="20"
|
||||
parent="account.menu_finance_reports"/>
|
||||
|
||||
<menuitem id="menu_finance_audit_reports"
|
||||
name="Audit Reports"
|
||||
sequence="30"
|
||||
parent="account.menu_finance_reports"/>
|
||||
|
||||
<record id="account.account_reports_management_menu" model="ir.ui.menu">
|
||||
<field name="sequence" eval="40"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
37
addons/accounting_pdf_reports/views/settings.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.accountant</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<app name="account" position="inside">
|
||||
<h2>Enhanced Financial Reports</h2>
|
||||
<div>
|
||||
<div class="row mt16 o_settings_container" name="report_setting_container">
|
||||
<div class="col-6 col-lg-6 o_setting_box" id="enhanced_reports">
|
||||
<div>
|
||||
Preview financial reports without downloading
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<a target="_blank" href="https://apps.odoo.com/apps/modules/19.0/om_accounting_reports/"
|
||||
style="text-decoration: underline;">Enhanced Financial Reports</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-6 o_setting_box" id="excel_reports">
|
||||
<div>
|
||||
Financial Reports in Excel
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<a target="_blank" href="https://apps.odoo.com/apps/modules/19.0/accounting_excel_reports/"
|
||||
style="text-decoration: underline;">Excel Reports</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</app>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
12
addons/accounting_pdf_reports/wizard/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from . import account_report_common
|
||||
from . import account_report_common_journal
|
||||
from . import account_report_print_journal
|
||||
from . import account_report
|
||||
from . import account_report_common_partner
|
||||
from . import account_report_common_account
|
||||
from . import account_partner_ledger
|
||||
from . import account_general_ledger
|
||||
from . import account_trial_balance
|
||||
from . import account_tax_report
|
||||
from . import aged_partner
|
||||
from . import account_journal_audit
|
||||
@@ -0,0 +1,35 @@
|
||||
from odoo import fields, models, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountReportGeneralLedger(models.TransientModel):
|
||||
_name = "account.report.general.ledger"
|
||||
_inherit = "account.common.account.report"
|
||||
_description = "General Ledger Report"
|
||||
|
||||
initial_balance = fields.Boolean(
|
||||
string='Include Initial Balances',
|
||||
help='If you selected date, this field allow you to add a row '
|
||||
'to display the amount of debit/credit/balance that precedes '
|
||||
'the filter you have set.'
|
||||
)
|
||||
sortby = fields.Selection(
|
||||
[('sort_date', 'Date'), ('sort_journal_partner', 'Journal & Partner')],
|
||||
string='Sort by', required=True, default='sort_date'
|
||||
)
|
||||
journal_ids = fields.Many2many(
|
||||
'account.journal', 'account_report_general_ledger_journal_rel',
|
||||
'account_id', 'journal_id', string='Journals', required=True
|
||||
)
|
||||
|
||||
def _get_report_data(self, data):
|
||||
data = self.pre_print_report(data)
|
||||
data['form'].update(self.read(['initial_balance', 'sortby'])[0])
|
||||
if data['form'].get('initial_balance') and not data['form'].get('date_from'):
|
||||
raise UserError(_("You must define a Start Date"))
|
||||
records = self.env[data['model']].browse(data.get('ids', []))
|
||||
return records, data
|
||||
|
||||
def _print_report(self, data):
|
||||
records, data = self._get_report_data(data)
|
||||
return self.env.ref('accounting_pdf_reports.action_report_general_ledger').with_context(landscape=True).report_action(records, data=data)
|
||||
@@ -0,0 +1,21 @@
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class AccountPrintJournal(models.TransientModel):
|
||||
_name = "account.print.journal"
|
||||
_inherit = "account.common.journal.report"
|
||||
_description = "Account Print Journal"
|
||||
|
||||
sort_selection = fields.Selection([('date', 'Date'), ('move_name', 'Journal Entry Number')],
|
||||
'Entries Sorted by', required=True, default='move_name')
|
||||
journal_ids = fields.Many2many('account.journal', string='Journals', required=True,
|
||||
default=lambda self: self.env['account.journal'].search([('type', 'in', ['sale', 'purchase'])]))
|
||||
|
||||
def _get_report_data(self, data):
|
||||
data = self.pre_print_report(data)
|
||||
data['form'].update({'sort_selection': self.sort_selection})
|
||||
return data
|
||||
|
||||
def _print_report(self, data):
|
||||
data = self._get_report_data(data)
|
||||
return self.env.ref('accounting_pdf_reports.action_report_journal').with_context(landscape=True).report_action(self, data=data)
|
||||
@@ -0,0 +1,24 @@
|
||||
from odoo import fields, models, api, _
|
||||
|
||||
|
||||
class AccountPartnerLedger(models.TransientModel):
|
||||
_name = "account.report.partner.ledger"
|
||||
_inherit = "account.common.partner.report"
|
||||
_description = "Account Partner Ledger"
|
||||
|
||||
amount_currency = fields.Boolean("With Currency",
|
||||
help="It adds the currency column on "
|
||||
"report if the currency differs from "
|
||||
"the company currency.")
|
||||
reconciled = fields.Boolean('Reconciled Entries')
|
||||
|
||||
def _get_report_data(self, data):
|
||||
data = self.pre_print_report(data)
|
||||
data['form'].update({'reconciled': self.reconciled,
|
||||
'amount_currency': self.amount_currency})
|
||||
return data
|
||||
|
||||
def _print_report(self, data):
|
||||
data = self._get_report_data(data)
|
||||
return self.env.ref('accounting_pdf_reports.action_report_partnerledger').with_context(landscape=True).\
|
||||
report_action(self, data=data)
|
||||
55
addons/accounting_pdf_reports/wizard/account_report.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountingReport(models.TransientModel):
|
||||
_name = "accounting.report"
|
||||
_inherit = "account.common.report"
|
||||
_description = "Accounting Report"
|
||||
|
||||
@api.model
|
||||
def _get_account_report(self):
|
||||
reports = []
|
||||
if self.env.context.get('active_id'):
|
||||
menu = self.env['ir.ui.menu'].browse(self.env.context.get('active_id')).name
|
||||
reports = self.env['account.financial.report'].search([('name', 'ilike', menu)])
|
||||
return reports and reports[0] or False
|
||||
|
||||
enable_filter = fields.Boolean(string='Enable Comparison')
|
||||
account_report_id = fields.Many2one('account.financial.report', string='Account Reports',
|
||||
required=True, default=_get_account_report)
|
||||
label_filter = fields.Char(string='Column Label', help="This label will be displayed on report to "
|
||||
"show the balance computed for the given comparison filter.")
|
||||
filter_cmp = fields.Selection([('filter_no', 'No Filters'), ('filter_date', 'Date')],
|
||||
string='Filter by', required=True, default='filter_no')
|
||||
date_from_cmp = fields.Date(string='Date From')
|
||||
date_to_cmp = fields.Date(string='Date To')
|
||||
debit_credit = fields.Boolean(string='Display Debit/Credit Columns',
|
||||
help="This option allows you to get more details about "
|
||||
"the way your balances are computed."
|
||||
" Because it is space consuming, we do not allow to"
|
||||
" use it while doing a comparison.")
|
||||
|
||||
def _build_comparison_context(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 ''
|
||||
if data['form']['filter_cmp'] == 'filter_date':
|
||||
result['date_from'] = data['form']['date_from_cmp']
|
||||
result['date_to'] = data['form']['date_to_cmp']
|
||||
result['strict_range'] = True
|
||||
return result
|
||||
|
||||
def check_report(self):
|
||||
res = super(AccountingReport, self).check_report()
|
||||
data = {}
|
||||
data['form'] = self.read(['account_report_id', 'date_from_cmp', 'date_to_cmp', 'journal_ids', 'filter_cmp', 'target_move'])[0]
|
||||
for field in ['account_report_id']:
|
||||
if isinstance(data['form'][field], tuple):
|
||||
data['form'][field] = data['form'][field][0]
|
||||
comparison_context = self._build_comparison_context(data)
|
||||
res['data']['form']['comparison_context'] = comparison_context
|
||||
return res
|
||||
|
||||
def _print_report(self, data):
|
||||
data['form'].update(self.read(['date_from_cmp', 'debit_credit', 'date_to_cmp', 'filter_cmp', 'account_report_id', 'enable_filter', 'label_filter', 'target_move'])[0])
|
||||
return self.env.ref('accounting_pdf_reports.action_report_financial').report_action(self, data=data, config=False)
|
||||
@@ -0,0 +1,52 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.misc import get_lang
|
||||
|
||||
|
||||
class AccountCommonReport(models.TransientModel):
|
||||
_name = "account.common.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):
|
||||
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):
|
||||
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)
|
||||
@@ -0,0 +1,26 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountCommonAccountReport(models.TransientModel):
|
||||
_name = 'account.common.account.report'
|
||||
_inherit = "account.common.report"
|
||||
_description = 'Account Common Account Report'
|
||||
|
||||
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')
|
||||
analytic_account_ids = fields.Many2many('account.analytic.account',
|
||||
string='Analytic Accounts')
|
||||
account_ids = fields.Many2many('account.account', string='Accounts')
|
||||
partner_ids = fields.Many2many('res.partner', string='Partners')
|
||||
|
||||
def pre_print_report(self, data):
|
||||
data['form'].update(self.read(['display_account'])[0])
|
||||
data['form'].update({
|
||||
'analytic_account_ids': self.analytic_account_ids.ids,
|
||||
'partner_ids': self.partner_ids.ids,
|
||||
'account_ids': self.account_ids.ids,
|
||||
})
|
||||
return data
|
||||
@@ -0,0 +1,13 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountCommonJournalReport(models.TransientModel):
|
||||
_name = 'account.common.journal.report'
|
||||
_description = 'Common Journal Report'
|
||||
_inherit = "account.common.report"
|
||||
|
||||
amount_currency = fields.Boolean('With Currency', help="Print Report with the currency column if the currency differs from the company currency.")
|
||||
|
||||
def pre_print_report(self, data):
|
||||
data['form'].update({'amount_currency': self.amount_currency})
|
||||
return data
|
||||
@@ -0,0 +1,18 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountingCommonPartnerReport(models.TransientModel):
|
||||
_name = 'account.common.partner.report'
|
||||
_inherit = "account.common.report"
|
||||
_description = 'Account Common Partner Report'
|
||||
|
||||
result_selection = fields.Selection([('customer', 'Receivable Accounts'),
|
||||
('supplier', 'Payable Accounts'),
|
||||
('customer_supplier', 'Receivable and Payable Accounts')
|
||||
], string="Partner's", required=True, default='customer')
|
||||
partner_ids = fields.Many2many('res.partner', string='Partners')
|
||||
|
||||
def pre_print_report(self, data):
|
||||
data['form'].update(self.read(['result_selection'])[0])
|
||||
data['form'].update({'partner_ids': self.partner_ids.ids})
|
||||
return data
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_common_report_view" model="ir.ui.view">
|
||||
<field name="name">Common Report</field>
|
||||
<field name="model">account.common.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>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,21 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountPrintJournal(models.TransientModel):
|
||||
_inherit = "account.common.journal.report"
|
||||
_name = "account.print.journal"
|
||||
_description = "Account Print Journal"
|
||||
|
||||
sort_selection = fields.Selection(
|
||||
[('date', 'Date'), ('move_name', 'Journal Entry Number')],
|
||||
'Entries Sorted by', required=True, default='move_name'
|
||||
)
|
||||
journal_ids = fields.Many2many(
|
||||
'account.journal', string='Journals', required=True,
|
||||
default=lambda self: self.env['account.journal'].search([('type', 'in', ['sale', 'purchase'])])
|
||||
)
|
||||
|
||||
def _print_report(self, data):
|
||||
data = self.pre_print_report(data)
|
||||
data['form'].update({'sort_selection': self.sort_selection})
|
||||
return self.env.ref('account.action_report_journal').with_context(landscape=True).report_action(self, data=data)
|
||||
20
addons/accounting_pdf_reports/wizard/account_tax_report.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from odoo import models, api, fields
|
||||
from datetime import date
|
||||
|
||||
|
||||
class AccountTaxReport(models.TransientModel):
|
||||
_name = 'account.tax.report.wizard'
|
||||
_inherit = "account.common.report"
|
||||
_description = 'Tax Report'
|
||||
|
||||
date_from = fields.Date(
|
||||
string='Date From', required=True,
|
||||
default=lambda self: fields.Date.to_string(date.today().replace(day=1))
|
||||
)
|
||||
date_to = fields.Date(
|
||||
string='Date To', required=True,
|
||||
default=lambda self: fields.Date.to_string(date.today())
|
||||
)
|
||||
|
||||
def _print_report(self, data):
|
||||
return self.env.ref('accounting_pdf_reports.action_report_account_tax').report_action(self, data=data)
|
||||
@@ -0,0 +1,26 @@
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class AccountBalanceReport(models.TransientModel):
|
||||
_name = 'account.balance.report'
|
||||
_inherit = "account.common.account.report"
|
||||
_description = 'Trial Balance Report'
|
||||
|
||||
journal_ids = fields.Many2many(
|
||||
'account.journal', 'account_balance_report_journal_rel',
|
||||
'account_id', 'journal_id',
|
||||
string='Journals', required=True, default=[]
|
||||
)
|
||||
analytic_account_ids = fields.Many2many(
|
||||
'account.analytic.account',
|
||||
'account_trial_balance_analytic_rel', string='Analytic Accounts'
|
||||
)
|
||||
|
||||
def _get_report_data(self, data):
|
||||
data = self.pre_print_report(data)
|
||||
records = self.env[data['model']].browse(data.get('ids', []))
|
||||
return records, data
|
||||
|
||||
def _print_report(self, data):
|
||||
records, data = self._get_report_data(data)
|
||||
return self.env.ref('accounting_pdf_reports.action_report_trial_balance').report_action(records, data=data)
|
||||
41
addons/accounting_pdf_reports/wizard/aged_partner.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import time
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountAgedTrialBalance(models.TransientModel):
|
||||
_name = 'account.aged.trial.balance'
|
||||
_inherit = 'account.common.partner.report'
|
||||
_description = 'Account Aged Trial balance Report'
|
||||
|
||||
period_length = fields.Integer(string='Period Length (days)', required=True, default=30)
|
||||
journal_ids = fields.Many2many('account.journal', string='Journals', required=True)
|
||||
date_from = fields.Date(default=lambda *a: time.strftime('%Y-%m-%d'))
|
||||
|
||||
def _get_report_data(self, data):
|
||||
res = {}
|
||||
data = self.pre_print_report(data)
|
||||
data['form'].update(self.read(['period_length'])[0])
|
||||
period_length = data['form']['period_length']
|
||||
if period_length <= 0:
|
||||
raise UserError(_('You must set a period length greater than 0.'))
|
||||
if not data['form']['date_from']:
|
||||
raise UserError(_('You must set a start date.'))
|
||||
start = data['form']['date_from']
|
||||
for i in range(5)[::-1]:
|
||||
stop = start - relativedelta(days=period_length - 1)
|
||||
res[str(i)] = {
|
||||
'name': (i != 0 and (str((5 - (i + 1)) * period_length) + '-' + str((5 - i) * period_length)) or (
|
||||
'+' + str(4 * period_length))),
|
||||
'stop': start.strftime('%Y-%m-%d'),
|
||||
'start': (i != 0 and stop.strftime('%Y-%m-%d') or False),
|
||||
}
|
||||
start = stop - relativedelta(days=1)
|
||||
data['form'].update(res)
|
||||
return data
|
||||
|
||||
def _print_report(self, data):
|
||||
data = self._get_report_data(data)
|
||||
return self.env.ref('accounting_pdf_reports.action_report_aged_partner_balance').\
|
||||
with_context(landscape=True).report_action(self, data=data)
|
||||
85
addons/accounting_pdf_reports/wizard/aged_partner.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_aged_balance_view" model="ir.ui.view">
|
||||
<field name="name">Aged Partner Balance</field>
|
||||
<field name="model">account.aged.trial.balance</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Report Options">
|
||||
<group col="4">
|
||||
<field name="date_from"/>
|
||||
<field name="period_length"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<newline/>
|
||||
<field name="result_selection" widget="radio"
|
||||
invisible="context.get('hide_result_selection')"/>
|
||||
<field name="target_move" widget="radio"/>
|
||||
</group>
|
||||
<field name="journal_ids" required="0" invisible="1"/>
|
||||
<xpath expr="//field[@name='journal_ids']" position="before">
|
||||
<group>
|
||||
<field name="partner_ids" widget="many2many_tags"
|
||||
options="{'no_open': True, 'no_create': True}"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<footer>
|
||||
<button name="check_report" class="oe_highlight"
|
||||
string="Print" type="object"/>
|
||||
<button string="Cancel" class="btn btn-default" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_aged_balance_view" model="ir.actions.act_window">
|
||||
<field name="name">Aged Partner Balance</field>
|
||||
<field name="res_model">account.aged.trial.balance</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="view_id" ref="account_aged_balance_view"/>
|
||||
<field name="context"></field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_aged_trial_balance"
|
||||
name="Aged Partner Balance"
|
||||
sequence="10"
|
||||
action="action_account_aged_balance_view"
|
||||
parent="menu_finance_partner_reports"/>
|
||||
|
||||
<record id="action_account_aged_receivable" model="ir.actions.act_window">
|
||||
<field name="name">Aged Receivable</field>
|
||||
<field name="res_model">account.aged.trial.balance</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="view_id" ref="account_aged_balance_view"/>
|
||||
<field name="context">{'default_result_selection': 'customer',
|
||||
'hide_result_selection': 1}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_aged_receivable"
|
||||
name="Aged Receivable"
|
||||
sequence="20"
|
||||
action="action_account_aged_receivable"
|
||||
parent="menu_finance_partner_reports"/>
|
||||
|
||||
|
||||
<record id="action_account_aged_payable" model="ir.actions.act_window">
|
||||
<field name="name">Aged Payable</field>
|
||||
<field name="res_model">account.aged.trial.balance</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="view_id" ref="account_aged_balance_view"/>
|
||||
<field name="context">{'default_result_selection': 'supplier',
|
||||
'hide_result_selection': 1}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_aged_payable"
|
||||
name="Aged Payable"
|
||||
sequence="30"
|
||||
action="action_account_aged_payable"
|
||||
parent="menu_finance_partner_reports"/>
|
||||
|
||||
</odoo>
|
||||
116
addons/accounting_pdf_reports/wizard/balance_sheet.xml
Normal file
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_financial_report_profitandloss0" model="account.financial.report">
|
||||
<field name="name">Profit and Loss</field>
|
||||
<field name="sign">-1</field>
|
||||
<field name="type">sum</field>
|
||||
</record>
|
||||
|
||||
<record id="account_financial_report_income0" model="account.financial.report">
|
||||
<field name="name">Income</field>
|
||||
<field name="sign">-1</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" eval="[(4,ref('accounting_pdf_reports.data_account_type_other_income')), (4,ref('accounting_pdf_reports.data_account_type_revenue'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="account_financial_report_expense0" model="account.financial.report">
|
||||
<field name="name">Expense</field>
|
||||
<field name="sign">-1</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" eval="[(4,ref('accounting_pdf_reports.data_account_type_expenses')),(4,ref('accounting_pdf_reports.data_account_type_direct_costs')), (4,ref('accounting_pdf_reports.data_account_type_depreciation'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="account_financial_report_balancesheet0" model="account.financial.report">
|
||||
<field name="name">Balance Sheet</field>
|
||||
<field name="type">sum</field>
|
||||
</record>
|
||||
|
||||
<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" eval="[(4,ref('accounting_pdf_reports.data_account_type_receivable')),
|
||||
(4,ref('accounting_pdf_reports.data_account_type_liquidity')), (4,ref('accounting_pdf_reports.data_account_type_current_assets')),
|
||||
(4,ref('accounting_pdf_reports.data_account_type_non_current_assets'), (4,ref('accounting_pdf_reports.data_account_type_prepayments'))),
|
||||
(4,ref('accounting_pdf_reports.data_account_type_fixed_assets'))]"/>
|
||||
|
||||
</record>
|
||||
|
||||
<record id="account_financial_report_liabilitysum0" model="account.financial.report">
|
||||
<field name="name">Liability</field>
|
||||
<field name="parent_id" ref="account_financial_report_balancesheet0"/>
|
||||
<field name="display_detail">no_detail</field>
|
||||
<field name="type">sum</field>
|
||||
</record>
|
||||
|
||||
<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" eval="[(4,ref('accounting_pdf_reports.data_account_type_payable')),
|
||||
(4,ref('accounting_pdf_reports.data_account_type_equity')), (4,ref('accounting_pdf_reports.data_account_type_current_liabilities')),
|
||||
(4,ref('accounting_pdf_reports.data_account_type_non_current_liabilities'))]"/>
|
||||
</record>
|
||||
|
||||
<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>
|
||||
|
||||
<record id="accounting_report_view" model="ir.ui.view">
|
||||
<field name="name">Accounting Report</field>
|
||||
<field name="model">accounting.report</field>
|
||||
<field name="inherit_id" ref="accounting_pdf_reports.account_common_report_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="target_move" position="before">
|
||||
<field name="account_report_id" domain="[('parent_id','=',False)]"/>
|
||||
</field>
|
||||
<field name="target_move" position="after">
|
||||
<field name="enable_filter"/>
|
||||
<field name="debit_credit" invisible="enable_filter == True"/>
|
||||
</field>
|
||||
<field name="journal_ids" position="after">
|
||||
<notebook tabpos="up" colspan="4">
|
||||
<page string="Comparison" name="comparison" invisible="enable_filter == False">
|
||||
<group>
|
||||
<field name="label_filter" required="enable_filter == True"/>
|
||||
<field name="filter_cmp"/>
|
||||
</group>
|
||||
<group string="Dates" invisible="filter_cmp != 'filter_date'">
|
||||
<field name="date_from_cmp" required="filter_cmp == 'filter_date'"/>
|
||||
<field name="date_to_cmp" required="filter_cmp == 'filter_date'"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_report_bs" model="ir.actions.act_window">
|
||||
<field name="name">Balance Sheet</field>
|
||||
<field name="res_model">accounting.report</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="accounting_report_view"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context" eval="{'default_account_report_id':ref('accounting_pdf_reports.account_financial_report_balancesheet0')}"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_account_report_bs"
|
||||
name="Balance Sheet"
|
||||
sequence="5"
|
||||
action="action_account_report_bs"
|
||||
parent="menu_finance_legal_statement"
|
||||
groups="account.group_account_user,account.group_account_manager"/>
|
||||
|
||||
</odoo>
|
||||
48
addons/accounting_pdf_reports/wizard/general_ledger.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_report_general_ledger_view" model="ir.ui.view">
|
||||
<field name="name">General Ledger</field>
|
||||
<field name="model">account.report.general.ledger</field>
|
||||
<field name="inherit_id" ref="accounting_pdf_reports.account_common_report_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='journal_ids']" position="after">
|
||||
<field name="analytic_account_ids" widget="many2many_tags"
|
||||
options="{'no_open': True, 'no_create': True}"
|
||||
invisible="1"
|
||||
groups="analytic.group_analytic_accounting"/>
|
||||
<field name="account_ids" widget="many2many_tags"
|
||||
options="{'no_open': True, 'no_create': True}"/>
|
||||
<field name="partner_ids" widget="many2many_tags"
|
||||
options="{'no_open': True, 'no_create': True}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='target_move']" position="after">
|
||||
<field name="sortby" widget="radio"/>
|
||||
<field name="display_account" widget="radio"/>
|
||||
<field name="initial_balance"/>
|
||||
<newline/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_general_ledger_menu" model="ir.actions.act_window">
|
||||
<field name="name">General Ledger</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.report.general.ledger</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="account_report_general_ledger_view"/>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="account.model_account_account" />
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_general_ledger"
|
||||
name="General Ledger"
|
||||
sequence="10"
|
||||
parent="menu_finance_audit_reports"
|
||||
action="action_account_general_ledger_menu"
|
||||
groups="account.group_account_user,account.group_account_manager"/>
|
||||
|
||||
</odoo>
|
||||
35
addons/accounting_pdf_reports/wizard/journal_audit.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_report_print_journal_view" model="ir.ui.view">
|
||||
<field name="name">Journals Audit</field>
|
||||
<field name="model">account.print.journal</field>
|
||||
<field name="inherit_id" ref="accounting_pdf_reports.account_common_report_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='target_move']" position="after">
|
||||
<field name="amount_currency" groups="base.group_multi_currency"/>
|
||||
<field name="sort_selection" widget="radio"/>
|
||||
<newline/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_print_journal_menu" model="ir.actions.act_window">
|
||||
<field name="name">Journals Audit</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.print.journal</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="account_report_print_journal_view"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_print_journal"
|
||||
name="Journals Audit"
|
||||
sequence="40"
|
||||
parent="menu_finance_audit_reports"
|
||||
action="action_account_print_journal_menu"
|
||||
groups="account.group_account_manager,account.group_account_user"/>
|
||||
|
||||
</odoo>
|
||||
62
addons/accounting_pdf_reports/wizard/partner_ledger.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_report_partner_ledger_view" model="ir.ui.view">
|
||||
<field name="name">Partner Ledger</field>
|
||||
<field name="model">account.report.partner.ledger</field>
|
||||
<field name="inherit_id" ref="accounting_pdf_reports.account_common_report_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='journal_ids']" position="before">
|
||||
<field name="partner_ids" widget="many2many_tags"
|
||||
options="{'no_open': True, 'no_create': True}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='target_move']" position="after">
|
||||
<field name="result_selection"/>
|
||||
<field name="amount_currency" groups="base.group_multi_currency"/>
|
||||
<newline/>
|
||||
<field name="reconciled"/>
|
||||
<newline/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_partner_ledger_menu" model="ir.actions.act_window">
|
||||
<field name="name">Partner Ledger</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.report.partner.ledger</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="account_report_partner_ledger_view"/>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="account.model_account_account" />
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_partner_ledger"
|
||||
name="Partner Ledger"
|
||||
sequence="5"
|
||||
parent="menu_finance_partner_reports"
|
||||
action="action_account_partner_ledger_menu"
|
||||
groups="account.group_account_invoice"/>
|
||||
|
||||
<!-- Add to Partner Print button -->
|
||||
<record id="action_partner_report_partnerledger" model="ir.actions.act_window">
|
||||
<field name="name">Balance Statement (Partner Ledger)</field>
|
||||
<field name="res_model">account.report.partner.ledger</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="account_report_partner_ledger_view" />
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="base.model_res_partner" />
|
||||
<field name="binding_type">report</field>
|
||||
<field name="context">{
|
||||
'default_partner_ids':active_ids,
|
||||
'default_target_move': 'posted',
|
||||
'default_result_selection': 'customer_supplier',
|
||||
'default_reconciled': True,
|
||||
'hide_partner':1,
|
||||
}</field>
|
||||
<field name="group_ids" eval="[(4, ref('account.group_account_invoice'))]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
21
addons/accounting_pdf_reports/wizard/profit_and_loss.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="action_account_report_pl" model="ir.actions.act_window">
|
||||
<field name="name">Profit and Loss</field>
|
||||
<field name="res_model">accounting.report</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="accounting_report_view"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context" eval="{'default_account_report_id':ref('accounting_pdf_reports.account_financial_report_profitandloss0')}"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_account_report_pl"
|
||||
name="Profit and Loss"
|
||||
sequence="6"
|
||||
action="action_account_report_pl"
|
||||
parent="accounting_pdf_reports.menu_finance_legal_statement"
|
||||
groups="account.group_account_user,account.group_account_manager"/>
|
||||
|
||||
</odoo>
|
||||
45
addons/accounting_pdf_reports/wizard/tax_report.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="accounting_tax_report_view" model="ir.ui.view">
|
||||
<field name="name">Tax Reports</field>
|
||||
<field name="model">account.tax.report.wizard</field>
|
||||
<field name="inherit_id" eval="False"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Report Options">
|
||||
<group>
|
||||
<group>
|
||||
<field name="target_move" widget="radio"/>
|
||||
<field name="date_from"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="date_to" />
|
||||
</group>
|
||||
</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>
|
||||
|
||||
<record id="action_account_tax_report" model="ir.actions.act_window">
|
||||
<field name="name">Tax Reports</field>
|
||||
<field name="res_model">account.tax.report.wizard</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="accounting_tax_report_view"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_account_report"
|
||||
name="Tax Report"
|
||||
sequence="30"
|
||||
action="action_account_tax_report"
|
||||
parent="menu_finance_audit_reports"
|
||||
groups="account.group_account_manager,account.group_account_user"/>
|
||||
|
||||
</odoo>
|
||||
41
addons/accounting_pdf_reports/wizard/trial_balance.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_report_balance_view" model="ir.ui.view">
|
||||
<field name="name">Trial Balance</field>
|
||||
<field name="model">account.balance.report</field>
|
||||
<field name="inherit_id" ref="accounting_pdf_reports.account_common_report_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='target_move']" position="after">
|
||||
<field name="display_account" widget="radio"/>
|
||||
<newline/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='journal_ids']" position="after">
|
||||
<field name="analytic_account_ids" widget="many2many_tags"
|
||||
invisible="1"
|
||||
options="{'no_open': True, 'no_create': True}"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_balance_menu" model="ir.actions.act_window">
|
||||
<field name="name">Trial Balance</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">account.balance.report</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="account_report_balance_view"/>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="account.model_account_account" />
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_general_balance_report"
|
||||
name="Trial Balance"
|
||||
sequence="20"
|
||||
parent="menu_finance_audit_reports"
|
||||
action="action_account_balance_menu"
|
||||
groups="account.group_account_user,account.group_account_manager"/>
|
||||
|
||||
</odoo>
|
||||
@@ -1,3 +0,0 @@
|
||||
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:54:00.548137
|
||||
from .pyarmor_runtime_009742 import __pyarmor__
|
||||
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00_\x04\x00\x00\x12\t\x05\x00\xe2\xf5bdff\x8b\xfd@x\xee\xd0\xcf\x91\x97E\x00\x00\x00\x00\x00\x00\x00\x007\t{\xa7Q\xfb#\x11\x9f\x7f\xd1\x7f\\\x9d\x84\xbd\xa4\x1bS\xed\xc1S\x12E\xce\xd4 \x0c\xbf\xf0P\xca\xdf\x88O<\xb3\x19\x16)\xaba\xce\xdc\xed\xc8\x9e\xf8\xd6\xb4T\xdb^\x02\xe9\x13\xd9\x05p\x86\xccr0#\x04\xb9\xc8 \'\xe9V\xe7\x03\xda\xec\xf2ol\x99\xb37pc\xc8\xaeip\xb8\xb0U\x03\x02\xb6+H\xda\xff\xb3j\xcf\xd6\x1e\xf7VU\x19\x90\xd9\x07e\xa9\xcc\xd6\x15T`\x88\x90B\xd3/\x10\xe4\xe6b\xa0\x19\x0c\x14\xf9\x1b\xd9]\xd6=\xb9\xc9U\xe1\xe4\xd6b-\x9f\xd7\x12\x13\xdd\xc6\xda\xa4\xd9\xa9\x8f3\x02\xda\xcd\xf0E*Z\xfd\xe2\xba\xd6T\x0eQ\xfd\'XM\xc2#i \xb8O!4\xab\xfd\xacS\xda^\x08\xcf\xb1\x9c\xa4\xcb\x00\x06\x89\xafI\xcd>F\x8e\x91.\xeb\x94+E\x89<w\x15\x86]p\x86rS;\xdc:\xf9\xdainE\x80"\x9fA3\xca\xfa\x96\xdev`\x06C\t\xf8\xa5.\x98\x04%\xd5\x8d\xc59\x892\x0c\xc9a\xbfqa\xc4\xac\x80\xe0\xab\xa6\xf9fHd\xa0\xbd\xfe?)\x0eQ\xca\xeeol\xa2\xf8\xd5\xb11\x95_\xda[]\x19\x82\xb4\xe0Iv\xb2\x95\xdcx\xb2\x11\x94\x9bL\x7f\xce\x9e\xb5O\xd4n\xc5?\xfaRJ\x06\xa5\xf5\x95\xe7\x85\xb3gg0\x80\x8b\x8b\x93\xbb\xdb\xed]\xcdc:\xeb\xaa,#?\xd0n\x86.\xd2\x17\x0b\x89\x89vGB\xf9\xdb\xe4\xe44\xa2\xc7&\xdf\xc0f\x140|s\xfe\x95\x1aT\x83\xb9\xe9\x8f\xbd\x94G\x07\xb0\xb3X[\x9d\xb1\x80\xb8\xbd\xa7\x8dGiH\xf9\x92\xaa\xed\xa1\x12\x06Y\xb1\xa4\x83\x07\x8c\xe8E\x87^\xaca[\xf0\xf8\xc4I\xeb\x1dN8mW\xb0\x011\n/X\x8e9\xd8\x8c\x11\\\x15"\xb9%-\xb7\x84\xcd\xa6\x1d\xc1\xc3]\x1b\xaf\xa9\xba\xab\xfd\x99\xf1`\xf3-\x07\x8f\x07,\x89\x9b\x11\x8e\r\xfc\xa8\x8bG3\xd9\xfaF\xf5-6\xfa\xdd(\xcf\xc4J\x0f\x9e6m\xb2\'\x9e]8u\xaf+o~\xdeS`M\xf5\xd4\\\x98\x88\x02\x9d\x87U\xa1\xc3q\xab\r\xd5\x81h\xc1v*\x18\xaf\xdb\xc8v\x83\x19G6\x08\x1fA\x7f\xccK\x0b\xd0\x89G\xc7\xe3\xfb\xa9\x93\xb6\xc1D\xb2\xc9\x9d6\n|\xbd\x00\x0e\xec\xe2\xcf\x7f\x86\xdd\xd2\x82\x99\xa1\x00<~\xe1_\x1c8k\xcf\xef\xa3d\xdd\xef-IK\x10\x17\xc0\xc9C?\x02x\x8ey 2u\xda\x8eDAh\xa2Ah\x9a\xfc\xfb\\w"_\t\x12\xae\xfcZG\x01\xcc\xe1\x1c\xab\x98k?\x8e8\xe3\x94+\x1b\n\xf9D\xec\xdd\xf0\x12\xeb\x0b\xe4=}\x88\x97\x8c\xf3\xa7\xc8\x18\x99\xd2>\t\xf9e\xea\xa1FxD+c\xe4V\x14\xe74\xd2\x8e\xf9\',\xe1\xfck,\x14:0\xba?q\x10\xdc\xee;Y\xa7\x1e\x14\x93h\xc5xg]F\x06J\xae\xfa\xa5c\x91n\t\x0c#\x1f+>\xa9\xd8\x03kB\xe6\xb3\x8cyc\xdc!\x92\xfa\x1da\xf1K\x85t$\xd2\xf6\xb2\x8b\x0bH_\xacSq\xbe\xb4\xb3yVq\xe3\xbb\x8c.)\\\xf2\xc6\xb2\x1d>y\xbf\xea-b\xe6l\r\x9cPnTu`\x13!\x14\x8bj\x94N\xf4\x94\x16\x19\x8bih\xca.G\x19Ys\x01\xb1\x15\xce\xfbk\xd0\xe6\xd6\xa8n\x8eK+\x9f\x85\xc7\x02\xc4"T\xd7\x98s\x08tw:\xfa\x08\xcd\xc9F\xa1Hr\x03\xf6u\x8f\xcdE\xdbdb\xe6\xfd\x8a[VI1I\x8e\x9f\xf7\xc3\x1a5\xeb\xa0\xa5a0\xb1\xf5je\x0e\xf3\xebe\xf0\xb0\x83b{S/\xebO\xf3\xd2p\xb5<\xe1\xbb\xd5\xf4\xcds\xbdwO \xc9\xbfA\xa1\xfbT`\xf4<\x10\x02\xb1-\x88\xe9H\xcd\x19d\xbdEP\x17\xde\x1a\xe4/\xd0\xb7\xbf\xf1m\x12U\xb7zD\x03\xbf\xd5\xf1\x80/\xa1\x1d\xa0\n,\xdb>vk\x1e\xd3\xb3\xe9\xe8b\x01\xf9U\xad\xad4\xbd%\xeb}\x83\xb5\x1e\xdfo\x0e~6\xa9\xbcE\xab\xc9\xd9\x93@5\xe5T\x05\x82\x03\xcf\xa4@\xa0\xe9\xbc\xc0\x9b\x04\xf3\x90K\xe9]6\x96\xa4\x8c\xff9m35\x03\x16\xdc\x91} c\x89r\xed0\xea\xc6\x1f\x06#\xbf\x8e\xd9\xb8\x9b9l\x1a8\xb8\xb5+\x1a\xdd@x3\xb0*\xe8\xc0F[bG\x9d\x86\xd0n\x0b.\x06\x8a\xd3-U\xdd>\xb7\xc23D\n\\\x7fY\xc1\x80\xbe7\xd4\x85\xf9\xdf\xf5\xa3\xd7\xeb\x92\xdd:\xbc\xf7e %BK4\x95\x9c\t\xe5\x01N\xfa\x7f\x16\x82"_\xb0\x0c$\xf2f1h\xd6\xb4\xd8@\x0f\x8e\xa8')
|
||||
@@ -1,58 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Odoo Arabic Fonts',
|
||||
'version': '16.0.2.0.0',
|
||||
'summary': 'إدارة موحدة للخطوط العربية - 6 خطوط',
|
||||
'description': '''
|
||||
Odoo Arabic Fonts - إضافة لتحسين الخطوط العربية في أودوو
|
||||
========================================================
|
||||
|
||||
المميزات:
|
||||
---------
|
||||
* 6 خطوط عربية احترافية:
|
||||
- دبي (Dubai) - عصري
|
||||
- الجزيرة (Al Jazeera) - إخباري
|
||||
- القاهرة (Cairo) - احترافي
|
||||
- تجوال (Tajawal) - أنيق
|
||||
- أميري (Amiri) - نسخي تقليدي
|
||||
- المراعي (Almarai) - سعودي احترافي
|
||||
* إعدادات مرنة لأحجام الخطوط
|
||||
* ثلاثة أنماط جاهزة (مدمج - متوازن - مريح)
|
||||
* دعم خطوط الطباعة والتقارير
|
||||
* تحسين واجهة المستخدم للغة العربية
|
||||
* توحيد إدارة الخطوط للباك إند والتقارير
|
||||
''',
|
||||
'category': 'Tools',
|
||||
'author': 'Mostafa Elhavari',
|
||||
'website': 'https://havari.me',
|
||||
'maintainer': 'Mostafa Elhavari <m@havari.me>',
|
||||
'support': 'm@havari.me',
|
||||
'license': 'LGPL-3',
|
||||
# Developer Contact: +90 543 774 3103 (WhatsApp)
|
||||
'depends': ['base', 'web', 'havari_license_client'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/assets.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/base_document_layout_views.xml',
|
||||
'report/report_templates.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'havari_arabic_fonts/static/src/scss/fonts.scss',
|
||||
'havari_arabic_fonts/static/src/scss/variables.scss',
|
||||
'havari_arabic_fonts/static/src/scss/backend.scss',
|
||||
'havari_arabic_fonts/static/src/scss/presets/balanced.scss',
|
||||
'havari_arabic_fonts/static/src/js/font_settings.js',
|
||||
'havari_arabic_fonts/static/src/js/font_preview_simple.js',
|
||||
],
|
||||
'web.report_assets_common': [
|
||||
'havari_arabic_fonts/static/src/scss/fonts.scss',
|
||||
'havari_arabic_fonts/static/src/scss/report.scss',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
'post_init_hook': '_register_license',
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:54:00.569512
|
||||
from ..pyarmor_runtime_009742 import __pyarmor__
|
||||
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\xee\x01\x00\x00\x12\t\x05\x00\x10;\xe8\x1eTQ\xc6\xb2\xaf!\xa74\xdf\xcfb\xed\x00\x00\x00\x00\x00\x00\x00\x00\xe7Z+\xff}\xef\xcc\x08\x82\\h\xeei\xc2\x08C\xba<\x94\x0fW\xd4\xc3\xf7\xf2,\x17E\xe7\xee\x89\x9fW\xc3H\xba\xb7\x98\xe5\xef~I\xb1H\xf0\xd6s\xeb\x1e!I\xcd\xd1{\x16\x14\xeb\xf7@XW\xe9p\x1eXu\xed\xe7\xbb\x1d\xa9\xb5B\x1c\xf2\xa0\x19\xc1s9\xf4\xce\x119\xd3\x0c\xb4\x13P\x85z\xd9\x8b\xdc\xf1\x8c\xa4\x83U{\xad\xf8a@H\xdb}!|z\x80^\x8dy\'A\xf1\xaf\xf8\x81\x08\x03\xf6\xa4\x9c\xb8h\xbb\x17\x8c\xf9\xe7\xc0\xc1\x9cuCJX~\x10\xcam1F~kb\x12\xb5\xedf\x80\xd8s\xc8C\x13o\xbbx\xecG\xcc\xc8\x035\xf6\xd9\xd9\xd7\x80\x8ad\x87y\x86k\xef\xe0\xbc\xde\x08\xbe!\xaeV\x7f\xd4\xa3\xcf\x1fi(\r\x92\xc0\x9bW\xfc%\\;50\x85\xc7\xd6D\xa4\x96&\xec\x88\xbb\xefu4O\xb4w;\xaav\xe6\xc61\x13>q\x8c,Lu\xefu\xdf\xd9\xd9\x11bZ\xb6f\xf5\x0c\xc3\xbc\xf4\x9dE\x1cI\xd2&\xfb\xd9\x8d\x03\x05!a\xb1\xbe\x10A\x8e\xb5\x81\x0e\xe6\xe2\r)\xd5\xf3\x12\x99\x07\xb0\x8f\xcb=:\x9f\xec\xa0B\x19\xe2\x1fJ\\yq\xb5\xfa\r\xd3\xe1\'W\xfaI\x16Ij\xaa*\xd2\x9c\xeb\xf4\x81\xedJ\x80\t+\x89\xa8\xb5\x9e-\xbe\xbf\xde)\xf2W\xeb\xd7\xf4UF\xfe\x1d\x7fX\xb9[^\x14\x8a;\xf1\xacsr\xe0\xd5\x92dy\xf8T\xd9s\xad2Z\'\xa9h\xb5\xb6\x81\xeb] \xfe\x88\x8d\xc5\x99\xcd\xb5C\xdc;l\xe5\xf8x?\xa6A\xc85\xa4\xa1\xe3\xc3n\x12\x14Q1\n\xc1|\xf7\x87VS5\xb9\x00S\xd1\xe6a\xc0\xd9+\xca1\x92(\x1d\x93\x7fdD\x0c\x80\x93W\xe3\xec\x94\xcaI\xda\x19}\x7fVT\xf2\xa5\xd5\x96\xd6\x1e#\x03b\xa2\xc1\x85"\x86\xd6_\x02\xca[\x16\xcf\x1c\x92tN\xf4Dd\x8ej4s\xe5\xd0\x856h<\x1a\x83\xfc\x85\xb3^\x99\xa3\x92P\x96\x9bQ\x8d\x9f\\$')
|
||||
@@ -1,3 +0,0 @@
|
||||
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:54:00.562657
|
||||
from ..pyarmor_runtime_009742 import __pyarmor__
|
||||
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\xbd\x03\x00\x00\x12\t\x05\x00NR}\xb1\xe1\xf4Q\xf5\xa18\xbf%\xd8>\xabq\x00\x00\x00\x00\x00\x00\x00\x00\xe3\x1d5J_\'\xd8-\x14\x12M5&X[\xa0\r(\x9c2\nD\x0f\x12\x03\x17\xcb,2\xcdHl\xfcj\xba\xb5\x8dE\x13\xaepw\x1bN\x8b\xd2\x8dW6s\xe5\xd5\xa2\x11fs\xdfx\x8c\'\x83B\xb8\x93cQ\xdaQ\x05[\xafl\x81]I\xd9^t\xe7\xb8\xfb^\x04Z\xf6\xd5\xbdo\x8f\xdfuI\xf6k\xe6\x1fJ\xde\xbc\xd7\xdf\xf1{a\xcbI\x91\xfd\xfd\x904\xb6\x85:nQ\xde\x13\xd3o\xbaqi\xc4^0Q\xc2J\x1e\x0fo\x83(d\xba--\x97A\x1b\x14q\xc6\x05\xa4\x06\xda}\x12\x00\xa7q\xf3\x7f\x07\xf3\xba\x8dM\xbekd-\x1d\xf1\xa7\xd5\x03-T\xc8\x1b"\x0f\x10$\xfb\x08\x98\xa7\x10\xbeLy\xa915\x07\x1c\xd5\x91OSU\x80\xc1\xba\xcd\x00\x8a\xe9 \xcc;\xfa\x9bIQ<\xca{\n\xf2\xa2J\x0f\xd4\xa5\x8e\xbbV\xf5\xa2H\x9eeL?\x0c\xfa\xa4Sj\x00\xffF!\x0f\x90\xb5T\xe3\x17\x02\x057\x13\x00\xe2\x95\xff\xaf=\x10\xa9\x17\xad%u\xcb\x7f8\x88(\xedk\xfe\x12\xf4C\x9cR_\xa7\xb4\xe2N\x0cNCD\xcb>P\xc4Fv\x03\'\xd3\x14@\x893\x94\x1f\xc8\xf4U\x99e\xcb\x9a\xae((\xebR\x08;E\xbd\x91u\xd8!m\xeb\x8f2\xb0\xda\xad\xe9\x83[Z\x944Hc\x87\xf8\x9csK#[\'m\xd4m\xac\x88\x9c\xea\xfbmu\x83`\x01\x81\xdf\xc9m\xa7\xec\xf1\x87r\xfbj\xcd_\xed\xa2\x08\xf4\xcd\x06\xb9\x08\xd5B\x89\xa6g\xf8Rz\xe4\x1d\xab?cw\x11\xea\xadl6\x92\x0e*\xb6\xdc4\xd0U\xd6X\xbf&R}\x08_\x13\xb9\xa3:[lH^\x83\xa9\x01J\xab\x8aB:F2\xd9\xcd\xf7\x82\xec\x82\xd0\xdbx[\x95\x99\xa9\xfb=\x0c\r\x90k\xd6\x07\xd5v\xd0\x97\xed\xe5Z\x1dJ\xc6\xa8z?\x08J?\x15\x98\x80\xd8 \x8dq\\\xb8\'\xfa\xe6e(\x99\x13b\x95\xee\xf9\xfbc\x05\xf1\xba\x8dD\xd6\xbf\xad\x1a\xd8X\xdf/\xae\xf8v\x1b7\x8c\x1d\xd8\xf5\x92$db/\x03wx\x98)\xdee\xb5\r\x19cf\xe0lM\x89+xl{\x81O\xe1\'\xa4\x8d\xab\x1c,k\x89\xe7\x16\xa2\x1b\x00\xb42\x950\xe4+e\x16\x9c\xaa%\xa2\x0bj{D\x0cm\x10\x8fq\xd2\xa9\xe6ax\xff^!&F\x1b\xc1Y\xba\xbcB\xc2\xe3qm\xf4+\xe8m\xf26l\x01\xd73\x91\x10&\xf4\xab\x0e\x99>\x1c\x88\xcdG_F\xacA]~b\x02F\xd1\xea[\x87\x84E\x1c\xdb\x18\xb47b6\x8ehv\x022d\x02\xf6b\xc4\xd8vU\xa6^\x1d\xb7\xff\xd1\x87\xd3R\x8b\xa9\xabvx\x81\xe6\xb8:%2{]p\xc0y*\x90\xfd\xf5)\x1b\xfdE\x1d\xfd\xd2~\rRC,\xb2\xe7%\x1d\x83\'\xe5\xbf\x83\x82\x11\xf3\xb6z\x80u\xe5\x12\\\x07\xa1\x91\x11\xd1\x1dj\xd2[x\x91\x01\xc4\xb67\xff\x99\xc6\n^7\\\xf4\xee\x05\xe1~R\x8d_1\xb0\xd2\xe9\x04g\x05\x85\x90\xd9\x1d\x7f\xd5]\xd1\x7f\x8fJ\xb3P\x00H\xf8\xdb\x10\xf6\xa3\xe0\xa2\x13a\xdf\xe39\\\x15\x97\xfa\xbd`U\xb7\x88\x87\xef\x00\xc1\x84\xb3\x87\nH#\xc8\xbf\xd1\x10\xa1\xca\xe4\xddm@\xd5\xb4D\x98GRN\xb9\xf1x=&D\xd1vY\xf1F{\xf1\xe7\xf4j+\x19Aj\xb1\xc5\xcdZ]:\xdd\xeb\\h\x87\xa9\xcey0\xc1\x18\x03\x82\xbd\r[\x04(o\xdd\xf0\xf2\x98\xaf\x11\x8et\xc7*\xbb\xf3;s\x86\r\xa9-,%\xc1\x7f.B;\xe1B\x80\xf5\xfai$S\xefe\x9c\xefU\xd7\x9a\xe2\xdd.\x10\xd8\x88\x18`\xb2\xf2)\xfd\xeb\x08^\x87S\x8c\xa23\x1eM\xf8/m]h\x8aP\xe5\x88\x94A\x12V\xf7S\xc8\x13q2+\xda!n6\xf0\xb3x\xdb\xc5\x90;\x16\xd9\x93\xbc\xf5\xe9.!\x8f\x96\xae@')
|
||||
@@ -1,3 +0,0 @@
|
||||
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:54:00.576354
|
||||
from ..pyarmor_runtime_009742 import __pyarmor__
|
||||
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\x0c\x03\x00\x00\x12\t\x05\x00\xd0\xdb\xb0v\xde\x07j}[\xe6\xb3\xd4\x9d\x0c\xad4\x00\x00\x00\x00\x00\x00\x00\x00\xb9\x8f\xde#\x94\xc5\xce<\xb7\x0c50\xcax\xefC\xe1[\xb6.<~\xb8M\xd3\x0b{e\x96\x9e\x87\xc1{H\x01Vo\xd6\x8f\xdd\xe6\xd8\xe4\xe3\x9e?X\xa2\xe9\xa3$Q\xfa\xab\xdeg\\d1%\xb8j\xb5C\xc0^\x8f\xddV\xe9a\xa8K\x19\xe3+\x94[\x8e\xbd\xe7\xb7\xa2\xc3\xf6\xab\x01f7\x18\xaau\x91\xd2\xb4&\x95\x1c:\x19i^\xf9Z\xe6!\x8e6\t\x01\x0e\xa7\x9a\x1a:\x7fqqOj\x06\xad)g\x11\xbc\x00\xa3\xc4O\x06\x82\x089\x96`\x1e\x00\xcfh\xe5S\x91\x83\xf1+\xe1\xacmOk\xdd0\xc5n\x1b\x92\xca\xc4\xfb\xd5Z\x00d\x0f\x0fj\x81\xff6\xcf\xf7\xcf\xa8i\xb1\xebg\xa3\xf0\xd2|\xc6\x83\xda\x94moW\x96NK\x86%\xcf_\n$|\x057=3\x8bK\xf0cS \xf8%k\xcd\xb5\xb8\xf5\x97\xa3\xa3\x15\x008\x87\x99%\x10af`\xe7\xad\xcb\x08\x01\x7f\xcca\xd7\x14mh\xa4\x03\xfd\xa2\xfbM`\x95\x97\x89\x9d\x8fY\x87\xd5h`\x07\x18\xc9\xe3\x0e\xfe\xdf\xeb\xf4s\xeb\xcc\x02@\xc5I$qU)2U\xd7]\x8ef*N\x9dR\xd5^d+\x93\xba\xff\x91C\x1f\x8bX\xd9y\xb0\xbd\xb2\xfc\x00SN\xa8cM\xe7\x05M\xc6\xa6\x996k\x9c\xb0`\x10#Af!\xdb`o\xf1\xec\x12\xb8\x10-\xf7\xf6\x14R\xdb\x84\x85\xdb\xbb\x03\xab\xdb\x98\xef\xcd\xc1c\xf7\xcd\xe3_\x17\xe2\x83\xca\xc3`n2\\\xc0w\xd1\x8a!|L\xc8U\xd7\x9d\x11\xac\xa0\xc7\xc6\xe5\x17\xff\x9bw6\xad\xb9n\x84&\xf9\xdc4x\x02eWa\xb1\xb3\x13\xae7\nj\xd8J\x86\'\x99s\x91\x0f\xde8!\xd1\xa7\n%@\x19\xf5[=\no\x03sBT?=\xd6\x0c\xb6+\x9c\xe6\x0f6\x9e\xe3 \xd8\x12D\xe8\xe6\ry\x08\xb0\xe8<E\x80\xf1OQ\xd4H\x1f\x08\x0f\xa1\xe0\x9e\x91\x04=\xca\xeb\xbc42P#\xfb\x0b\xdfi\x85\x08g\xe1Z\x8dZJ\xe3f\x96\xcd\xbf\x88[q\x8c\xa3A\xd4N\xe18\xd4\'\xec\x03\xea\x92\xba\x9089%\xdf\x0b\x9b\x07\xb3\xa9\x17\xe0\'U\x13i\x7fm\xd6\rV\xff\xcbR\xdf\xe0\xdaYpu\xd0\xd8\x05h%%\x07\x84"\xe81\xf6\xc0\xee\x08ETD\x99g\xaeZ\xe7\x99\xba\xb4\x84\xe0\x8f\x83\xf8\x8d\xc8\x83\xb6\xdf\xf6\x84\xfc\x00\t8\xc7\xff\x90\xe9Sk\xf7\xd1\xe4\xaf\xfd\xe5\xb4O\xa3\x08\xbd\xcc\x16H\xddl\xab\xa2TB(@\xe4\x8e\xe7\x9f\'D\x19A}\xa5\x95<\xfd\x1d\xdbu\xda5I\xcb\xa7u\xc6\xde\xa5\xe8\x16a\x13\xa40Td*_\xf7\x00 \x8e\xa4\x0b\xd0\x1f\x13\xa0\x0c\x7f!\x18x\xb8!\xd8H\xda\xeej\xdf$\xc2\xfd.xi\xec\x12\x08\xa6\xbfkk]\n\xdf\xa7\xda\x19O\x18\x02\xd0\xcf\x02\xe8\xb6\x0b\x97\xa8\x8a\xd8\xbb\xf2\xc7H\xb4\xfdv8\x87\xc0;\xc4\xd42"(\x93&+s\xa4O\x9b\x89\xe9\x9cQ\x136b\x11\xe0P^9?!\xb9\x9b\xee\x08\xa3\x88\xd5\xa2\xb6c\xdd\xb7\xc76\xcd\xe3\xffW\x9a\xb2h\x92\xfb\xb5\x8fVO\xf9G0@\xe3\xdc\xefio\xa8')
|
||||
@@ -1,2 +0,0 @@
|
||||
# Pyarmor 9.2.3 (basic), 009742, 2026-03-02T02:54:00.531077
|
||||
from .pyarmor_runtime import __pyarmor__
|
||||
@@ -1,311 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!--
|
||||
Font Fourth Arabic - Report Templates
|
||||
قوالب التقارير والطباعة مع دعم RTL
|
||||
18 خط عربي محلي - لا يتطلب اتصال بالإنترنت
|
||||
|
||||
Note: Header/Footer functionality moved to havari_odoo_printx module
|
||||
-->
|
||||
|
||||
<!-- وراثة تخطيط التقارير الأساسي -->
|
||||
<template id="report_layout_custom_fonts" inherit_id="web.report_layout">
|
||||
<xpath expr="//head" position="inside">
|
||||
<style>
|
||||
/* ======================================
|
||||
تحميل جميع الخطوط العربية للتقارير
|
||||
====================================== */
|
||||
|
||||
/* خط دبي */
|
||||
@font-face {
|
||||
font-family: 'Dubai';
|
||||
src: url('/havari_arabic_fonts/static/fonts/dubai/Dubai-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Dubai';
|
||||
src: url('/havari_arabic_fonts/static/fonts/dubai/Dubai-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Dubai';
|
||||
src: url('/havari_arabic_fonts/static/fonts/dubai/Dubai-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Dubai';
|
||||
src: url('/havari_arabic_fonts/static/fonts/dubai/Dubai-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط الجزيرة */
|
||||
@font-face {
|
||||
font-family: 'Al Jazeera';
|
||||
src: url('/havari_arabic_fonts/static/fonts/aljazeera/AlJazeera-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Al Jazeera';
|
||||
src: url('/havari_arabic_fonts/static/fonts/aljazeera/AlJazeera-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Al Jazeera';
|
||||
src: url('/havari_arabic_fonts/static/fonts/aljazeera/AlJazeera-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Al Jazeera';
|
||||
src: url('/havari_arabic_fonts/static/fonts/aljazeera/AlJazeera-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط القاهرة */
|
||||
@font-face {
|
||||
font-family: 'Cairo';
|
||||
src: url('/havari_arabic_fonts/static/fonts/cairo/Cairo-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cairo';
|
||||
src: url('/havari_arabic_fonts/static/fonts/cairo/Cairo-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cairo';
|
||||
src: url('/havari_arabic_fonts/static/fonts/cairo/Cairo-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cairo';
|
||||
src: url('/havari_arabic_fonts/static/fonts/cairo/Cairo-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط تجوال */
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
src: url('/havari_arabic_fonts/static/fonts/tajawal/Tajawal-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
src: url('/havari_arabic_fonts/static/fonts/tajawal/Tajawal-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
src: url('/havari_arabic_fonts/static/fonts/tajawal/Tajawal-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
src: url('/havari_arabic_fonts/static/fonts/tajawal/Tajawal-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط أميري */
|
||||
@font-face {
|
||||
font-family: 'Amiri';
|
||||
src: url('/havari_arabic_fonts/static/fonts/amiri/Amiri-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Amiri';
|
||||
src: url('/havari_arabic_fonts/static/fonts/amiri/Amiri-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط المراعي */
|
||||
@font-face {
|
||||
font-family: 'Almarai';
|
||||
src: url('/havari_arabic_fonts/static/fonts/almarai/Almarai-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Almarai';
|
||||
src: url('/havari_arabic_fonts/static/fonts/almarai/Almarai-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Almarai';
|
||||
src: url('/havari_arabic_fonts/static/fonts/almarai/Almarai-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ====================================== */
|
||||
/* الخطوط الجديدة - 12 خط إضافي */
|
||||
/* ====================================== */
|
||||
|
||||
/* خط IBM Plex Sans Arabic */
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/ibmplexsansarabic/IBMPlexSansArabic-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/ibmplexsansarabic/IBMPlexSansArabic-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/ibmplexsansarabic/IBMPlexSansArabic-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/ibmplexsansarabic/IBMPlexSansArabic-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط Noto Sans Arabic */
|
||||
@font-face {
|
||||
font-family: 'Noto Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-sans-arabic/NotoSansArabic-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Noto Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-sans-arabic/NotoSansArabic-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Noto Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-sans-arabic/NotoSansArabic-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Noto Sans Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-sans-arabic/NotoSansArabic-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط Noto Naskh Arabic */
|
||||
@font-face {
|
||||
font-family: 'Noto Naskh Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-naskh-arabic/NotoNaskhArabic-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Noto Naskh Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-naskh-arabic/NotoNaskhArabic-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط Noto Kufi Arabic */
|
||||
@font-face {
|
||||
font-family: 'Noto Kufi Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-kufi-arabic/NotoKufiArabic-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Noto Kufi Arabic';
|
||||
src: url('/havari_arabic_fonts/static/fonts/noto-kufi-arabic/NotoKufiArabic-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط Readex Pro */
|
||||
@font-face {
|
||||
font-family: 'Readex Pro';
|
||||
src: url('/havari_arabic_fonts/static/fonts/readex-pro/ReadexPro-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* خط Scheherazade */
|
||||
@font-face {
|
||||
font-family: 'Scheherazade';
|
||||
src: url('/havari_arabic_fonts/static/fonts/scheherazade/Scheherazade-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Scheherazade';
|
||||
src: url('/havari_arabic_fonts/static/fonts/scheherazade/Scheherazade-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* خط Reem Kufi */
|
||||
@font-face {
|
||||
font-family: 'Reem Kufi';
|
||||
src: url('/havari_arabic_fonts/static/fonts/reem-kufi/ReemKufi-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* خط El Messiri */
|
||||
@font-face {
|
||||
font-family: 'El Messiri';
|
||||
src: url('/havari_arabic_fonts/static/fonts/el-messiri/ElMessiri-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* خط Markazi Text */
|
||||
@font-face {
|
||||
font-family: 'Markazi Text';
|
||||
src: url('/havari_arabic_fonts/static/fonts/markazi-text/MarkaziText-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* خط Mada */
|
||||
@font-face {
|
||||
font-family: 'Mada';
|
||||
src: url('/havari_arabic_fonts/static/fonts/mada/Mada-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* خط Changa */
|
||||
@font-face {
|
||||
font-family: 'Changa';
|
||||
src: url('/havari_arabic_fonts/static/fonts/changa/Changa-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* خط Aref Ruqaa */
|
||||
@font-face {
|
||||
font-family: 'Aref Ruqaa';
|
||||
src: url('/havari_arabic_fonts/static/fonts/aref-ruqaa/ArefRuqaa-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Aref Ruqaa';
|
||||
src: url('/havari_arabic_fonts/static/fonts/aref-ruqaa/ArefRuqaa-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- تنسيقات RTL المحسنة للتقارير - تعمل فقط عندما يكون الاتجاه RTL -->
|
||||
<template id="report_rtl_styles" inherit_id="web.report_layout">
|
||||
<xpath expr="//head" position="inside">
|
||||
<style>
|
||||
/* === تنسيقات RTL للغات العربية والعبرية === */
|
||||
/* تعمل فقط عندما يحدد Odoo dir="rtl" تلقائيًا */
|
||||
body[dir="rtl"] .page {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* تنسيقات جداول RTL */
|
||||
body[dir="rtl"] table,
|
||||
body[dir="rtl"] thead,
|
||||
body[dir="rtl"] tbody,
|
||||
body[dir="rtl"] tr {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
body[dir="rtl"] th,
|
||||
body[dir="rtl"] td {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* استثناء الأرقام والعملات - دائما LTR */
|
||||
.o_price_total,
|
||||
.text-end,
|
||||
.amount-col {
|
||||
direction: ltr;
|
||||
}
|
||||
</style>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -1 +0,0 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
|
BIN
addons/havari_arabic_fonts/static/.DS_Store
vendored
|
Before Width: | Height: | Size: 53 KiB |
@@ -1,55 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
|
||||
<defs>
|
||||
<linearGradient id="bgGrad3" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
||||
<stop offset="50%" style="stop-color:#16213e"/>
|
||||
<stop offset="100%" style="stop-color:#0f3460"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="goldGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f7d794"/>
|
||||
<stop offset="50%" style="stop-color:#f5cd79"/>
|
||||
<stop offset="100%" style="stop-color:#e1a83b"/>
|
||||
</linearGradient>
|
||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background rounded square - dark elegant -->
|
||||
<rect x="8" y="8" width="240" height="240" rx="48" ry="48" fill="url(#bgGrad3)"/>
|
||||
|
||||
<!-- Decorative Islamic geometric pattern border -->
|
||||
<rect x="24" y="24" width="208" height="208" rx="36" ry="36" fill="none" stroke="url(#goldGrad)" stroke-width="2" opacity="0.4"/>
|
||||
|
||||
<!-- Arabic letter "ع" (Ain) - represents Arabic beautifully -->
|
||||
<g filter="url(#glow)">
|
||||
<path d="M165 85
|
||||
Q175 85 180 95
|
||||
Q185 110 175 125
|
||||
Q165 140 145 145
|
||||
L120 150
|
||||
Q95 155 85 175
|
||||
Q80 190 90 200
|
||||
Q100 210 120 205
|
||||
L140 198"
|
||||
fill="none" stroke="url(#goldGrad)" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
|
||||
<!-- Stylized Latin "F" for Fonts -->
|
||||
<g transform="translate(55, 70)" opacity="0.9">
|
||||
<path d="M30 0 L30 90 M30 0 L70 0 M30 40 L60 40"
|
||||
fill="none" stroke="white" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
|
||||
<!-- Three dots decoration (Arabic style) -->
|
||||
<circle cx="188" cy="70" r="6" fill="url(#goldGrad)"/>
|
||||
<circle cx="205" cy="85" r="5" fill="url(#goldGrad)" opacity="0.7"/>
|
||||
<circle cx="198" cy="55" r="4" fill="url(#goldGrad)" opacity="0.5"/>
|
||||
|
||||
<!-- Bottom decorative line -->
|
||||
<line x1="60" y1="225" x2="196" y2="225" stroke="url(#goldGrad)" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,215 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="rtl" lang="ar">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Font Fourth Arabic</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, sans-serif;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #714B67, #875A7B);
|
||||
color: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
.header p {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.section {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.section h2 {
|
||||
color: #714B67;
|
||||
border-bottom: 2px solid #714B67;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.feature {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-right: 4px solid #714B67;
|
||||
}
|
||||
.feature h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #333;
|
||||
}
|
||||
.feature p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
}
|
||||
.font-preview {
|
||||
background: #f0f0f0;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.font-preview h3 {
|
||||
font-size: 24px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.font-preview p {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: right;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background: #714B67;
|
||||
color: white;
|
||||
}
|
||||
tr:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h1>Font Fourth Arabic</h1>
|
||||
<p>تحسين الخطوط العربية في أودوو</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>نظرة عامة</h2>
|
||||
<p>
|
||||
إضافة Font Fourth Arabic توفر تحسيناً شاملاً للخطوط العربية في نظام أودوو،
|
||||
مع دعم كامل لخط دبي وخط الجزيرة، وإمكانية التحكم الكامل في الأحجام والأوزان.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>المميزات</h2>
|
||||
<div class="features">
|
||||
<div class="feature">
|
||||
<h3>خطوط احترافية</h3>
|
||||
<p>دعم خط دبي وخط الجزيرة بجميع الأوزان</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<h3>تحكم كامل</h3>
|
||||
<p>إعدادات مرنة لأحجام وأوزان الخطوط</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<h3>ثلاثة أنماط</h3>
|
||||
<p>أنماط جاهزة: مدمج، متوازن، مريح</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<h3>دعم التقارير</h3>
|
||||
<p>خطوط محسنة للطباعة والتقارير</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>الخطوط المدعومة</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>الخط</th>
|
||||
<th>الوصف</th>
|
||||
<th>الاستخدام المقترح</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>دبي</strong></td>
|
||||
<td>خط عصري وأنيق من حكومة دبي</td>
|
||||
<td>النصوص والقوائم</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>الجزيرة</strong></td>
|
||||
<td>خط احترافي وواضح</td>
|
||||
<td>العناوين والأقسام</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>الإعدادات</h2>
|
||||
<p>يمكنك الوصول للإعدادات من:</p>
|
||||
<p><strong>الإعدادات ← الخطوط العربية</strong></p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>الإعداد</th>
|
||||
<th>الوصف</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>النمط العام</td>
|
||||
<td>مدمج / متوازن / مريح</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>خط العناوين</td>
|
||||
<td>اختيار الخط للعناوين</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>خط النصوص</td>
|
||||
<td>اختيار الخط للنصوص</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>الأوزان</td>
|
||||
<td>من خفيف إلى عريض</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>حجم الخط</td>
|
||||
<td>من 12px إلى 15px</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>التثبيت</h2>
|
||||
<ol>
|
||||
<li>انسخ المجلد إلى مسار الإضافات</li>
|
||||
<li>قم بتحديث قائمة التطبيقات</li>
|
||||
<li>ثبت إضافة "Fourth Arabic Fonts"</li>
|
||||
<li>أضف ملفات الخطوط (دبي والجزيرة) إلى مجلد static/fonts</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>الدعم</h2>
|
||||
<p>للدعم والاستفسارات، تواصل معنا عبر:</p>
|
||||
<p><strong>Fourth</strong></p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||