Compare commits

...

315 Commits

Author SHA1 Message Date
9e8b644dc6 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) (force-overwrite) 2026-05-02 07:38:23 +00:00
8f624fee10 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) (force-overwrite) 2026-05-02 07:27:30 +00:00
efe8ee068a Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) (force-overwrite) 2026-05-02 07:27:24 +00:00
7ae04247ce Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) (force-overwrite) 2026-05-02 07:27:15 +00:00
d05b1f4bab Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) (force-overwrite) 2026-05-02 07:27:01 +00:00
96eeaa2d51 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) (force-overwrite) 2026-05-02 07:26:41 +00:00
69ae27892d Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) (force-overwrite) 2026-05-02 07:26:16 +00:00
c9a8cb1792 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) (force-overwrite) 2026-05-02 07:26:07 +00:00
120bff231c Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) (force-overwrite) 2026-05-02 07:25:53 +00:00
7f6aeebbe1 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:39 +00:00
3f66de30c4 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:37 +00:00
389ad788e3 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:36 +00:00
cc13e048b2 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:34 +00:00
085380a411 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:32 +00:00
1f3176a7b7 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:31 +00:00
88f26e9397 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:29 +00:00
2549ce8bb7 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:29 +00:00
1e585a419d Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:27 +00:00
9d3ef18528 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:26 +00:00
df40adb5db Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:25 +00:00
9fc91ed47a Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:24 +00:00
ef1ef68222 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:22 +00:00
aaeb29171b Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:20 +00:00
1a80cd8aa8 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:19 +00:00
a02c02b8e5 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:18 +00:00
720966186a Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:16 +00:00
d8924a234a Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:14 +00:00
a5d21a8853 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:13 +00:00
cca846b84e Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:12 +00:00
b88dcbfaa8 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:10 +00:00
8c7bde04d7 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:09 +00:00
52fcc8afa5 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:08 +00:00
e71b7a33c4 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:07 +00:00
529d640d2c Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:06 +00:00
db06b274c8 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:04 +00:00
d51e343b0e Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:02:01 +00:00
724e09d35b Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:59 +00:00
f46eca6af8 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:59 +00:00
cc3d0f3a6f Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:57 +00:00
693cd33119 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:56 +00:00
98c4ac4167 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:55 +00:00
8392ae58d9 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:53 +00:00
a25f5df019 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:51 +00:00
d7983aab11 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:50 +00:00
54ff49035e Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:49 +00:00
a2172f4581 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:47 +00:00
85147b59a1 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:45 +00:00
c5e332bf92 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:43 +00:00
fda43a256b Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:40 +00:00
2b34b6680a Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:37 +00:00
8af5977dc1 Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:35 +00:00
298759fdcd Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:33 +00:00
33b4abdfca Tower: upload om_account_followup 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 07:01:30 +00:00
cee7dfbc8c Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:27 +00:00
4651545305 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:24 +00:00
05e9d292ab Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:22 +00:00
d3d43cecb8 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:19 +00:00
227e71b14f Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:16 +00:00
d10ef5216c Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:14 +00:00
f1ed7c92f7 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:12 +00:00
1cc27af9ac Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:10 +00:00
a4b715534d Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:08 +00:00
16548c9204 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:05 +00:00
7acc979f33 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:01:02 +00:00
f4a7d664f2 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:59 +00:00
b596416791 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:56 +00:00
310de14ee8 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:54 +00:00
f626c90b77 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:52 +00:00
23cede2cec Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:49 +00:00
cc0e6493ae Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:47 +00:00
23cc52e9dc Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:46 +00:00
0f353f1df9 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:44 +00:00
317cdd1618 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:42 +00:00
c25e179e67 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:39 +00:00
59cdd6db51 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:37 +00:00
998ab2b18a Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:35 +00:00
6c6e2a6d06 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:32 +00:00
cba963b090 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:30 +00:00
273ca36a82 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:29 +00:00
cbcb69f040 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:27 +00:00
01b304ad67 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:24 +00:00
a3235eb5e7 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:22 +00:00
24f3e5b775 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:19 +00:00
361c3708dc Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:16 +00:00
923f087b7a Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:14 +00:00
590c130efa Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:12 +00:00
23249091b1 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:10 +00:00
a255dc7aae Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:07 +00:00
66a9f804ff Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 07:00:03 +00:00
c07af0a20f Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:59:59 +00:00
6b42a1a341 Tower: upload om_account_asset 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:59:57 +00:00
580d950eab Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:54 +00:00
08f8103f4f Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:51 +00:00
2c856313a5 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:48 +00:00
ce5240738c Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:47 +00:00
69804d0600 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:44 +00:00
5e7f44f96e Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:41 +00:00
1325283047 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:39 +00:00
097e327943 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:37 +00:00
1e2d165c13 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:34 +00:00
4b8cb108fa Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:32 +00:00
b458358351 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:30 +00:00
6d4b091bd0 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:28 +00:00
a473e89adb Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:26 +00:00
9035144daa Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:24 +00:00
8c11e98400 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:21 +00:00
4c6a032214 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:19 +00:00
89c01cde3a Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:17 +00:00
335ae7cc37 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:15 +00:00
aeb0dfa32b Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:13 +00:00
ab0fcc907d Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:12 +00:00
488373ef1d Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:09 +00:00
ed88e8b964 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:06 +00:00
c2fd100b77 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:03 +00:00
6c358ca955 Tower: upload om_fiscal_year 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:59:00 +00:00
2f387e8a65 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:56 +00:00
efaeade746 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:54 +00:00
599e09df61 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:52 +00:00
52c65aa9b5 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:49 +00:00
41c4ff034a Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:47 +00:00
ced6377266 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:44 +00:00
fb04b47190 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:41 +00:00
cd6812f3b8 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:39 +00:00
84183488f8 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:36 +00:00
2c552e1c97 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:34 +00:00
b4810b9f98 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:31 +00:00
5dbfced933 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:28 +00:00
afc1b45d6a Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:26 +00:00
5ff0a2841e Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:24 +00:00
59199e3702 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:20 +00:00
93e36e7e29 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:17 +00:00
4618b061c8 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:14 +00:00
626ede148f Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:09 +00:00
cad549b100 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:05 +00:00
953b69ca28 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:58:02 +00:00
ed9fdb17f3 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:58 +00:00
9ba4a97fe3 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:53 +00:00
c97dd79db1 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:49 +00:00
f2c47e8c13 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:43 +00:00
0c09645e6d Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:39 +00:00
48aee47770 Tower: upload om_account_budget 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:36 +00:00
cb3d25ce0f Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:31 +00:00
b6660d899a Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:27 +00:00
b22ccc147f Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:25 +00:00
5e1b2881db Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:21 +00:00
59397bbbaf Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:17 +00:00
43650a81e8 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:13 +00:00
e9114765c0 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:10 +00:00
f8e7bcad5d Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:06 +00:00
d919f48451 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:57:01 +00:00
81ce31a57a Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:58 +00:00
f95eca7b2e Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:54 +00:00
d353bd94ab Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:52 +00:00
f6d3119fdc Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:47 +00:00
7903652796 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:44 +00:00
c0d4958555 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:39 +00:00
4f21f98520 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:36 +00:00
dfcda13313 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:31 +00:00
6b0de8a446 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:30 +00:00
148c3a12d9 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:28 +00:00
357c73e7d1 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:26 +00:00
0553a3faf7 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:23 +00:00
12c3d9dcc0 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:20 +00:00
23b40d5922 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:19 +00:00
3f63f8d209 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:15 +00:00
6fd4de2cc8 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:11 +00:00
794edc73a0 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:08 +00:00
bf822b8178 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:05 +00:00
875db440fc Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:56:04 +00:00
76be4825f1 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:55:59 +00:00
77ff5a54f2 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:55:58 +00:00
a33c2380a2 Tower: upload om_account_daily_reports 19.0.1.0.1 (was 1.0.1, via marketplace) 2026-05-02 06:55:55 +00:00
c9d191cd7c Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:51 +00:00
e5882ef4fc Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:49 +00:00
0afa863d76 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:45 +00:00
b6fe704976 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:41 +00:00
afe3108bac Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:38 +00:00
940cd44458 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:34 +00:00
4982f777ca Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:32 +00:00
f2794c7b4b Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:28 +00:00
0b1545593b Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:24 +00:00
0a026f7122 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:21 +00:00
d3b616b5e9 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:19 +00:00
67d9c5a66d Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:15 +00:00
27973de8db Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:13 +00:00
d21fc69654 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:10 +00:00
8ef99a0351 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:09 +00:00
15dba9eeac Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:06 +00:00
5b5bc26980 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:04 +00:00
8f72d4c181 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:55:00 +00:00
689a600475 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:58 +00:00
125229bf32 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:56 +00:00
311491585e Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:54 +00:00
89215de3cc Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:51 +00:00
3ed3987ff2 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:49 +00:00
bc62e0679e Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:48 +00:00
4540f89d56 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:45 +00:00
76a9794601 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:43 +00:00
2f74414402 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:41 +00:00
b7c000ec2b Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:39 +00:00
9bbea37ffa Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:37 +00:00
bd51b90454 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:35 +00:00
410f68fd85 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:32 +00:00
6264906b96 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:28 +00:00
de1c0a7a97 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:26 +00:00
5a4b57e071 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:23 +00:00
fa7ec32530 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:20 +00:00
79c0e0c7df Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:18 +00:00
551c5ff078 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:14 +00:00
cee6b58491 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:12 +00:00
71749fa98b Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:09 +00:00
0bf6239825 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:07 +00:00
b69300873e Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:04 +00:00
013f1ebf8a Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:54:02 +00:00
2392132751 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:58 +00:00
3885674952 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:57 +00:00
3933decc5d Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:56 +00:00
98e3afdfa6 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:53 +00:00
25e374d814 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:49 +00:00
9a9a0d093e Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:44 +00:00
d8c641942e Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:40 +00:00
a72d7b7497 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:35 +00:00
699905af74 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:30 +00:00
1dbf28c898 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:25 +00:00
135cd35e8e Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:23 +00:00
e7a07af65a Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:19 +00:00
8f92c36a90 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:16 +00:00
1139606ba9 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:12 +00:00
5e7dc1dace Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:08 +00:00
30500a1a79 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:05 +00:00
49e35bb590 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:53:01 +00:00
1de375ee02 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:58 +00:00
a35e329b22 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:53 +00:00
7323f4e828 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:51 +00:00
e2cb07e990 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:49 +00:00
98d315cef4 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:48 +00:00
78b95aff58 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:46 +00:00
bf5b83a46d Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:42 +00:00
452a529dca Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:40 +00:00
7e3704ba35 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:38 +00:00
6ad452e5af Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:34 +00:00
6911b4e06e Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:30 +00:00
ff240d6e5f Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:27 +00:00
12125792f2 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:24 +00:00
bb945c2bbe Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:20 +00:00
be8bec6163 Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:18 +00:00
2d85f731af Tower: upload accounting_pdf_reports 19.0.1.0.2 (was 1.0.2, via marketplace) 2026-05-02 06:52:15 +00:00
bfce835bee Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:52:11 +00:00
a4a0184e3a Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:52:09 +00:00
ba9d23abcc Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:52:05 +00:00
4c811eb71b Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:52:01 +00:00
3183ea091c Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:58 +00:00
cb5a39181f Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:55 +00:00
b80e594535 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:52 +00:00
487da7eb79 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:48 +00:00
55b006145a Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:46 +00:00
272e50202c Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:44 +00:00
c7e08801e3 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:43 +00:00
98c68049eb Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:41 +00:00
4837ebb77b Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:39 +00:00
2c6b22b221 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:37 +00:00
040d040536 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:35 +00:00
1c7b7877a3 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:33 +00:00
25a626bdc8 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:30 +00:00
08af20cc8d Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:27 +00:00
f2d3140e01 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:24 +00:00
da0f9cbcf2 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:22 +00:00
eaa58f6116 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:18 +00:00
74792b00dd Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:16 +00:00
e8d03372a9 Tower: upload om_recurring_payments 19.0.1.0.0 (was 1.0.0, via marketplace) 2026-05-02 06:51:11 +00:00
b05e4dc5c2 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:51:07 +00:00
ed17b07946 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:51:03 +00:00
be48603ea5 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:51:00 +00:00
065927abb9 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:55 +00:00
46e8c1e977 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:51 +00:00
dca8863bbd Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:49 +00:00
55bdcfbdc6 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:46 +00:00
10975b612e Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:42 +00:00
2bea9c692e Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:38 +00:00
29052e9644 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:34 +00:00
3f30b37505 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:31 +00:00
5d363428a2 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:28 +00:00
9a14ce45c9 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:26 +00:00
49198d27cc Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:22 +00:00
2002bd8dc2 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:18 +00:00
7522e64961 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:15 +00:00
7c8e4574e7 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:12 +00:00
c42184c3b2 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:10 +00:00
b7f1e8f324 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:07 +00:00
966efb56c0 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:50:02 +00:00
91a3aac647 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:58 +00:00
ec80e5f007 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:53 +00:00
87df9231e4 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:48 +00:00
1bd64417b8 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:44 +00:00
0a800bf1aa Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:41 +00:00
f0027eee3f Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:40 +00:00
0bca86c256 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:37 +00:00
1e473a1677 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:34 +00:00
1725eafcd9 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:31 +00:00
1ad797d9f0 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:29 +00:00
53be87e93c Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:26 +00:00
1ead3282cf Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:24 +00:00
2c142ccb76 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:23 +00:00
eda163a405 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:19 +00:00
adce085798 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:18 +00:00
495fe47dfa Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:16 +00:00
037d1438e1 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:13 +00:00
54e693e3f1 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:11 +00:00
9d84acdc94 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:07 +00:00
8132d571ac Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:04 +00:00
28567ff9e2 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:49:01 +00:00
ad566ac852 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:48:58 +00:00
e1d95717d6 Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:48:56 +00:00
824265c91a Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:48:54 +00:00
5c5d93f18f Tower: upload om_account_accountant 19.0.1.0.3 (was 1.0.3, via marketplace) 2026-05-02 06:48:51 +00:00
123 changed files with 29203 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
{
'name': 'Odoo 19 Accounting Financial Reports',
'version': '1.0.2',
'version': 1.0.219.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',

View File

@@ -1,6 +1,6 @@
{
'name': 'Odoo 19 Accounting Community',
'version': '1.0.3',
'version': 1.0.319.0.1.0.3', # __odoosky_original_version__: '1.0.3'
'category': 'Accounting',
'summary': 'Accounting Reports, Asset Management and Budget, Recurring Payments, '
'Lock Dates, Fiscal Year, Accounting Dashboard, Financial Reports, '

View File

@@ -0,0 +1,3 @@
from . import wizard
from . import models
from . import report

View File

@@ -0,0 +1,32 @@
{
'name': 'Odoo 19 Assets Management',
'version': 1.0.019.0.1.0.0', # __odoosky_original_version__: '1.0.0'
'author': 'Odoo Mates, Odoo SA',
'depends': ['account'],
'description': """Manage assets owned by a company or a person.
Keeps track of depreciation's, and creates corresponding journal entries""",
'summary': 'Odoo 19 Assets Management',
'category': 'Accounting',
'sequence': 10,
'website': 'https://www.odoomates.tech',
'license': 'LGPL-3',
'images': ['static/description/assets.gif'],
'data': [
'data/account_asset_data.xml',
'security/account_asset_security.xml',
'security/ir.model.access.csv',
'wizard/asset_depreciation_confirmation_wizard_views.xml',
'wizard/asset_modify_views.xml',
'views/account_asset_views.xml',
'views/account_move_views.xml',
'views/account_asset_templates.xml',
'views/asset_category_views.xml',
'views/product_views.xml',
'report/account_asset_report_views.xml',
],
'assets': {
'web.assets_backend': [
'om_account_asset/static/src/scss/account_asset.scss',
],
},
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding='UTF-8'?>
<odoo>
<data noupdate="1">
<record id="account_asset_cron" model="ir.cron">
<field name="name">Account Asset: Generate asset entries</field>
<field name="model_id" ref="model_account_asset_asset"/>
<field name="state">code</field>
<field name="code">model._cron_generate_entries()</field>
<field name="interval_number">1</field>
<field name="interval_type">months</field>
</record>
</data>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account
from . import account_asset
from . import account_move
from . import product

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class AccountMove(models.Model):
_inherit = 'account.move'
asset_depreciation_ids = fields.One2many('account.asset.depreciation.line', 'move_id',
string='Assets Depreciation Lines')
def button_cancel(self):
for move in self:
for line in move.asset_depreciation_ids:
line.move_posted_check = False
return super(AccountMove, self).button_cancel()
def action_post(self):
for move in self:
for depreciation_line in move.asset_depreciation_ids:
depreciation_line.post_lines_and_close_asset()
return super(AccountMove, self).action_post()

View File

@@ -0,0 +1,728 @@
import calendar
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_is_zero
from markupsafe import Markup
class AccountAssetCategory(models.Model):
_name = 'account.asset.category'
_description = 'Asset category'
_inherit = ['mail.thread', 'mail.activity.mixin', 'analytic.mixin']
exclude_types = ['asset_receivable', 'asset_cash', 'liability_payable',
'liability_credit_card', 'equity', 'equity_unaffected']
active = fields.Boolean(default=True)
name = fields.Char(required=True, index=True, string="Asset Type")
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
account_asset_id = fields.Many2one(
'account.account', string='Asset Account',
required=True,
domain=[('account_type', 'not in', exclude_types)],
help="Account used to record the purchase of the asset at its original price."
)
account_depreciation_id = fields.Many2one(
'account.account', string='Depreciation Entries: Asset Account',
required=True,
domain=[('account_type', 'not in', exclude_types)],
help="Account used in the depreciation entries, to decrease the asset value."
)
account_depreciation_expense_id = fields.Many2one(
'account.account', string='Depreciation Entries: Expense Account',
required=True,
domain=[('account_type', 'not in', exclude_types)],
help="Account used in the periodical entries "
"to record a part of the asset as expense."
)
journal_id = fields.Many2one(
'account.journal', string='Journal', required=True
)
company_id = fields.Many2one(
'res.company', string='Company',
required=True, default=lambda self: self.env.company
)
method = fields.Selection(
[('linear', 'Linear'), ('degressive', 'Degressive')],
string='Computation Method', required=True, default='linear',
help="Choose the method to use to compute the amount of depreciation lines.\n"
" * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor"
)
method_number = fields.Integer(
string='Number of Depreciations', default=5,
help="The number of depreciations needed to depreciate your asset"
)
method_period = fields.Integer(
string='Period Length', default=1,
help="State here the time between 2 depreciations, in months", required=True
)
method_progress_factor = fields.Float(
'Degressive Factor', default=0.3
)
method_time = fields.Selection(
[('number', 'Number of Entries'), ('end', 'Ending Date')],
string='Time Method', required=True, default='number',
help="Choose the method to use to compute the dates and number of entries.\n"
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond."
)
method_end = fields.Date('Ending date')
prorata = fields.Boolean(
string='Prorata Temporis',
help='Indicates that the first depreciation entry for this asset have to be done from the '
'purchase date instead of the first of January'
)
open_asset = fields.Boolean(
string='Auto-Confirm Assets',
help="Check this if you want to automatically confirm the assets "
"of this category when created by invoices."
)
group_entries = fields.Boolean(
string='Group Journal Entries',
help="Check this if you want to group the generated entries by categories."
)
type = fields.Selection(
[('sale', 'Sale: Revenue Recognition'), ('purchase', 'Purchase: Asset')],
required=True, index=True, default='purchase'
)
date_first_depreciation = fields.Selection([
('last_day_period', 'Based on Last Day of Purchase Period'),
('manual', 'Manual (Defaulted on Purchase Date)')],
string='Depreciation Dates', default='manual', required=True,
help='The way to compute the date of the first depreciation.\n'
' * Based on last day of purchase period: The depreciation dates will'
' be based on the last day of the purchase month or the purchase'
' year (depending on the periodicity of the depreciations).\n'
' * Based on purchase date: The depreciation dates will be based on the purchase date.')
@api.onchange('account_asset_id')
def onchange_account_asset(self):
if self.type == "purchase":
self.account_depreciation_id = self.account_asset_id
elif self.type == "sale":
self.account_depreciation_expense_id = self.account_asset_id
@api.onchange('type')
def onchange_type(self):
if self.type == 'sale':
self.prorata = True
self.method_period = 1
else:
self.method_period = 12
@api.onchange('method_time')
def _onchange_method_time(self):
if self.method_time != 'number':
self.prorata = False
class AccountAssetAsset(models.Model):
_name = 'account.asset.asset'
_description = 'Asset/Revenue Recognition'
_inherit = ['mail.thread', 'mail.activity.mixin', 'analytic.mixin']
entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries')
name = fields.Char(string='Asset Name', required=True)
code = fields.Char(string='Reference', size=32)
value = fields.Monetary(string='Gross Value', required=True)
currency_id = fields.Many2one(
'res.currency', string='Currency', required=True,
default=lambda self: self.env.user.company_id.currency_id.id
)
company_id = fields.Many2one(
'res.company', string='Company', required=True,
default=lambda self: self.env.company)
note = fields.Text()
category_id = fields.Many2one(
'account.asset.category', string='Category',
required=True, change_default=True
)
date = fields.Date(string='Date', required=True, default=fields.Date.context_today)
state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')],
'Status', required=True, copy=False, default='draft',
help="When an asset is created, the status is 'Draft'.\n"
"If the asset is confirmed, the status goes in 'Running' and the depreciation "
"lines can be posted in the accounting.\n"
"You can manually close an asset when the depreciation is over. If the last line"
" of depreciation is posted, the asset automatically goes in that status.")
active = fields.Boolean(default=True)
partner_id = fields.Many2one('res.partner', string='Partner')
method = fields.Selection(
[('linear', 'Linear'), ('degressive', 'Degressive')],
string='Computation Method', required=True, default='linear',
help="Choose the method to use to compute the amount of depreciation lines.\n * Linear:"
" Calculated on basis of: Gross Value / Number of Depreciations\n"
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor"
)
method_number = fields.Integer(string='Number of Depreciations', default=5,
help="The number of depreciations needed to depreciate your asset")
method_period = fields.Integer(
string='Number of Months in a Period', required=True, default=12,
help="The amount of time between two depreciations, in months"
)
method_end = fields.Date(string='Ending Date')
method_progress_factor = fields.Float(
string='Degressive Factor', default=0.3
)
value_residual = fields.Monetary(compute='_amount_residual', string='Residual Value')
method_time = fields.Selection(
[('number', 'Number of Entries'), ('end', 'Ending Date')],
string='Time Method', required=True, default='number',
help="Choose the method to use to compute the dates and number of entries.\n"
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond."
)
prorata = fields.Boolean(
string='Prorata Temporis',
help='Indicates that the first depreciation entry for this asset'
' have to be done from the asset date (purchase date) '
'instead of the first January / Start date of fiscal year'
)
depreciation_line_ids = fields.One2many(
'account.asset.depreciation.line', 'asset_id', string='Depreciation Lines'
)
salvage_value = fields.Monetary(
string='Salvage Value',
help="It is the amount you plan to have that you cannot depreciate."
)
invoice_id = fields.Many2one('account.move', string='Invoice', copy=False)
type = fields.Selection(related="category_id.type", string='Type', required=True)
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
date_first_depreciation = fields.Selection([
('last_day_period', 'Based on Last Day of Purchase Period'),
('manual', 'Manual')],
string='Depreciation Dates', default='manual',
required=True,
help='The way to compute the date of the first depreciation.\n'
' * Based on last day of purchase period: The depreciation'
' dates will be based on the last day of the purchase month or the '
'purchase year (depending on the periodicity of the depreciations).\n'
' * Based on purchase date: The depreciation dates will be based on the purchase date.\n')
first_depreciation_manual_date = fields.Date(
string='First Depreciation Date',
help='Note that this date does not alter the computation of the first '
'journal entry in case of prorata temporis assets. It simply changes its accounting date'
)
def unlink(self):
for asset in self:
if asset.state in ['open', 'close']:
raise UserError(_('You cannot delete a document that is in %s state.') % (asset.state,))
for depreciation_line in asset.depreciation_line_ids:
if depreciation_line.move_id:
raise UserError(_('You cannot delete a document that contains posted entries.'))
return super(AccountAssetAsset, self).unlink()
@api.model
def _cron_generate_entries(self):
self.compute_generated_entries(datetime.today())
@api.model
def compute_generated_entries(self, date, asset_type=None):
# Entries generated : one by grouped category and one by asset from ungrouped category
created_move_ids = []
type_domain = []
if asset_type:
type_domain = [('type', '=', asset_type)]
ungrouped_assets = self.env['account.asset.asset'].search(type_domain + [('state', '=', 'open'), ('category_id.group_entries', '=', False)])
created_move_ids += ungrouped_assets._compute_entries(date, group_entries=False)
for grouped_category in self.env['account.asset.category'].search(type_domain + [('group_entries', '=', True)]):
assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id', '=', grouped_category.id)])
created_move_ids += assets._compute_entries(date, group_entries=True)
return created_move_ids
def _compute_board_amount(self, sequence, residual_amount, amount_to_depr,
undone_dotation_number, posted_depreciation_line_ids,
total_days, depreciation_date):
amount = 0
if sequence == undone_dotation_number:
amount = residual_amount
else:
if self.method == 'linear':
amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids))
if self.prorata:
amount = amount_to_depr / self.method_number
if sequence == 1:
date = self.date
if self.method_period % 12 != 0:
month_days = calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
amount = (amount_to_depr / self.method_number) / month_days * days
else:
days = (self.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1
amount = (amount_to_depr / self.method_number) / total_days * days
elif self.method == 'degressive':
amount = residual_amount * self.method_progress_factor
if self.prorata:
if sequence == 1:
date = self.date
if self.method_period % 12 != 0:
month_days = calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
amount = (residual_amount * self.method_progress_factor) / month_days * days
else:
days = (self.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1
amount = (residual_amount * self.method_progress_factor) / total_days * days
return amount
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
undone_dotation_number = self.method_number
if self.method_time == 'end':
end_date = self.method_end
undone_dotation_number = 0
while depreciation_date <= end_date:
depreciation_date = date(depreciation_date.year, depreciation_date.month,
depreciation_date.day) + relativedelta(months=+self.method_period)
undone_dotation_number += 1
if self.prorata:
undone_dotation_number += 1
return undone_dotation_number
def compute_depreciation_board(self):
self.ensure_one()
posted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: x.move_check).sorted(key=lambda l: l.depreciation_date)
unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: not x.move_check)
# Remove old unposted depreciation lines. We cannot use unlink() with One2many field
commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids]
if self.value_residual != 0.0:
amount_to_depr = residual_amount = self.value_residual
# if we already have some previous validated entries, starting date is last entry + method period
if posted_depreciation_line_ids and posted_depreciation_line_ids[-1].depreciation_date:
last_depreciation_date = fields.Date.from_string(posted_depreciation_line_ids[-1].depreciation_date)
depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period)
else:
# depreciation_date computed from the purchase date
depreciation_date = self.date
if self.date_first_depreciation == 'last_day_period':
# depreciation_date = the last day of the month
depreciation_date = depreciation_date + relativedelta(day=31)
# ... or fiscalyear depending the number of period
if self.method_period == 12:
depreciation_date = depreciation_date + relativedelta(month=int(self.company_id.fiscalyear_last_month))
depreciation_date = depreciation_date + relativedelta(day=int(self.company_id.fiscalyear_last_day))
if depreciation_date < self.date:
depreciation_date = depreciation_date + relativedelta(years=1)
elif self.first_depreciation_manual_date and self.first_depreciation_manual_date != self.date:
# depreciation_date set manually from the 'first_depreciation_manual_date' field
depreciation_date = self.first_depreciation_manual_date
total_days = (depreciation_date.year % 4) and 365 or 366
month_day = depreciation_date.day
undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days)
for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
sequence = x + 1
amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr,
undone_dotation_number, posted_depreciation_line_ids,
total_days, depreciation_date)
amount = self.currency_id.round(amount)
if float_is_zero(amount, precision_rounding=self.currency_id.rounding):
continue
residual_amount -= amount
vals = {
'amount': amount,
'asset_id': self.id,
'sequence': sequence,
'name': (self.code or '') + '/' + str(sequence),
'remaining_value': residual_amount,
'depreciated_value': self.value - (self.salvage_value + residual_amount),
'depreciation_date': depreciation_date,
}
commands.append((0, False, vals))
depreciation_date = depreciation_date + relativedelta(months=+self.method_period)
if month_day > 28 and self.date_first_depreciation == 'manual':
max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1]
depreciation_date = depreciation_date.replace(day=min(max_day_in_month, month_day))
# datetime doesn't take into account that the number of days is not the same for each month
if not self.prorata and self.method_period % 12 != 0 and self.date_first_depreciation == 'last_day_period':
max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1]
depreciation_date = depreciation_date.replace(day=max_day_in_month)
self.write({'depreciation_line_ids': commands})
return True
def validate(self):
self.write({'state': 'open'})
fields = [
'method',
'method_number',
'method_period',
'method_end',
'method_progress_factor',
'method_time',
'salvage_value',
'invoice_id',
]
ref_tracked_fields = self.env['account.asset.asset'].fields_get(fields)
for asset in self:
tracked_fields = ref_tracked_fields.copy()
if asset.method == 'linear':
del(tracked_fields['method_progress_factor'])
if asset.method_time != 'end':
del(tracked_fields['method_end'])
else:
del(tracked_fields['method_number'])
dummy, tracking_value_ids = asset._mail_track(tracked_fields, dict.fromkeys(fields))
asset.message_post(subject=_('Asset created'), tracking_value_ids=tracking_value_ids)
def _return_disposal_view(self, move_ids):
name = _('Disposal Move')
view_mode = 'form'
if len(move_ids) > 1:
name = _('Disposal Moves')
view_mode = 'tree,form'
return {
'name': name,
'view_type': 'form',
'view_mode': view_mode,
'res_model': 'account.move',
'type': 'ir.actions.act_window',
'target': 'current',
'res_id': move_ids[0],
}
def _get_disposal_moves(self):
move_ids = []
for asset in self:
unposted_depreciation_line_ids = asset.depreciation_line_ids.filtered(lambda x: not x.move_check)
if unposted_depreciation_line_ids:
old_values = {
'method_end': asset.method_end,
'method_number': asset.method_number,
}
# Remove all unposted depr. lines
commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids]
# Create a new depr. line with the residual amount and post it
sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1
today = fields.Datetime.today()
vals = {
'amount': asset.value_residual,
'asset_id': asset.id,
'sequence': sequence,
'name': (asset.code or '') + '/' + str(sequence),
'remaining_value': 0,
'depreciated_value': asset.value - asset.salvage_value, # the asset is completely depreciated
'depreciation_date': today,
}
commands.append((0, False, vals))
asset.write({'depreciation_line_ids': commands, 'method_end': today, 'method_number': sequence})
tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_end'])
changes, tracking_value_ids = asset._mail_track(tracked_fields, old_values)
if changes:
asset.message_post(subject=_('Asset sold or disposed. Accounting entry awaiting for validation.'), tracking_value_ids=tracking_value_ids)
move_ids += asset.depreciation_line_ids[-1].create_move(post_move=False)
return move_ids
def set_to_close(self):
move_ids = self._get_disposal_moves()
if move_ids:
return self._return_disposal_view(move_ids)
# Fallback, as if we just clicked on the smartbutton
return self.open_entries()
def set_to_draft(self):
self.write({'state': 'draft'})
@api.depends('value', 'salvage_value', 'depreciation_line_ids.move_check', 'depreciation_line_ids.amount')
def _amount_residual(self):
for rec in self:
total_amount = 0.0
for line in rec.depreciation_line_ids:
if line.move_check:
total_amount += line.amount
rec.value_residual = rec.value - total_amount - rec.salvage_value
@api.onchange('company_id')
def onchange_company_id(self):
self.currency_id = self.company_id.currency_id.id
@api.onchange('date_first_depreciation')
def onchange_date_first_depreciation(self):
for record in self:
if record.date_first_depreciation == 'manual':
record.first_depreciation_manual_date = record.date
@api.depends('depreciation_line_ids.move_id')
def _entry_count(self):
for asset in self:
res = self.env['account.asset.depreciation.line'].search_count([('asset_id', '=', asset.id), ('move_id', '!=', False)])
asset.entry_count = res or 0
@api.constrains('prorata', 'method_time')
def _check_prorata(self):
if self.prorata and self.method_time != 'number':
raise ValidationError(_('Prorata temporis can be applied only for the "number of depreciations" time method.'))
@api.onchange('category_id')
def onchange_category_id(self):
vals = self.onchange_category_id_values(self.category_id.id)
# We cannot use 'write' on an object that doesn't exist yet
if vals:
for k, v in vals['value'].items():
setattr(self, k, v)
def onchange_category_id_values(self, category_id):
if category_id:
category = self.env['account.asset.category'].browse(category_id)
return {
'value': {
'method': category.method,
'method_number': category.method_number,
'method_time': category.method_time,
'method_period': category.method_period,
'method_progress_factor': category.method_progress_factor,
'method_end': category.method_end,
'prorata': category.prorata,
'date_first_depreciation': category.date_first_depreciation,
'account_analytic_id': category.account_analytic_id.id,
'analytic_distribution': category.analytic_distribution,
}
}
@api.onchange('method_time')
def onchange_method_time(self):
if self.method_time != 'number':
self.prorata = False
def copy_data(self, default=None):
if default is None:
default = {}
default['name'] = self.name + _(' (copy)')
return super(AccountAssetAsset, self).copy_data(default)
def _compute_entries(self, date, group_entries=False):
depreciation_ids = self.env['account.asset.depreciation.line'].search([
('asset_id', 'in', self.ids), ('depreciation_date', '<=', date),
('move_check', '=', False)])
if group_entries:
return depreciation_ids.create_grouped_move()
return depreciation_ids.create_move()
@api.model_create_multi
def create(self, vals_list):
assets = super(AccountAssetAsset, self.with_context(mail_create_nolog=True)).create(vals_list)
for asset in assets:
asset.sudo().compute_depreciation_board()
return assets
def write(self, vals):
res = super(AccountAssetAsset, self).write(vals)
if 'depreciation_line_ids' not in vals and 'state' not in vals:
for rec in self:
rec.compute_depreciation_board()
return res
def open_entries(self):
move_ids = []
for asset in self:
for depreciation_line in asset.depreciation_line_ids:
if depreciation_line.move_id:
move_ids.append(depreciation_line.move_id.id)
return {
'name': _('Journal Entries'),
'view_type': 'form',
'view_mode': 'list,form',
'res_model': 'account.move',
'view_id': False,
'type': 'ir.actions.act_window',
'domain': [('id', 'in', move_ids)],
}
class AccountAssetDepreciationLine(models.Model):
_name = 'account.asset.depreciation.line'
_description = 'Asset depreciation line'
name = fields.Char(string='Depreciation Name', required=True, index=True)
sequence = fields.Integer(required=True)
asset_id = fields.Many2one('account.asset.asset', string='Asset',
required=True, ondelete='cascade')
parent_state = fields.Selection(related='asset_id.state',
string='State of Asset')
amount = fields.Monetary(string='Current Depreciation',
required=True)
remaining_value = fields.Monetary(string='Next Period Depreciation',
required=True)
depreciated_value = fields.Monetary(string='Cumulative Depreciation',
required=True)
depreciation_date = fields.Date('Depreciation Date', index=True)
move_id = fields.Many2one('account.move', string='Depreciation Entry')
move_check = fields.Boolean(compute='_get_move_check', string='Linked',
store=True)
move_posted_check = fields.Boolean(compute='_get_move_posted_check',
string='Posted', store=True)
currency_id = fields.Many2one('res.currency', string='Currency',
related='asset_id.currency_id',
readonly=True)
@api.depends('move_id')
def _get_move_check(self):
for line in self:
line.move_check = bool(line.move_id)
@api.depends('move_id.state')
def _get_move_posted_check(self):
for line in self:
line.move_posted_check = True if line.move_id and line.move_id.state == 'posted' else False
def create_move(self, post_move=True):
created_moves = self.env['account.move']
for line in self:
if line.move_id:
raise UserError(_('This depreciation is already linked to a journal entry. Please post or delete it.'))
move_vals = self._prepare_move(line)
move = self.env['account.move'].create(move_vals)
line.write({'move_id': move.id, 'move_check': True})
created_moves |= move
if post_move and created_moves:
created_moves.filtered(lambda m: any(m.asset_depreciation_ids.mapped('asset_id.category_id.open_asset'))).action_post()
return [x.id for x in created_moves]
def _prepare_move(self, line):
category_id = line.asset_id.category_id
analytic_distribution = line.asset_id.analytic_distribution
depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self)
company_currency = line.asset_id.company_id.currency_id
current_currency = line.asset_id.currency_id
prec = company_currency.decimal_places
amount = current_currency._convert(
line.amount, company_currency, line.asset_id.company_id, depreciation_date)
asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids))
move_line_1 = {
'name': asset_name,
'account_id': category_id.account_depreciation_id.id,
'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
'partner_id': line.asset_id.partner_id.id,
'analytic_distribution': analytic_distribution,
'currency_id': company_currency != current_currency and current_currency.id or company_currency.id,
'amount_currency': - 1.0 * line.amount
}
move_line_2 = {
'name': asset_name,
'account_id': category_id.account_depreciation_expense_id.id,
'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
'partner_id': line.asset_id.partner_id.id,
'analytic_distribution': analytic_distribution,
'currency_id': company_currency != current_currency and current_currency.id or company_currency.id,
'amount_currency': line.amount,
}
move_vals = {
'ref': line.asset_id.code,
'date': depreciation_date or False,
'journal_id': category_id.journal_id.id,
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
}
return move_vals
def _prepare_move_grouped(self):
asset_id = self[0].asset_id
category_id = asset_id.category_id # we can suppose that all lines have the same category
account_analytic_id = asset_id.account_analytic_id
# analytic_tag_ids = asset_id.analytic_tag_ids
analytic_distribution = asset_id.analytic_distribution
depreciation_date = self.env.context.get('depreciation_date') or fields.Date.context_today(self)
amount = 0.0
for line in self:
# Sum amount of all depreciation lines
company_currency = line.asset_id.company_id.currency_id
current_currency = line.asset_id.currency_id
company = line.asset_id.company_id
amount += current_currency._convert(line.amount, company_currency, company, fields.Date.today())
name = category_id.name + _(' (grouped)')
move_line_1 = {
'name': name,
'account_id': category_id.account_depreciation_id.id,
'debit': 0.0,
'credit': amount,
'journal_id': category_id.journal_id.id,
'analytic_distribution': analytic_distribution,
}
move_line_2 = {
'name': name,
'account_id': category_id.account_depreciation_expense_id.id,
'credit': 0.0,
'debit': amount,
'journal_id': category_id.journal_id.id,
'analytic_distribution': analytic_distribution,
}
move_vals = {
'ref': category_id.name,
'date': depreciation_date or False,
'journal_id': category_id.journal_id.id,
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
}
return move_vals
def create_grouped_move(self, post_move=True):
if not self.exists():
return []
created_moves = self.env['account.move']
move = self.env['account.move'].create(self._prepare_move_grouped())
self.write({'move_id': move.id, 'move_check': True})
created_moves |= move
if post_move and created_moves:
created_moves.action_post()
return [x.id for x in created_moves]
def post_lines_and_close_asset(self):
# we re-evaluate the assets to determine whether we can close them
for line in self:
line.log_message_when_posted()
asset = line.asset_id
if asset.currency_id.is_zero(asset.value_residual):
asset.message_post(body=_("Document closed."))
asset.write({'state': 'close'})
def log_message_when_posted(self):
def _format_message(message_description, tracked_values):
message = ''
if message_description:
message = '<span>%s</span>' % message_description
for name, values in tracked_values.items():
message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % name
message += '%s</div>' % values
return Markup(message)
for line in self:
if line.move_id and line.move_id.state == 'draft':
partner_name = line.asset_id.partner_id.name
currency_name = line.asset_id.currency_id.name
msg_values = {_('Currency'): currency_name, _('Amount'): line.amount}
if partner_name:
msg_values[_('Partner')] = partner_name
msg = _format_message(_('Depreciation line posted.'), msg_values)
line.asset_id.message_post(body=msg)
def unlink(self):
for record in self:
if record.move_check:
if record.asset_id.category_id.type == 'purchase':
msg = _("You cannot delete posted depreciation lines.")
else:
msg = _("You cannot delete posted installment lines.")
raise UserError(msg)
return super(AccountAssetDepreciationLine, self).unlink()

View File

@@ -0,0 +1,160 @@
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
class AccountMove(models.Model):
_inherit = 'account.move'
asset_ids = fields.One2many(
'account.asset.asset', 'invoice_id', string="Assets"
)
def button_draft(self):
res = super(AccountMove, self).button_draft()
for move in self:
if any(asset_id.state != 'draft' for asset_id in move.asset_ids):
raise ValidationError(_(
'You cannot reset to draft for an entry having a posted asset'))
if move.asset_ids:
move.asset_ids.sudo().write({'active': False})
for asset in move.asset_ids:
asset.sudo().message_post(body=_("Vendor bill cancelled."))
return res
@api.model
def _refund_cleanup_lines(self, lines):
result = super(AccountMove, self)._refund_cleanup_lines(lines)
for i, line in enumerate(lines):
for name, field in line._fields.items():
if name == 'asset_category_id':
result[i][2][name] = False
break
return result
def action_cancel(self):
res = super(AccountMove, self).action_cancel()
assets = self.env['account.asset.asset'].sudo().search(
[('invoice_id', 'in', self.ids)])
if assets:
assets.sudo().write({'active': False})
for asset in assets:
asset.sudo().message_post(body=_("Vendor bill cancelled."))
return res
def action_post(self):
result = super(AccountMove, self).action_post()
for inv in self:
context = dict(self.env.context)
context.pop('default_type', None)
for mv_line in inv.invoice_line_ids:
mv_line.with_context(context).asset_create()
return result
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
asset_category_id = fields.Many2one(
'account.asset.category', string='Asset Category'
)
asset_start_date = fields.Date(
string='Asset Start Date', compute='_get_asset_date',
readonly=True, store=True
)
asset_end_date = fields.Date(
string='Asset End Date', compute='_get_asset_date',
readonly=True, store=True
)
asset_mrr = fields.Float(
string='Monthly Recurring Revenue', compute='_get_asset_date',
readonly=True, store=True
)
@api.model
def default_get(self, fields):
res = super(AccountMoveLine, self).default_get(fields)
if self.env.context.get('create_bill') and not self.asset_category_id:
if self.product_id and self.move_id.move_type == 'out_invoice' and \
self.product_id.product_tmpl_id.deferred_revenue_category_id:
self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id.id
elif self.product_id and self.product_id.product_tmpl_id.asset_category_id and \
self.move_id.move_type == 'in_invoice':
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
self.onchange_asset_category_id()
return res
@api.depends('asset_category_id', 'move_id.invoice_date')
def _get_asset_date(self):
for rec in self:
rec.asset_mrr = 0
rec.asset_start_date = False
rec.asset_end_date = False
cat = rec.asset_category_id
if cat:
if cat.method_number == 0 or cat.method_period == 0:
raise UserError(_('The number of depreciations or the period length of '
'your asset category cannot be 0.'))
months = cat.method_number * cat.method_period
if rec.move_id.move_type in ['out_invoice', 'out_refund']:
price_subtotal = self.currency_id._convert(
self.price_subtotal,
self.company_currency_id,
self.company_id,
self.move_id.invoice_date or fields.Date.context_today(
self))
rec.asset_mrr = price_subtotal / months
if rec.move_id.invoice_date:
start_date = rec.move_id.invoice_date.replace(day=1)
end_date = (start_date + relativedelta(months=months, days=-1))
rec.asset_start_date = start_date
rec.asset_end_date = end_date
def asset_create(self):
if self.asset_category_id:
price_subtotal = self.currency_id._convert(
self.price_subtotal,
self.company_currency_id,
self.company_id,
self.move_id.invoice_date or fields.Date.context_today(
self))
vals = {
'name': self.name,
'code': self.name or False,
'category_id': self.asset_category_id.id,
'value': price_subtotal,
'partner_id': self.move_id.partner_id.id,
'company_id': self.move_id.company_id.id,
'currency_id': self.move_id.company_currency_id.id,
'date': self.move_id.invoice_date or self.move_id.date,
'invoice_id': self.move_id.id,
}
changed_vals = self.env['account.asset.asset'].onchange_category_id_values(vals['category_id'])
vals.update(changed_vals['value'])
asset = self.env['account.asset.asset'].create(vals)
if self.asset_category_id.open_asset:
if asset.date_first_depreciation == 'manual':
asset.first_depreciation_manual_date = asset.date
asset.validate()
return True
@api.onchange('asset_category_id', 'product_uom_id')
def onchange_asset_category_id(self):
if self.move_id.move_type == 'out_invoice' and self.asset_category_id:
self.account_id = self.asset_category_id.account_asset_id.id
elif self.move_id.move_type == 'in_invoice' and self.asset_category_id:
self.account_id = self.asset_category_id.account_asset_id.id
@api.onchange('product_id')
def _inverse_product_id(self):
res = super(AccountMoveLine, self)._inverse_product_id()
for rec in self:
if rec.product_id:
if rec.move_id.move_type == 'out_invoice':
rec.asset_category_id = rec.product_id.product_tmpl_id.deferred_revenue_category_id.id
elif rec.move_id.move_type == 'in_invoice':
rec.asset_category_id = rec.product_id.product_tmpl_id.asset_category_id.id
def get_invoice_line_account(self, type, product, fpos, company):
return product.asset_category_id.account_asset_id or super(AccountMoveLine, self).get_invoice_line_account(type, product, fpos, company)

View File

@@ -0,0 +1,22 @@
from odoo import api, fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
asset_category_id = fields.Many2one(
'account.asset.category', string='Asset Type',
company_dependent=True, ondelete="restrict"
)
deferred_revenue_category_id = fields.Many2one(
'account.asset.category', string='Deferred Revenue Type',
company_dependent=True, ondelete="restrict"
)
def _get_asset_accounts(self):
res = super(ProductTemplate, self)._get_asset_accounts()
if self.asset_category_id:
res['stock_input'] = self.property_account_expense_id
if self.deferred_revenue_category_id:
res['stock_output'] = self.property_account_income_id
return res

View File

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

View File

@@ -0,0 +1,65 @@
from odoo import api, fields, models, tools
class AssetAssetReport(models.Model):
_name = "asset.asset.report"
_description = "Assets Analysis"
_auto = False
name = fields.Char(string='Year', required=False, readonly=True)
date = fields.Date(readonly=True)
depreciation_date = fields.Date(string='Depreciation Date', readonly=True)
asset_id = fields.Many2one('account.asset.asset', string='Asset', readonly=True)
asset_category_id = fields.Many2one('account.asset.category', string='Asset category', readonly=True)
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], string='Status', readonly=True)
depreciation_value = fields.Float(string='Amount of Depreciation Lines', readonly=True)
installment_value = fields.Float(string='Amount of Installment Lines', readonly=True)
move_check = fields.Boolean(string='Posted', readonly=True)
installment_nbr = fields.Integer(string='Installment Count', readonly=True)
depreciation_nbr = fields.Integer(string='Depreciation Count', readonly=True)
gross_value = fields.Float(string='Gross Amount', readonly=True)
posted_value = fields.Float(string='Posted Amount', readonly=True)
unposted_value = fields.Float(string='Unposted Amount', readonly=True)
company_id = fields.Many2one('res.company', string='Company', readonly=True)
def init(self):
tools.drop_view_if_exists(self.env.cr, 'asset_asset_report')
self.env.cr.execute("""
create or replace view asset_asset_report as (
select
min(dl.id) as id,
dl.name as name,
dl.depreciation_date as depreciation_date,
a.date as date,
(CASE WHEN dlmin.id = min(dl.id)
THEN a.value
ELSE 0
END) as gross_value,
dl.amount as depreciation_value,
dl.amount as installment_value,
(CASE WHEN dl.move_check
THEN dl.amount
ELSE 0
END) as posted_value,
(CASE WHEN NOT dl.move_check
THEN dl.amount
ELSE 0
END) as unposted_value,
dl.asset_id as asset_id,
dl.move_check as move_check,
a.category_id as asset_category_id,
a.partner_id as partner_id,
a.state as state,
count(dl.*) as installment_nbr,
count(dl.*) as depreciation_nbr,
a.company_id as company_id
from account_asset_depreciation_line dl
left join account_asset_asset a on (dl.asset_id=a.id)
left join (select min(d.id) as id,ac.id as ac_id from account_asset_depreciation_line as d inner join account_asset_asset as ac ON (ac.id=d.asset_id) group by ac_id) as dlmin on dlmin.ac_id=a.id
where a.active is true
group by
dl.amount,dl.asset_id,dl.depreciation_date,dl.name,
a.date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id,
a.value, a.id, a.salvage_value, dlmin.id
)""")

View File

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

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="account_asset_category_multi_company_rule" model="ir.rule">
<field name="name">Account Asset Category multi-company</field>
<field ref="model_account_asset_category" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="account_asset_asset_multi_company_rule" model="ir.rule">
<field name="name">Account Asset multi-company</field>
<field ref="model_account_asset_asset" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_asset_category,account.asset.category,model_account_asset_category,account.group_account_user,1,0,0,0
access_asset_depreciation_confirmation_wizard,access_asset_depreciation_confirmation_wizard,model_asset_depreciation_confirmation_wizard,account.group_account_user,1,1,1,0
access_asset_modify,access_asset_modify,model_asset_modify,account.group_account_user,1,1,1,0
access_account_asset_asset,account.asset.asset,model_account_asset_asset,account.group_account_user,1,0,0,0
access_account_asset_category_manager,account.asset.category,model_account_asset_category,account.group_account_manager,1,1,1,1
access_account_asset_asset_manager,account.asset.asset,model_account_asset_asset,account.group_account_manager,1,1,1,1
access_account_asset_depreciation_line,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_user,1,0,0,0
access_account_asset_depreciation_line_manager,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_manager,1,1,1,1
access_asset_asset_report,asset.asset.report,model_asset_asset_report,account.group_account_user,1,0,0,0
access_asset_asset_report_manager,asset.asset.report,model_asset_asset_report,account.group_account_manager,1,1,1,1
access_account_asset_category_invoicing_payment,account.asset.category,model_account_asset_category,account.group_account_invoice,1,0,0,0
access_account_asset_asset_invoicing_payment,account.asset.asset,model_account_asset_asset,account.group_account_invoice,1,0,1,0
access_account_asset_depreciation_line_invoicing_payment,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_invoice,1,0,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_asset_category account.asset.category model_account_asset_category account.group_account_user 1 0 0 0
3 access_asset_depreciation_confirmation_wizard access_asset_depreciation_confirmation_wizard model_asset_depreciation_confirmation_wizard account.group_account_user 1 1 1 0
4 access_asset_modify access_asset_modify model_asset_modify account.group_account_user 1 1 1 0
5 access_account_asset_asset account.asset.asset model_account_asset_asset account.group_account_user 1 0 0 0
6 access_account_asset_category_manager account.asset.category model_account_asset_category account.group_account_manager 1 1 1 1
7 access_account_asset_asset_manager account.asset.asset model_account_asset_asset account.group_account_manager 1 1 1 1
8 access_account_asset_depreciation_line account.asset.depreciation.line model_account_asset_depreciation_line account.group_account_user 1 0 0 0
9 access_account_asset_depreciation_line_manager account.asset.depreciation.line model_account_asset_depreciation_line account.group_account_manager 1 1 1 1
10 access_asset_asset_report asset.asset.report model_asset_asset_report account.group_account_user 1 0 0 0
11 access_asset_asset_report_manager asset.asset.report model_asset_asset_report account.group_account_manager 1 1 1 1
12 access_account_asset_category_invoicing_payment account.asset.category model_account_asset_category account.group_account_invoice 1 0 0 0
13 access_account_asset_asset_invoicing_payment account.asset.asset model_account_asset_asset account.group_account_invoice 1 0 1 0
14 access_account_asset_depreciation_line_invoicing_payment account.asset.depreciation.line model_account_asset_depreciation_line account.group_account_invoice 1 0 1 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#DA956B"/><stop offset="100%" stop-color="#CC7039"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M4 69c-2 0-4-.146-4-4.09V40.738L18.16 24H52l1 2.045v6.137l-10.585 11.3 10.05 4.09L37.071 69H4z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#000" d="M53 42.084v5.66c0 1.837-1.111 3.34-3.556 3.34h-28c-2.466 0-4.444-1.503-4.444-3.34V28.34c0-1.837 1.978-3.34 4.444-3.34H49c2.444 0 4 1.368 4 3.205v5.88H37c-2.667 0-4 1.857-4 3.957 0 2.1 1.333 4.042 4 4.042h16zm-15-1.39a2.656 2.656 0 0 1-2.667-2.652A2.656 2.656 0 0 1 38 35.39a2.656 2.656 0 0 1 2.667 2.653A2.656 2.656 0 0 1 38 40.695z" opacity=".3"/><path fill="#FFF" d="M53 40.084v5.66c0 1.837-1.111 3.34-3.556 3.34h-28c-2.466 0-4.444-1.503-4.444-3.34V26.34c0-1.837 1.978-3.34 4.444-3.34H49c2.444 0 4 1.368 4 3.205v5.88H37c-2.667 0-4 1.857-4 3.957 0 2.1 1.333 4.042 4 4.042h16zm-15-1.39a2.656 2.656 0 0 1-2.667-2.652A2.656 2.656 0 0 1 38 33.39a2.656 2.656 0 0 1 2.667 2.653A2.656 2.656 0 0 1 38 38.695z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,84 @@
<section class="oe_container oe_dark">
<div class="col-md-12">
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Odoo 19 Asset Management</b></h2>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div style="text-align:center;">
<p class="fa fa-hand-o-right" style="color:CRIMSON;font-size: 25px;">
<span style="color:#2dd280;font-size: 15px;">Manage assets owned by a company or a person.</span>
</p><br/>
<p class="fa fa-hand-o-right" style="color:CRIMSON;font-size: 25px;">
<span style="color:#2dd280;font-size: 15px;">Keeps track of depreciation's, and creates corresponding journal entries</span>
</p><br/>
</div>
<br/>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_centeralign oe_websiteonly">
<h4 class="oe_slogan"><a href="https://www.youtube.com/watch?v=KudvDOTvx2I" 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"></i>
Watch on YouTube
</a></h4>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Asset Category</h3>
<div class="oe_demo oe_picture oe_screenshot">
<img src="asset_types.png">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Assets</h3>
<div class="oe_demo oe_picture oe_screenshot">
<img src="assets.png">
</div>
</div>
</section>
<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 help 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;">

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,9 @@
.o_web_client .o_deprec_lines_toggler {
color: theme-color('danger');
&.o_is_posted {
color: theme-color('success');
}
&.o_unposted {
color: theme-color('warning');
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_assets_scss_backend" model="ir.asset">
<field name="name">aAccount Assets SCSS</field>
<field name="bundle">web.assets_backend</field>
<field name="path">/om_account_asset/static/src/scss/account_asset.scss</field>
<field name="sequence" eval="18"/>
</record>
</odoo>

View File

@@ -0,0 +1,238 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_asset_asset_form" model="ir.ui.view">
<field name="name">account.asset.asset.form</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<form string="Asset">
<header>
<button name="validate" string="Confirm" type="object" class="oe_highlight" invisible="state != 'draft'"/>
<button type="object" name="compute_depreciation_board" string="Compute Depreciation"
invisible="state != 'draft'"/>
<button name="set_to_close" invisible="state != 'open'" string="Sell or Dispose" type="object"
class="oe_highlight"/>
<button name="set_to_draft" string="Set to Draft" type="object"
invisible="entry_count != 0 or state != 'open'"/>
<button name="%(action_asset_modify)d" invisible="state != 'open'" string="Modify Depreciation" type="action"/>
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="open_entries" type="object" icon="fa-pencil">
<field string="Items" name="entry_count" widget="statinfo"/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Laptop iBook" readonly="state != 'draft'"/>
</h1>
</div>
<group>
<group>
<field name="category_id" string="Asset Category"
domain="[('type', '=', 'purchase')]"
readonly="state != 'draft'"
context="{'default_type': 'purchase'}" help="Category of asset"/>
<field name="code" readonly="state != 'draft'"/>
<field name="date" help="Date of asset" readonly="state != 'draft'"/>
<field name="date_first_depreciation" readonly="state != 'draft'"/>
<field name="first_depreciation_manual_date"
readonly="state != 'draft'"
invisible="date_first_depreciation != 'manual'"
required="date_first_depreciation == 'manual'"/>
<field name="type" invisible="1"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
</group>
<group>
<field name="currency_id" groups="base.group_multi_currency"
readonly="state != 'draft'"/>
<field name="company_id" options="{'no_create': True}"
readonly="state != 'draft'"
groups="base.group_multi_company"/>
<field name="value" widget="monetary"
readonly="state != 'draft'" help="Gross value of asset"/>
<field name="salvage_value" widget="monetary"
readonly="state != 'draft'"
invisible="type == 'sale'"/>
<field name="value_residual" widget="monetary"/>
<field name="partner_id" string="Vendor" widget="res_partner_many2one"
readonly="state != 'draft'"
context="{'res_partner_search_mode': 'supplier'}"/>
<field name="invoice_id" string="Invoice" options="{'no_create': True}"/>
<field name="analytic_distribution" widget="analytic_distribution"/>
</group>
</group>
<notebook>
<page string="Depreciation Board">
<field name="depreciation_line_ids" mode="list"
readonly="state not in ('draft', 'open')"
options="{'reload_whole_on_button': true}">
<list string="Depreciation Lines" decoration-info="(move_check == False)"
create="false">
<field name="depreciation_date"/>
<field name="amount" widget="monetary" string="Depreciation"/>
<field name="depreciated_value" readonly="1"/>
<field name="remaining_value" readonly="1" widget="monetary" string="Residual"/>
<field name="move_check" widget="deprec_lines_toggler"
invisible="parent_state != 'open'"/>
<field name="move_posted_check" invisible="1"/>
<field name="parent_state" invisible="1"/>
</list>
<form string="Depreciation Lines" create="false">
<group>
<group>
<field name="parent_state" invisible="1"/>
<field name="name"/>
<field name="sequence"/>
<field name="move_id"/>
<field name="move_check"/>
<field name="parent_state" invisible="1"/>
</group>
<group>
<field name="amount" widget="monetary"/>
<field name="depreciation_date"/>
<field name="depreciated_value"/>
<field name="remaining_value"/>
</group>
</group>
</form>
</field>
</page>
<page string="Depreciation Information">
<group>
<field name="method" widget="radio"
invisible="type == 'sale'" readonly="state != 'draft'"/>
<field name="method_progress_factor"
readonly="state != 'draft'"
invisible="method == 'linear'"
required="method == 'degressive'"/>
<field name="method_time" string="Time Method Based On"
widget="radio"
readonly="state != 'draft'"
invisible="type != 'purchase'"/>
<field name="prorata"
invisible="method_time == 'end'"
readonly="state != 'draft'"/>
</group>
<group>
<field name="method_number"
readonly="state != 'draft'"
invisible="method_time == 'end'"
required="method_time == 'number'"/>
<field name="method_period" readonly="state != 'draft'"/>
<field name="method_end"
readonly="state != 'draft'"
invisible="method_time == 'end'"
required="method_time == 'number'"/>
</group>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_account_asset_asset_kanban" model="ir.ui.view">
<field name="name">account.asset.asset.kanban</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="category_id"/>
<field name="date"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="row mb4">
<div class="col-6">
<strong>
<span>
<t t-esc="record.name.value"/>
</span>
</strong>
</div>
<div class="col-6 text-end">
<strong>
<t t-esc="record.date.value"/>
</strong>
</div>
</div>
<div class="row">
<div class="col-6 text-muted">
<span>
<t t-esc="record.category_id.value"/>
</span>
</div>
<div class="col-6">
<span class="float-right text-end">
<field name="state" widget="kanban_label_selection"
options="{'classes': {'draft': 'primary', 'open': 'success', 'close': 'default'}}"/>
</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_account_asset_asset_purchase_tree" model="ir.ui.view">
<field name="name">account.asset.asset.purchase.list</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<list string="Assets" decoration-info="(state == 'draft')" decoration-muted="(state == 'close')">
<field name="name"/>
<field name="category_id" string="Asset Category"/>
<field name="date"/>
<field name="partner_id" string="Vendor"/>
<field name="value"/>
<field name="value_residual" widget="monetary"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="state"/>
</list>
</field>
</record>
<record id="view_account_asset_search" model="ir.ui.view">
<field name="name">account.asset.asset.search</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<search string="Asset Account">
<field name="name" string="Asset"/>
<field name="date"/>
<filter string="Current" name="current" domain="[('state','in', ('draft','open'))]"
help="Assets in draft and open states"/>
<filter string="Closed" name="closed" domain="[('state','=', 'close')]"
help="Assets in closed state"/>
<field name="category_id" string="Asset Category"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<group>
<filter string="Date" name="month" domain="[]" context="{'group_by':'date'}"/>
<filter string="Asset Category" name="category" domain="[]" context="{'group_by':'category_id'}"/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_account_asset_asset_form">
<field name="name">Assets</field>
<field name="res_model">account.asset.asset</field>
<field name="view_mode">list,kanban,form</field>
<field name="view_id" ref="view_account_asset_asset_purchase_tree"/>
<field name="domain">[('category_id.type', '=', 'purchase')]</field>
</record>
<menuitem id="menu_action_account_asset_asset_form"
parent="account.account_account_menu"
action="action_account_asset_asset_form"
sequence="101"
groups="account.group_account_manager"/>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_invoice_asset_category" model="ir.ui.view">
<field name="name">account.move.supplier.form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='invoice_line_ids']/list/field[@name='account_id']" position="before">
<field string="Asset Category" name="asset_category_id"
force_save="1" column_invisible="parent.move_type != 'in_invoice'"
domain="[('type','=','purchase')]" context="{'default_type':'purchase'}"/>
</xpath>
<xpath expr="//field[@name='line_ids']/list/field[@name='account_id']" position="before">
<field string="Asset Category" name="asset_category_id"
column_invisible="1"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_asset_category_form" model="ir.ui.view">
<field name="name">account.asset.category.form</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<form string="Asset category">
<sheet>
<div class="oe_title">
<label for="name" string="Asset Type" class="oe_edit_only"
invisible="type != 'purchase'"/>
<label for="name" string="Deferred Revenue Type" class="oe_edit_only"
invisible="type == 'purchase'"/>
<h1>
<field name="name" placeholder="e.g. Computers"/>
</h1>
</div>
<group string="Journal Entries">
<group>
<field name="journal_id"/>
<div>
<label for="account_asset_id" invisible="type != 'purchase'"
style="font-weight: bold" class="o_light_label"/>
<label for="account_asset_id" string="Deferred Revenue Account"
invisible="type != 'sale'" style="font-weight: bold"
class="o_light_label"/>
</div>
<field name="account_asset_id" nolabel="1"
domain="[('company_ids', 'in', company_id)]"/>
<div>
<label for="account_depreciation_id" invisible="type != 'purchase'"
style="font-weight: bold" class="o_light_label"/>
<label for="account_depreciation_id" string="Recognition Income Account"
invisible="type != 'sale'"
style="font-weight: bold" class="o_light_label"/>
</div>
<field name="account_depreciation_id" nolabel="1"
domain="[('company_ids', 'in', company_id)]"/>
</group>
<group>
<div>
<label for="account_depreciation_expense_id"
invisible="type != 'purchase'"
style="font-weight: bold" class="o_light_label"/>
<label for="account_depreciation_expense_id" string="Recognition Account"
invisible="type != 'sale'"
style="font-weight: bold" class="o_light_label"/>
</div>
<field name="account_depreciation_expense_id" nolabel="1"
domain="[('company_ids', 'in', company_id)]"/>
<field name="account_analytic_id" domain="[('company_id', 'in', company_id)]"
groups="analytic.group_analytic_accounting"/>
<field name="analytic_distribution" widget="analytic_distribution"/>
</group>
</group>
<group>
<group string="Periodicity">
<field name="method_time" string="Time Method Based On" widget="radio"
invisible="type != 'purchase'"/>
<field name="method_number" string="Number of Entries"
required="method_time == 'number'"
invisible="method_time != 'number' or type == False"/>
<label for="method_period" string="One Entry Every"/>
<div>
<field name="method_period" nolabel="1"
invisible="type == False" class="oe_inline"/>
months
</div>
<field name="method_end"
required="method_time == 'end'"
invisible="method_time != 'end'"/>
</group>
<group>
<field name="type" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="company_id" options="{'no_create': True}"
groups="base.group_multi_company"/>
</group>
<group string="Additional Options">
<field name="open_asset"/>
<field name="group_entries"/>
<field name="date_first_depreciation"/>
</group>
<group invisible="type == 'sale'" string="Depreciation Method">
<field name="method" widget="radio"/>
<field name="method_progress_factor"
invisible="method_time == 'linear'" required="method == 'degressive'"/>
<field name="prorata" invisible="method_time == 'end'"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_account_asset_asset_category_kanban" model="ir.ui.view">
<field name="name">account.asset.category.kanban</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="journal_id"/>
<field name="method"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="row mb4">
<div class="col-6">
<strong>
<span>
<t t-esc="record.name.value"/>
</span>
</strong>
</div>
<div class="col-6 text-end">
<span class="badge badge-pill">
<strong>
<t t-esc="record.method.value"/>
</strong>
</span>
</div>
</div>
<div>
<t t-esc="record.journal_id.value"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_account_asset_category_tree" model="ir.ui.view">
<field name="name">account.asset.category.list</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<list string="Asset category">
<field name="name"/>
<field name="journal_id"/>
<field name="method"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</field>
</record>
<record id="view_account_asset_category_search" model="ir.ui.view">
<field name="name">account.asset.category.search</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<search string="Search Asset Category">
<filter string="Sales" name="sales" domain="[('type','=', 'sale')]" help="Deferred Revenues"/>
<filter string="Purchase" name="purchase" domain="[('type','=', 'purchase')]" help="Assets"/>
<field name="name" string="Category"/>
<field name="journal_id"/>
<group>
<filter string="Type" name="type" domain="[]" context="{'group_by':'type'}"/>
</group>
</search>
</field>
</record>
<record id="action_account_asset_asset_list_normal_purchase" model="ir.actions.act_window">
<field name="name">Asset Category</field>
<field name="res_model">account.asset.category</field>
<field name="domain">[('type', '=', 'purchase')]</field>
<field name="view_mode">list,kanban,form</field>
<field name="context">{'default_type': 'purchase'}</field>
</record>
<menuitem id="menu_action_account_asset_asset_list_normal_purchase"
parent="account.account_account_menu"
action="action_account_asset_asset_list_normal_purchase"
sequence="6"/>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_product_template_form_inherit" model="ir.ui.view">
<field name="name">Product Template (form)</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="account.product_template_form_view"/>
<field name="arch" type="xml">
<field name="property_account_expense_id" position="after">
<field name="asset_category_id"
domain="[('type', '=', 'purchase')]"
context="{'default_type': 'purchase'}"
groups="account.group_account_user"/>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import asset_depreciation_confirmation_wizard
from . import asset_modify

View File

@@ -0,0 +1,29 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
class AssetDepreciationConfirmationWizard(models.TransientModel):
_name = "asset.depreciation.confirmation.wizard"
_description = "asset.depreciation.confirmation.wizard"
date = fields.Date(
'Account Date', required=True,
help="Choose the period for which you want to automatically post the depreciation lines of running assets",
default=fields.Date.context_today
)
def asset_compute(self):
self.ensure_one()
context = self.env.context
created_move_ids = self.env['account.asset.asset'].compute_generated_entries(self.date, asset_type=context.get('asset_type'))
return {
'name': _('Created Asset Moves') if context.get('asset_type') == 'purchase' else _('Created Revenue Moves'),
'view_type': 'form',
'view_mode': 'list,form',
'res_model': 'account.move',
'view_id': False,
'domain': "[('id','in',[" + ','.join(str(id) for id in created_move_ids) + "])]",
'type': 'ir.actions.act_window',
}

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_asset_depreciation_confirmation_wizard" model="ir.ui.view">
<field name="name">asset.depreciation.confirmation.wizard</field>
<field name="model">asset.depreciation.confirmation.wizard</field>
<field name="arch" type="xml">
<form string="Compute Asset">
<div>
<p>
This wizard will post installment/depreciation lines for the selected month.<br/>
This will generate journal entries for all related installment lines on this period
of asset/revenue recognition as well.
</p>
</div>
<group>
<field name="date"/>
</group>
<footer>
<button string="Generate Entries" name="asset_compute" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_asset_depreciation_confirmation_wizard" model="ir.actions.act_window">
<field name="name">Post Depreciation Lines</field>
<field name="res_model">asset.depreciation.confirmation.wizard</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="view_asset_depreciation_confirmation_wizard"/>
<field name="target">new</field>
<field name="context">{'asset_type': 'purchase'}</field>
</record>
<menuitem id="menu_finance_entries_generate_entries"
parent="account.menu_finance_entries"
name="Generate Entries"/>
<menuitem id="menu_asset_depreciation_confirmation_wizard"
name="Generate Assets Entries"
action="action_asset_depreciation_confirmation_wizard"
parent="om_account_asset.menu_finance_entries_generate_entries"
sequence="111"
groups="account.group_account_manager"/>
</odoo>

View File

@@ -0,0 +1,67 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class AssetModify(models.TransientModel):
_name = 'asset.modify'
_description = 'Modify Asset'
name = fields.Text(string='Reason', required=True)
method_number = fields.Integer(
string='Number of Depreciation', required=True
)
method_period = fields.Integer(string='Period Length')
method_end = fields.Date(string='Ending date')
asset_method_time = fields.Char(
compute='_get_asset_method_time', string='Asset Method Time', readonly=True
)
def _get_asset_method_time(self):
if self.env.context.get('active_id'):
asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
self.asset_method_time = asset.method_time
@api.model
def default_get(self, fields):
res = super(AssetModify, self).default_get(fields)
asset_id = self.env.context.get('active_id')
asset = self.env['account.asset.asset'].browse(asset_id)
if 'name' in fields:
res.update({'name': asset.name})
if 'method_number' in fields and asset.method_time == 'number':
res.update({'method_number': asset.method_number})
if 'method_period' in fields:
res.update({'method_period': asset.method_period})
if 'method_end' in fields and asset.method_time == 'end':
res.update({'method_end': asset.method_end})
if self.env.context.get('active_id'):
active_asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
res['asset_method_time'] = active_asset.method_time
return res
def modify(self):
""" Modifies the duration of asset for calculating depreciation
and maintains the history of old values, in the chatter.
"""
asset_id = self.env.context.get('active_id', False)
asset = self.env['account.asset.asset'].browse(asset_id)
old_values = {
'method_number': asset.method_number,
'method_period': asset.method_period,
'method_end': asset.method_end,
}
asset_vals = {
'method_number': self.method_number,
'method_period': self.method_period,
'method_end': self.method_end,
}
if asset_vals['method_number'] <= asset.entry_count:
raise UserError(_('The number of depreciations must be greater than the number of posted or draft entries '
'to allow for complete depreciation of the asset.'))
asset.write(asset_vals)
asset.compute_depreciation_board()
tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_period', 'method_end'])
changes, tracking_value_ids = asset._mail_track(tracked_fields, old_values)
if changes:
asset.message_post(subject=_('Depreciation board modified'), body=self.name, tracking_value_ids=tracking_value_ids)
return {'type': 'ir.actions.act_window_close'}

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="asset_modify_form" model="ir.ui.view">
<field name="name">wizard.asset.modify.form</field>
<field name="model">asset.modify</field>
<field name="arch" type="xml">
<form string="Modify Asset">
<field name="asset_method_time" invisible="1"/>
<group string="Asset Durations to Modify" col="4">
<group colspan="2" col="2">
<field name="name"/>
<field name="method_number" invisible="asset_method_time == 'end'"/>
</group>
<group colspan="2" col="2">
<field name="method_end" invisible="asset_method_time == 'number'"/>
<label for="method_period"/>
<div>
<field name="method_period" class="oe_inline"/> months
</div>
</group>
</group>
<footer>
<button name="modify" string="Modify" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_asset_modify" model="ir.actions.act_window">
<field name="name">Modify Asset</field>
<field name="res_model">asset.modify</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="asset_modify_form"/>
<field name="target">new</field>
</record>
</odoo>

View File

@@ -2,7 +2,7 @@
'name': 'Odoo 19 Budget Management',
'author': 'Odoo Mates, Odoo SA',
'category': 'Accounting',
'version': '1.0.1',
'version': 1.0.119.0.1.0.1', # __odoosky_original_version__: '1.0.1'
'description': """Use budgets to compare actual with expected revenues and costs""",
'summary': 'Odoo 19 Budget Management',
'sequence': 10,

View File

@@ -0,0 +1,232 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="crossovered_budget_budgetoptimistic0" model="crossovered.budget">
<field eval="'Budget '+str(datetime.now().year+1)+': Optimistic'" name="name"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="&quot;&quot;&quot;draft&quot;&quot;&quot;" name="state"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="user_id" ref="base.user_root"/>
</record>
<record id="budget_line_analytic_admin" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">-35000</field>
<field name="analytic_account_id" ref="analytic.analytic_administratif"/>
</record>
<record id="budget_line_analytic_agrolait1" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-01-31'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait2" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-02-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-02-28'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait3" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-03-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-03-31'" name="date_to"/>
<field name="planned_amount">12000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait4" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-04-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-04-30'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait5" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-05-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-05-31'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait6" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-06-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-06-30'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait7" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-07-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-07-31'" name="date_to"/>
<field name="planned_amount">13000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait8" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-08-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-08-31'" name="date_to"/>
<field name="planned_amount">9000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait9" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-09-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-09-30'" name="date_to"/>
<field name="planned_amount">8000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait10" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-10-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-10-31'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait11" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-11-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-11-30'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait12" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0"/>
<field eval="str(datetime.now().year+1)+'-12-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">18000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<!-- pessimistic-->
<record id="crossovered_budget_budgetpessimistic0" model="crossovered.budget">
<field eval="'Budget '+str(datetime.now().year+1)+': Pessimistic'" name="name"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="&quot;&quot;&quot;draft&quot;&quot;&quot;" name="state"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="user_id" ref="base.user_root"/>
</record>
<record id="budget_line_analytic_admin_pessim" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">-55000</field>
<field name="analytic_account_id" ref="analytic.analytic_administratif"/>
</record>
<record id="budget_line_analytic_agrolait_pessim1" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-01-31'" name="date_to"/>
<field name="planned_amount">9000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim2" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-02-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-02-28'" name="date_to"/>
<field name="planned_amount">8000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim3" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-03-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-03-31'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim4" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-04-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-04-30'" name="date_to"/>
<field name="planned_amount">14000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim5" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-05-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-05-31'" name="date_to"/>
<field name="planned_amount">16000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim6" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-06-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-06-30'" name="date_to"/>
<field name="planned_amount">13000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim7" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-07-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-07-31'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim8" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-08-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-08-31'" name="date_to"/>
<field name="planned_amount">8000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim9" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-09-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-09-30'" name="date_to"/>
<field name="planned_amount">7000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim10" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-10-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-10-31'" name="date_to"/>
<field name="planned_amount">12000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim11" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-11-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-11-30'" name="date_to"/>
<field name="planned_amount">18000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim12" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0"/>
<field eval="str(datetime.now().year+1)+'-12-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">18000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,508 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_account_budget
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-06 03:01+0000\n"
"PO-Revision-Date: 2022-07-06 03:01+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"\"End Date\" of the budget line should be included in the Period of the "
"budget"
msgstr ""
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"\"Start Date\" of the budget line should be included in the Period of the "
"budget"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_kanban
msgid "<i class=\"fa fa-clock-o\" role=\"img\" aria-label=\"Period\" title=\"Period\"/>"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__account_ids
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_form
msgid "Accounts"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__percentage
msgid "Achievement"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_needaction
msgid "Action Needed"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__practical_amount
msgid "Amount really earned/spent."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__theoritical_amount
msgid "Amount you are supposed to have earned/spent at this date."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__planned_amount
msgid ""
"Amount you plan to earn/spend. Record a positive amount if it is a revenue "
"and a negative amount if it is a cost."
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_account_analytic_account
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__analytic_account_id
msgid "Analytic Account"
msgstr "الحساب التحليلي"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__analytic_plan_id
msgid "Analytic Group"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Approve"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_attachment_count
msgid "Attachment Count"
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_crossovered_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__crossovered_budget_id
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_tree
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Budget"
msgstr ""
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_account_analytic_account_cb_lines
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Budget Items"
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_crossovered_budget_lines
msgid "Budget Line"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_analytic_account__crossovered_budget_line
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__crossovered_budget_line
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_tree
msgid "Budget Lines"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__name
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Budget Name"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__crossovered_budget_state
msgid "Budget State"
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_account_budget_post
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__general_budget_id
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_search
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_tree
msgid "Budgetary Position"
msgstr ""
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.open_budget_post_form
#: model:ir.ui.menu,name:om_account_budget.menu_budget_post_form
msgid "Budgetary Positions"
msgstr "وظائف الميزانية\n"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_crossovered_budget_view
#: model:ir.ui.menu,name:om_account_budget.menu_act_crossovered_budget_view
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Budgets"
msgstr "الميزانيات\n"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_crossovered_budget_lines_view
#: model:ir.ui.menu,name:om_account_budget.menu_act_crossovered_budget_lines_view
msgid "Budgets Analysis"
msgstr "تحليل الميزانيات\n"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Cancel Budget"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__cancel
msgid "Cancelled"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.actions.act_window,help:om_account_budget.act_crossovered_budget_view
msgid "Click to create a new budget."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__company_id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__company_id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__company_id
msgid "Company"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__percentage
msgid ""
"Comparison between practical and theoretical amount. This measure tells you "
"if you are below or over budget."
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Confirm"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__confirm
msgid "Confirmed"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__create_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__create_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__create_uid
msgid "Created by"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__create_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__create_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__create_date
msgid "Created on"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__currency_id
msgid "Currency"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__display_name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__display_name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__display_name
msgid "Display Name"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__done
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Done"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__draft
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Draft"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Draft Budgets"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__date_to
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__date_to
msgid "End Date"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Entries..."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_follower_ids
msgid "Followers"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_partner_ids
msgid "Followers (Partners)"
msgstr "المتابعون (الشركاء)\n"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Group By"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__has_message
msgid "Has Message"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__id
msgid "ID"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_needaction
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_unread
msgid "If checked, new messages require your attention."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_error
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_sms_error
msgid "If checked, some messages have a delivery error."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__is_above_budget
msgid "Is Above Budget"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_is_follower
msgid "Is Follower"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post____last_update
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget____last_update
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines____last_update
msgid "Last Modified on"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__write_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__write_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__write_uid
msgid "Last Updated by"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__write_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__write_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__write_date
msgid "Last Updated on"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_main_attachment_id
msgid "Main Attachment"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_error
msgid "Message Delivery error"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_ids
msgid "Messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__name
msgid "Name"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Not Cancelled"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_needaction_counter
msgid "Number of Actions"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_error_counter
msgid "Number of errors"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_needaction_counter
msgid "Number of messages which requires an action"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_error_counter
msgid "Number of messages with delivery error"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_unread_counter
msgid "Number of unread messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__paid_date
msgid "Paid Date"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Period"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__planned_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Planned Amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Planned amount"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__practical_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Practical Amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Practical amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Reset to Draft"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__user_id
msgid "Responsible"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_sms_error
msgid "SMS Delivery error"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__date_from
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__date_from
msgid "Start Date"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__state
msgid "Status"
msgstr ""
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid "The budget must have at least one account."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__theoritical_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Theoretical Amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Theoritical Amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Theoritical amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "To Approve"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "To Approve Budgets"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_unread
msgid "Unread Messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_unread_counter
msgid "Unread Messages Counter"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.actions.act_window,help:om_account_budget.act_crossovered_budget_view
msgid "Use budgets to compare actual with expected revenues and costs"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__validate
msgid "Validated"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__website_message_ids
msgid "Website Messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__website_message_ids
msgid "Website communication history"
msgstr ""
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"You have to enter at least a budgetary position or analytic account on a "
"budget line."
msgstr ""

View File

@@ -0,0 +1,513 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_account_budget
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-07 07:13+0000\n"
"PO-Revision-Date: 2022-07-07 07:13+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"\"End Date\" of the budget line should be included in the Period of the "
"budget"
msgstr ""
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"\"Start Date\" of the budget line should be included in the Period of the "
"budget"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_kanban
msgid "<i class=\"fa fa-clock-o\" role=\"img\" aria-label=\"Period\" title=\"Period\"/>"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__account_ids
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_form
msgid "Accounts"
msgstr "Рахунки"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__percentage
msgid "Achievement"
msgstr "Досягнення"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_needaction
msgid "Action Needed"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__practical_amount
msgid "Amount really earned/spent."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__theoritical_amount
msgid "Amount you are supposed to have earned/spent at this date."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__planned_amount
msgid ""
"Amount you plan to earn/spend. Record a positive amount if it is a revenue "
"and a negative amount if it is a cost."
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_account_analytic_account
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__analytic_account_id
msgid "Analytic Account"
msgstr "Аналітичний рахунок"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__analytic_plan_id
msgid "Analytic Group"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Approve"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_attachment_count
msgid "Attachment Count"
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_crossovered_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__crossovered_budget_id
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_tree
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Budget"
msgstr ""
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_account_analytic_account_cb_lines
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Budget Items"
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_crossovered_budget_lines
msgid "Budget Line"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_analytic_account__crossovered_budget_line
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__crossovered_budget_line
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_tree
msgid "Budget Lines"
msgstr "Рядки бюджету"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__name
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Budget Name"
msgstr "Назва бюджету"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__crossovered_budget_state
msgid "Budget State"
msgstr ""
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_account_budget_post
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__general_budget_id
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_search
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_tree
msgid "Budgetary Position"
msgstr "Стаття бюджету"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.open_budget_post_form
#: model:ir.ui.menu,name:om_account_budget.menu_budget_post_form
msgid "Budgetary Positions"
msgstr "Стаття бюджету"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_crossovered_budget_view
#: model:ir.ui.menu,name:om_account_budget.menu_act_crossovered_budget_view
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Budgets"
msgstr "Бюджети"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_crossovered_budget_lines_view
#: model:ir.ui.menu,name:om_account_budget.menu_act_crossovered_budget_lines_view
msgid "Budgets Analysis"
msgstr "Аналіз бюджетів"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Cancel Budget"
msgstr "Скасувати бюджет"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__cancel
msgid "Cancelled"
msgstr "Скасовано"
#. module: om_account_budget
#: model_terms:ir.actions.act_window,help:om_account_budget.act_crossovered_budget_view
msgid "Click to create a new budget."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__company_id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__company_id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__company_id
msgid "Company"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__percentage
msgid ""
"Comparison between practical and theoretical amount. This measure tells you "
"if you are below or over budget."
msgstr ""
"Порівняння практичної та теоритичної сум. Цей захід сповістить Вас при "
"недовикористанні або превищенні бюджету."
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Confirm"
msgstr "Підтвердити"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__confirm
msgid "Confirmed"
msgstr "Підтверджено"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__create_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__create_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__create_uid
msgid "Created by"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__create_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__create_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__create_date
msgid "Created on"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__currency_id
msgid "Currency"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_analytic_account__display_name
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__display_name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__display_name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__display_name
msgid "Display Name"
msgstr "Відобразити назву"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__done
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Done"
msgstr "Виконано"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__draft
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Draft"
msgstr "Чернетка"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Draft Budgets"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__date_to
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__date_to
msgid "End Date"
msgstr "Дата закінчення"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Entries..."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_follower_ids
msgid "Followers"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_channel_ids
msgid "Followers (Channels)"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_partner_ids
msgid "Followers (Partners)"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Group By"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_analytic_account__id
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__id
msgid "ID"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_needaction
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_unread
msgid "If checked, new messages require your attention."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_error
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_sms_error
msgid "If checked, some messages have a delivery error."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__is_above_budget
msgid "Is Above Budget"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_is_follower
msgid "Is Follower"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_analytic_account____last_update
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post____last_update
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget____last_update
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines____last_update
msgid "Last Modified on"
msgstr "Останні зміни"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__write_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__write_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__write_uid
msgid "Last Updated by"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__write_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__write_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__write_date
msgid "Last Updated on"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_main_attachment_id
msgid "Main Attachment"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_error
msgid "Message Delivery error"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_ids
msgid "Messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__name
msgid "Name"
msgstr "Назва"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Not Cancelled"
msgstr "Не скасовано"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_needaction_counter
msgid "Number of Actions"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_error_counter
msgid "Number of errors"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_needaction_counter
msgid "Number of messages which requires an action"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_error_counter
msgid "Number of messages with delivery error"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_unread_counter
msgid "Number of unread messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__paid_date
msgid "Paid Date"
msgstr "Дата оплати"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Period"
msgstr "Період"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__planned_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Planned Amount"
msgstr "Запланована сума"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Planned amount"
msgstr "Запланована сума"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__practical_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Practical Amount"
msgstr "Фактична сума"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Practical amount"
msgstr "Фактична сума"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Reset to Draft"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__user_id
msgid "Responsible"
msgstr "Відповідальний"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_sms_error
msgid "SMS Delivery error"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__date_from
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__date_from
msgid "Start Date"
msgstr "Дата початку"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__state
msgid "Status"
msgstr "Статус"
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid "The budget must have at least one account."
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__theoritical_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Theoretical Amount"
msgstr "Теоретична сума"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Theoritical Amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Theoritical amount"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "To Approve"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "To Approve Budgets"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_unread
msgid "Unread Messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_unread_counter
msgid "Unread Messages Counter"
msgstr ""
#. module: om_account_budget
#: model_terms:ir.actions.act_window,help:om_account_budget.act_crossovered_budget_view
msgid "Use budgets to compare actual with expected revenues and costs"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__validate
msgid "Validated"
msgstr "Перевірено"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__website_message_ids
msgid "Website Messages"
msgstr ""
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__website_message_ids
msgid "Website communication history"
msgstr ""
#. module: om_account_budget
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"You have to enter at least a budgetary position or analytic account on a "
"budget line."
msgstr ""

View File

@@ -0,0 +1,496 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_account_budget
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0-20231105\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-23 21:30+0000\n"
"PO-Revision-Date: 2023-11-24 06:16+0800\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.4.1\n"
#. module: om_account_budget
#. odoo-python
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"\"End Date\" of the budget line should be included in the Period of the "
"budget"
msgstr "預算項目的「結束日期」應包含在預算期間"
#. module: om_account_budget
#. odoo-python
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"\"Start Date\" of the budget line should be included in the Period of the "
"budget"
msgstr "預算項目的「開始日期」應包含在預算期間"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_kanban
msgid ""
"<i class=\"fa fa-clock-o\" role=\"img\" aria-label=\"Period\" "
"title=\"Period\"/>"
msgstr ""
"<i class=\"fa fa-clock-o\" role=\"img\" aria-label=\"Period\" "
"title=\"Period\"/>"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__account_ids
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_form
msgid "Accounts"
msgstr "會計帳戶"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__percentage
msgid "Achievement"
msgstr "達成"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_needaction
msgid "Action Needed"
msgstr "需採取行動"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__practical_amount
msgid "Amount really earned/spent."
msgstr "實際賺取/花費的金額。"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__theoritical_amount
msgid "Amount you are supposed to have earned/spent at this date."
msgstr "您在該日期應賺取/支出的金額。"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__planned_amount
msgid ""
"Amount you plan to earn/spend. Record a positive amount if it is a revenue "
"and a negative amount if it is a cost."
msgstr ""
"您計劃賺取/支出的金額。如果是收入,則記錄正數;如果是成本,則記錄負數。"
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_account_analytic_account
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__analytic_account_id
msgid "Analytic Account"
msgstr "分析科目"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__analytic_plan_id
msgid "Analytic Plan"
msgstr "分析計劃"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Approve"
msgstr "批准"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_attachment_count
msgid "Attachment Count"
msgstr "附件數"
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_crossovered_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__crossovered_budget_id
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_tree
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Budget"
msgstr "預算"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_account_analytic_account_cb_lines
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Budget Items"
msgstr "預算項目"
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_crossovered_budget_lines
msgid "Budget Line"
msgstr "預算明細"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_analytic_account__crossovered_budget_line
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__crossovered_budget_line
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_tree
msgid "Budget Lines"
msgstr "預算明細"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__name
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Budget Name"
msgstr "預算名稱"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__crossovered_budget_state
msgid "Budget State"
msgstr "預算國家"
#. module: om_account_budget
#: model:ir.model,name:om_account_budget.model_account_budget_post
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__general_budget_id
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_search
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_budget_post_tree
msgid "Budgetary Position"
msgstr "預算狀況"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.open_budget_post_form
#: model:ir.ui.menu,name:om_account_budget.menu_budget_post_form
msgid "Budgetary Positions"
msgstr "預算項目"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_crossovered_budget_view
#: model:ir.ui.menu,name:om_account_budget.menu_act_crossovered_budget_view
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Budgets"
msgstr "預算"
#. module: om_account_budget
#: model:ir.actions.act_window,name:om_account_budget.act_crossovered_budget_lines_view
#: model:ir.ui.menu,name:om_account_budget.menu_act_crossovered_budget_lines_view
msgid "Budgets Analysis"
msgstr "預算分析"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Cancel Budget"
msgstr "取消預算"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__cancel
msgid "Cancelled"
msgstr "已取消"
#. module: om_account_budget
#: model_terms:ir.actions.act_window,help:om_account_budget.act_crossovered_budget_view
msgid "Click to create a new budget."
msgstr "點選以建立新預算。"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__company_id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__company_id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__company_id
msgid "Company"
msgstr "公司"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget_lines__percentage
msgid ""
"Comparison between practical and theoretical amount. This measure tells you "
"if you are below or over budget."
msgstr "實際金額與理論金額的比較。此指標可以告訴您是否低於或超過預算。"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Confirm"
msgstr "確認"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__confirm
msgid "Confirmed"
msgstr "已確認"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__create_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__create_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__create_uid
msgid "Created by"
msgstr "建立者"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__create_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__create_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__create_date
msgid "Created on"
msgstr "建立於"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__currency_id
msgid "Currency"
msgstr "幣別"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__display_name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__display_name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__display_name
msgid "Display Name"
msgstr "顯示名稱"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__done
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Done"
msgstr "完成"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__draft
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Draft"
msgstr "草稿"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "Draft Budgets"
msgstr "預算草案"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__date_to
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__date_to
msgid "End Date"
msgstr "結束日期"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Entries..."
msgstr "細項"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_follower_ids
msgid "Followers"
msgstr "關注者"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_partner_ids
msgid "Followers (Partners)"
msgstr "訂閱者(合作夥伴)"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Group By"
msgstr "分組按"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__has_message
msgid "Has Message"
msgstr "有訊息"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__id
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__id
msgid "ID"
msgstr "ID"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_needaction
msgid "If checked, new messages require your attention."
msgstr "勾選代表有新訊息需要您留意."
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_error
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_sms_error
msgid "If checked, some messages have a delivery error."
msgstr "勾選代表有訊息發生傳送錯誤."
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__is_above_budget
msgid "Is Above Budget"
msgstr "超出預算"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_is_follower
msgid "Is Follower"
msgstr "是訂閱者"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__write_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__write_uid
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__write_uid
msgid "Last Updated by"
msgstr "最後更新人"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__write_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__write_date
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__write_date
msgid "Last Updated on"
msgstr "最後更新時間"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_error
msgid "Message Delivery error"
msgstr "訊息遞送錯誤"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_ids
msgid "Messages"
msgstr "訊息"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_account_budget_post__name
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__name
msgid "Name"
msgstr "名稱"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_search
msgid "Not Cancelled"
msgstr "未取消"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_needaction_counter
msgid "Number of Actions"
msgstr "動作數"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_error_counter
msgid "Number of errors"
msgstr "錯誤數"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_needaction_counter
msgid "Number of messages requiring action"
msgstr "需要執行操作的訊息數量"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__message_has_error_counter
msgid "Number of messages with delivery error"
msgstr "有發送錯誤的郵件數"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__paid_date
msgid "Paid Date"
msgstr "付款日期"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Period"
msgstr "會計期間"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__planned_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Planned Amount"
msgstr "計劃金額"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Planned amount"
msgstr "計劃金額"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__practical_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Practical Amount"
msgstr "實際金額"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Practical amount"
msgstr "實際數量"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__rating_ids
msgid "Ratings"
msgstr "評分"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Reset to Draft"
msgstr "重設為草稿"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__user_id
msgid "Responsible"
msgstr "負責人"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__message_has_sms_error
msgid "SMS Delivery error"
msgstr "簡訊發送錯誤"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__date_from
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__date_from
msgid "Start Date"
msgstr "開始日期"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__state
msgid "Status"
msgstr "狀態"
#. module: om_account_budget
#. odoo-python
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid "The budget must have at least one account."
msgstr "預算必須至少有一個帳戶。"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget_lines__theoritical_amount
#: model_terms:ir.ui.view,arch_db:om_account_budget.crossovered_budget_view_form
msgid "Theoretical Amount"
msgstr "理論金額"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_account_analytic_account_form_inherit_budget
msgid "Theoritical Amount"
msgstr "理論金額"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_graph
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_line_pivot
msgid "Theoritical amount"
msgstr "理論金額"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "To Approve"
msgstr "待批准"
#. module: om_account_budget
#: model_terms:ir.ui.view,arch_db:om_account_budget.view_crossovered_budget_search
msgid "To Approve Budgets"
msgstr "待批准預算"
#. module: om_account_budget
#: model_terms:ir.actions.act_window,help:om_account_budget.act_crossovered_budget_view
msgid "Use budgets to compare actual with expected revenues and costs"
msgstr "使用預算來比較實際與預期的收入和成本"
#. module: om_account_budget
#: model:ir.model.fields.selection,name:om_account_budget.selection__crossovered_budget__state__validate
msgid "Validated"
msgstr "已驗證"
#. module: om_account_budget
#: model:ir.model.fields,field_description:om_account_budget.field_crossovered_budget__website_message_ids
msgid "Website Messages"
msgstr "網站訊息"
#. module: om_account_budget
#: model:ir.model.fields,help:om_account_budget.field_crossovered_budget__website_message_ids
msgid "Website communication history"
msgstr "網站溝通記錄"
#. module: om_account_budget
#. odoo-python
#: code:addons/om_account_budget/models/account_budget.py:0
#, python-format
msgid ""
"You have to enter at least a budgetary position or analytic account on a "
"budget line."
msgstr "您必須在預算明細上至少輸入預算狀況或分析帳戶。"

View File

@@ -0,0 +1,2 @@
from . import account_budget
from . import account_analytic_account

View File

@@ -0,0 +1,37 @@
from odoo import fields, models, api
class AccountAnalyticAccount(models.Model):
_inherit = "account.analytic.account"
crossovered_budget_line = fields.One2many(
'crossovered.budget.lines', 'analytic_account_id', 'Budget Lines'
)
class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.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._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)

View File

@@ -0,0 +1,270 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class AccountBudgetPost(models.Model):
_name = "account.budget.post"
_order = "name"
_description = "Budgetary Position"
name = fields.Char('Name', required=True)
account_ids = fields.Many2many(
'account.account', 'account_budget_rel', 'budget_id',
'account_id', 'Accounts'
)
company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
def _check_account_ids(self, vals):
# Raise an error to prevent the account.budget.post to have not specified account_ids.
# This check is done on create because require=True doesn't work on Many2many fields.
if 'account_ids' in vals:
account_ids = self.new({'account_ids': vals['account_ids']}, origin=self).account_ids
else:
account_ids = self.account_ids
if not account_ids:
raise ValidationError(_('The budget must have at least one account.'))
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
self._check_account_ids(vals)
return super(AccountBudgetPost, self).create(vals_list)
def write(self, vals):
self._check_account_ids(vals)
return super(AccountBudgetPost, self).write(vals)
class CrossoveredBudget(models.Model):
_name = "crossovered.budget"
_description = "Budget"
_inherit = ['mail.thread']
name = fields.Char('Budget Name', required=True)
user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user)
date_from = fields.Date('Start Date', required=True)
date_to = fields.Date('End Date', required=True)
state = fields.Selection([
('draft', 'Draft'),
('cancel', 'Cancelled'),
('confirm', 'Confirmed'),
('validate', 'Validated'),
('done', 'Done')
], 'Status', default='draft', index=True, required=True, readonly=True, copy=False, tracking=True)
crossovered_budget_line = fields.One2many(
'crossovered.budget.lines', 'crossovered_budget_id',
'Budget Lines', copy=True
)
company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
def action_budget_confirm(self):
self.write({'state': 'confirm'})
def action_budget_draft(self):
self.write({'state': 'draft'})
def action_budget_validate(self):
self.write({'state': 'validate'})
def action_budget_cancel(self):
self.write({'state': 'cancel'})
def action_budget_done(self):
self.write({'state': 'done'})
class CrossoveredBudgetLines(models.Model):
_name = "crossovered.budget.lines"
_description = "Budget Line"
name = fields.Char(compute='_compute_line_name')
crossovered_budget_id = fields.Many2one('crossovered.budget', 'Budget', ondelete='cascade', index=True, required=True)
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account')
analytic_plan_id = fields.Many2one(related='analytic_account_id.plan_id')
general_budget_id = fields.Many2one('account.budget.post', 'Budgetary Position')
date_from = fields.Date('Start Date', required=True)
date_to = fields.Date('End Date', required=True)
paid_date = fields.Date('Paid Date')
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True)
planned_amount = fields.Monetary(
'Planned Amount', required=True,
help="Amount you plan to earn/spend. Record a positive amount if it is a revenue and a negative amount if it is a cost.")
practical_amount = fields.Monetary(
compute='_compute_practical_amount', string='Practical Amount', help="Amount really earned/spent.")
theoritical_amount = fields.Monetary(
compute='_compute_theoritical_amount', string='Theoretical Amount',
help="Amount you are supposed to have earned/spent at this date.")
percentage = fields.Float(
compute='_compute_percentage', string='Achievement',
help="Comparison between practical and theoretical amount. This measure tells you if you are below or over budget.")
company_id = fields.Many2one(related='crossovered_budget_id.company_id', comodel_name='res.company',
string='Company', store=True, readonly=True)
is_above_budget = fields.Boolean(compute='_is_above_budget')
crossovered_budget_state = fields.Selection(related='crossovered_budget_id.state', string='Budget State', store=True, readonly=True)
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
# overrides the default read_group in order to compute the computed fields manually for the group
fields_list = {'practical_amount', 'theoritical_amount', 'percentage'}
fields = {field.split(':', 1)[0] if field.split(':', 1)[0] in fields_list else field for field in fields}
result = super(CrossoveredBudgetLines, self).read_group(domain, fields, groupby, offset=offset, limit=limit,
orderby=orderby, lazy=lazy)
if any(x in fields for x in fields_list):
for group_line in result:
# initialise fields to compute to 0 if they are requested
if 'practical_amount' in fields:
group_line['practical_amount'] = 0
if 'theoritical_amount' in fields:
group_line['theoritical_amount'] = 0
if 'percentage' in fields:
group_line['percentage'] = 0
group_line['practical_amount'] = 0
group_line['theoritical_amount'] = 0
if group_line.get('__domain'):
all_budget_lines_that_compose_group = self.search(group_line['__domain'])
else:
all_budget_lines_that_compose_group = self.search([])
for budget_line_of_group in all_budget_lines_that_compose_group:
if 'practical_amount' in fields or 'percentage' in fields:
group_line['practical_amount'] += budget_line_of_group.practical_amount
if 'theoritical_amount' in fields or 'percentage' in fields:
group_line['theoritical_amount'] += budget_line_of_group.theoritical_amount
if 'percentage' in fields:
if group_line['theoritical_amount']:
# use a weighted average
group_line['percentage'] = float(
(group_line['practical_amount'] or 0.0) / group_line['theoritical_amount']) * 100
return result
def _is_above_budget(self):
for line in self:
if line.theoritical_amount >= 0:
line.is_above_budget = line.practical_amount > line.theoritical_amount
else:
line.is_above_budget = line.practical_amount < line.theoritical_amount
def _compute_line_name(self):
#just in case someone opens the budget line in form view
for line in self:
computed_name = line.crossovered_budget_id.name
if line.general_budget_id:
computed_name += ' - ' + line.general_budget_id.name
if line.analytic_account_id:
computed_name += ' - ' + line.analytic_account_id.name
line.name = computed_name
def _compute_practical_amount(self):
for line in self:
acc_ids = line.general_budget_id.account_ids.ids
date_to = line.date_to
date_from = line.date_from
if line.analytic_account_id.id:
analytic_line_obj = self.env['account.analytic.line']
domain = [('account_id', '=', line.analytic_account_id.id),
('date', '>=', date_from),
('date', '<=', date_to),
]
if acc_ids:
domain += [('general_account_id', 'in', acc_ids)]
where_query = analytic_line_obj._where_calc(domain)
analytic_line_obj._apply_ir_rules(where_query, 'read')
from_string, from_params = where_query.from_clause
where_string, where_params = where_query.where_clause
from_clause, where_clause, where_clause_params = from_string, where_string, from_params + where_params
select = "SELECT SUM(amount) from " + from_clause + " where " + where_clause
else:
aml_obj = self.env['account.move.line']
domain = [('account_id', 'in',
line.general_budget_id.account_ids.ids),
('date', '>=', date_from),
('date', '<=', date_to)
]
where_query = aml_obj._where_calc(domain)
aml_obj._apply_ir_rules(where_query, 'read')
from_string, from_params = where_query.from_clause
where_string, where_params = where_query.where_clause
from_clause, where_clause, where_clause_params = from_string, where_string, from_params + where_params
select = "SELECT sum(credit)-sum(debit) from " + from_clause + " where " + where_clause
self.env.cr.execute(select, where_clause_params)
line.practical_amount = self.env.cr.fetchone()[0] or 0.0
def _compute_theoritical_amount(self):
# beware: 'today' variable is mocked in the python tests and thus, its implementation matter
today = fields.Date.today()
for line in self:
if line.paid_date:
if today <= line.paid_date:
theo_amt = 0.00
else:
theo_amt = line.planned_amount
else:
line_timedelta = line.date_to - line.date_from
elapsed_timedelta = today - line.date_from
if elapsed_timedelta.days < 0:
# If the budget line has not started yet, theoretical amount should be zero
theo_amt = 0.00
elif line_timedelta.days > 0 and today < line.date_to:
# If today is between the budget line date_from and date_to
theo_amt = (elapsed_timedelta.total_seconds() / line_timedelta.total_seconds()) * line.planned_amount
else:
theo_amt = line.planned_amount
line.theoritical_amount = theo_amt
def _compute_percentage(self):
for line in self:
if line.theoritical_amount != 0.00:
line.percentage = float((line.practical_amount or 0.0) / line.theoritical_amount)
else:
line.percentage = 0.00
@api.constrains('general_budget_id', 'analytic_account_id')
def _must_have_analytical_or_budgetary_or_both(self):
if not self.analytic_account_id and not self.general_budget_id:
raise ValidationError(
_("You have to enter at least a budgetary position or analytic account on a budget line."))
def action_open_budget_entries(self):
if self.analytic_account_id:
# if there is an analytic account, then the analytic items are loaded
action = self.env['ir.actions.act_window']._for_xml_id('analytic.account_analytic_line_action_entries')
action['domain'] = [('account_id', '=', self.analytic_account_id.id),
('date', '>=', self.date_from),
('date', '<=', self.date_to)
]
if self.general_budget_id:
action['domain'] += [('general_account_id', 'in', self.general_budget_id.account_ids.ids)]
else:
# otherwise the journal entries booked on the accounts of the budgetary postition are opened
action = self.env['ir.actions.act_window']._for_xml_id('account.action_account_moves_all_a')
action['domain'] = [('account_id', 'in',
self.general_budget_id.account_ids.ids),
('date', '>=', self.date_from),
('date', '<=', self.date_to)
]
return action
@api.constrains('date_from', 'date_to')
def _line_dates_between_budget_dates(self):
for rec in self:
budget_date_from = rec.crossovered_budget_id.date_from
budget_date_to = rec.crossovered_budget_id.date_to
if rec.date_from:
date_from = rec.date_from
if date_from < budget_date_from or date_from > budget_date_to:
raise ValidationError(_('"Start Date" of the budget line should be included in the Period of the budget'))
if rec.date_to:
date_to = rec.date_to
if date_to < budget_date_from or date_to > budget_date_to:
raise ValidationError(_('"End Date" of the budget line should be included in the Period of the budget'))

View File

@@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_crossovered_budget,crossovered.budget,model_crossovered_budget,account.group_account_manager,1,0,0,0
access_account_budget_post,account.budget.post,model_account_budget_post,account.group_account_manager,1,0,0,0
access_account_budget_post_accountant,account.budget.post accountant,model_account_budget_post,account.group_account_user,1,1,1,1
access_crossovered_budget_accountant,crossovered.budget accountant,model_crossovered_budget,account.group_account_user,1,1,1,1
access_crossovered_budget_lines_accountant,crossovered.budget.lines accountant,model_crossovered_budget_lines,account.group_account_user,1,1,1,1
access_budget,crossovered.budget.lines manager,model_crossovered_budget_lines,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_crossovered_budget crossovered.budget model_crossovered_budget account.group_account_manager 1 0 0 0
3 access_account_budget_post account.budget.post model_account_budget_post account.group_account_manager 1 0 0 0
4 access_account_budget_post_accountant account.budget.post accountant model_account_budget_post account.group_account_user 1 1 1 1
5 access_crossovered_budget_accountant crossovered.budget accountant model_crossovered_budget account.group_account_user 1 1 1 1
6 access_crossovered_budget_lines_accountant crossovered.budget.lines accountant model_crossovered_budget_lines account.group_account_user 1 1 1 1
7 access_budget crossovered.budget.lines manager model_crossovered_budget_lines base.group_user 1 1 1 0

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="budget_post_comp_rule" model="ir.rule">
<field name="name">Budget post multi-company</field>
<field name="model_id" ref="model_account_budget_post"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="budget_comp_rule" model="ir.rule">
<field name="name">Budget multi-company</field>
<field name="model_id" ref="model_crossovered_budget"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="budget_lines_comp_rule" model="ir.rule">
<field name="name">Budget lines multi-company</field>
<field name="model_id" ref="model_crossovered_budget_lines"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_analytic_account_form_inherit_budget" model="ir.ui.view">
<field name="name">account.analytic.account.form.inherit.budget</field>
<field name="model">account.analytic.account</field>
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
<field name="priority" eval="50"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='main']" position='after'>
<notebook groups="account.group_account_user">
<page string="Budget Items">
<field name="crossovered_budget_line" widget="one2many_list" colspan="4" nolabel="1"
mode="list">
<list string="Budget Items" editable="top">
<field name="crossovered_budget_id"/>
<field name="general_budget_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="paid_date"/>
<field name="planned_amount" widget="monetary"/>
<field name="practical_amount" sum="Practical Amount" widget="monetary"/>
<field name="theoritical_amount" sum="Theoritical Amount" widget="monetary"/>
<field name="percentage"/>
</list>
<form string="Budget Items">
<field name="crossovered_budget_id"/>
<field name="general_budget_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="paid_date"/>
<field name="planned_amount" widget="monetary"/>
</form>
</field>
</page>
</notebook>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,382 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_budget_post_search" model="ir.ui.view">
<field name="name">account.budget.post.search</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<search string="Budgetary Position">
<field name="name" filter_domain="[('name','ilike',self)]" string="Budgetary Position"/>
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="view_budget_post_tree" model="ir.ui.view">
<field name="name">account.budget.post.list</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<list string="Budgetary Position">
<field name="name"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</list>
</field>
</record>
<record id="open_budget_post_form" model="ir.actions.act_window">
<field name="name">Budgetary Positions</field>
<field name="res_model">account.budget.post</field>
<field name="view_mode">list,kanban,form</field>
<field name="view_id" ref="view_budget_post_tree"/>
<field name="search_view_id" ref="view_budget_post_search"/>
</record>
<menuitem id="menu_budget_post_form"
action="open_budget_post_form"
parent="account.account_account_menu"
sequence="5"/>
<record id="view_budget_post_form" model="ir.ui.view">
<field name="name">account.budget.post.form</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<form string="Budgetary Position">
<group col="4">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
<notebook>
<page string="Accounts">
<field name="account_ids">
<list>
<field name="code"/>
<field name="name"/>
</list>
</field>
</page>
</notebook>
</form>
</field>
</record>
<record id="crossovered_budget_view_form" model="ir.ui.view">
<field name="name">crossovered.budget.view.form</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<form string="Budget">
<header>
<button string="Confirm" name="action_budget_confirm" type="object"
invisible="state != 'draft'"
class="oe_highlight"/>
<button string="Approve" name="action_budget_validate" type="object"
invisible="state != 'confirm'"
class="oe_highlight"/>
<button string="Done" name="action_budget_done" type="object"
invisible="state != 'validate'"
class="oe_highlight"/>
<button string="Reset to Draft" name="action_budget_draft"
invisible="state != 'cancel'" type="object"/>
<button string="Cancel Budget" name="action_budget_cancel" invisible="state not in ('confirm', 'validate')" type="object"/>
<field name="state" widget="statusbar" />
</header>
<sheet string="Budget">
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" readonly="state != 'draft'" placeholder="Budget Name"/>
</h1>
</div>
<group>
<group>
<field name="user_id" readonly="state != 'draft'"/>
</group>
<group>
<label for="date_from" string="Period"/>
<div>
<field name="date_from" class="oe_inline"
readonly="state != 'draft'"/>
-
<field name="date_to" class="oe_inline" readonly="state != 'draft'"
nolabel="1"/>
</div>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</group>
<notebook>
<page string="Budget Lines">
<field name="crossovered_budget_line"
context="{'default_date_from': date_from,'default_date_to': date_to}" colspan="4"
nolabel="1" readonly="state != 'draft'">
<list string="Budget Lines" decoration-success="is_above_budget and planned_amount &gt; 0" decoration-danger="is_above_budget and planned_amount &lt; 0" editable="bottom">
<field name="general_budget_id"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="paid_date" groups="base.group_no_one"/>
<field name="currency_id" invisible="1"/>
<field name="planned_amount" sum="Planned Amount"/>
<field name="practical_amount" sum="Practical Amount"/>
<field name="theoritical_amount" sum="Theoretical Amount"/>
<field name="percentage" widget="percentage" />
<button type="object" name="action_open_budget_entries" string="Entries..."
icon="fa-arrow-circle-o-right"/>
<field name="is_above_budget" invisible="1"/>
</list>
<form string="Budget Lines">
<group>
<group>
<field name="currency_id" invisible="1"/>
<field name="general_budget_id"/>
<field name="planned_amount"/>
<field name="analytic_account_id"
groups="analytic.group_analytic_accounting"/>
</group>
<group>
<label for="date_from" string="Period"/>
<div>
<field name="date_from" class="oe_inline"/>
-
<field name="date_to" class="oe_inline"/>
</div>
<field name="paid_date" groups="base.group_no_one"/>
<field name="company_id" options="{'no_create': True}"
groups="base.group_multi_company"/>
</group>
</group>
</form>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="crossovered_budget_view_tree" model="ir.ui.view">
<field name="name">crossovered.budget.view.list</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<list decoration-info="state == 'draft'" decoration-muted="state in ('done','cancel')" string="Budget">
<field name="name" colspan="1"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="user_id"/>
<field name="state"/>
</list>
</field>
</record>
<record id="view_crossovered_budget_kanban" model="ir.ui.view">
<field name="name">crossovered.budget.kanban</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="user_id"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="row mb4">
<div class="col-8">
<strong>
<field name="name"/>
</strong>
</div>
<div class="col-4">
<span class="float-right">
<field name="state" widget="kanban_label_selection"
options="{'classes': {'draft': 'default', 'done': 'success'}}"/>
</span>
</div>
</div>
<div class="row">
<div class="col-10">
<i class="fa fa-clock-o" role="img" aria-label="Period" title="Period"/>
<t t-esc="record.date_from.value"/>-
<t t-esc="record.date_to.value"/>
</div>
<div class="col-2">
<span class="float-right">
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)"
t-att-title="record.user_id.value" t-att-alt="record.user_id.value" width="24" height="24"
class="oe_kanban_avatar float-right"/>
</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_crossovered_budget_search" model="ir.ui.view">
<field name="name">crossovered.budget.search</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<search string="Budget">
<field name="name" filter_domain="[('name','ilike',self)]" string="Budget"/>
<field name="date_from"/>
<field name="date_to"/>
<filter string="Draft" name="draft" domain="[('state','=','draft')]" help="Draft Budgets"/>
<filter string="To Approve" name="toapprove" domain="[('state','=','confirm')]"
help="To Approve Budgets"/>
<field name="state"/>
</search>
</field>
</record>
<record id="act_crossovered_budget_view" model="ir.actions.act_window">
<field name="name">Budgets</field>
<field name="res_model">crossovered.budget</field>
<field name="view_mode">list,kanban,form</field>
<field name="view_id" ref="crossovered_budget_view_tree"/>
<field name="search_view_id" ref="view_crossovered_budget_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Click to create a new budget.
</p>
<p>
Use budgets to compare actual with expected revenues and costs
</p>
</field>
</record>
<menuitem id="menu_act_crossovered_budget_view"
parent="account.account_account_menu"
name="Budgets"
action="act_crossovered_budget_view"
sequence="60"
groups="account.group_account_manager"/>
<record id="view_crossovered_budget_line_search" model="ir.ui.view">
<field name="name">account.budget.line.search</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<search string="Budget Lines">
<field name="analytic_account_id"/>
<field name="crossovered_budget_id"/>
<filter name="filter_not_cancelled" string="Not Cancelled"
domain="[('crossovered_budget_state','!=','cancel')]"/>
<group>
<filter name="group_crossevered_budgdet_id" string="Budgets"
domain="[]" context="{'group_by':'crossovered_budget_id'}"/>
</group>
</search>
</field>
</record>
<record id="view_crossovered_budget_line_tree" model="ir.ui.view">
<field name="name">crossovered.budget.line.list</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<list string="Budget Lines" create="0">
<field name="currency_id" invisible="1"/>
<field name="crossovered_budget_id" invisible="1"/>
<field name="general_budget_id" />
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" />
<field name="date_from" />
<field name="date_to" />
<field name="paid_date" groups="base.group_no_one" />
<field name="planned_amount"/>
<field name="practical_amount"/>
<field name="theoritical_amount"/>
<field name="percentage" widget="percentage"/>
</list>
</field>
</record>
<record id="view_crossovered_budget_line_form" model="ir.ui.view">
<field name="name">crossovered.budget.line.form</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<form string="Budget Lines">
<sheet>
<group col="4">
<field name="currency_id" invisible="1"/>
<field name="crossovered_budget_state" invisible="1"/>
<field name="crossovered_budget_id"
readonly="crossovered_budget_state != 'draft'"/>
<field name="analytic_account_id"
readonly="crossovered_budget_state != 'draft'"/>
<field name="general_budget_id"
readonly="crossovered_budget_state != 'draft'"/>
<field name="date_from"
readonly="crossovered_budget_state != 'draft'"/>
<field name="date_to"
readonly="crossovered_budget_state != 'draft'"/>
<field name="paid_date"
readonly="crossovered_budget_state != 'draft'"/>
<field name="planned_amount"
readonly="crossovered_budget_state != 'draft'"/>
<field name="practical_amount"
readonly="crossovered_budget_state != 'draft'"/>
<field name="theoritical_amount"
readonly="crossovered_budget_state != 'draft'"/>
<field name="percentage" widget="percentage"
readonly="crossovered_budget_state != 'draft'"/>
<field name="company_id" options="{'no_create': True}"
groups="base.group_multi_company"
readonly="crossovered_budget_state != 'draft'"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_crossovered_budget_line_pivot" model="ir.ui.view">
<field name="name">crossovered.budget.line.pivot</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<pivot string="Budget Lines">
<field name="crossovered_budget_id" type="row"/>
<field name="planned_amount" type="measure" string="Planned amount"/>
<field name="theoritical_amount" type="measure" string="Theoritical amount"/>
<field name="practical_amount" type="measure" string="Practical amount"/>
<field name="percentage" type="measure" widget="percentage"/>
</pivot>
</field>
</record>
<record id="view_crossovered_budget_line_graph" model="ir.ui.view">
<field name="name">crossovered.budget.line.graph</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<graph string="Budget Lines">
<field name="crossovered_budget_id" type="row"/>
<field name="planned_amount" type="measure" string="Planned amount"/>
<field name="theoritical_amount" type="measure" string="Theoritical amount"/>
<field name="practical_amount" type="measure" string="Practical amount"/>
</graph>
</field>
</record>
<record id="act_crossovered_budget_lines_view" model="ir.actions.act_window">
<field name="name">Budgets Analysis</field>
<field name="res_model">crossovered.budget.lines</field>
<field name="view_mode">list,form,pivot,graph</field>
<field name="view_id" eval="False"/>
<field name="context">{'search_default_group_crossevered_budgdet_id': True,
'search_default_filter_not_cancelled':True}</field>
</record>
<menuitem id="menu_act_crossovered_budget_lines_view"
parent="account.account_reports_management_menu"
action="act_crossovered_budget_lines_view"
sequence="20"
groups="account.group_account_user"/>
<record id="act_account_analytic_account_cb_lines" model="ir.actions.act_window">
<field name="name">Budget Items</field>
<field name="res_model">crossovered.budget.lines</field>
<field name="view_mode">list,kanban,form</field>
<field name="context">{'search_default_analytic_account_id': [active_id],
'default_analytic_account_id': active_id}</field>
</record>
</odoo>

View File

@@ -0,0 +1,13 @@
<?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.account.budget</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//setting[@id='account_budget']" position="replace"/>
</field>
</record>
</odoo>

View File

@@ -1,6 +1,6 @@
{
'name': 'Cash Book, Day Book, Bank Book Financial Reports',
'version': '1.0.1',
'version': 1.0.119.0.1.0.1', # __odoosky_original_version__: '1.0.1'
'category': 'Invoicing Management',
'summary': 'Cash Book, Day Book and Bank Book Report For Odoo 19',
'description': 'Cash Book, Day Book and Bank Book Report For Odoo 19',

View File

@@ -0,0 +1,45 @@
=============================
Customer Follow Up Management
=============================
This Module will add customer follow up management in 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
=============
Configure follow up levels
Credits
=======
Contributors
------------
* Odoo Mates <odoomates@gmail.com>
Author & Maintainer
-------------------
This module is maintained by the Odoo Mates

View File

@@ -0,0 +1,3 @@
from . import wizard
from . import models
from . import report

View File

@@ -0,0 +1,27 @@
{
'name': 'Customer Follow Up Management',
'version': 1.0.219.0.1.0.2', # __odoosky_original_version__: '1.0.2'
'category': 'Accounting',
'description': """Customer FollowUp Management""",
'summary': """Customer FollowUp Management""",
'author': 'Odoo Mates, Odoo S.A',
'license': 'LGPL-3',
'website': 'https://www.odoomates.tech',
'depends': ['account', 'mail'],
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'data/mail_template_data.xml',
'wizard/followup_print_view.xml',
'wizard/followup_results_view.xml',
'views/followup_view.xml',
'views/account_move.xml',
'views/partners.xml',
'views/report_followup.xml',
'views/reports.xml',
'views/followup_partner_view.xml',
'report/followup_report.xml',
],
'demo': ['demo/demo.xml'],
'images': ['static/description/banner.png'],
}

View File

@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="email_template_om_account_followup_level0" model="mail.template">
<field name="name">First polite payment follow-up reminder email</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take
appropriate measures in order to carry out this payment in the next 8 days.
Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to
contact our accounting department.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<!--Mail template level 1 -->
<record id="email_template_om_account_followup_level1" model="mail.template">
<field name="name">A bit urging second payment follow-up reminder email</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="email_to">{{ object.email }}</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
We are disappointed to see that despite sending a reminder, that your account is now seriously overdue.
It is essential that immediate payment is made, otherwise we will have to consider placing a stop on your account
which means that we will no longer be able to supply your company with (goods/services).
Please, take appropriate measures in order to carry out this payment in the next 8 days.
If there is a problem with paying invoice that we are not aware of, do not hesitate to contact our accounting
department. so that we can resolve the matter quickly.
Details of due payments is printed below.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<!--Mail template level 2 -->
<record id="email_template_om_account_followup_level2" model="mail.template">
<field name="name">Urging payment follow-up reminder email</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="email_to">{{ object.email }}</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, legal action for the recovery of the debt will be taken without
further notice.
I trust that this action will prove unnecessary and details of due payments is printed below.
In case of any queries concerning this matter, do not hesitate to contact our accounting department.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<!-- Default follow up message -->
<record id="email_template_om_account_followup_default" model="mail.template">
<field name="name">Default payment follow-up reminder e-mail</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="email_to">{{ object.email }}</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take
appropriate measures in order to carry out this payment in the next 8 days.
Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to
contact our accounting department.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<record id="demo_followup1" model="followup.followup" forcecreate="False">
<field name="company_id" ref="base.main_company"/>
</record>
<record id="demo_followup_line1" model="followup.line" forcecreate="False">
<field name="name">Send first reminder email</field>
<field name="sequence">0</field>
<field name="delay">15</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="send_email">True</field>
<field name="description">
Dear %(partner_name)s,
Exception made if there was a mistake of ours, it seems that
the following amount stays unpaid. Please, take appropriate
measures in order to carry out this payment in the next 8 days.
Would your payment have been carried out after this mail was
sent, please ignore this message. Do not hesitate to contact
our accounting department.
Best Regards,
</field>
<field name="email_template_id" ref="email_template_om_account_followup_level0"/>
</record>
<record id="demo_followup_line2" model="followup.line" forcecreate="False">
<field name="name">Send reminder letter and email</field>
<field name="sequence">1</field>
<field name="delay">30</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="email_template_id"
ref="email_template_om_account_followup_level1"/>
<field name="send_email">True</field>
<field name="send_letter">True</field>
<field name="description">
Dear %(partner_name)s,
We are disappointed to see that despite sending a reminder,
that your account is now seriously overdue.
It is essential that immediate payment is made, otherwise we
will have to consider placing a stop on your account which
means that we will no longer be able to supply your company
with (goods/services).
Please, take appropriate measures in order to carry out this
payment in the next 8 days.
If there is a problem with paying invoice that we are not aware
of, do not hesitate to contact our accounting department, so
that we can resolve the matter quickly.
Details of due payments is printed below.
Best Regards,
</field>
</record>
<record id="demo_followup_line3" model="followup.line" forcecreate="False">
<field name="name">Call the customer on the phone</field>
<field name="sequence">3</field>
<field name="delay">40</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="email_template_id"
ref="email_template_om_account_followup_level2"/>
<field eval="False" name="send_email"/>
<field name="manual_action">True</field>
<field name="manual_action_note">Call the customer on the phone!</field>
<field name="description">
Dear %(partner_name)s,
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, then legal action
for the recovery of the debt will be taken without further
notice.
I trust that this action will prove unnecessary and details of
due payments is printed below.
In case of any queries concerning this matter, do not hesitate
to contact our accounting department.
Best Regards,
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="demo_followup_line4" model="followup.line">
<field name="name">Urging reminder email</field>
<field name="sequence">4</field>
<field name="delay">50</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="send_email">True</field>
<field name="email_template_id" ref="email_template_om_account_followup_level2"/>
<field name="description">
Dear %(partner_name)s,
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, then legal action
for the recovery of the debt will be taken without further
notice.
I trust that this action will prove unnecessary and details of
due payments is printed below.
In case of any queries concerning this matter, do not hesitate
to contact our accounting department.
Best Regards,
</field>
</record>
<record id="demo_followup_line5" model="followup.line">
<field name="name">Urging reminder letter</field>
<field name="sequence">5</field>
<field name="delay">60</field>
<field name="followup_id" ref="demo_followup1"/>
<field eval="False" name="send_email"/>
<field name="send_letter">True</field>
<field name="email_template_id" ref="email_template_om_account_followup_level2"/>
<field name="description">
Dear %(partner_name)s,
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, then legal action
for the recovery of the debt will be taken without further
notice.
I trust that this action will prove unnecessary and details of
due payments is printed below.
In case of any queries concerning this matter, do not hesitate
to contact our accounting department.
Best Regards,
</field>
</record>
</data>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import account_move
from . import followup
from . import followup_partner
from . import partner
from . import settings

View File

@@ -0,0 +1,13 @@
from odoo import api, fields, models, _
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
followup_line_id = fields.Many2one('followup.line', 'Follow-up Level')
followup_date = fields.Date('Latest Follow-up')
result = fields.Float(compute='_get_result', string="Balance Amount")
def _get_result(self):
for aml in self:
aml.result = aml.debit - aml.credit

View File

@@ -0,0 +1,91 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class FollowupFollowup(models.Model):
_name = 'followup.followup'
_description = 'Account Follow-up'
_rec_name = 'name'
name = fields.Char(string="Name", related='company_id.name', readonly=True)
followup_line = fields.One2many('followup.line', 'followup_id', 'Follow-up', copy=True)
company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
_company_uniq = models.Constraint(
'unique(company_id)',
'Only one follow-up per company is allowed',
)
class FollowupLine(models.Model):
_name = 'followup.line'
_description = 'Follow-up Criteria'
_order = 'delay'
def _compute_sequence(self):
delays = [line.delay for line in self.followup_id.followup_line]
delays.sort()
for line in self.followup_id.followup_line:
sequence = delays.index(line.delay)
line.sequence = sequence+1
@api.model
def default_get(self, default_fields):
values = super(FollowupLine, self).default_get(default_fields)
if self.env.ref('om_account_followup.email_template_om_account_followup_default'):
values['email_template_id'] = self.env.ref('om_account_followup.email_template_om_account_followup_default').id
return values
name = fields.Char('Follow-Up Action', required=True)
sequence = fields.Integer('Sequence', compute='_compute_sequence',
store=False,
help="Gives the sequence order when displaying a list of follow-up lines.")
followup_id = fields.Many2one('followup.followup', 'Follow Ups',
required=True, ondelete="cascade")
delay = fields.Integer('Due Days',
help="The number of days after the due date of the "
"invoice to wait before sending the reminder. Could be negative if you want "
"to send a polite alert beforehand.",
required=True)
description = fields.Text('Printed Message', translate=True, default="""
Dear %(partner_name)s,
Exception made if there was a mistake of ours, it seems that the following
amount stays unpaid. Please, take appropriate measures in order to carry out
this payment in the next 8 days.
Would your payment have been carried out after this mail was sent, please
ignore this message. Do not hesitate to contact our accounting department.
Best Regards,
""", )
send_email = fields.Boolean('Send an Email', default=True,
help="When processing, it will send an email")
send_letter = fields.Boolean('Send a Letter', default=True,
help="When processing, it will print a letter")
manual_action = fields.Boolean('Manual Action', default=False,
help="When processing, it will set the "
"manual action to be taken for that customer. ")
manual_action_note = fields.Text('Action To Do')
manual_action_responsible_id = fields.Many2one('res.users',
string='Assign a Responsible', ondelete='set null')
email_template_id = fields.Many2one('mail.template', 'Email Template',
ondelete='set null')
_days_uniq = models.Constraint(
'unique(followup_id, delay)',
'Days of the follow-up levels must be different',
)
@api.constrains('description')
def _check_description(self):
for line in self:
if line.description:
try:
line.description % {'partner_name': '', 'date': '',
'user_signature': '',
'company_name': ''}
except ValidationError:
raise ValidationError(
_('Your description is invalid, use the right legend '
'or %% if you want to use the percent character.'))

View File

@@ -0,0 +1,50 @@
from odoo import api, fields, models, _
from odoo import tools
class FollowupStatByPartner(models.Model):
_name = "followup.stat.by.partner"
_description = "Follow-up Statistics by Partner"
_rec_name = 'partner_id'
_auto = False
def _get_invoice_partner_id(self):
for rec in self:
rec.invoice_partner_id = rec.partner_id.address_get(
adr_pref=['invoice']).get('invoice', rec.partner_id.id)
partner_id = fields.Many2one('res.partner', 'Partner', readonly=True)
date_move = fields.Date('First move', readonly=True)
date_move_last = fields.Date('Last move', readonly=True)
date_followup = fields.Date('Latest follow-up', readonly=True)
max_followup_id = fields.Many2one('followup.line', 'Max Follow Up Level', readonly=True, ondelete="cascade")
balance = fields.Float('Balance', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
invoice_partner_id = fields.Many2one('res.partner', compute='_get_invoice_partner_id', string='Invoice Address')
@api.model
def init(self):
tools.drop_view_if_exists(self.env.cr, 'followup_stat_by_partner')
self.env.cr.execute("""
create view followup_stat_by_partner as (
SELECT
l.partner_id * 10000::bigint + l.company_id as id,
l.partner_id AS partner_id,
min(l.date) AS date_move,
max(l.date) AS date_move_last,
max(l.followup_date) AS date_followup,
max(l.followup_line_id) AS max_followup_id,
sum(l.debit - l.credit) AS balance,
l.company_id as company_id
FROM
account_move_line l
LEFT JOIN account_account a ON (l.account_id = a.id)
WHERE
a.account_type = 'asset_receivable' AND
l.full_reconcile_id is NULL AND
l.partner_id IS NOT NULL
GROUP BY
l.partner_id, l.company_id
)""")

View File

@@ -0,0 +1,408 @@
from functools import reduce
from lxml import etree
from odoo import api, fields, models, _
from datetime import datetime
from odoo.exceptions import ValidationError
from odoo.tools.misc import formatLang
class ResPartner(models.Model):
_inherit = "res.partner"
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
res = super(ResPartner, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
if view_type == 'form' and self.env.context.get('Followupfirst'):
doc = etree.XML(res['arch'], parser=None, base_url=None)
first_node = doc.xpath("//page[@name='followup_tab']")
root = first_node[0].getparent()
root.insert(0, first_node[0])
res['arch'] = etree.tostring(doc, encoding="utf-8")
return res
def _get_latest(self):
company = self.env.user.company_id
for partner in self:
amls = partner.unreconciled_aml_ids
latest_date = False
latest_level = False
latest_days = False
latest_level_without_lit = False
latest_days_without_lit = False
for aml in amls:
aml_followup = aml.followup_line_id
if (aml.company_id == company) and aml_followup and \
(not latest_days or latest_days < aml_followup.delay):
latest_days = aml_followup.delay
latest_level = aml_followup.id
if (aml.company_id == company) and aml.followup_date and (
not latest_date or latest_date < aml.followup_date):
latest_date = aml.followup_date
if (aml.company_id == company) and \
(aml_followup and (not latest_days_without_lit or
latest_days_without_lit < aml_followup.delay)):
latest_days_without_lit = aml_followup.delay
latest_level_without_lit = aml_followup.id
partner.latest_followup_date = latest_date
partner.latest_followup_level_id = latest_level
partner.latest_followup_level_id_without_lit = latest_level_without_lit
def do_partner_manual_action_dermanord(self, followup_line):
action_text = followup_line.manual_action_note or ''
action_date = self.payment_next_action_date or \
fields.Date.today()
if self.payment_responsible_id:
responsible_id = self.payment_responsible_id.id
else:
p = followup_line.manual_action_responsible_id
responsible_id = p and p.id or False
self.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_manual_action(self, partner_ids):
for partner in self.browse(partner_ids):
followup_without_lit = partner.latest_followup_level_id_without_lit
if partner.payment_next_action:
action_text = \
(partner.payment_next_action or '') + "\n" + \
(followup_without_lit.manual_action_note or '')
else:
action_text = followup_without_lit.manual_action_note or ''
action_date = partner.payment_next_action_date or \
fields.Date.today()
if partner.payment_responsible_id:
responsible_id = partner.payment_responsible_id.id
else:
p = followup_without_lit.manual_action_responsible_id
responsible_id = p and p.id or False
partner.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_print(self, wizard_partner_ids, data):
if not wizard_partner_ids:
return {}
data['partner_ids'] = wizard_partner_ids
datas = {
'ids': wizard_partner_ids,
'model': 'followup.followup',
'form': data
}
return self.env.ref(
'om_account_followup.action_report_followup').report_action(
self, data=datas)
def do_partner_mail(self):
ctx = self.env.context.copy()
ctx['followup'] = True
template = 'om_account_followup.email_template_om_account_followup_default'
unknown_mails = 0
for partner in self:
partners_to_email = [child for child in partner.child_ids if
child.type == 'invoice' and child.email]
if not partners_to_email and partner.email:
partners_to_email = [partner]
if partners_to_email:
level = partner.latest_followup_level_id_without_lit
for partner_to_email in partners_to_email:
if level and level.send_email and \
level.email_template_id and \
level.email_template_id.id:
level.email_template_id.with_context(ctx).send_mail(
partner_to_email.id)
else:
mail_template_id = self.env.ref(template)
mail_template_id.with_context(ctx).send_mail(
partner_to_email.id)
if partner not in partners_to_email:
partner.message_post(body=_(
'Overdue email sent to %s' % ', '.join(
['%s <%s>' % (partner.name, partner.email) for
partner in partners_to_email])))
else:
unknown_mails = unknown_mails + 1
action_text = _("Email not sent because of email address "
"of partner not filled in")
if partner.payment_next_action_date:
payment_action_date = min(
fields.Date.today(),
partner.payment_next_action_date)
else:
payment_action_date = fields.Date.today()
if partner.payment_next_action:
payment_next_action = \
partner.payment_next_action + " \n " + action_text
else:
payment_next_action = action_text
partner.with_context(ctx).write(
{'payment_next_action_date': payment_action_date,
'payment_next_action': payment_next_action})
return unknown_mails
def get_followup_table_html(self):
self.ensure_one()
partner = self.commercial_partner_id
followup_table = ''
if partner.unreconciled_aml_ids:
company = self.env.user.company_id
current_date = fields.Date.today()
report = self.env['report.om_account_followup.report_followup']
final_res = report._lines_get_with_partner(partner, company.id)
for currency_dict in final_res:
currency = currency_dict.get('line', [
{'currency_id': company.currency_id}])[0]['currency_id']
followup_table += '''
<table border="2" width=100%%>
<tr>
<td>''' + _("Invoice Date") + '''</td>
<td>''' + _("Description") + '''</td>
<td>''' + _("Reference") + '''</td>
<td>''' + _("Due Date") + '''</td>
<td>''' + _("Amount") + " (%s)" % (
currency.symbol) + '''</td>
<td>''' + _("Lit.") + '''</td>
</tr>
'''
total = 0
for aml in currency_dict['line']:
total += aml['balance']
strbegin = "<TD>"
strend = "</TD>"
date = aml['date_maturity'] or aml['date']
date = datetime.strptime(date, "%m/%d/%Y").date()
if date <= current_date and aml['balance'] > 0:
strbegin = "<TD><B>"
strend = "</B></TD>"
followup_table += "<TR>" + strbegin + str(aml['date']) + \
strend + strbegin + aml['name'] + \
strend + strbegin + \
(aml['ref'] or '') + strend + \
strbegin + str(date) + strend + \
strbegin + str(aml['balance']) + \
strend + "</TR>"
total = reduce(lambda x, y: x + y['balance'],
currency_dict['line'], 0.00)
total = formatLang(self.env, total, currency_obj=currency)
followup_table += '''<tr> </tr>
</table>
<center>''' + _(
"Amount due") + ''' : %s </center>''' % (total)
return followup_table
def write(self, vals):
if vals.get("payment_responsible_id", False):
for part in self:
if part.payment_responsible_id != \
self.env['res.users'].browse(vals["payment_responsible_id"]):
# Find partner_id of user put as responsible
responsible_partner_id = self.env["res.users"].browse(
vals['payment_responsible_id']).partner_id.id
part.message_post(
body=_("You became responsible to do the next action "
"for the payment follow-up of") +
" <b><a href='#id=" + str(part.id) +
"&view_type=form&model=res.partner'> " + part.name +
" </a></b>",
type='comment',
context=self.env.context,
partner_ids=[responsible_partner_id])
return super(ResPartner, self).write(vals)
def action_done(self):
return self.write({'payment_next_action_date': False,
'payment_next_action': '',
'payment_responsible_id': False})
def do_button_print(self):
self.ensure_one()
company_id = self.env.user.company_id.id
if not self.env['account.move.line'].search(
[('partner_id', '=', self.id),
('account_id.account_type', '=', 'asset_receivable'),
('full_reconcile_id', '=', False),
('company_id', '=', company_id),
'|', ('date_maturity', '=', False),
('date_maturity', '<=', fields.Date.today())]):
raise ValidationError(
_("The partner does not have any accounting entries to "
"print in the overdue report for the current company."))
self.message_post(body=_('Printed overdue payments report'))
self.message_post(body=_('Printed overdue payments report'))
wizard_partner_ids = [self.id * 10000 + company_id]
followup_ids = self.env['followup.followup'].search(
[('company_id', '=', company_id)])
if not followup_ids:
raise ValidationError(_(
"There is no followup plan defined for the current company."))
data = {
'date': fields.date.today(),
'followup_id': followup_ids[0].id,
}
return self.do_partner_print(wizard_partner_ids, data)
def _get_amounts_and_date(self):
company = self.env.user.company_id
current_date = fields.Date.today()
for partner in self:
worst_due_date = False
amount_due = amount_overdue = 0.0
for aml in partner.unreconciled_aml_ids:
if (aml.company_id == company):
date_maturity = aml.date_maturity or aml.date
if not worst_due_date or date_maturity < worst_due_date:
worst_due_date = date_maturity
amount_due += aml.result
if (date_maturity <= current_date):
amount_overdue += aml.result
partner.payment_amount_due = amount_due
partner.payment_amount_overdue = amount_overdue
partner.payment_earliest_due_date = worst_due_date
def _get_followup_overdue_query(self, args, overdue_only=False):
company_id = self.env.user.company_id.id
having_clauses = []
having_values = []
for field, operator, value in args:
if operator in ['=', '!=', '>', '>=', '<', '<=']:
having_clauses.append(f'SUM(bal2) {operator} %s')
having_values.append(value)
else:
raise ValueError(f"Unsupported operator: {operator}")
having_where_clause = ' AND '.join(having_clauses)
overdue_only_str = 'AND date_maturity <= NOW()' if overdue_only else ''
query = ('''
SELECT pid AS partner_id, SUM(bal2) FROM (
SELECT
CASE WHEN bal IS NOT NULL THEN bal ELSE 0.0 END AS bal2,
p.id as pid
FROM (
SELECT
(debit - credit) AS bal,
partner_id
FROM account_move_line l
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
%s AND full_reconcile_id IS NULL
AND l.company_id = %%s
) AS l
RIGHT JOIN res_partner p ON p.id = partner_id
) AS pl
GROUP BY pid HAVING %s
''') % (overdue_only_str, having_where_clause)
params = [company_id] + having_values
return query, params
def _payment_overdue_search(self, operator, operand):
args = [('payment_amount_overdue', operator, operand)]
query, params = self._get_followup_overdue_query(args, overdue_only=True)
self.env.cr.execute(query, params)
res = self.env.cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_earliest_date_search(self, operator, operand):
args = [('payment_earliest_due_date', operator, operand)]
company_id = self.env.user.company_id.id
having_where_clause = ' AND '.join(
map(lambda x: "(MIN(l.date_maturity) %s '%%s')" % (x[1]), args))
having_values = [x[2] for x in args]
having_where_clause = having_where_clause % (having_values[0])
query = """SELECT partner_id FROM account_move_line l
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
AND l.company_id = %s
AND l.full_reconcile_id IS NULL
AND partner_id IS NOT NULL GROUP BY partner_id"""
query = query % company_id
if having_where_clause:
query += ' HAVING %s ' % (having_where_clause)
self.env.cr.execute(query)
res = self.env.cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_due_search(self, operator, operand):
args = [('payment_amount_due', operator, operand)]
query, params = self._get_followup_overdue_query(args, overdue_only=False)
self.env.cr.execute(query, params)
res = self.env.cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _get_partners(self):
partners = set()
for aml in self:
if aml.partner_id:
partners.add(aml.partner_id.id)
return list(partners)
payment_responsible_id = fields.Many2one(
'res.users', ondelete='set null',
string='Follow-up Responsible', tracking=True, copy=False,
help="Optionally you can assign a user to this field, which will make "
"him responsible for the action.")
payment_note = fields.Text(
'Customer Payment Promise', help="Payment Note", copy=False
)
payment_next_action = fields.Text(
'Next Action', copy=False, tracking=True,
help="This is the next action to be taken. It will automatically be "
"set when the partner gets a follow-up level that requires a manual action. "
)
payment_next_action_date = fields.Date(
'Next Action Date', copy=False,
help="This is when the manual follow-up is needed. The date will be "
"set to the current date when the partner gets a follow-up level "
"that requires a manual action. Can be practical to set manually "
"e.g. to see if he keeps his promises."
)
unreconciled_aml_ids = fields.One2many(
'account.move.line', 'partner_id',
domain=[('full_reconcile_id', '=', False), ('account_id.account_type', '=', 'asset_receivable')]
)
latest_followup_date = fields.Date(
compute='_get_latest', string="Latest Follow-up Date", compute_sudo=True,
help="Latest date that the follow-up level of the partner was changed"
)
latest_followup_level_id = fields.Many2one(
'followup.line', compute='_get_latest', compute_sudo=True,
string="Latest Follow-up Level", help="The maximum follow-up level"
)
latest_followup_sequence = fields.Integer(
'Sequence',
help="Gives the sequence order when displaying a list of follow-up lines.", default=0
)
latest_followup_level_id_without_lit = fields.Many2one(
'followup.line', compute='_get_latest', compute_sudo=True,
string="Latest Follow-up Level without litigation",
help="The maximum follow-up level without taking into "
"account the account move lines with litigation")
payment_amount_due = fields.Float(
compute='_get_amounts_and_date',
string="Amount Due", search='_payment_due_search'
)
payment_amount_overdue = fields.Float(
compute='_get_amounts_and_date',
string="Amount Overdue", search='_payment_overdue_search'
)
payment_earliest_due_date = fields.Date(
compute='_get_amounts_and_date', string="Worst Due Date",
search='_payment_earliest_date_search'
)

View File

@@ -0,0 +1,15 @@
from odoo import api, fields, models, _
class AccountConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
def open_followup_level_form(self):
res_ids = self.env['followup.followup'].search([], limit=1)
return {
'type': 'ir.actions.act_window',
'name': 'Follow-up Levels',
'res_model': 'followup.followup',
'res_id': res_ids and res_ids.id or False,
'view_mode': 'form,list',
}

View File

@@ -0,0 +1,2 @@
from . import followup_print
from . import followup_report

View File

@@ -0,0 +1,110 @@
import time
from collections import defaultdict
from odoo.exceptions import ValidationError
from odoo import api, fields, models, _
from odoo.tools import format_date
class ReportFollowup(models.AbstractModel):
_name = 'report.om_account_followup.report_followup'
_description = 'Report Followup'
@api.model
def _get_report_values(self, docids, data=None):
model = self.env['followup.sending.results']
ids = self.env.context.get('active_ids') or False
docs = model.browse(ids)
return {
'docs': docs,
'doc_ids': docids,
'doc_model': model,
'time': time,
'ids_to_objects': self._ids_to_objects,
'getLines': self._lines_get,
'get_text': self._get_text,
'data': data and data['form'] or {}}
def _ids_to_objects(self, ids):
all_lines = []
for line in self.env['followup.stat.by.partner'].browse(ids):
if line not in all_lines:
all_lines.append(line)
return all_lines
def _lines_get(self, stat_by_partner_line):
return self._lines_get_with_partner(stat_by_partner_line.partner_id,
stat_by_partner_line.company_id.id)
def _lines_get_with_partner(self, partner, company_id):
moveline_obj = self.env['account.move.line']
moveline_ids = moveline_obj.search(
[('partner_id', '=', partner.id),
('account_id.account_type', '=', 'asset_receivable'),
('full_reconcile_id', '=', False),
('company_id', '=', company_id),
'|', ('date_maturity', '=', False),
('date_maturity', '<=', fields.Date.today())])
lines_per_currency = defaultdict(list)
total = 0
for line in moveline_ids:
currency = line.currency_id or line.company_id.currency_id
balance = line.debit - line.credit
if currency != line.company_id.currency_id:
balance = line.amount_currency
line_data = {
'name': line.move_id.name,
'ref': line.ref,
'date': format_date(self.env, line.date),
'date_maturity': format_date(self.env, line.date_maturity),
'balance': balance,
'currency_id': currency,
}
total = total + line_data['balance']
lines_per_currency[currency].append(line_data)
return [{'total': total, 'line': lines, 'currency': currency} for
currency, lines in
lines_per_currency.items()]
def _get_text(self, stat_line, followup_id, context=None):
fp_obj = self.env['followup.followup']
fp_line = fp_obj.browse(followup_id).followup_line
if not fp_line:
raise ValidationError(
_("The followup plan defined for the current company does not "
"have any followup action."))
default_text = ''
li_delay = []
for line in fp_line:
if not default_text and line.description:
default_text = line.description
li_delay.append(line.delay)
li_delay.sort(reverse=True)
partner_line_ids = self.env['account.move.line'].search(
[('partner_id', '=', stat_line.partner_id.id),
('full_reconcile_id', '=', False),
('company_id', '=', stat_line.company_id.id),
('debit', '!=', False),
('account_id.account_type', '=', 'asset_receivable'),
('followup_line_id', '!=', False)])
partner_max_delay = 0
partner_max_text = ''
for i in partner_line_ids:
if i.followup_line_id.delay > partner_max_delay and \
i.followup_line_id.description:
partner_max_delay = i.followup_line_id.delay
partner_max_text = i.followup_line_id.description
text = partner_max_delay and partner_max_text or default_text
if text:
lang_obj = self.env['res.lang']
lang_ids = lang_obj.search(
[('code', '=', stat_line.partner_id.lang)], limit=1)
date_format = lang_ids and lang_ids.date_format or '%Y-%m-%d'
text = text % {
'partner_name': stat_line.partner_id.name,
'date': time.strftime(date_format),
'company_name': stat_line.company_id.name,
'user_signature': self.env.user.signature or '',
}
return text

View File

@@ -0,0 +1,47 @@
from odoo import api, fields, models
from odoo import tools
class AccountFollowupStat(models.Model):
_name = "followup.stat"
_description = "Follow-up Statistics"
_rec_name = 'partner_id'
_order = 'date_move'
_auto = False
partner_id = fields.Many2one('res.partner', 'Partner', readonly=True)
date_move = fields.Date('First move', readonly=True)
date_move_last = fields.Date('Last move', readonly=True)
date_followup = fields.Date('Latest followup', readonly=True)
followup_id = fields.Many2one('followup.line', 'Follow Ups', readonly=True, ondelete="cascade")
balance = fields.Float('Balance', readonly=True)
debit = fields.Float('Debit', readonly=True)
credit = fields.Float('Credit', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
@api.model
def init(self):
tools.drop_view_if_exists(self.env.cr, 'followup_stat')
self.env.cr.execute("""
create or replace view followup_stat as (
SELECT
l.id as id,
l.partner_id AS partner_id,
min(l.date) AS date_move,
max(l.date) AS date_move_last,
max(l.followup_date) AS date_followup,
max(l.followup_line_id) AS followup_id,
sum(l.debit) AS debit,
sum(l.credit) AS credit,
sum(l.debit - l.credit) AS balance,
l.company_id AS company_id
FROM
account_move_line l
LEFT JOIN account_account a ON (l.account_id = a.id)
WHERE
a.account_type = 'asset_receivable' AND
l.full_reconcile_id is NULL AND
l.partner_id IS NOT NULL
GROUP BY
l.id, l.partner_id, l.company_id
)""")

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_stat_graph" model="ir.ui.view">
<field name="name">followup.stat.graph</field>
<field name="model">followup.stat</field>
<field name="arch" type="xml">
<graph string="Follow-up lines">
<field name="followup_id" type="row"/>
<field name="date_followup" type="col"/>
<field name="balance" type="measure"/>
</graph>
</field>
</record>
<record id="view_om_account_followup_stat_search" model="ir.ui.view">
<field name="name">followup.stat.search</field>
<field name="model">followup.stat</field>
<field name="arch" type="xml">
<search string="Follow-ups Sent">
<field name="date_move"/>
<field name="date_move_last"/>
<separator/>
<filter string="Not Litigation" name="not_litigation"
help="Including journal entries marked as a litigation"/>
<field name="partner_id"/>
<field name="balance"/>
<group>
<filter string="Partner" name="partner"
context="{'group_by':'partner_id'}"/>
<filter string="Follow-up Level" name="followup_level"
context="{'group_by':'followup_id'}"/>
<filter string="Company" name="company"
groups="base.group_multi_company"
context="{'group_by':'company_id'}"/>
<separator/>
<filter string="Latest Follow-up Month" name="lastest_month"
context="{'group_by':'date_followup:month'}"/>
</group>
</search>
</field>
</record>
<record id="action_followup_stat" model="ir.actions.act_window">
<field name="name">Follow-ups Analysis</field>
<field name="res_model">followup.stat</field>
<field name="view_mode">graph</field>
<field name="context">{'search_default_followup_level':1}</field>
<field name="search_view_id" ref="view_om_account_followup_stat_search"/>
</record>
<menuitem action="action_followup_stat"
id="menu_action_followup_stat_follow"
parent="om_account_followup.menu_finance_followup"
groups="account.group_account_invoice"
name="Follow-ups Analysis"
sequence="20"/>
</data>
</odoo>

View File

@@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_followup_followup_line,followup.followup.line,model_followup_line,account.group_account_invoice,1,0,0,0
access_followup_followup_line_manager,followup.followup.line.manager,model_followup_line,account.group_account_manager,1,1,1,1
access_followup_followup_accountant,followup.followup.user,model_followup_followup,account.group_account_invoice,1,0,0,0
access_followup_followup_manager,followup.followup.manager,model_followup_followup,account.group_account_manager,1,1,1,1
access_followup_stat_invoice,followup.stat.invoice,model_followup_stat,account.group_account_invoice,1,1,0,0
access_followup_stat_by_partner_manager,followup.stat.by.partner,model_followup_stat_by_partner,account.group_account_user,1,1,0,0
access_followup_stat_user,followup.stat.user,model_followup_stat,account.group_account_user,1,1,0,0
access_followup_stat_manager,followup.stat.manager,model_followup_stat,account.group_account_manager,1,1,1,1
access_followup_print,access_followup_print,model_followup_print,base.group_user,1,1,1,1
access_followup_sending_results,access_followup_sending_results,model_followup_sending_results,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_followup_followup_line followup.followup.line model_followup_line account.group_account_invoice 1 0 0 0
3 access_followup_followup_line_manager followup.followup.line.manager model_followup_line account.group_account_manager 1 1 1 1
4 access_followup_followup_accountant followup.followup.user model_followup_followup account.group_account_invoice 1 0 0 0
5 access_followup_followup_manager followup.followup.manager model_followup_followup account.group_account_manager 1 1 1 1
6 access_followup_stat_invoice followup.stat.invoice model_followup_stat account.group_account_invoice 1 1 0 0
7 access_followup_stat_by_partner_manager followup.stat.by.partner model_followup_stat_by_partner account.group_account_user 1 1 0 0
8 access_followup_stat_user followup.stat.user model_followup_stat account.group_account_user 1 1 0 0
9 access_followup_stat_manager followup.stat.manager model_followup_stat account.group_account_manager 1 1 1 1
10 access_followup_print access_followup_print model_followup_print base.group_user 1 1 1 1
11 access_followup_sending_results access_followup_sending_results model_followup_sending_results base.group_user 1 1 1 1

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="om_account_followup_comp_rule" model="ir.rule">
<field name="name">Account Follow-up multi company rule</field>
<field name="model_id" ref="model_followup_followup"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="om_account_followup_stat_by_partner_comp_rule" model="ir.rule">
<field name="name">Account Follow-up Statistics by Partner Rule</field>
<field ref="model_followup_stat_by_partner" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,81 @@
<section class="oe_container oe_dark">
<div class="col-md-12">
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Customer Follow Up Management</b></h2>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Follow-Up Levels</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="follow_up_level.png">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Send Follow-Ups</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="followup.png">
</div>
</div>
</section>
<br/>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Manual Follow-Ups</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="manual_followup.png">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Follow-ups Analysis</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="follow_up_analysis.png">
</div>
</div>
</section>
<br/>
<br/>
<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;">

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_move_line_reconcile_tree" model="ir.ui.view">
<field name="name">account.move.line.list</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<list string="Journal Items to Reconcile" create="false">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="account_id"/>
<field name="journal_id" invisible="1"/>
<field name="full_reconcile_id"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
</list>
</field>
</record>
<record id="account_manual_reconcile_action" model="ir.actions.act_window">
<field name="context">{'search_default_unreconciled': 1,'view_mode':True}</field>
<field name="name">Journal Items to Reconcile</field>
<field name="res_model">account.move.line</field>
<field name="view_id" ref="view_move_line_reconcile_tree"/>
<field name="view_mode">list</field>
<field name="help" type="html">
<p>No journal items found.</p>
</field>
</record>
<record id="account_move_line_partner_tree" model="ir.ui.view">
<field name="name">account.move.line.partner.list</field>
<field name="model">account.move.line</field>
<field eval="32" name="priority"/>
<field name="arch" type="xml">
<list editable="bottom" string="Partner Entries">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="company_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
<field name="account_id" options="{'no_open': True, 'no_create': True}"
domain="[('company_ids', 'in', company_id)]" groups="account.group_account_readonly"/>
<field name="followup_line_id"/>
<field name="followup_date"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
<field name="date_maturity"/>
</list>
</field>
</record>
<record id="view_move_line_form" model="ir.ui.view">
<field name="name">account.move.line.form.followup</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_form"/>
<field name="arch" type="xml">
<field name="date_maturity" position="after">
<field name="followup_line_id"/>
<field name="followup_date"/>
</field>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="om_account_followup_stat_by_partner_search" model="ir.ui.view">
<field name="name">followup.stat.by.partner.search</field>
<field name="model">followup.stat.by.partner</field>
<field name="arch" type="xml">
<search string="Partner to Remind">
<field name="date_followup"/>
<filter string="Balance > 0"
domain="[('balance','&gt;',0)]" icon="terp-dolar"
name="balance_positive"/>
<field name="partner_id"/>
<field name="max_followup_id"/>
<field name="company_id"
groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="om_account_followup_stat_by_partner_tree" model="ir.ui.view">
<field name="name">followup.stat.by.partner.list</field>
<field name="model">followup.stat.by.partner</field>
<field name="arch" type="xml">
<list string="Partner to Remind">
<field name="partner_id"/>
<field name="balance"/>
<field name="max_followup_id"/>
<field name="date_followup"/>
<field name="date_move_last"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,212 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_followup_line_tree" model="ir.ui.view">
<field name="name">followup.line.list</field>
<field name="model">followup.line</field>
<field name="arch" type="xml">
<list string="Follow-up Steps">
<field name="name"/>
<field name="delay"/>
<field name="send_email"/>
<field name="send_letter"/>
<field name="manual_action"/>
</list>
</field>
</record>
<record id="view_om_account_followup_followup_line_form" model="ir.ui.view">
<field name="name">followup.line.form</field>
<field name="model">followup.line</field>
<field name="arch" type="xml">
<form string="Follow-up Steps">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<div class="oe_inline">
After
<field name="delay" class="oe_inline"/>
days overdue, do the following actions:
</div>
<div>
<field name="manual_action" class="oe_inline"/>
<label for="manual_action"/>
</div>
<div>
<field name="send_email" class="oe_inline"/>
<label for="send_email"/>
</div>
<div>
<field name="send_letter" class="oe_inline"/>
<label for="send_letter"/>
</div>
<group string="Manual Action" invisible="not manual_action">
<field name="manual_action_responsible_id"/>
<field name="manual_action_note"/>
</group>
<group string="Send an Email" invisible="not send_email">
<field name="email_template_id"/>
</group>
<group string="Send a Letter or Email"
invisible="not send_email and not send_letter">
<p colspan="2" class="oe_grey">
Write here the introduction in the letter,
according to the level of the follow-up. You can
use the following keywords in the text. Don't
forget to translate in all languages you installed
using to top right icon.
<table>
<tr>
<td t-translation="off">%%(partner_name)s
</td>
<td>: Partner Name</td>
</tr>
<tr>
<td t-translation="off">%%(date)s</td>
<td>: Current Date</td>
</tr>
<tr>
<td t-translation="off">
%%(user_signature)s
</td>
<td>: User Name</td>
</tr>
<tr>
<td t-translation="off">%%(company_name)s
</td>
<td>: User's Company Name</td>
</tr>
</table>
</p>
<field name="description" nolabel="1" colspan="2"/>
</group>
</form>
</field>
</record>
<record id="view_om_account_followup_followup_form" model="ir.ui.view">
<field name="name">followup.followup.form</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<form string="Follow-up">
<h1>
<field name="name"/>
</h1>
<label for="company_id" groups="base.group_multi_company"/>
<field name="company_id" widget="selection"
class="oe_inline"
groups="base.group_multi_company"/>
<p class="oe_grey">
To remind customers of paying their invoices, you can
define different actions depending on how severely
overdue the customer is. These actions are bundled
into follow-up levels that are triggered when the due
date of an invoice has passed a certain
number of days. If there are other overdue invoices for
the
same customer, the actions of the most
overdue invoice will be executed.
</p>
<field name="followup_line"/>
</form>
</field>
</record>
<record id="view_om_account_followup_followup_tree" model="ir.ui.view">
<field name="name">followup.followup.list</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<list string="Follow-up">
<field name="company_id"/>
</list>
</field>
</record>
<record id="view_om_account_followup_filter" model="ir.ui.view">
<field name="name">account.followup.select</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<search string="Search Follow-up">
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="action_om_account_followup_definition_form" model="ir.actions.act_window">
<field name="name">Follow-up Levels</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">followup.followup</field>
<field name="search_view_id" ref="view_om_account_followup_filter"/>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define follow-up levels and their related actions.
</p>
<p>
For each step, specify the actions to be taken and delay in
days. It is
possible to use print and e-mail templates to send specific
messages to
the customer.
</p>
</field>
</record>
<record id="view_move_line_reconcile_tree" model="ir.ui.view">
<field name="name">account.move.line.list</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<list string="Journal Items to Reconcile" create="false">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="account_id"/>
<field name="journal_id" invisible="1"/>
<field name="full_reconcile_id"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
</list>
</field>
</record>
<record id="account_manual_reconcile_action" model="ir.actions.act_window">
<field name="context">{'search_default_unreconciled': 1,'view_mode':True}</field>
<field name="name">Journal Items to Reconcile</field>
<field name="res_model">account.move.line</field>
<field name="view_id" ref="view_move_line_reconcile_tree"/>
<field name="view_mode">list</field>
<field name="help" type="html">
<p>
No journal items found.
</p>
</field>
</record>
<menuitem id="om_account_followup_main_menu"
parent="account.menu_finance_configuration"
name="Follow-up"/>
<menuitem id="om_account_followup_menu"
name="Follow-up Levels"
action="action_om_account_followup_definition_form"
parent="om_account_followup_main_menu" />
<menuitem id="om_account_followup_main_menu"
parent="account.menu_finance_configuration"
name="Follow-up"/>
<menuitem id="om_account_followup_menu"
name="Follow-up Levels"
action="action_om_account_followup_definition_form"
parent="om_account_followup_main_menu"/>
</data>
</odoo>

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="customer_followup_tree" model="ir.ui.view">
<field name="name">res.partner.followup.inherit.list</field>
<field name="model">res.partner</field>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<list string="Customer Followup" create="false" delete="false">
<field name="display_name"/>
<field name="payment_next_action_date"/>
<field name="payment_next_action"/>
<field name="user_id" invisible="1"/>
<field name="country_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
<field name="payment_responsible_id"/>
<field name="payment_earliest_due_date"/>
<field name="latest_followup_level_id"/>
<field name="payment_amount_overdue"/>
<field name="payment_amount_due"/>
</list>
</field>
</record>
<record id="view_partner_inherit_customer_followup_tree" model="ir.ui.view">
<field name="name">res.partner.followup.inherit.list</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="complete_name" position="after">
<field name="payment_responsible_id" invisible="1"/>
</field>
</field>
</record>
<record id="customer_followup_search_view" model="ir.ui.view">
<field name="name">Search</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<xpath expr="//group[1]" position="after">
<group>
<filter string="Partners with Overdue Credits" domain="[('payment_amount_overdue', '>', 0.0)]"
name="credits"/>
<separator/>
<filter string="Follow-ups To Do"
domain="[('payment_next_action_date', '&lt;=', time.strftime('%%Y-%%m-%%d')), ('payment_amount_overdue', '>', 0.0)]"
name="todo"/>
<separator/>
<filter string="No Responsible" name="no_responsibe" domain="[('payment_responsible_id', '=', False)]"/>
<filter string="My Follow-ups" domain="[('payment_responsible_id','=', uid)]" name="my"/>
</group>
</xpath>
<xpath expr="//group[1]" position="inside">
<filter string="Follow-up Responsible" name="responsibe"
context="{'group_by':'payment_responsible_id'}"/>
<filter string="Followup Level" name="followup_level"
context="{'group_by':'latest_followup_level_id'}"/>
</xpath>
</field>
</record>
<record id="action_customer_followup" model="ir.actions.act_window">
<field name="name">Manual Follow-Ups</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="res_model">res.partner</field>
<field name="view_mode">list,form</field>
<field name="domain">[('payment_amount_due', '>', 0.0)]</field>
<field name="context">{'Followupfirst':True, 'search_default_todo': True}</field>
<field name="search_view_id" ref="customer_followup_search_view"/>
</record>
<record id="view_partner_inherit_followup_form" model="ir.ui.view">
<field name="name">res.partner.followup.form.inherit</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']" position="after">
<page string="Payment Follow-up"
groups="account.group_account_invoice"
name="followup_tab">
<div class="oe_right"
name="followup_button">
<button name="do_button_print" type="object"
string="Print Overdue Payments"
groups="account.group_account_user"
help="Print overdue payments report independent of follow-up line"
invisible="payment_amount_due &lt;= 0.0"/>
<button name="do_partner_mail" type="object"
string="Send Overdue Email"
groups="account.group_account_user"
help="If not specified by the latest follow-up level, it will send from the default email template"
invisible="payment_amount_due &lt;= 0.0"/>
</div>
<p invisible="not latest_followup_date">
The
<field name="latest_followup_date"
class="oe_inline"/>
, the latest payment follow-up was:
<field name="latest_followup_level_id"
class="oe_inline"/>
</p>
<group>
<field name="payment_responsible_id"
placeholder="Responsible of credit collection"
class="oe_inline"/>
<label for="payment_next_action"/>
<div>
<field name="payment_next_action_date"
class="oe_inline"/>
<button name="action_done" type="object"
string="⇾ Mark as Done"
help="Click to mark the action as done."
class="oe_link"
invisible="not payment_next_action_date"
groups="account.group_account_user"/>
<field name="payment_next_action"
placeholder="Action to be taken e.g. Give a phonecall, Check if it's paid, ..."/>
</div>
</group>
<label for="payment_note" class="oe_edit_only"/>
<field name="payment_note"
placeholder="He said the problem was temporary and promised to pay 50%% before 15th of May, balance before 1st of July."/>
<p class="oe_grey">
Below is the history of the transactions of this
customer. You can check "No Follow-up" in
order to exclude it from the next follow-up
actions.
</p>
<field name="unreconciled_aml_ids">
<list string="Account Move line" editable="bottom"
create="false" delete="false"
colors="red:(not date_maturity or date_maturity&lt;=current_date) and result&gt;0">
<field name="date" readonly="True"/>
<field name="company_id" readonly="True"
groups="base.group_multi_company"/>
<field name="move_id" readonly="True"/>
<field name="date_maturity" readonly="True"/>
<field name="result" readonly="True"/>
<field name="followup_line_id" invisible='1'/>
</list>
</field>
<group class="oe_subtotal_footer oe_right">
<field name="payment_amount_due"/>
</group>
<div class="oe_clear"/>
</page>
</xpath>
</field>
</record>
<record id="action_view_customer_followup_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_partner_inherit_followup_form"/>
<field name="act_window_id" ref="action_customer_followup"/>
</record>
<record id="action_view_customer_followup_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">list</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="act_window_id" ref="action_customer_followup"/>
</record>
<menuitem id="om_account_followup_s"
action="action_customer_followup"
parent="menu_finance_followup"
name="Do Manual Follow-Ups"
sequence="3"/>
<record id="action_customer_my_followup" model="ir.actions.act_window">
<field name="name">My Follow-Ups</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="res_model">res.partner</field>
<field name="view_mode">list,form</field>
<field name="domain">[('payment_amount_due', '>', 0.0)]</field>
<field name="context">{'Followupfirst':True, 'search_default_todo': True, 'search_default_my': True}</field>
<field name="search_view_id" ref="customer_followup_search_view"/>
</record>
<menuitem id="menu_sale_followup"
parent="menu_finance_followup"
sequence="10"
action="action_customer_my_followup"
groups="account.group_account_invoice"/>
</data>
</odoo>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="report_followup">
<t t-call="web.html_container">
<t t-foreach="ids_to_objects(data['partner_ids'])" t-as="o">
<t t-set="o" t-value="o.with_context({'lang':o.partner_id.lang})"/>
<t t-call="web.external_layout">
<div class="page">
<p>
<span t-field="o.invoice_partner_id"/>
<br/>
<t t-if="o.partner_id.vat">
<span t-field="o.partner_id.vat"/>
<br/>
</t>
Document: Customer account statement
<br/>
Date:
<span t-esc="data['date']"/>
<br/>
Customer ref:
<span t-field="o.partner_id.ref"/>
</p>
<p t-raw="get_text(o,data['followup_id']).replace('\n', '&lt;br&gt;')"/>
<t t-foreach="getLines(o)" t-as="cur_lines">
<table class="table table-condensed"
style="margin-top: 50px;">
<thead>
<tr>
<th>Invoice Date</th>
<th>Description</th>
<th class="text-center">Ref</th>
<th class="text-center">Maturity Date</th>
<th class="text-right">Amount</th>
<th class="text-center">Due</th>
</tr>
</thead>
<tbody>
<tr t-foreach="cur_lines['line']"
t-as="line">
<td>
<span t-esc="line['date']"/>
</td>
<td>
<span t-esc="line['name']"/>
</td>
<td>
<span t-esc="line['ref']"/>
</td>
<td class="text-center">
<span t-esc="line['date_maturity']"/>
</td>
<td class="text-right">
<span t-esc="line['balance']"/>
</td>
</tr>
</tbody>
</table>
<p>Total:
<span t-esc="cur_lines['total']"/>
</p>
</t>
</div>
</t>
</t>
</t>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<odoo>
<data>
<record id="action_report_followup" model="ir.actions.report">
<field name="name">Follow-up Report</field>
<field name="model">followup.followup</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">om_account_followup.report_followup</field>
<field name="report_file">om_account_followup.report_followup</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,3 @@
from . import followup_print
from . import followup_results

View File

@@ -0,0 +1,224 @@
import datetime
import time
from odoo import api, fields, models, _
from markupsafe import Markup
class FollowupPrint(models.TransientModel):
_name = 'followup.print'
_description = 'Print Follow-up & Send Mail to Customers'
def _get_followup(self):
if self.env.context.get('active_model',
'ir.ui.menu') == 'followup.followup':
return self.env.context.get('active_id', False)
company_id = self.env.user.company_id.id
followp_id = self.env['followup.followup'].search(
[('company_id', '=', company_id)], limit=1)
return followp_id or False
date = fields.Date('Follow-up Sending Date', required=True,
help="This field allow you to select a forecast date "
"to plan your follow-ups",
default=lambda *a: time.strftime('%Y-%m-%d'))
followup_id = fields.Many2one('followup.followup', 'Follow-Up',
required=True, readonly=True,
default=_get_followup)
partner_ids = fields.Many2many('followup.stat.by.partner',
'partner_stat_rel', 'osv_memory_id',
'partner_id', 'Partners', required=True)
company_id = fields.Many2one('res.company', readonly=True,
related='followup_id.company_id')
email_conf = fields.Boolean('Send Email Confirmation')
email_subject = fields.Char('Email Subject', size=64,
default=_('Invoices Reminder'))
partner_lang = fields.Boolean(
'Send Email in Partner Language', default=True,
help='Do not change message text, if you want to send email in '
'partner language, or configure from company')
email_body = fields.Text('Email Body', default='')
summary = fields.Text('Summary', readonly=True)
test_print = fields.Boolean(
'Test Print', help='Check if you want to print follow-ups without '
'changing follow-up level.')
def process_partners(self, partner_ids, data):
partner_obj = self.env['res.partner']
partner_ids_to_print = []
nbmanuals = 0
manuals = {}
nbmails = 0
nbunknownmails = 0
nbprints = 0
resulttext = " "
for partner in self.env['followup.stat.by.partner'].browse(
partner_ids):
if partner.max_followup_id.manual_action:
partner_obj.do_partner_manual_action([partner.partner_id.id])
nbmanuals = nbmanuals + 1
key = partner.partner_id.payment_responsible_id.name or _(
"Anybody")
if key not in manuals.keys():
manuals[key] = 1
else:
manuals[key] = manuals[key] + 1
if partner.max_followup_id.send_email:
nbunknownmails += partner.partner_id.do_partner_mail()
nbmails += 1
if partner.max_followup_id.send_letter:
partner_ids_to_print.append(partner.id)
nbprints += 1
followup_without_lit = \
partner.partner_id.latest_followup_level_id_without_lit
message = "%s<I> %s </I>%s" % (_("Follow-up letter of "),
followup_without_lit.name,
_(" will be sent"))
partner.partner_id.message_post(body=message)
if nbunknownmails == 0:
resulttext += str(nbmails) + _(" email(s) sent")
else:
resulttext += str(nbmails) + _(
" email(s) should have been sent, but ") + str(
nbunknownmails) + _(
" had unknown email address(es)") + "\n <BR/> "
resulttext += "<BR/>" + str(nbprints) + _(
" letter(s) in report") + " \n <BR/>" + str(nbmanuals) + _(
" manual action(s) assigned:")
needprinting = False
if nbprints > 0:
needprinting = True
resulttext += "<p align=\"center\">"
for item in manuals:
resulttext = resulttext + "<li>" + item + ":" + str(
manuals[item]) + "\n </li>"
resulttext += "</p>"
result = {}
action = partner_obj.do_partner_print(partner_ids_to_print, data)
result['needprinting'] = needprinting
result['resulttext'] = Markup(resulttext)
result['action'] = action or {}
return result
def do_update_followup_level(self, to_update, partner_list, date):
for id in to_update.keys():
if to_update[id]['partner_id'] in partner_list:
self.env['account.move.line'].browse([int(id)]).write(
{'followup_line_id': to_update[id]['level'],
'followup_date': date})
def clear_manual_actions(self, partner_list):
partner_list_ids = [partner.partner_id.id for partner in self.env[
'followup.stat.by.partner'].browse(partner_list)]
ids = self.env['res.partner'].search(
['&', ('id', 'not in', partner_list_ids), '|',
('payment_responsible_id', '!=', False),
('payment_next_action_date', '!=', False)])
partners_to_clear = []
for part in ids:
if not part.unreconciled_aml_ids:
partners_to_clear.append(part.id)
part.action_done()
return len(partners_to_clear)
def do_process(self):
context = dict(self.env.context or {})
tmp = self._get_partners_followp()
partner_list = tmp['partner_ids']
to_update = tmp['to_update']
date = self.date
data = self.read()[0]
data['followup_id'] = data['followup_id'][0]
self.do_update_followup_level(to_update, partner_list, date)
restot_context = context.copy()
restot = self.with_context(restot_context).process_partners(
partner_list, data)
context.update(restot_context)
nbactionscleared = self.clear_manual_actions(partner_list)
if nbactionscleared > 0:
restot['resulttext'] = restot['resulttext'] + "<li>" + _(
"%s partners have no credits and as such the "
"action is cleared") % (str(nbactionscleared)) + "</li>"
resource_id = self.env.ref(
'om_account_followup.view_om_account_followup_sending_results')
context.update({'description': restot['resulttext'],
'needprinting': restot['needprinting'],
'report_data': restot['action']})
return {
'name': _('Send Letters and Emails: Actions Summary'),
'view_type': 'form',
'context': context,
'view_mode': 'list,form',
'res_model': 'followup.sending.results',
'views': [(resource_id.id, 'form')],
'type': 'ir.actions.act_window',
'target': 'new',
}
def _get_msg(self):
return self.env.user.company_id.follow_up_msg
def _get_partners_followp(self):
data = self
company_id = data.company_id.id
context = self.env.context
self.env.cr.execute(
'''SELECT
l.partner_id,
l.followup_line_id,
l.date_maturity,
l.date, l.id
FROM account_move_line AS l
LEFT JOIN account_account AS a
ON (l.account_id=a.id)
WHERE (l.full_reconcile_id IS NULL)
AND a.account_type = 'asset_receivable'
AND (l.partner_id is NOT NULL)
AND (l.debit > 0)
AND (l.company_id = %s)
ORDER BY l.date''' % (company_id))
move_lines = self.env.cr.fetchall()
old = None
fups = {}
fup_id = 'followup_id' in context and context[
'followup_id'] or data.followup_id.id
date = 'date' in context and context['date'] or data.date
date = fields.Date.to_string(date)
current_date = datetime.date(*time.strptime(date, '%Y-%m-%d')[:3])
self.env.cr.execute(
'''SELECT *
FROM followup_line
WHERE followup_id=%s
ORDER BY delay''' % (fup_id,))
for result in self.env.cr.dictfetchall():
delay = datetime.timedelta(days=result['delay'])
fups[old] = (current_date - delay, result['id'])
old = result['id']
partner_list = []
to_update = {}
for partner_id, followup_line_id, date_maturity, date, id in \
move_lines:
if not partner_id:
continue
if followup_line_id not in fups:
continue
stat_line_id = partner_id * 10000 + company_id
if date_maturity:
date_maturity = fields.Date.to_string(date_maturity)
if date_maturity <= fups[followup_line_id][0].strftime(
'%Y-%m-%d'):
if stat_line_id not in partner_list:
partner_list.append(stat_line_id)
to_update[str(id)] = {'level': fups[followup_line_id][1],
'partner_id': stat_line_id}
elif date and date <= fups[followup_line_id][0]:
if stat_line_id not in partner_list:
partner_list.append(stat_line_id)
to_update[str(id)] = {'level': fups[followup_line_id][1],
'partner_id': stat_line_id}
return {'partner_ids': partner_list, 'to_update': to_update}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_print" model="ir.ui.view">
<field name="name">account.followup.print.form</field>
<field name="model">followup.print</field>
<field name="arch" type="xml">
<form string="Send follow-ups">
<group col="4">
<field name="date" groups="base.group_no_one"/>
<field name="followup_id"
groups="base.group_multi_company"/>
</group>
<p class="oe_grey">
This action will send follow-up emails, print the
letters and
set the manual actions per customer, according to the
follow-up levels defined.
</p>
<footer>
<button name="do_process"
string="Send emails and generate letters"
type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link"
special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_om_account_followup_print" model="ir.actions.act_window">
<field name="name">Send Follow-Ups</field>
<field name="res_model">followup.print</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="menu_finance_followup" parent="account.menu_finance"
name="Follow-Ups"
groups="account.group_account_invoice"/>
<menuitem action="action_om_account_followup_print"
id="om_account_followup_print_menu"
parent="menu_finance_followup"
name="Send Letters and Emails"
groups="account.group_account_user,account.group_account_manager"
sequence="2"/>
</data>
</odoo>

View File

@@ -0,0 +1,21 @@
from odoo import api, fields, models, _
class FollowupSendingResults(models.TransientModel):
_name = 'followup.sending.results'
_description = 'Results from the sending of the different letters and emails'
def do_report(self):
return self.env.context.get('report_data')
def do_done(self):
return {}
def _get_description(self):
return self.env.context.get('description')
def _get_need_printing(self):
return self.env.context.get('needprinting')
description = fields.Html("Description", readonly=True, default=_get_description)
needprinting = fields.Boolean("Needs Printing", default=_get_need_printing)

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_sending_results" model="ir.ui.view">
<field name="name">followup.sending.results.form</field>
<field name="model">followup.sending.results</field>
<field name="arch" type="xml">
<form string="Summary of actions">
<field name="description" class="oe_view_only"/>
<footer>
<field name="needprinting" invisible="1"/>
<div invisible="not needprinting">
<button name="do_report" string="Download Letters"
type="object" class="oe_highlight"/>
</div>
<div invisible="needprinting">
<button name="do_done" string="Close" type="object"
class="oe_highlight"/>
</div>
</footer>
</form>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,2 @@
from . import wizard
from . import models

View File

@@ -0,0 +1,23 @@
{
'name': 'Odoo 19 Fiscal Year & Lock Date',
'version': 1.0.119.0.1.0.1', # __odoosky_original_version__: '1.0.1'
'category': 'Accounting',
'summary': 'Odoo 19 Fiscal Year, Fiscal Year in Odoo 19, Lock Date in Odoo 19',
'description': 'Odoo 19 Fiscal Year, Fiscal Year in Odoo 19',
'live_test_url': 'https://www.youtube.com/watch?v=Kj4hR7_uNs4',
'sequence': '1',
'website': 'https://www.odoomates.tech',
'author': 'Odoo Mates, Odoo SA',
'maintainer': 'Odoo Mates',
'license': 'LGPL-3',
'support': 'odoomates@gmail.com',
'depends': ['account'],
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'wizard/change_lock_date.xml',
'views/fiscal_year.xml',
'views/settings.xml',
],
'images': ['static/description/banner.png'],
}

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