Compare commits
680 Commits
fix-cashfl
...
migrate-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a10b58a7c6 | ||
|
|
72818759a5 | ||
|
|
270b421a6c | ||
|
|
ddaea20d16 | ||
|
|
7e82080cb7 | ||
|
|
3bf5f4be86 | ||
|
|
6f870ea1e1 | ||
|
|
ee284196eb | ||
|
|
52362a43ab | ||
|
|
fdfb766587 | ||
|
|
1773df1858 | ||
|
|
abf92ac83f | ||
|
|
385d84d654 | ||
|
|
2bf58d9cb4 | ||
|
|
ba176394c8 | ||
|
|
1869ba216f | ||
|
|
b72f85b394 | ||
|
|
8bacf3a001 | ||
|
|
505c4b28a5 | ||
|
|
3ad34ba56f | ||
|
|
a819d6c1ba | ||
|
|
1b15261adb | ||
|
|
4938db704e | ||
|
|
3191076762 | ||
|
|
b046edf337 | ||
|
|
515a984714 | ||
|
|
77bbf6828d | ||
|
|
caf235e2b5 | ||
|
|
9f9b75cd31 | ||
|
|
736cedd63d | ||
|
|
cd84872a61 | ||
|
|
a6932d76f3 | ||
|
|
336171081e | ||
|
|
8a12caf48d | ||
|
|
cb8fd68d46 | ||
|
|
dc52f784b6 | ||
|
|
330192c042 | ||
|
|
1f32a7c59a | ||
|
|
83dfaa00fd | ||
|
|
93bf6d9d3d | ||
|
|
bfff56c470 | ||
|
|
87e9cd64e8 | ||
|
|
0a112c5655 | ||
|
|
70211980aa | ||
|
|
2ba31148ca | ||
|
|
1906d9f3f5 | ||
|
|
d640dc1f40 | ||
|
|
8cd1b36a02 | ||
|
|
5a8d9cc7e8 | ||
|
|
6323e2ffec | ||
|
|
7af2e7ccbc | ||
|
|
baf4c691d6 | ||
|
|
c633fa8522 | ||
|
|
1d54947764 | ||
|
|
477da0e7c0 | ||
|
|
b9963aa241 | ||
|
|
994c441bb8 | ||
|
|
0a5115fc20 | ||
|
|
11d7a40326 | ||
|
|
46719ef361 | ||
|
|
14ae978bde | ||
|
|
beec09788e | ||
|
|
391dc77071 | ||
|
|
38f2004e56 | ||
|
|
5a5eac246b | ||
|
|
a7bafd4f62 | ||
|
|
a25ab39647 | ||
|
|
7dd09e2903 | ||
|
|
6ab461a212 | ||
|
|
fabc88c81a | ||
|
|
3a19518440 | ||
|
|
56b5e3469e | ||
|
|
542763ddf5 | ||
|
|
1010d97a92 | ||
|
|
d5dacaa988 | ||
|
|
154ade9647 | ||
|
|
5b75fa9286 | ||
|
|
05cf94940e | ||
|
|
03b0d2519b | ||
|
|
000c3e40e1 | ||
|
|
ffb06f5194 | ||
|
|
73ab92e693 | ||
|
|
dd1392cdc8 | ||
|
|
17b3bbe1d8 | ||
|
|
e02ad1e795 | ||
|
|
df8391201f | ||
|
|
aa4aaeb612 | ||
|
|
09b98664c5 | ||
|
|
be46147d9a | ||
|
|
1fe7d58c8c | ||
|
|
4f57782be4 | ||
|
|
7b5f0d3930 | ||
|
|
831fb9180c | ||
|
|
2594e37dc7 | ||
|
|
ca44d6346d | ||
|
|
df41de7239 | ||
|
|
459bf4cd55 | ||
|
|
3537a05ea2 | ||
|
|
da47418f17 | ||
|
|
d5b0546301 | ||
|
|
b6f3c0145f | ||
|
|
c5c85bdfbe | ||
|
|
63a95df534 | ||
|
|
6103f1e4c7 | ||
|
|
3e591beb03 | ||
|
|
c6db54175f | ||
|
|
f7bf24acb3 | ||
|
|
ddffe630ff | ||
|
|
2c54092591 | ||
|
|
7e65f3f642 | ||
|
|
7df6aa4110 | ||
|
|
7df316aa56 | ||
|
|
53ab40a075 | ||
|
|
d115ebde12 | ||
|
|
0e99ac88d3 | ||
|
|
05f4b49b58 | ||
|
|
5d6f901d33 | ||
|
|
908bbb9fa6 | ||
|
|
6c1870be8f | ||
|
|
19080a67ab | ||
|
|
f5834c72c6 | ||
|
|
7ee3392d3e | ||
|
|
c58822fd6c | ||
|
|
ba8091d697 | ||
|
|
d668d60ed5 | ||
|
|
a34b7a2106 | ||
|
|
6f12127095 | ||
|
|
b4e5bbf376 | ||
|
|
a11530d190 | ||
|
|
43d4425da5 | ||
|
|
4c1909cb73 | ||
|
|
a7b6b7a03e | ||
|
|
1f46275bde | ||
|
|
aa7e5d4ae9 | ||
|
|
bb482df3ce | ||
|
|
f878786646 | ||
|
|
652851a1a9 | ||
|
|
850f4956cb | ||
|
|
94223b6ebf | ||
|
|
e9d34e19ad | ||
|
|
107532fe26 | ||
|
|
c32aff82ee | ||
|
|
de8a867d33 | ||
|
|
17a8aba23f | ||
|
|
04b601626b | ||
|
|
802775c118 | ||
|
|
b6baa80134 | ||
|
|
b2d0f2ed3c | ||
|
|
d23f33bae4 | ||
|
|
22ea557337 | ||
|
|
b3ebbb429c | ||
|
|
51218797af | ||
|
|
2d18a6573e | ||
|
|
2646ad5bc4 | ||
|
|
51aec8d8b3 | ||
|
|
638bd95d6f | ||
|
|
f2fcc3a649 | ||
|
|
48795748d8 | ||
|
|
6ba54a994a | ||
|
|
6687db4085 | ||
|
|
ba1d9b3f28 | ||
|
|
51905825fd | ||
|
|
bd5e33855a | ||
|
|
f7fbc0e31c | ||
|
|
cb06fa342c | ||
|
|
581229053a | ||
|
|
209da69b8f | ||
|
|
d09aebcebb | ||
|
|
0cc80bc179 | ||
|
|
79dcc592bc | ||
|
|
687111851a | ||
|
|
dbbaa387bd | ||
|
|
470bfd32f7 | ||
|
|
5fddd080fd | ||
|
|
e10c530b4b | ||
|
|
12189f018d | ||
|
|
0111b0e6ff | ||
|
|
0930b0428d | ||
|
|
289f40014e | ||
|
|
01cc0568f9 | ||
|
|
42ee8ed9fa | ||
|
|
1dae65cb74 | ||
|
|
ce40d67ea2 | ||
|
|
26088a71ee | ||
|
|
cadf6b81a0 | ||
|
|
728b4cacd9 | ||
|
|
b4d3ac2f96 | ||
|
|
bc8e440814 | ||
|
|
4c0f9a0aef | ||
|
|
c321d90575 | ||
|
|
03e6372f14 | ||
|
|
c0481f67ad | ||
|
|
b7f316d25a | ||
|
|
dffd818396 | ||
|
|
bbc19df6b4 | ||
|
|
c8c2786893 | ||
|
|
d79f26f1b5 | ||
|
|
32ba6f9a6c | ||
|
|
ccbb399685 | ||
|
|
f381d433ec | ||
|
|
2ee49f7496 | ||
|
|
bb299aa595 | ||
|
|
a66867463e | ||
|
|
de50b89e5c | ||
|
|
c4ee143354 | ||
|
|
a2227016e5 | ||
|
|
4d6f65b179 | ||
|
|
758ebbe261 | ||
|
|
279890e922 | ||
|
|
44fae36b82 | ||
|
|
fc2fac80af | ||
|
|
5ad9a9654b | ||
|
|
8a4034cc5d | ||
|
|
5649657bf0 | ||
|
|
c929a7cb27 | ||
|
|
eeedb789a9 | ||
|
|
321af8c271 | ||
|
|
fd4d86e797 | ||
|
|
49988e27a2 | ||
|
|
8c94ee5982 | ||
|
|
2e73a34fef | ||
|
|
ea7f987fe3 | ||
|
|
d55503f0c7 | ||
|
|
f59b8166b6 | ||
|
|
2c735d7edf | ||
|
|
5b5ab9fe1e | ||
|
|
28ac9b2d90 | ||
|
|
3300a6a499 | ||
|
|
152a22baa0 | ||
|
|
e873198238 | ||
|
|
68a0db91ee | ||
|
|
ddea7be24a | ||
|
|
b7b86bb0c5 | ||
|
|
817ef906dc | ||
|
|
863c7693fa | ||
|
|
cf78255220 | ||
|
|
f9aa6abdd7 | ||
|
|
0a5e40a0d8 | ||
|
|
4aa445fe35 | ||
|
|
1a1095c99b | ||
|
|
d92b46aa7b | ||
|
|
682d40cbf8 | ||
|
|
b62f3b3fa6 | ||
|
|
e76d3b15ce | ||
|
|
9edfb83221 | ||
|
|
bbdfe00c7a | ||
|
|
e3942551cd | ||
|
|
a0c1a21983 | ||
|
|
3358ce58bc | ||
|
|
3cd54653a8 | ||
|
|
6cad929738 | ||
|
|
184648040c | ||
|
|
df9d277e66 | ||
|
|
75ec315de2 | ||
|
|
c89b2367e6 | ||
|
|
bca5b3481c | ||
|
|
59996e7a40 | ||
|
|
af5726c48c | ||
|
|
90f08c5d51 | ||
|
|
a0a9f4a768 | ||
|
|
2649f1c326 | ||
|
|
c5ff1e4d4a | ||
|
|
c74c8e896a | ||
|
|
55fdc47ff0 | ||
|
|
126eb221d0 | ||
|
|
3c7e22be43 | ||
|
|
b23112bc92 | ||
|
|
cbc60b3c73 | ||
|
|
6caa1311fd | ||
|
|
cd0bbd11c3 | ||
|
|
2a944f8507 | ||
|
|
8a2754d9ce | ||
|
|
ace75f2dfa | ||
|
|
7ceb785c1b | ||
|
|
904a52f5a1 | ||
|
|
04fe65b176 | ||
|
|
7ac6e0d349 | ||
|
|
4ec3586173 | ||
|
|
4b6ab7035e | ||
|
|
3fe7babe00 | ||
|
|
f21570982e | ||
|
|
ad8fe52b84 | ||
|
|
15ce6ac710 | ||
|
|
783387dce6 | ||
|
|
863c7ad99f | ||
|
|
776b69475c | ||
|
|
6b6027a588 | ||
|
|
d465ee15bd | ||
|
|
be2049ca6e | ||
|
|
9b63c176cd | ||
|
|
e506a7ba35 | ||
|
|
2191ad0d40 | ||
|
|
ca162206a3 | ||
|
|
c5d7a2bfd8 | ||
|
|
b9506424d1 | ||
|
|
46a145ae58 | ||
|
|
e4044ef563 | ||
|
|
1cc71eb368 | ||
|
|
323b95de7b | ||
|
|
b0658be041 | ||
|
|
b222d56148 | ||
|
|
946872204b | ||
|
|
2f9adfd908 | ||
|
|
1c8e19378f | ||
|
|
37fd4a1fdb | ||
|
|
7aed3d9c8c | ||
|
|
b125e3e58b | ||
|
|
65788e344a | ||
|
|
abc242d117 | ||
|
|
6dd4968327 | ||
|
|
70bba4a6ed | ||
|
|
1570995021 | ||
|
|
8109236e72 | ||
|
|
9ba651decb | ||
|
|
eb5fdbf4ee | ||
|
|
9827a84857 | ||
|
|
3308133736 | ||
|
|
3129c76c30 | ||
|
|
c0a4c965f0 | ||
|
|
d16c57b63b | ||
|
|
e04f5d26a3 | ||
|
|
ad74007d58 | ||
|
|
6271d6c268 | ||
|
|
7756b5b304 | ||
|
|
8de8695b25 | ||
|
|
11c56c75a4 | ||
|
|
f5a1d68c52 | ||
|
|
0ae7a25c27 | ||
|
|
16eaacd4bc | ||
|
|
809973730f | ||
|
|
2ebb4595a8 | ||
|
|
77f628509c | ||
|
|
d2cd32a735 | ||
|
|
4665f529e6 | ||
|
|
df706d2573 | ||
|
|
5270e99de8 | ||
|
|
eb48f66f6e | ||
|
|
5e7cff0eb7 | ||
|
|
34e781b4a2 | ||
|
|
5f40d50852 | ||
|
|
bb0d91a9cb | ||
|
|
2c790427fa | ||
|
|
4f59b27d70 | ||
|
|
94c08f0b9e | ||
|
|
ef4beaa564 | ||
|
|
2b42215381 | ||
|
|
18d6ec7b59 | ||
|
|
430cf19533 | ||
|
|
542e61dbfc | ||
|
|
8566422ce3 | ||
|
|
9517b4e279 | ||
|
|
70551bee30 | ||
|
|
d690c6a3fe | ||
|
|
28319c2cdc | ||
|
|
df0f73f338 | ||
|
|
411ac55986 | ||
|
|
12226d469a | ||
|
|
632c4629de | ||
|
|
a7df23cebc | ||
|
|
ef74e250f1 | ||
|
|
c0769662bd | ||
|
|
5b6270a184 | ||
|
|
4541d28b68 | ||
|
|
716dec799a | ||
|
|
77a1e35ff4 | ||
|
|
317adfa0de | ||
|
|
f0dfc3d1b0 | ||
|
|
67904f52af | ||
|
|
f644ed6708 | ||
|
|
dc18bde6be | ||
|
|
132c1dfdbe | ||
|
|
162b92ce84 | ||
|
|
9247745ab0 | ||
|
|
c5c0342c7b | ||
|
|
f5e9485a12 | ||
|
|
a183666df6 | ||
|
|
e6bad27771 | ||
|
|
6d24474162 | ||
|
|
5962b990c4 | ||
|
|
9f21f649f0 | ||
|
|
d3d2112b8a | ||
|
|
3795322a65 | ||
|
|
fe5cd5a8ea | ||
|
|
c032a5db16 | ||
|
|
3fcb6fefde | ||
|
|
16f5cb713d | ||
|
|
f7a7925028 | ||
|
|
66fb0c9fa3 | ||
|
|
85acc85f17 | ||
|
|
c76ce09191 | ||
|
|
e3532098b2 | ||
|
|
b6783eb22e | ||
|
|
4ef00ab122 | ||
|
|
3dd827417a | ||
|
|
cbacd02aa2 | ||
|
|
3b7e0fb78a | ||
|
|
9add716395 | ||
|
|
083ea28a1f | ||
|
|
1b51742c36 | ||
|
|
0c6f23e770 | ||
|
|
37a8ca4e97 | ||
|
|
795303c3a8 | ||
|
|
63ba3f0898 | ||
|
|
62594efa00 | ||
|
|
9ac0dcbdc3 | ||
|
|
df588dc4ce | ||
|
|
d54f14a87a | ||
|
|
a4d4be54c1 | ||
|
|
ddd17e74b5 | ||
|
|
81c0761fbe | ||
|
|
0812e3087e | ||
|
|
0dd05493b2 | ||
|
|
bfb3909d26 | ||
|
|
266902026e | ||
|
|
791c4a4e9e | ||
|
|
2cfa9123b8 | ||
|
|
bb9614aafb | ||
|
|
302564a56e | ||
|
|
dcfc231d4d | ||
|
|
64080ed678 | ||
|
|
263935d91e | ||
|
|
7f5ffb8da1 | ||
|
|
f07d25edbe | ||
|
|
7c07d6b5ff | ||
|
|
e433f4ad68 | ||
|
|
a79b9caff6 | ||
|
|
2227cead66 | ||
|
|
410c4ea3e2 | ||
|
|
f92acbcbe0 | ||
|
|
c986585cd9 | ||
|
|
250f0a30ef | ||
|
|
84b5e1adc1 | ||
|
|
2ab28370db | ||
|
|
a88a525326 | ||
|
|
fce8e2c5a4 | ||
|
|
095608266c | ||
|
|
0ec8aaa330 | ||
|
|
9fcb3ef77d | ||
|
|
dc61c57daf | ||
|
|
af284f3f6d | ||
|
|
c43123db76 | ||
|
|
ebbcab3926 | ||
|
|
a235f573c0 | ||
|
|
84a0b8f495 | ||
|
|
b87321c897 | ||
|
|
fc6ebfea5c | ||
|
|
c9fe6d9b37 | ||
|
|
161d60393a | ||
|
|
79413fa85e | ||
|
|
58552c6c94 | ||
|
|
2072e35cfa | ||
|
|
1eaac9d691 | ||
|
|
a916e8a0cb | ||
|
|
a56f560036 | ||
|
|
0fb886936c | ||
|
|
670136916f | ||
|
|
768297f137 | ||
|
|
ef505a0a62 | ||
|
|
42d40620ec | ||
|
|
ee2d8d3065 | ||
|
|
959ef7a691 | ||
|
|
60f03f534b | ||
|
|
e44ebb700a | ||
|
|
8e94c7a755 | ||
|
|
3a2ca36c07 | ||
|
|
88ece74c8a | ||
|
|
67a8610328 | ||
|
|
278d61ce61 | ||
|
|
e72d6ad6b8 | ||
|
|
bf66b31679 | ||
|
|
b4d426d2e8 | ||
|
|
7f7dd270e7 | ||
|
|
f6bad8fe30 | ||
|
|
820b363f79 | ||
|
|
07740a51ef | ||
|
|
d15fb6fe19 | ||
|
|
5749ccec81 | ||
|
|
4a99f6c0cf | ||
|
|
59f480f9d5 | ||
|
|
6cb9c919b5 | ||
|
|
1062b65b5b | ||
|
|
bf3a70cabd | ||
|
|
f46cd28f87 | ||
|
|
dffcfe50aa | ||
|
|
fac55efbc7 | ||
|
|
8b90ce5f6c | ||
|
|
705b8da053 | ||
|
|
4a05ccc692 | ||
|
|
3200d65d90 | ||
|
|
3f23038227 | ||
|
|
408c807fc2 | ||
|
|
d29079a8c5 | ||
|
|
cca596b4a9 | ||
|
|
b768f18294 | ||
|
|
fed620505d | ||
|
|
a008aea3f3 | ||
|
|
25297bc191 | ||
|
|
1989887b25 | ||
|
|
e4fb126d39 | ||
|
|
5fcb2d9cc9 | ||
|
|
06ea631732 | ||
|
|
2f21107a43 | ||
|
|
fb8118bea8 | ||
|
|
4ba1c0aa22 | ||
|
|
169f115fa0 | ||
|
|
61ab2b78d9 | ||
|
|
93732430fc | ||
|
|
0215206220 | ||
|
|
3c8956fedf | ||
|
|
4477ada1ad | ||
|
|
fde9ccc5ca | ||
|
|
bbbd96f159 | ||
|
|
9f4de8115f | ||
|
|
d9f241a2f8 | ||
|
|
ee96dc68cc | ||
|
|
b12f090d13 | ||
|
|
f6ce761a27 | ||
|
|
8c89e04f54 | ||
|
|
f97b127a69 | ||
|
|
5c1fa8f5cd | ||
|
|
64c4d7b5a4 | ||
|
|
ea2fad648b | ||
|
|
9b8b51cb91 | ||
|
|
09b7e74d65 | ||
|
|
7137e06d99 | ||
|
|
fc29b765f7 | ||
|
|
2946475f89 | ||
|
|
d2193fdac0 | ||
|
|
ec2b7e332e | ||
|
|
a3704df6dd | ||
|
|
c3c784e52c | ||
|
|
bbcf695a6c | ||
|
|
038d4dd5a7 | ||
|
|
961e4b99e8 | ||
|
|
9991eebaaf | ||
|
|
cd90fede54 | ||
|
|
a2d28648bd | ||
|
|
3097d05eda | ||
|
|
ff94d8d9b2 | ||
|
|
79cc09fad9 | ||
|
|
c1b29c3f23 | ||
|
|
cf4bb3007e | ||
|
|
193a86cf30 | ||
|
|
7a81f14eb2 | ||
|
|
14d1f0bd1d | ||
|
|
82f8648c59 | ||
|
|
c928940d32 | ||
|
|
0a78d56015 | ||
|
|
1a5716873e | ||
|
|
01b7c86ab9 | ||
|
|
0ca209b195 | ||
|
|
be6f6e3c73 | ||
|
|
cb016be78c | ||
|
|
fc085f2328 | ||
|
|
9a34f3e283 | ||
|
|
7054e862d5 | ||
|
|
faa81abee4 | ||
|
|
6d01f2a323 | ||
|
|
72678bb936 | ||
|
|
df8b68fda6 | ||
|
|
9ae5644af9 | ||
|
|
e8830c5911 | ||
|
|
7699889bd6 | ||
|
|
35a061d188 | ||
|
|
c7c021c969 | ||
|
|
be8352654e | ||
|
|
fb58ab8cc1 | ||
|
|
8da89ebe8b | ||
|
|
d43d46ebec | ||
|
|
ac3a514795 | ||
|
|
f67c63a4fa | ||
|
|
0025dcf8d4 | ||
|
|
81995dc94f | ||
|
|
3fcc70c1d8 | ||
|
|
a986c7a250 | ||
|
|
37e25a8061 | ||
|
|
cfba628465 | ||
|
|
3d200f4d7d | ||
|
|
f21b01b1d6 | ||
|
|
3cbcfac333 | ||
|
|
cc21e1856f | ||
|
|
efd0e1e225 | ||
|
|
521b083ed7 | ||
|
|
a09fe26df7 | ||
|
|
c7a85c4cf8 | ||
|
|
f6350d3d61 | ||
|
|
64c0732e5f | ||
|
|
8e99a31455 | ||
|
|
6d0d0689e1 | ||
|
|
9836129e49 | ||
|
|
86631ea8c3 | ||
|
|
475ccd4903 | ||
|
|
8608144ec1 | ||
|
|
f9cf6d325a | ||
|
|
fc0240c692 | ||
|
|
b84675325f | ||
|
|
647bed5c67 | ||
|
|
00f5bb1d73 | ||
|
|
208800b411 | ||
|
|
5e12a4cea4 | ||
|
|
fdf3e34f1c | ||
|
|
d74337fb94 | ||
|
|
8cab012324 | ||
|
|
940b4f9175 | ||
|
|
5d0dd1fe3f | ||
|
|
ded4e2bb59 | ||
|
|
219e6fb466 | ||
|
|
7147e230de | ||
|
|
5ce11f192f | ||
|
|
71e865e9b7 | ||
|
|
590506f183 | ||
|
|
bed281a637 | ||
|
|
47dd767b3a | ||
|
|
8623b69991 | ||
|
|
a1ddc81dac | ||
|
|
832cdacebf | ||
|
|
9f979080b6 | ||
|
|
7f7301b31e | ||
|
|
6affbedef4 | ||
|
|
ba7f32c1bf | ||
|
|
305ce29ebb | ||
|
|
4cd0405078 | ||
|
|
783102449f | ||
|
|
ae617b2e1d | ||
|
|
9090d0a7b2 | ||
|
|
ffc55fa81b | ||
|
|
07c57ed539 | ||
|
|
788150f80d | ||
|
|
c4e77e4e3b | ||
|
|
c09384e49b | ||
|
|
4490c2d4b4 | ||
|
|
e11f1a95f6 | ||
|
|
b91273eee4 | ||
|
|
b5d570417b | ||
|
|
acd3265e35 | ||
|
|
894c899847 | ||
|
|
f6d4ec504f | ||
|
|
1a01461f5d | ||
|
|
f5e18fc1fe | ||
|
|
f64cd32985 | ||
|
|
89552d7ee2 | ||
|
|
4345623ea9 | ||
|
|
f457759e39 | ||
|
|
14d5e82b4a | ||
|
|
333b6f5a4b | ||
|
|
1660df20af | ||
|
|
14a9c4ba28 | ||
|
|
383be111fa | ||
|
|
7720b1cc34 | ||
|
|
db634cbb79 | ||
|
|
53f37f4f48 | ||
|
|
0a7b522b87 | ||
|
|
9e6500ac79 | ||
|
|
b93cb546f4 | ||
|
|
6d17f9cbeb | ||
|
|
998e6de211 | ||
|
|
6fb02f9869 | ||
|
|
449390143d | ||
|
|
51471ed000 | ||
|
|
fe214b1b2d | ||
|
|
6b6b73b77c | ||
|
|
d805703c08 | ||
|
|
c2815afbe3 | ||
|
|
fa7e6b1fca | ||
|
|
107a6f793b | ||
|
|
67d155759e | ||
|
|
7e2e87256f | ||
|
|
df7790d7c1 | ||
|
|
72128a72c4 | ||
|
|
eb3f23554f | ||
|
|
69ddf43b3e | ||
|
|
249eadaeaa | ||
|
|
59168bc691 | ||
|
|
81b26c6f13 | ||
|
|
da435d85d9 | ||
|
|
d096e49d45 | ||
|
|
73acdb6240 | ||
|
|
38d4122d11 | ||
|
|
24a77c81b3 | ||
|
|
7f41b4280e |
@@ -132,6 +132,42 @@
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "oleynikd",
|
||||
"name": "Denis",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3976868?v=4",
|
||||
"profile": "https://github.com/oleynikd",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mittalsam98",
|
||||
"name": "Sachin Mittal",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/42431274?v=4",
|
||||
"profile": "https://myself.vercel.app/",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Champetaman",
|
||||
"name": "Camilo Oviedo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/64604272?v=4",
|
||||
"profile": "https://www.camilooviedo.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nklmantey",
|
||||
"name": "Mantey",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/90279429?v=4",
|
||||
"profile": "https://nklmantey.com/",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
11
.env.example
11
.env.example
@@ -89,3 +89,14 @@ S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
S3_ENDPOINT=
|
||||
S3_BUCKET=
|
||||
|
||||
# PostHog
|
||||
POSTHOG_API_KEY=
|
||||
POSTHOG_HOST=
|
||||
|
||||
# Stripe Payment
|
||||
STRIPE_PAYMENT_SECRET_KEY=
|
||||
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
||||
STRIPE_PAYMENT_CLIENT_ID=
|
||||
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
||||
STRIPE_PAYMENT_REDIRECT_URL=
|
||||
222
CHANGELOG.md
222
CHANGELOG.md
@@ -2,6 +2,228 @@
|
||||
|
||||
All notable changes to Bigcapital server-side will be in this file.
|
||||
|
||||
# [0.22.0]
|
||||
|
||||
* feat: estimate, receipt, credit note mail preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/757
|
||||
* feat: Add discount to transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/758
|
||||
* fix: update financial forms to use new formatted amount utilities and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/760
|
||||
* fix: total lines style by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/761
|
||||
* fix: discount & adjustment sale transactions bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/762
|
||||
* fix: discount transactions GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/763
|
||||
|
||||
# [0.21.2]
|
||||
|
||||
* hotbug: upload attachments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/755
|
||||
|
||||
# [0.21.1]
|
||||
|
||||
* fix: download invoice document on payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/750
|
||||
* fix: attach branding template attrs to payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/751
|
||||
* fix: make manual entries adjust decimal credit/debit amounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/754
|
||||
* feat: allow quantity of entries accept decimal value by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/753
|
||||
|
||||
# [0.21.0]
|
||||
|
||||
* fix: Credit and debit totals not balancing when decimal values are used by @nklmantey in https://github.com/bigcapitalhq/bigcapital/pull/722
|
||||
* docs: add nklmantey as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/725
|
||||
* feat: track more services events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/721
|
||||
* feat: Invoice mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/723
|
||||
* fix: change the send mail button on invoice drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/730
|
||||
* refactor: notification mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/731
|
||||
* fix: attach payment link in sending invoice mail receipt by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/732
|
||||
* fix: send invoice drawer layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/733
|
||||
* fix: hook up cc and bcc fields to mail sender by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/734
|
||||
* fix: company logo does not show up in mail receipt preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/736
|
||||
* fix: change default invoice mail message by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/737
|
||||
* fix: typing invoice send mail fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/738
|
||||
* fix: clean up ivnoice mail receipt preview component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/739
|
||||
* feat: add shared package to pdf templates to render in the server and… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/735
|
||||
* feat: getting invoice preview on send mail view by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/740
|
||||
* fix: style SSR invoice paper template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/741
|
||||
* fix: send invoice receipt addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/742
|
||||
* fix: due invoice server invoice by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/744
|
||||
* fix: `BIG-265` forgot password text by @ibutiti in https://github.com/bigcapitalhq/bigcapital/pull/745
|
||||
* Crims on sv translation by @Crims-on in https://github.com/bigcapitalhq/bigcapital/pull/671
|
||||
* feat: Added Spanish language to the App 🇪🇸 by @angelosorno in https://github.com/bigcapitalhq/bigcapital/pull/530
|
||||
* fix: mail services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/746
|
||||
* fix: company logo of the template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/747
|
||||
* fix: monorepo dependencies scope by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/748
|
||||
|
||||
# [0.20.6]
|
||||
|
||||
* fix: Import category column of item resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/710
|
||||
* fix: Parse the uppercase values in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/711
|
||||
* chore: Move i18nApply localization to the account transformer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/713
|
||||
* fix: Sync Plaid credit card account type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/714
|
||||
* fix: Sync account normal of cashflow GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/715
|
||||
* feat: Add quantity column to pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/716
|
||||
* feat: Pre-line invoice statements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/717
|
||||
* feat: Invoice number in downloaded pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/718
|
||||
* feat: Track events of pdf documents views by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/719
|
||||
* fix: Customer note does not appear in pdf document by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/720
|
||||
|
||||
# [0.20.5]
|
||||
|
||||
* fix: Disable tabs of the pdf customization if the first field not filed up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/701
|
||||
* fix: Invoice form layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/705
|
||||
* refactor: Invoice, estimate, receipt, credit note and payment received date input fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/707
|
||||
* feat: Add customize templates button to edit forms by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/708
|
||||
* feat: Track account, invoice and item viewed events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/709
|
||||
|
||||
# [0.20.4]
|
||||
|
||||
* fix: Delete company logo from the PDF template by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/699
|
||||
* fix: Set max width/height to company logo of pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/700
|
||||
|
||||
# [0.20.3]
|
||||
|
||||
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
|
||||
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
|
||||
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
|
||||
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
|
||||
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
|
||||
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
|
||||
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
|
||||
|
||||
# [0.20.2]
|
||||
|
||||
* feat: Assign default PDF template automatically by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/687
|
||||
* fix: pdf template addresses controlling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/688
|
||||
* fix: Remove empty lines from address formats by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/690
|
||||
* fix: Pdf templates layout by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/691
|
||||
* feat: Download invoice pdf of the payment link by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/689
|
||||
* fix: Display country name by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/693
|
||||
* feat: Add shared packages to Docker container by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/694
|
||||
|
||||
# [0.20.1]
|
||||
|
||||
* fix: Getting uploaded object uri by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/684
|
||||
|
||||
# [0.20.0]
|
||||
|
||||
* feat: Customize pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/667
|
||||
* feat: Onboard accounts to Stripe Connect by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/668
|
||||
* feat: Upload company logo to invoice templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/670
|
||||
* fix: Invoice pdf customize by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/672
|
||||
* fix: Invoice customize bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/673
|
||||
* feat: Clean up payment links endpoints by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/674
|
||||
* feat: Hook up company logo to server-side pdf templates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/675
|
||||
* feat: Company branding preferences by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/677
|
||||
* feat: Pdf templates customer/company addresses by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/678
|
||||
* fix: Listen to Stripe session completed event by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/679
|
||||
* feat: Track pdf templates Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/680
|
||||
* fix: Branding customize content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/681
|
||||
* feat: Listen to Stripe integration events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/682
|
||||
* feat: Hook up customer/company address to invoice preview of payment page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/683
|
||||
|
||||
# [0.19.17]
|
||||
|
||||
* fix: Un-categorize bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/663
|
||||
|
||||
# [0.19.16]
|
||||
|
||||
* feat: Tracking more Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/653
|
||||
* fix: Expense cannot accept credit card as payment account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/654
|
||||
* fix: Suspense the lazy loaded components in banking pages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/657
|
||||
* feat: Add help dropdown menu by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/656
|
||||
* feat: Bank pages layout breaking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/658
|
||||
* feat: Datatable UI improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/655
|
||||
* fix: Array cast of recognize function rule ids by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/660
|
||||
* fix: Payment made filling the form full amount field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/661
|
||||
* feat: Tabular number of all money columns by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/659
|
||||
* refactor: The expense G/L writer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/662
|
||||
|
||||
## [0.19.15] -
|
||||
|
||||
* fix: Bank transactions infinity scrolling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/648
|
||||
* feat: Integrate multiple branches and warehouses to resource importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/645
|
||||
* fix: Integrate multiple branches with expense resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/649
|
||||
* feat: Cover more tracking events. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/650
|
||||
* feat: Track banking service events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/651
|
||||
|
||||
## [0.19.14]
|
||||
|
||||
* fix: Import bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/643
|
||||
* fix: Set default index to transaction entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/644
|
||||
* feat(server): Events tracking using Posthog by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/646
|
||||
|
||||
## [0.19.13]
|
||||
|
||||
* fix: Subscription middleware by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/624
|
||||
* fix: Getting the sheet columns in import sheet by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/641
|
||||
|
||||
## [0.19.12]
|
||||
|
||||
* fix: Typo one-click demo page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/640
|
||||
|
||||
## [0.19.11]
|
||||
|
||||
* fix: Avoid running the cost job in import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/635
|
||||
* fix: Debounce scheduling calculating items cost by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/634
|
||||
* fix: Expand the resources export page size limitation by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/636
|
||||
* feat: Optimize loading perf. by splitting big chunks and lazy loading them by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/632
|
||||
* fix: Use standard ISO 8601 format for exported data by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/638
|
||||
* fix: Add customer type to customers resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/639
|
||||
|
||||
## [0.19.10]
|
||||
|
||||
* fix: Add subscription plans offer text by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/629
|
||||
|
||||
## [0.19.9]
|
||||
|
||||
* fix: Make webapp package env variables dynamic by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/628
|
||||
|
||||
## [v0.19.8]
|
||||
|
||||
* fix: Cannot import items income and cost accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/617
|
||||
* fix: Some bank account details hidden by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/618
|
||||
* feat(ee): One-click demo account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/616
|
||||
* feat: change banking service language by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/619
|
||||
* feat(banking): Filter uncategorized bank transactions by date by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/590
|
||||
* Fix: Syntax error caused error by @wolone in https://github.com/bigcapitalhq/bigcapital/pull/622
|
||||
* fix: Listen to payment webhooks by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/623
|
||||
* fix: Add prefix J-00001 to manual journals increments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/625
|
||||
* fix: Disable sms service until Twilo integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/626
|
||||
* fix: Style tweaks in onboarding page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/627
|
||||
|
||||
## [0.19.5] - 18-08-2024
|
||||
|
||||
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
|
||||
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
|
||||
* fix: Transaction type and description do not show in general ledger. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/596
|
||||
* fix: Refresh accounts and account transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/597
|
||||
* fix: Typo payments made by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/598
|
||||
* fix: Typo categories list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/600
|
||||
* fix: Autofill the quick created customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/601
|
||||
* fix: Remove views tabs from receipts list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/602
|
||||
* fix: Typo payment receive messages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/599
|
||||
* fix: Enhance Dropzone visual of accept and reject modes by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/603
|
||||
* fix: Matching bank transactions should create associate payment transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/606
|
||||
* fix: Change Dropzone title and subtitle by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/607
|
||||
* fix: Inconsistance page size of paginated data tables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/604
|
||||
* fix: Database connection lost error by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/611
|
||||
* fix: Language typos by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/613
|
||||
* Fix: Correctly display Date, Published At, and Created At in ExpenseDrawerHeader by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/612
|
||||
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
|
||||
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
|
||||
|
||||
## [v0.19.4]
|
||||
|
||||
* feat: Import and export tax rates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/591
|
||||
* feat: Un-categorize bank transactions in bulk by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/587
|
||||
* feat: Pending bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/589
|
||||
* fix: Update `dev` Script in `package.json` to Use `cross-env` by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/588
|
||||
* fix: Should not load branches on reconcile matching form if the branches not enabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/592
|
||||
* fix: Rounding the total amount the pending and matched transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/593
|
||||
|
||||
## [v0.18.0] - 10-08-2024
|
||||
|
||||
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
||||
* feat: Categorize & match bank transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
||||
* feat: Reconcile match transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/522
|
||||
* fix: Issues in matching transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/523
|
||||
* fix: Cashflow transactions types by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/524
|
||||
|
||||
## [v0.17.5] - 17-06-2024
|
||||
|
||||
* fix: Balance sheet and P/L nested accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/501
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/u/bigcapitalhq">
|
||||
<img src="https://img.shields.io/docker/pulls/bigcapitalhq/webapp" />
|
||||
</a>
|
||||
<a href="https://discord.com/invite/c8nPBJafeb">
|
||||
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
|
||||
</a>
|
||||
@@ -126,6 +129,12 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://vederis.id"><img src="https://avatars.githubusercontent.com/u/13505006?v=4?s=100" width="100px;" alt="Vederis Leunardus"/><br /><sub><b>Vederis Leunardus</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=cloudsbird" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.pivoten.com"><img src="https://avatars.githubusercontent.com/u/104120598?v=4?s=100" width="100px;" alt="Chris Cantrell"/><br /><sub><b>Chris Cantrell</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Accantrell72" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/oleynikd"><img src="https://avatars.githubusercontent.com/u/3976868?v=4?s=100" width="100px;" alt="Denis"/><br /><sub><b>Denis</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aoleynikd" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://myself.vercel.app/"><img src="https://avatars.githubusercontent.com/u/42431274?v=4?s=100" width="100px;" alt="Sachin Mittal"/><br /><sub><b>Sachin Mittal</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Amittalsam98" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.camilooviedo.com/"><img src="https://avatars.githubusercontent.com/u/64604272?v=4?s=100" width="100px;" alt="Camilo Oviedo"/><br /><sub><b>Camilo Oviedo</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=Champetaman" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://nklmantey.com/"><img src="https://avatars.githubusercontent.com/u/90279429?v=4?s=100" width="100px;" alt="Mantey"/><br /><sub><b>Mantey</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Anklmantey" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "independent",
|
||||
"npmClient": "pnpm",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
"packages/*",
|
||||
"shared/*"
|
||||
]
|
||||
}
|
||||
|
||||
17
package.json
17
package.json
@@ -4,12 +4,14 @@
|
||||
"scripts": {
|
||||
"dev": "lerna run dev",
|
||||
"build": "lerna run build",
|
||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\"",
|
||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\"",
|
||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\"",
|
||||
"build:server": "lerna run build --scope \"@bigcapital/server\"",
|
||||
"serve:server": "lerna run serve --scope \"@bigcapital/server\"",
|
||||
"test:e2e": "playwright test",
|
||||
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
||||
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
|
||||
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
||||
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
|
||||
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
|
||||
"server2:start": "lerna run start:dev --scope \"@bigcapital/server2\"",
|
||||
"test:watch": "lerna run test:watch",
|
||||
"test:e2e": "lerna run test:e2e",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -29,5 +31,8 @@
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"tsup": "^8.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
102
packages/server-nest/.env.example
Normal file
102
packages/server-nest/.env.example
Normal file
@@ -0,0 +1,102 @@
|
||||
# Mail
|
||||
MAIL_HOST=
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_PORT=
|
||||
MAIL_SECURE=
|
||||
MAIL_FROM_NAME=
|
||||
MAIL_FROM_ADDRESS=
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_USER=bigcapital
|
||||
DB_PASSWORD=bigcapital
|
||||
DB_ROOT_PASSWORD=root
|
||||
DB_CHARSET=utf8
|
||||
|
||||
# System database
|
||||
SYSTEM_DB_NAME=bigcapital_system
|
||||
# SYSTEM_DB_USER=
|
||||
# SYSTEM_DB_PASSWORD=
|
||||
# SYSTEM_DB_NAME=
|
||||
# SYSTEM_DB_CHARSET=
|
||||
|
||||
# Tenant databases
|
||||
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
||||
# TENANT_DB_HOST=
|
||||
# TENANT_DB_USER=
|
||||
# TENANT_DB_PASSWORD=
|
||||
# TENANT_DB_CHARSET=
|
||||
|
||||
# Application
|
||||
BASE_URL=http://example.com
|
||||
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
||||
|
||||
# Jobs MongoDB
|
||||
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
|
||||
|
||||
# App proxy
|
||||
PUBLIC_PROXY_PORT=80
|
||||
PUBLIC_PROXY_SSL_PORT=443
|
||||
|
||||
# Agendash
|
||||
AGENDASH_AUTH_USER=agendash
|
||||
AGENDASH_AUTH_PASSWORD=123123
|
||||
|
||||
# Sign-up restrictions
|
||||
SIGNUP_DISABLED=false
|
||||
SIGNUP_ALLOWED_DOMAINS=
|
||||
SIGNUP_ALLOWED_EMAILS=
|
||||
|
||||
# Sign-up Email Confirmation
|
||||
SIGNUP_EMAIL_CONFIRMATION=false
|
||||
|
||||
# API rate limit (points,duration,block duration).
|
||||
API_RATE_LIMIT=120,60,600
|
||||
|
||||
# Gotenberg API for PDF printing - (production).
|
||||
GOTENBERG_URL=http://gotenberg:3000
|
||||
GOTENBERG_DOCS_URL=http://server:3000/public/
|
||||
|
||||
# Gotenberg API - (development)
|
||||
# GOTENBERG_URL=http://localhost:9000
|
||||
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/
|
||||
|
||||
# Exchange Rate Service
|
||||
EXCHANGE_RATE_SERVICE=open-exchange-rate
|
||||
|
||||
# Open Exchange Rate
|
||||
OPEN_EXCHANGE_RATE_APP_ID=
|
||||
|
||||
# The Plaid environment to use ('sandbox' or 'development').
|
||||
# https://plaid.com/docs/#api-host
|
||||
PLAID_ENV=sandbox
|
||||
|
||||
# Your Plaid keys, which can be found in the Plaid Dashboard.
|
||||
# https://dashboard.plaid.com/account/keys
|
||||
PLAID_CLIENT_ID=
|
||||
PLAID_SECRET=
|
||||
PLAID_LINK_WEBHOOK=
|
||||
|
||||
# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key
|
||||
LEMONSQUEEZY_API_KEY=
|
||||
LEMONSQUEEZY_STORE_ID=
|
||||
LEMONSQUEEZY_WEBHOOK_SECRET=
|
||||
|
||||
# S3 documents and attachments
|
||||
S3_REGION=US
|
||||
S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
S3_ENDPOINT=
|
||||
S3_BUCKET=
|
||||
|
||||
# PostHog
|
||||
POSTHOG_API_KEY=
|
||||
POSTHOG_HOST=
|
||||
|
||||
# Stripe Payment
|
||||
STRIPE_PAYMENT_SECRET_KEY=
|
||||
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
||||
STRIPE_PAYMENT_CLIENT_ID=
|
||||
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
||||
STRIPE_PAYMENT_REDIRECT_URL=
|
||||
25
packages/server-nest/.eslintrc.js
Normal file
25
packages/server-nest/.eslintrc.js
Normal file
@@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
||||
56
packages/server-nest/.gitignore
vendored
Normal file
56
packages/server-nest/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
/build
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# temp directory
|
||||
.temp
|
||||
.tmp
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
4
packages/server-nest/.prettierrc
Normal file
4
packages/server-nest/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
1
packages/server-nest/.todo
Normal file
1
packages/server-nest/.todo
Normal file
@@ -0,0 +1 @@
|
||||
- Build authentication services.
|
||||
1
packages/server-nest/README.md
Normal file
1
packages/server-nest/README.md
Normal file
@@ -0,0 +1 @@
|
||||
## @bigcapitalhq/server
|
||||
11
packages/server-nest/nest-cli.json
Normal file
11
packages/server-nest/nest-cli.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true,
|
||||
"assets": [
|
||||
{ "include": "i18n/**/*", "watchAssets": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
127
packages/server-nest/package.json
Normal file
127
packages/server-nest/package.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"name": "@bigcapital/server2",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bigcapital/email-components": "*",
|
||||
"@bigcapital/pdf-templates": "*",
|
||||
"@bigcapital/utils": "*",
|
||||
"@nestjs/bull": "^10.2.1",
|
||||
"@nestjs/bullmq": "^10.2.1",
|
||||
"@nestjs/cache-manager": "^2.2.2",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.3",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/event-emitter": "^2.0.4",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@nestjs/throttler": "^6.2.1",
|
||||
"@supercharge/promise-pool": "^3.2.0",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/ramda": "^0.30.2",
|
||||
"accounting": "^0.4.1",
|
||||
"async": "^3.2.0",
|
||||
"axios": "^1.6.0",
|
||||
"bluebird": "^3.7.2",
|
||||
"bull": "^4.16.3",
|
||||
"bullmq": "^5.21.1",
|
||||
"cache-manager": "^6.1.1",
|
||||
"cache-manager-redis-store": "^3.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"express-validator": "^7.2.0",
|
||||
"form-data": "^4.0.0",
|
||||
"fp-ts": "^2.16.9",
|
||||
"is-my-json-valid": "^2.20.5",
|
||||
"js-money": "^0.6.3",
|
||||
"knex": "^3.1.0",
|
||||
"lamda": "^0.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^3.11.3",
|
||||
"nestjs-cls": "^4.4.1",
|
||||
"nestjs-i18n": "^10.4.9",
|
||||
"nodemailer": "^6.3.0",
|
||||
"object-hash": "^2.0.3",
|
||||
"objection": "^3.1.5",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"plaid": "^10.3.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"posthog-node": "^4.3.2",
|
||||
"pug": "^3.0.2",
|
||||
"ramda": "^0.30.1",
|
||||
"redis": "^4.7.0",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"serialize-interceptor": "^1.1.7",
|
||||
"strategy": "^1.1.1",
|
||||
"uniqid": "^5.2.0",
|
||||
"uuid": "^10.0.0",
|
||||
"yup": "^0.28.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@types/yup": "^0.29.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"prettier": "^3.0.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
6
packages/server-nest/src/common/config/gotenberg.ts
Normal file
6
packages/server-nest/src/common/config/gotenberg.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('gotenberg', () => ({
|
||||
url: process.env.GOTENBERG_URL,
|
||||
docsUrl: process.env.GOTENBERG_DOCS_URL,
|
||||
}));
|
||||
21
packages/server-nest/src/common/config/index.ts
Normal file
21
packages/server-nest/src/common/config/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import systemDatabase from './system-database';
|
||||
import tenantDatabase from './tenant-database';
|
||||
import signup from './signup';
|
||||
import gotenberg from './gotenberg';
|
||||
import plaid from './plaid';
|
||||
import lemonsqueezy from './lemonsqueezy';
|
||||
import s3 from './s3';
|
||||
import openExchange from './open-exchange';
|
||||
import posthog from './posthog';
|
||||
|
||||
export const config = [
|
||||
systemDatabase,
|
||||
tenantDatabase,
|
||||
signup,
|
||||
gotenberg,
|
||||
plaid,
|
||||
lemonsqueezy,
|
||||
s3,
|
||||
openExchange,
|
||||
posthog,
|
||||
];
|
||||
7
packages/server-nest/src/common/config/lemonsqueezy.ts
Normal file
7
packages/server-nest/src/common/config/lemonsqueezy.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('lemonsqueezy', () => ({
|
||||
apiKey: process.env.LEMONSQUEEZY_API_KEY,
|
||||
storeId: process.env.LEMONSQUEEZY_STORE_ID,
|
||||
webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
|
||||
}));
|
||||
5
packages/server-nest/src/common/config/open-exchange.ts
Normal file
5
packages/server-nest/src/common/config/open-exchange.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('openExchange', () => ({
|
||||
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
|
||||
}));
|
||||
8
packages/server-nest/src/common/config/plaid.ts
Normal file
8
packages/server-nest/src/common/config/plaid.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('plaid', () => ({
|
||||
env: process.env.PLAID_ENV || 'sandbox',
|
||||
clientId: process.env.PLAID_CLIENT_ID,
|
||||
secret: process.env.PLAID_SECRET,
|
||||
linkWebhook: process.env.PLAID_LINK_WEBHOOK,
|
||||
}));
|
||||
6
packages/server-nest/src/common/config/posthog.ts
Normal file
6
packages/server-nest/src/common/config/posthog.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('posthog', () => ({
|
||||
apiKey: process.env.POSTHOG_API_KEY,
|
||||
host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com',
|
||||
}));
|
||||
9
packages/server-nest/src/common/config/s3.ts
Normal file
9
packages/server-nest/src/common/config/s3.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('s3', () => ({
|
||||
region: process.env.S3_REGION || 'US',
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
bucket: process.env.S3_BUCKET,
|
||||
}));
|
||||
12
packages/server-nest/src/common/config/signup.ts
Normal file
12
packages/server-nest/src/common/config/signup.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('signup', () => ({
|
||||
disabled: process.env.SIGNUP_DISABLED === 'true',
|
||||
allowedDomains: process.env.SIGNUP_ALLOWED_DOMAINS
|
||||
? process.env.SIGNUP_ALLOWED_DOMAINS.split(',')
|
||||
: [],
|
||||
allowedEmails: process.env.SIGNUP_ALLOWED_EMAILS
|
||||
? process.env.SIGNUP_ALLOWED_EMAILS.split(',')
|
||||
: [],
|
||||
emailConfirmation: process.env.SIGNUP_EMAIL_CONFIRMATION === 'true',
|
||||
}));
|
||||
9
packages/server-nest/src/common/config/stripe-payment.ts
Normal file
9
packages/server-nest/src/common/config/stripe-payment.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('stripePayment', () => ({
|
||||
secretKey: process.env.STRIPE_PAYMENT_SECRET_KEY,
|
||||
publishableKey: process.env.STRIPE_PAYMENT_PUBLISHABLE_KEY,
|
||||
clientId: process.env.STRIPE_PAYMENT_CLIENT_ID,
|
||||
webhooksSecret: process.env.STRIPE_PAYMENT_WEBHOOKS_SECRET,
|
||||
redirectUrl: process.env.STRIPE_PAYMENT_REDIRECT_URL,
|
||||
}));
|
||||
10
packages/server-nest/src/common/config/system-database.ts
Normal file
10
packages/server-nest/src/common/config/system-database.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('systemDatabase', () => ({
|
||||
client: 'mysql',
|
||||
host: process.env.SYSTEM_DB_HOST || process.env.DB_HOST,
|
||||
port: process.env.SYSTEM_DB_PORT || process.env.DB_PORT || 5432,
|
||||
user: process.env.SYSTEM_DB_USER || process.env.DB_USER,
|
||||
password: process.env.SYSTEM_DB_PASSWORD || process.env.DB_PASSWORD,
|
||||
databaseName: process.env.SYSTEM_DB_NAME || process.env.DB_NAME,
|
||||
}));
|
||||
@@ -0,0 +1,9 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('tenantDatabase', () => ({
|
||||
client: 'mysql',
|
||||
host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
||||
port: process.env.TENANT_DB_PORT || process.env.DB_PORT || 5432,
|
||||
user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
||||
password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
||||
}));
|
||||
756
packages/server-nest/src/common/events/events.ts
Normal file
756
packages/server-nest/src/common/events/events.ts
Normal file
@@ -0,0 +1,756 @@
|
||||
export const events = {
|
||||
/**
|
||||
* Authentication service.
|
||||
*/
|
||||
auth: {
|
||||
signIn: 'onSignIn',
|
||||
signingIn: 'onSigningIn',
|
||||
|
||||
signUp: 'onSignUp',
|
||||
signingUp: 'onSigningUp',
|
||||
|
||||
signUpConfirming: 'signUpConfirming',
|
||||
signUpConfirmed: 'signUpConfirmed',
|
||||
|
||||
sendingResetPassword: 'onSendingResetPassword',
|
||||
sendResetPassword: 'onSendResetPassword',
|
||||
|
||||
resetPassword: 'onResetPassword',
|
||||
resetingPassword: 'onResetingPassword',
|
||||
},
|
||||
|
||||
/**
|
||||
* Invite users service.
|
||||
*/
|
||||
inviteUser: {
|
||||
acceptInvite: 'onUserAcceptInvite',
|
||||
sendInvite: 'onUserSendInvite',
|
||||
resendInvite: 'onUserInviteResend',
|
||||
checkInvite: 'onUserCheckInvite',
|
||||
sendInviteTenantSynced: 'onUserSendInviteTenantSynced',
|
||||
},
|
||||
|
||||
/**
|
||||
* Organization managment service.
|
||||
*/
|
||||
organization: {
|
||||
build: 'onOrganizationBuild',
|
||||
built: 'onOrganizationBuilt',
|
||||
|
||||
seeded: 'onOrganizationSeeded',
|
||||
|
||||
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
||||
},
|
||||
|
||||
/**
|
||||
* Organization subscription.
|
||||
*/
|
||||
subscription: {
|
||||
onSubscriptionCancel: 'onSubscriptionCancel',
|
||||
onSubscriptionCancelled: 'onSubscriptionCancelled',
|
||||
|
||||
onSubscriptionResume: 'onSubscriptionResume',
|
||||
onSubscriptionResumed: 'onSubscriptionResumed',
|
||||
|
||||
onSubscriptionPlanChange: 'onSubscriptionPlanChange',
|
||||
onSubscriptionPlanChanged: 'onSubscriptionPlanChanged',
|
||||
|
||||
onSubscriptionSubscribed: 'onSubscriptionSubscribed',
|
||||
|
||||
onSubscriptionPaymentSucceed: 'onSubscriptionPaymentSucceed',
|
||||
onSubscriptionPaymentFailed: 'onSubscriptionPaymentFailed',
|
||||
},
|
||||
|
||||
/**
|
||||
* Tenants managment service.
|
||||
*/
|
||||
tenantManager: {
|
||||
databaseCreated: 'onDatabaseCreated',
|
||||
tenantMigrated: 'onTenantMigrated',
|
||||
tenantSeeded: 'onTenantSeeded',
|
||||
},
|
||||
|
||||
/**
|
||||
* Accounts service.
|
||||
*/
|
||||
accounts: {
|
||||
onViewed: 'onAccountViewed',
|
||||
onListViewed: 'onAccountsListViewed',
|
||||
|
||||
onCreating: 'onAccountCreating',
|
||||
onCreated: 'onAccountCreated',
|
||||
|
||||
onEditing: 'onAccountEditing',
|
||||
onEdited: 'onAccountEdited',
|
||||
|
||||
onDelete: 'onAccountDelete',
|
||||
onDeleted: 'onAccountDeleted',
|
||||
|
||||
onBulkDeleted: 'onBulkDeleted',
|
||||
onBulkActivated: 'onAccountBulkActivated',
|
||||
|
||||
onActivated: 'onAccountActivated',
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual journals service.
|
||||
*/
|
||||
manualJournals: {
|
||||
onCreating: 'onManualJournalCreating',
|
||||
onCreated: 'onManualJournalCreated',
|
||||
|
||||
onEditing: 'onManualJournalEditing',
|
||||
onEdited: 'onManualJournalEdited',
|
||||
|
||||
onDeleting: 'onManualJournalDeleting',
|
||||
onDeleted: 'onManualJournalDeleted',
|
||||
|
||||
onPublished: 'onManualJournalPublished',
|
||||
onPublishing: 'onManualJournalPublishing',
|
||||
},
|
||||
|
||||
/**
|
||||
* Expenses service.
|
||||
*/
|
||||
expenses: {
|
||||
onCreating: 'onExpenseCreating',
|
||||
onCreated: 'onExpenseCreated',
|
||||
|
||||
onEditing: 'onExpenseEditing',
|
||||
onEdited: 'onExpenseEdited',
|
||||
|
||||
onDeleting: 'onExpenseDeleting',
|
||||
onDeleted: 'onExpenseDeleted',
|
||||
|
||||
onPublishing: 'onExpensePublishing',
|
||||
onPublished: 'onExpensePublished',
|
||||
},
|
||||
|
||||
/**
|
||||
* Sales invoices service.
|
||||
*/
|
||||
saleInvoice: {
|
||||
onViewed: 'onSaleInvoiceItemViewed',
|
||||
onListViewed: 'onSaleInvoiceListViewed',
|
||||
|
||||
onPdfViewed: 'onSaleInvoicePdfViewed',
|
||||
|
||||
onCreate: 'onSaleInvoiceCreate',
|
||||
onCreating: 'onSaleInvoiceCreating',
|
||||
onCreated: 'onSaleInvoiceCreated',
|
||||
|
||||
onEdit: 'onSaleInvoiceEdit',
|
||||
onEditing: 'onSaleInvoiceEditing',
|
||||
onEdited: 'onSaleInvoiceEdited',
|
||||
|
||||
onDelete: 'onSaleInvoiceDelete',
|
||||
onDeleting: 'onSaleInvoiceDeleting',
|
||||
onDeleted: 'onSaleInvoiceDeleted',
|
||||
|
||||
onDelivering: 'onSaleInvoiceDelivering',
|
||||
onDeliver: 'onSaleInvoiceDeliver',
|
||||
onDelivered: 'onSaleInvoiceDelivered',
|
||||
|
||||
onPublish: 'onSaleInvoicePublish',
|
||||
onPublished: 'onSaleInvoicePublished',
|
||||
|
||||
onWriteoff: 'onSaleInvoiceWriteoff',
|
||||
onWrittenoff: 'onSaleInvoiceWrittenoff',
|
||||
onWrittenoffCancel: 'onSaleInvoiceWrittenoffCancel',
|
||||
onWrittenoffCanceled: 'onSaleInvoiceWrittenoffCanceled',
|
||||
|
||||
onNotifySms: 'onSaleInvoiceNotifySms',
|
||||
onNotifiedSms: 'onSaleInvoiceNotifiedSms',
|
||||
|
||||
onNotifyMail: 'onSaleInvoiceNotifyMail',
|
||||
onNotifyReminderMail: 'onSaleInvoiceNotifyReminderMail',
|
||||
|
||||
onPreMailSend: 'onSaleInvoicePreMailSend',
|
||||
onMailSend: 'onSaleInvoiceMailSend',
|
||||
onMailSent: 'onSaleInvoiceMailSent',
|
||||
|
||||
onMailReminderSend: 'onSaleInvoiceMailReminderSend',
|
||||
onMailReminderSent: 'onSaleInvoiceMailReminderSent',
|
||||
|
||||
onPublicLinkGenerating: 'onPublicSharableLinkGenerating',
|
||||
onPublicLinkGenerated: 'onPublicSharableLinkGenerated',
|
||||
},
|
||||
|
||||
/**
|
||||
* Sales estimates service.
|
||||
*/
|
||||
saleEstimate: {
|
||||
onPdfViewed: 'onSaleEstimatePdfViewed',
|
||||
|
||||
onCreating: 'onSaleEstimateCreating',
|
||||
onCreated: 'onSaleEstimateCreated',
|
||||
|
||||
onEditing: 'onSaleEstimateEditing',
|
||||
onEdited: 'onSaleEstimateEdited',
|
||||
|
||||
onDeleting: 'onSaleEstimatedDeleting',
|
||||
onDeleted: 'onSaleEstimatedDeleted',
|
||||
|
||||
onPublishing: 'onSaleEstimatedPublishing',
|
||||
onPublished: 'onSaleEstimatedPublished',
|
||||
|
||||
onNotifySms: 'onSaleEstimateNotifySms',
|
||||
onNotifiedSms: 'onSaleEstimateNotifiedSms',
|
||||
|
||||
onDelivering: 'onSaleEstimateDelivering',
|
||||
onDelivered: 'onSaleEstimateDelivered',
|
||||
|
||||
onConvertedToInvoice: 'onSaleEstimateConvertedToInvoice',
|
||||
|
||||
onApproving: 'onSaleEstimateApproving',
|
||||
onApproved: 'onSaleEstimateApproved',
|
||||
|
||||
onRejecting: 'onSaleEstimateRejecting',
|
||||
onRejected: 'onSaleEstimateRejected',
|
||||
|
||||
onNotifyMail: 'onSaleEstimateNotifyMail',
|
||||
|
||||
onPreMailSend: 'onSaleEstimatePreMailSend',
|
||||
onMailSend: 'onSaleEstimateMailSend',
|
||||
onMailSent: 'onSaleEstimateMailSend',
|
||||
|
||||
onViewed: 'onSaleEstimateViewed',
|
||||
},
|
||||
|
||||
/**
|
||||
* Sales receipts service.
|
||||
*/
|
||||
saleReceipt: {
|
||||
onPdfViewed: 'onSaleReceiptPdfViewed',
|
||||
|
||||
onCreating: 'onSaleReceiptsCreating',
|
||||
onCreated: 'onSaleReceiptsCreated',
|
||||
|
||||
onEditing: 'onSaleReceiptsEditing',
|
||||
onEdited: 'onSaleReceiptsEdited',
|
||||
|
||||
onDeleting: 'onSaleReceiptsDeleting',
|
||||
onDeleted: 'onSaleReceiptsDeleted',
|
||||
|
||||
onPublishing: 'onSaleReceiptPublishing',
|
||||
onPublished: 'onSaleReceiptPublished',
|
||||
|
||||
onClosed: 'onSaleReceiptClosed',
|
||||
onClosing: 'onSaleReceiptClosing',
|
||||
|
||||
onNotifySms: 'onSaleReceiptNotifySms',
|
||||
onNotifiedSms: 'onSaleReceiptNotifiedSms',
|
||||
|
||||
onPreMailSend: 'onSaleReceiptPreMailSend',
|
||||
onMailSend: 'onSaleReceiptMailSend',
|
||||
onMailSent: 'onSaleReceiptMailSent',
|
||||
},
|
||||
|
||||
/**
|
||||
* Payment receipts service.
|
||||
*/
|
||||
paymentReceive: {
|
||||
onPdfViewed: 'onPaymentReceivedPdfViewed',
|
||||
|
||||
onCreated: 'onPaymentReceiveCreated',
|
||||
onCreating: 'onPaymentReceiveCreating',
|
||||
|
||||
onEditing: 'onPaymentReceiveEditing',
|
||||
onEdited: 'onPaymentReceiveEdited',
|
||||
|
||||
onDeleting: 'onPaymentReceiveDeleting',
|
||||
onDeleted: 'onPaymentReceiveDeleted',
|
||||
|
||||
onPublishing: 'onPaymentReceivePublishing',
|
||||
onPublished: 'onPaymentReceivePublished',
|
||||
|
||||
onNotifySms: 'onPaymentReceiveNotifySms',
|
||||
onNotifiedSms: 'onPaymentReceiveNotifiedSms',
|
||||
|
||||
onPreMailSend: 'onPaymentReceivePreMailSend',
|
||||
onMailSend: 'onPaymentReceiveMailSend',
|
||||
onMailSent: 'onPaymentReceiveMailSent',
|
||||
},
|
||||
|
||||
/**
|
||||
* Bills service.
|
||||
*/
|
||||
bill: {
|
||||
onCreating: 'onBillCreating',
|
||||
onCreated: 'onBillCreated',
|
||||
|
||||
onEditing: 'onBillEditing',
|
||||
onEdited: 'onBillEdited',
|
||||
|
||||
onDeleting: 'onBillDeleting',
|
||||
onDeleted: 'onBillDeleted',
|
||||
|
||||
onPublishing: 'onBillPublishing',
|
||||
onPublished: 'onBillPublished',
|
||||
|
||||
onOpening: 'onBillOpening',
|
||||
onOpened: 'onBillOpened',
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill payments service.
|
||||
*/
|
||||
billPayment: {
|
||||
onCreating: 'onBillPaymentCreating',
|
||||
onCreated: 'onBillPaymentCreated',
|
||||
|
||||
onEditing: 'onBillPaymentEditing',
|
||||
onEdited: 'onBillPaymentEdited',
|
||||
|
||||
onDeleted: 'onBillPaymentDeleted',
|
||||
onDeleting: 'onBillPaymentDeleting',
|
||||
|
||||
onPublishing: 'onBillPaymentPublishing',
|
||||
onPublished: 'onBillPaymentPublished',
|
||||
},
|
||||
|
||||
/**
|
||||
* Customers services.
|
||||
*/
|
||||
customers: {
|
||||
onCreating: 'onCustomerCreating',
|
||||
onCreated: 'onCustomerCreated',
|
||||
|
||||
onEdited: 'onCustomerEdited',
|
||||
onEditing: 'onCustomerEditing',
|
||||
|
||||
onDeleted: 'onCustomerDeleted',
|
||||
onDeleting: 'onCustomerDeleting',
|
||||
onBulkDeleted: 'onBulkDeleted',
|
||||
|
||||
onOpeningBalanceChanging: 'onCustomerOpeningBalanceChanging',
|
||||
onOpeningBalanceChanged: 'onCustomerOpeingBalanceChanged',
|
||||
|
||||
onActivating: 'onCustomerActivating',
|
||||
onActivated: 'onCustomerActivated',
|
||||
},
|
||||
|
||||
/**
|
||||
* Vendors services.
|
||||
*/
|
||||
vendors: {
|
||||
onCreated: 'onVendorCreated',
|
||||
onCreating: 'onVendorCreating',
|
||||
|
||||
onEdited: 'onVendorEdited',
|
||||
onEditing: 'onVendorEditing',
|
||||
|
||||
onDeleted: 'onVendorDeleted',
|
||||
onDeleting: 'onVendorDeleting',
|
||||
|
||||
onOpeningBalanceChanging: 'onVendorOpeingBalanceChanging',
|
||||
onOpeningBalanceChanged: 'onVendorOpeingBalanceChanged',
|
||||
|
||||
onActivating: 'onVendorActivating',
|
||||
onActivated: 'onVendorActivated',
|
||||
},
|
||||
|
||||
/**
|
||||
* Items service.
|
||||
*/
|
||||
item: {
|
||||
onViewed: 'onItemViewed',
|
||||
|
||||
onCreated: 'onItemCreated',
|
||||
onCreating: 'onItemCreating',
|
||||
|
||||
onEditing: 'onItemEditing',
|
||||
onEdited: 'onItemEdited',
|
||||
|
||||
onDeleted: 'onItemDeleted',
|
||||
onDeleting: 'onItemDeleting',
|
||||
|
||||
onActivating: 'onItemActivating',
|
||||
onActivated: 'onItemActivated',
|
||||
|
||||
onInactivating: 'onInactivating',
|
||||
onInactivated: 'onItemInactivated',
|
||||
},
|
||||
|
||||
/**
|
||||
* Item category service.
|
||||
*/
|
||||
itemCategory: {
|
||||
onCreated: 'onItemCategoryCreated',
|
||||
onEdited: 'onItemCategoryEdited',
|
||||
onDeleted: 'onItemCategoryDeleted',
|
||||
onBulkDeleted: 'onItemCategoryBulkDeleted',
|
||||
},
|
||||
|
||||
/**
|
||||
* Inventory service.
|
||||
*/
|
||||
inventory: {
|
||||
onInventoryTransactionsCreated: 'onInventoryTransactionsCreated',
|
||||
onInventoryTransactionsDeleted: 'onInventoryTransactionsDeleted',
|
||||
|
||||
onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled',
|
||||
onComputeItemCostJobStarted: 'onComputeItemCostJobStarted',
|
||||
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted',
|
||||
|
||||
onInventoryCostEntriesWritten: 'onInventoryCostEntriesWritten',
|
||||
|
||||
onCostLotsGLEntriesBeforeWrite: 'onInventoryCostLotsGLEntriesBeforeWrite',
|
||||
onCostLotsGLEntriesWrite: 'onInventoryCostLotsGLEntriesWrite',
|
||||
},
|
||||
|
||||
/**
|
||||
* Inventory adjustment service.
|
||||
*/
|
||||
inventoryAdjustment: {
|
||||
onQuickCreating: 'onInventoryAdjustmentCreating',
|
||||
onQuickCreated: 'onInventoryAdjustmentQuickCreated',
|
||||
|
||||
onCreated: 'onInventoryAdjustmentCreated',
|
||||
|
||||
onDeleting: 'onInventoryAdjustmentDeleting',
|
||||
onDeleted: 'onInventoryAdjustmentDeleted',
|
||||
|
||||
onPublishing: 'onInventoryAdjustmentPublishing',
|
||||
onPublished: 'onInventoryAdjustmentPublished',
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill landed cost.
|
||||
*/
|
||||
billLandedCost: {
|
||||
onCreate: 'onBillLandedCostCreate',
|
||||
onCreated: 'onBillLandedCostCreated',
|
||||
onDelete: 'onBillLandedCostDelete',
|
||||
onDeleted: 'onBillLandedCostDeleted',
|
||||
},
|
||||
|
||||
cashflow: {
|
||||
onOwnerContributionCreate: 'onCashflowOwnerContributionCreate',
|
||||
onOwnerContributionCreated: 'onCashflowOwnerContributionCreated',
|
||||
|
||||
onOtherIncomeCreate: 'onCashflowOtherIncomeCreate',
|
||||
onOtherIncomeCreated: 'onCashflowOtherIncomeCreated',
|
||||
|
||||
onTransactionCreating: 'onCashflowTransactionCreating',
|
||||
onTransactionCreated: 'onCashflowTransactionCreated',
|
||||
|
||||
onTransactionDeleting: 'onCashflowTransactionDeleting',
|
||||
onTransactionDeleted: 'onCashflowTransactionDeleted',
|
||||
|
||||
onTransactionCategorizing: 'onTransactionCategorizing',
|
||||
onTransactionCategorized: 'onCashflowTransactionCategorized',
|
||||
|
||||
onTransactionUncategorizedCreating: 'onTransactionUncategorizedCreating',
|
||||
onTransactionUncategorizedCreated: 'onTransactionUncategorizedCreated',
|
||||
|
||||
onTransactionUncategorizing: 'onTransactionUncategorizing',
|
||||
onTransactionUncategorized: 'onTransactionUncategorized',
|
||||
|
||||
onTransactionCategorizingAsExpense: 'onTransactionCategorizingAsExpense',
|
||||
onTransactionCategorizedAsExpense: 'onTransactionCategorizedAsExpense',
|
||||
},
|
||||
|
||||
/**
|
||||
* Roles service events.
|
||||
*/
|
||||
roles: {
|
||||
onCreate: 'onRoleCreate',
|
||||
onCreated: 'onRoleCreated',
|
||||
onEdit: 'onRoleEdit',
|
||||
onEdited: 'onRoleEdited',
|
||||
onDelete: 'onRoleDelete',
|
||||
onDeleted: 'onRoleDeleted',
|
||||
},
|
||||
|
||||
tenantUser: {
|
||||
onEdited: 'onTenantUserEdited',
|
||||
onDeleted: 'onTenantUserDeleted',
|
||||
onActivated: 'onTenantUserActivated',
|
||||
onInactivated: 'onTenantUserInactivated',
|
||||
},
|
||||
|
||||
/**
|
||||
* Credit note service.
|
||||
*/
|
||||
creditNote: {
|
||||
onPdfViewed: 'onCreditNotePdfViewed',
|
||||
|
||||
onCreate: 'onCreditNoteCreate',
|
||||
onCreating: 'onCreditNoteCreating',
|
||||
onCreated: 'onCreditNoteCreated',
|
||||
|
||||
onEditing: 'onCreditNoteEditing',
|
||||
onEdit: 'onCreditNoteEdit',
|
||||
onEdited: 'onCreditNoteEdited',
|
||||
|
||||
onDelete: 'onCreditNoteDelete',
|
||||
onDeleting: 'onCreditNoteDeleting',
|
||||
onDeleted: 'onCreditNoteDeleted',
|
||||
|
||||
onOpen: 'onCreditNoteOpen',
|
||||
onOpening: 'onCreditNoteOpening',
|
||||
onOpened: 'onCreditNoteOpened',
|
||||
|
||||
onRefundCreate: 'onCreditNoteRefundCreate',
|
||||
onRefundCreating: 'onCreditNoteRefundCreating',
|
||||
onRefundCreated: 'onCreditNoteRefundCreated',
|
||||
|
||||
onRefundDelete: 'onCreditNoteRefundDelete',
|
||||
onRefundDeleting: 'onCreditNoteRefundDeleting',
|
||||
onRefundDeleted: 'onCreditNoteRefundDeleted',
|
||||
|
||||
onApplyToInvoicesCreated: 'onCreditNoteApplyToInvoiceCreated',
|
||||
onApplyToInvoicesCreate: 'onCreditNoteApplyToInvoiceCreate',
|
||||
onApplyToInvoicesDeleted: 'onCreditNoteApplyToInvoiceDeleted',
|
||||
},
|
||||
|
||||
/**
|
||||
* Vendor credit service.
|
||||
*/
|
||||
vendorCredit: {
|
||||
onCreate: 'onVendorCreditCreate',
|
||||
onCreating: 'onVendorCreditCreating',
|
||||
onCreated: 'onVendorCreditCreated',
|
||||
|
||||
onEdit: 'onVendorCreditEdit',
|
||||
onEditing: 'onVendorCreditEditing',
|
||||
onEdited: 'onVendorCreditEdited',
|
||||
|
||||
onDelete: 'onVendorCreditDelete',
|
||||
onDeleting: 'onVendorCreditDeleting',
|
||||
onDeleted: 'onVendorCreditDeleted',
|
||||
|
||||
onOpen: 'onVendorCreditOpen',
|
||||
onOpened: 'onVendorCreditOpened',
|
||||
|
||||
onRefundCreating: 'onVendorCreditRefundCreating',
|
||||
onRefundCreate: 'onVendorCreditRefundCreate',
|
||||
onRefundCreated: 'onVendorCreditRefundCreated',
|
||||
|
||||
onRefundDelete: 'onVendorCreditRefundDelete',
|
||||
onRefundDeleting: 'onVendorCreditRefundDeleting',
|
||||
onRefundDeleted: 'onVendorCreditRefundDeleted',
|
||||
|
||||
onApplyToInvoicesCreated: 'onVendorCreditApplyToInvoiceCreated',
|
||||
onApplyToInvoicesCreate: 'onVendorCreditApplyToInvoiceCreate',
|
||||
onApplyToInvoicesDeleted: 'onVendorCreditApplyToInvoiceDeleted',
|
||||
},
|
||||
|
||||
transactionsLocking: {
|
||||
locked: 'onTransactionLockingLocked',
|
||||
lockCanceled: 'onTransactionLockingLockCanceled',
|
||||
partialUnlocked: 'onTransactionLockingPartialUnlocked',
|
||||
partialUnlockCanceled: 'onTransactionLockingPartialUnlockCanceled',
|
||||
},
|
||||
|
||||
warehouse: {
|
||||
onCreate: 'onWarehouseCreate',
|
||||
onCreated: 'onWarehouseCreated',
|
||||
|
||||
onEdit: 'onWarehouseEdit',
|
||||
onEdited: 'onWarehouseEdited',
|
||||
|
||||
onDelete: 'onWarehouseDelete',
|
||||
onDeleted: 'onWarehouseDeleted',
|
||||
|
||||
onActivate: 'onWarehouseActivate',
|
||||
onActivated: 'onWarehouseActivated',
|
||||
|
||||
onMarkPrimary: 'onWarehouseMarkPrimary',
|
||||
onMarkedPrimary: 'onWarehouseMarkedPrimary',
|
||||
},
|
||||
|
||||
warehouseTransfer: {
|
||||
onCreate: 'onWarehouseTransferCreate',
|
||||
onCreated: 'onWarehouseTransferCreated',
|
||||
|
||||
onEdit: 'onWarehouseTransferEdit',
|
||||
onEdited: 'onWarehouseTransferEdited',
|
||||
|
||||
onDelete: 'onWarehouseTransferDelete',
|
||||
onDeleted: 'onWarehouseTransferDeleted',
|
||||
|
||||
onInitiate: 'onWarehouseTransferInitiate',
|
||||
onInitiated: 'onWarehouseTransferInitated',
|
||||
|
||||
onTransfer: 'onWarehouseTransferInitiate',
|
||||
onTransferred: 'onWarehouseTransferTransferred',
|
||||
},
|
||||
|
||||
/**
|
||||
* Branches.
|
||||
*/
|
||||
branch: {
|
||||
onActivate: 'onBranchActivate',
|
||||
onActivated: 'onBranchActivated',
|
||||
|
||||
onMarkPrimary: 'onBranchMarkPrimary',
|
||||
onMarkedPrimary: 'onBranchMarkedPrimary',
|
||||
},
|
||||
|
||||
/**
|
||||
* Projects.
|
||||
*/
|
||||
project: {
|
||||
onCreate: 'onProjectCreate',
|
||||
onCreating: 'onProjectCreating',
|
||||
onCreated: 'onProjectCreated',
|
||||
|
||||
onEdit: 'onEditProject',
|
||||
onEditing: 'onEditingProject',
|
||||
onEdited: 'onEditedProject',
|
||||
|
||||
onEditStatus: 'onEditStatusProject',
|
||||
onEditingStatus: 'onEditingStatusProject',
|
||||
onEditedStatus: 'onEditedStatusProject',
|
||||
|
||||
onDelete: 'onDeleteProject',
|
||||
onDeleting: 'onDeletingProject',
|
||||
onDeleted: 'onDeletedProject',
|
||||
},
|
||||
|
||||
/**
|
||||
* Project Tasks.
|
||||
*/
|
||||
projectTask: {
|
||||
onCreate: 'onProjectTaskCreate',
|
||||
onCreating: 'onProjectTaskCreating',
|
||||
onCreated: 'onProjectTaskCreated',
|
||||
|
||||
onEdit: 'onProjectTaskEdit',
|
||||
onEditing: 'onProjectTaskEditing',
|
||||
onEdited: 'onProjectTaskEdited',
|
||||
|
||||
onDelete: 'onProjectTaskDelete',
|
||||
onDeleting: 'onProjectTaskDeleting',
|
||||
onDeleted: 'onProjectTaskDeleted',
|
||||
},
|
||||
|
||||
/**
|
||||
* Project Times.
|
||||
*/
|
||||
projectTime: {
|
||||
onCreate: 'onProjectTimeCreate',
|
||||
onCreating: 'onProjectTimeCreating',
|
||||
onCreated: 'onProjectTimeCreated',
|
||||
|
||||
onEdit: 'onProjectTimeEdit',
|
||||
onEditing: 'onProjectTimeEditing',
|
||||
onEdited: 'onProjectTimeEdited',
|
||||
|
||||
onDelete: 'onProjectTimeDelete',
|
||||
onDeleting: 'onProjectTimeDeleting',
|
||||
onDeleted: 'onProjectTimeDeleted',
|
||||
},
|
||||
|
||||
taxRates: {
|
||||
onCreating: 'onTaxRateCreating',
|
||||
onCreated: 'onTaxRateCreated',
|
||||
|
||||
onEditing: 'onTaxRateEditing',
|
||||
onEdited: 'onTaxRateEdited',
|
||||
|
||||
onDeleting: 'onTaxRateDeleting',
|
||||
onDeleted: 'onTaxRateDeleted',
|
||||
|
||||
onActivating: 'onTaxRateActivating',
|
||||
onActivated: 'onTaxRateActivated',
|
||||
|
||||
onInactivating: 'onTaxRateInactivating',
|
||||
onInactivated: 'onTaxRateInactivated',
|
||||
},
|
||||
|
||||
plaid: {
|
||||
onItemCreated: 'onPlaidItemCreated',
|
||||
onTransactionsSynced: 'onPlaidTransactionsSynced',
|
||||
},
|
||||
|
||||
// Bank rules.
|
||||
bankRules: {
|
||||
onCreating: 'onBankRuleCreating',
|
||||
onCreated: 'onBankRuleCreated',
|
||||
|
||||
onEditing: 'onBankRuleEditing',
|
||||
onEdited: 'onBankRuleEdited',
|
||||
|
||||
onDeleting: 'onBankRuleDeleting',
|
||||
onDeleted: 'onBankRuleDeleted',
|
||||
},
|
||||
|
||||
// Bank matching.
|
||||
bankMatch: {
|
||||
onMatching: 'onBankTransactionMatching',
|
||||
onMatched: 'onBankTransactionMatched',
|
||||
|
||||
onUnmatching: 'onBankTransactionUnmathcing',
|
||||
onUnmatched: 'onBankTransactionUnmathced',
|
||||
},
|
||||
|
||||
bankTransactions: {
|
||||
onExcluding: 'onBankTransactionExclude',
|
||||
onExcluded: 'onBankTransactionExcluded',
|
||||
|
||||
onUnexcluding: 'onBankTransactionUnexcluding',
|
||||
onUnexcluded: 'onBankTransactionUnexcluded',
|
||||
|
||||
onPendingRemoving: 'onBankTransactionPendingRemoving',
|
||||
onPendingRemoved: 'onBankTransactionPendingRemoved',
|
||||
},
|
||||
|
||||
bankAccount: {
|
||||
onDisconnecting: 'onBankAccountDisconnecting',
|
||||
onDisconnected: 'onBankAccountDisconnected',
|
||||
},
|
||||
|
||||
// Import files.
|
||||
import: {
|
||||
onImportCommitted: 'onImportFileCommitted',
|
||||
},
|
||||
|
||||
// Branding templates
|
||||
pdfTemplate: {
|
||||
onCreating: 'onPdfTemplateCreating',
|
||||
onCreated: 'onPdfTemplateCreated',
|
||||
|
||||
onEditing: 'onPdfTemplateEditing',
|
||||
onEdited: 'onPdfTemplatedEdited',
|
||||
|
||||
onDeleting: 'onPdfTemplateDeleting',
|
||||
onDeleted: 'onPdfTemplateDeleted',
|
||||
|
||||
onAssignedDefault: 'onPdfTemplateAssignedDefault',
|
||||
onAssigningDefault: 'onPdfTemplateAssigningDefault',
|
||||
},
|
||||
|
||||
// Payment method.
|
||||
paymentMethod: {
|
||||
onEditing: 'onPaymentMethodEditing',
|
||||
onEdited: 'onPaymentMethodEdited',
|
||||
|
||||
onDeleted: 'onPaymentMethodDeleted',
|
||||
},
|
||||
|
||||
// Payment methods integrations
|
||||
paymentIntegrationLink: {
|
||||
onPaymentIntegrationLink: 'onPaymentIntegrationLink',
|
||||
onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink',
|
||||
},
|
||||
|
||||
// Stripe Payment Integration
|
||||
stripeIntegration: {
|
||||
onAccountCreated: 'onStripeIntegrationAccountCreated',
|
||||
onAccountDeleted: 'onStripeIntegrationAccountDeleted',
|
||||
|
||||
onPaymentLinkCreated: 'onStripePaymentLinkCreated',
|
||||
onPaymentLinkInactivated: 'onStripePaymentLinkInactivated',
|
||||
|
||||
onOAuthCodeGranted: 'onStripeOAuthCodeGranted',
|
||||
},
|
||||
|
||||
// Stripe Payment Webhooks
|
||||
stripeWebhooks: {
|
||||
onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted',
|
||||
onAccountUpdated: 'onStripeAccountUpdated',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export class ModelEntityNotFound extends Error {
|
||||
constructor(entityId, message?) {
|
||||
message = message || `Entity with id ${entityId} does not exist`;
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
|
||||
@Catch(ServiceError)
|
||||
export class ServiceErrorFilter implements ExceptionFilter {
|
||||
catch(exception: ServiceError, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const status = exception.getStatus();
|
||||
|
||||
response.status(status).json({
|
||||
statusCode: status,
|
||||
errorType: exception.errorType,
|
||||
message: exception.message,
|
||||
payload: exception.payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
type ExecutionContext,
|
||||
Injectable,
|
||||
type NestInterceptor,
|
||||
type CallHandler,
|
||||
Optional,
|
||||
} from '@nestjs/common';
|
||||
import { type Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export function camelToSnake<T = any>(value: T) {
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(camelToSnake);
|
||||
}
|
||||
if (typeof value === 'object' && !(value instanceof Date)) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, value]) => [
|
||||
key
|
||||
.split(/(?=[A-Z])/)
|
||||
.join('_')
|
||||
.toLowerCase(),
|
||||
camelToSnake(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function snakeToCamel<T = any>(value: T) {
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(snakeToCamel);
|
||||
}
|
||||
|
||||
const impl = (str: string) => {
|
||||
const converted = str.replace(/([-_]\w)/g, (group) =>
|
||||
group[1].toUpperCase(),
|
||||
);
|
||||
return converted[0].toLowerCase() + converted.slice(1);
|
||||
};
|
||||
|
||||
if (typeof value === 'object' && !(value instanceof Date)) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, value]) => [
|
||||
impl(key),
|
||||
snakeToCamel(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const DEFAULT_STRATEGY = {
|
||||
in: snakeToCamel,
|
||||
out: camelToSnake,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SerializeInterceptor implements NestInterceptor<any, any> {
|
||||
constructor(@Optional() readonly strategy = DEFAULT_STRATEGY) {}
|
||||
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler<any>,
|
||||
): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
request.body = this.strategy.in(request.body);
|
||||
|
||||
// handle returns stream..
|
||||
return next.handle().pipe(map(this.strategy.out));
|
||||
}
|
||||
}
|
||||
20
packages/server-nest/src/common/pipes/ZodValidation.pipe.ts
Normal file
20
packages/server-nest/src/common/pipes/ZodValidation.pipe.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
PipeTransform,
|
||||
ArgumentMetadata,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { ZodSchema } from 'zod';
|
||||
|
||||
export class ZodValidationPipe implements PipeTransform {
|
||||
constructor(private schema: ZodSchema) {}
|
||||
|
||||
transform(value: unknown, metadata: ArgumentMetadata) {
|
||||
try {
|
||||
const parsedValue = this.schema.parse(value);
|
||||
return parsedValue;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new BadRequestException(error.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/server-nest/src/common/repository/CachableRepository.ts
Normal file
260
packages/server-nest/src/common/repository/CachableRepository.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
// import hashObject from 'object-hash';
|
||||
// import { EntityRepository } from './EntityRepository';
|
||||
|
||||
// export class CachableRepository extends EntityRepository {
|
||||
// repositoryName: string;
|
||||
// cache: any;
|
||||
// i18n: any;
|
||||
|
||||
// /**
|
||||
// * Constructor method.
|
||||
// * @param {Knex} knex
|
||||
// * @param {Cache} cache
|
||||
// */
|
||||
// constructor(knex, cache, i18n) {
|
||||
// super(knex);
|
||||
|
||||
// this.cache = cache;
|
||||
// this.i18n = i18n;
|
||||
// this.repositoryName = this.constructor.name;
|
||||
// }
|
||||
|
||||
// getByCache(key, callback) {
|
||||
// return callback();
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve the cache key of the method name and arguments.
|
||||
// * @param {string} method
|
||||
// * @param {...any} args
|
||||
// * @return {string}
|
||||
// */
|
||||
// getCacheKey(method, ...args) {
|
||||
// const hashArgs = hashObject({ ...args });
|
||||
// const repositoryName = this.repositoryName;
|
||||
|
||||
// return `${repositoryName}-${method}-${hashArgs}`;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve all entries with specified relations.
|
||||
// * @param withRelations
|
||||
// */
|
||||
// all(withRelations?, trx?) {
|
||||
// const cacheKey = this.getCacheKey('all', withRelations);
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.all(withRelations, trx);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds list of entities with specified attributes
|
||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
||||
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// find(attributeValues = {}, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey('find', attributeValues, withRelations);
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.find(attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds list of entities with attribute values that are different from specified ones
|
||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// findWhereNot(attributeValues = {}, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey(
|
||||
// 'findWhereNot',
|
||||
// attributeValues,
|
||||
// withRelations
|
||||
// );
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findWhereNot(attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds list of entities with specified attributes (any of multiple specified values)
|
||||
// * Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
||||
// *
|
||||
// * @param {string|Object} searchParam - attribute name or search criteria object
|
||||
// * @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// findWhereIn(searchParam, attributeValues, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey(
|
||||
// 'findWhereIn',
|
||||
// attributeValues,
|
||||
// withRelations
|
||||
// );
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findWhereIn(searchParam, attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds first entity by given parameters
|
||||
// *
|
||||
// * @param {Object} attributeValues - values to filter retrieved entities by
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {Promise<Object>}
|
||||
// */
|
||||
// findOne(attributeValues = {}, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey(
|
||||
// 'findOne',
|
||||
// attributeValues,
|
||||
// withRelations
|
||||
// );
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findOne(attributeValues, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Finds first entity by given parameters
|
||||
// *
|
||||
// * @param {string || number} id - value of id column of the entity
|
||||
// * @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
// * @returns {Promise<Object>}
|
||||
// */
|
||||
// findOneById(id, withRelations?) {
|
||||
// const cacheKey = this.getCacheKey('findOneById', id, withRelations);
|
||||
|
||||
// return this.getByCache(cacheKey, () => {
|
||||
// return super.findOneById(id, withRelations);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Persists new entity or an array of entities.
|
||||
// * This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
||||
// * Batch insert only works on PostgreSQL
|
||||
// * @param {Object} entity - model instance or parameters for a new entity
|
||||
// * @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
// */
|
||||
// async create(entity, trx?) {
|
||||
// const result = await super.create(entity, trx);
|
||||
|
||||
// // Flushes the repository cache after insert operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
||||
// *
|
||||
// * @param {Object} entity - single entity instance
|
||||
// * @param {Object} [trx] - knex transaction instance. If not specified, new implicit transaction will be used.
|
||||
// * @returns {Promise<integer>} number of affected rows
|
||||
// */
|
||||
// async update(entity, whereAttributes?, trx?) {
|
||||
// const result = await super.update(entity, whereAttributes, trx);
|
||||
|
||||
// // Flushes the repository cache after update operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * @param {Object} attributeValues - values to filter deleted entities by
|
||||
// * @param {Object} [trx]
|
||||
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
// */
|
||||
// async deleteBy(attributeValues, trx?) {
|
||||
// const result = await super.deleteBy(attributeValues, trx);
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * @param {string || number} id - value of id column of the entity
|
||||
// * @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
// */
|
||||
// deleteById(id: number | string, trx?) {
|
||||
// const result = super.deleteById(id, trx);
|
||||
|
||||
// // Flushes the repository cache after insert operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {string|number[]} values -
|
||||
// */
|
||||
// async deleteWhereIn(field: string, values: string | number[]) {
|
||||
// const result = await super.deleteWhereIn(field, values);
|
||||
|
||||
// // Flushes the repository cache after delete operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {string|number[]} values
|
||||
// */
|
||||
// async deleteWhereIdIn(values: string | number[], trx?) {
|
||||
// const result = await super.deleteWhereIdIn(values, trx);
|
||||
|
||||
// // Flushes the repository cache after delete operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param graph
|
||||
// * @param options
|
||||
// */
|
||||
// async upsertGraph(graph, options) {
|
||||
// const result = await super.upsertGraph(graph, options);
|
||||
|
||||
// // Flushes the repository cache after insert operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {} whereAttributes
|
||||
// * @param {string} field
|
||||
// * @param {number} amount
|
||||
// */
|
||||
// async changeNumber(whereAttributes, field: string, amount: number, trx?) {
|
||||
// const result = await super.changeNumber(
|
||||
// whereAttributes,
|
||||
// field,
|
||||
// amount,
|
||||
// trx
|
||||
// );
|
||||
|
||||
// // Flushes the repository cache after update operation.
|
||||
// this.flushCache();
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Flush repository cache.
|
||||
// */
|
||||
// flushCache(): void {
|
||||
// this.cache.delStartWith(this.repositoryName);
|
||||
// }
|
||||
// }
|
||||
232
packages/server-nest/src/common/repository/EntityRepository.ts
Normal file
232
packages/server-nest/src/common/repository/EntityRepository.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { cloneDeep, forOwn, isString } from 'lodash';
|
||||
import { ModelEntityNotFound } from '../exceptions/ModelEntityNotFound';
|
||||
import { Model } from 'objection';
|
||||
|
||||
function applyGraphFetched(withRelations, builder) {
|
||||
const relations = Array.isArray(withRelations)
|
||||
? withRelations
|
||||
: typeof withRelations === 'string'
|
||||
? withRelations.split(',').map((relation) => relation.trim())
|
||||
: [];
|
||||
|
||||
relations.forEach((relation) => {
|
||||
builder.withGraphFetched(relation);
|
||||
});
|
||||
}
|
||||
|
||||
export class EntityRepository {
|
||||
idColumn: string = 'id';
|
||||
knex: any;
|
||||
|
||||
/**
|
||||
* Retrieve the repository model binded it to knex instance.
|
||||
*/
|
||||
get model(): typeof Model {
|
||||
throw new Error("The repository's model is not defined.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all entries with specified relations.
|
||||
* @param withRelations
|
||||
*/
|
||||
all(withRelations?, trx?) {
|
||||
const builder = this.model.query(trx);
|
||||
applyGraphFetched(withRelations, builder);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds list of entities with specified attributes
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve.
|
||||
* @returns {Promise<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
find(attributeValues = {}, withRelations?) {
|
||||
const builder = this.model.query().where(attributeValues);
|
||||
|
||||
applyGraphFetched(withRelations, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds list of entities with attribute values that are different from specified ones
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
findWhereNot(attributeValues = {}, withRelations?) {
|
||||
const builder = this.model.query().whereNot(attributeValues);
|
||||
|
||||
applyGraphFetched(withRelations, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds list of entities with specified attributes (any of multiple specified values)
|
||||
* Supports both ('attrName', ['value1', 'value2]) and ({attrName: ['value1', 'value2']} formats)
|
||||
*
|
||||
* @param {string|Object} searchParam - attribute name or search criteria object
|
||||
* @param {*[]} [attributeValues] - attribute values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {PromiseLike<Object[]>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
findWhereIn(searchParam, attributeValues, withRelations?) {
|
||||
const commonBuilder = (builder) => {
|
||||
applyGraphFetched(withRelations, builder);
|
||||
};
|
||||
if (isString(searchParam)) {
|
||||
return this.model
|
||||
.query()
|
||||
.whereIn(searchParam, attributeValues)
|
||||
.onBuild(commonBuilder);
|
||||
} else {
|
||||
const builder = this.model.query(this.knex).onBuild(commonBuilder);
|
||||
|
||||
forOwn(searchParam, (value, key) => {
|
||||
if (Array.isArray(value)) {
|
||||
builder.whereIn(key, value);
|
||||
} else {
|
||||
builder.where(key, value);
|
||||
}
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first entity by given parameters
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter retrieved entities by
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async findOne(attributeValues = {}, withRelations?) {
|
||||
const results = await this.find(attributeValues, withRelations);
|
||||
return results[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first entity by given parameters
|
||||
*
|
||||
* @param {string || number} id - value of id column of the entity
|
||||
* @param {string || string[]} [withRelations] - name of relation(s) to eagerly retrieve, as defined in model relationMappings()
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
findOneById(id, withRelations?) {
|
||||
return this.findOne({ [this.idColumn]: id }, withRelations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists new entity or an array of entities.
|
||||
* This method does not recursively persist related entities, use createRecursively (to be implemented) for that.
|
||||
* Batch insert only works on PostgreSQL
|
||||
*
|
||||
* @param {Object} entity - model instance or parameters for a new entity
|
||||
* @returns {Promise<Object>} - query builder. You can chain additional methods to it or call "await" or then() on it to execute
|
||||
*/
|
||||
create(entity, trx?) {
|
||||
// Keep the input parameter immutable
|
||||
const instanceDTO = cloneDeep(entity);
|
||||
|
||||
return this.model.query(trx).insert(instanceDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists updated entity. If previously set fields are not present, performs an incremental update (does not remove fields unless explicitly set to null)
|
||||
*
|
||||
* @param {Object} entity - single entity instance
|
||||
* @returns {Promise<integer>} number of affected rows
|
||||
*/
|
||||
async update(entity, whereAttributes?, trx?) {
|
||||
const entityDto = cloneDeep(entity);
|
||||
const identityClause = {};
|
||||
|
||||
if (Array.isArray(this.idColumn)) {
|
||||
this.idColumn.forEach(
|
||||
(idColumn) => (identityClause[idColumn] = entityDto[idColumn]),
|
||||
);
|
||||
} else {
|
||||
identityClause[this.idColumn] = entityDto[this.idColumn];
|
||||
}
|
||||
const whereConditions = whereAttributes || identityClause;
|
||||
const modifiedEntitiesCount = await this.model
|
||||
.query(trx)
|
||||
.where(whereConditions)
|
||||
.update(entityDto);
|
||||
|
||||
if (modifiedEntitiesCount === 0) {
|
||||
throw new ModelEntityNotFound(entityDto[this.idColumn]);
|
||||
}
|
||||
return modifiedEntitiesCount;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} attributeValues - values to filter deleted entities by
|
||||
* @param {Object} [trx]
|
||||
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
*/
|
||||
deleteBy(attributeValues, trx?) {
|
||||
return this.model.query(trx).delete().where(attributeValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string || number} id - value of id column of the entity
|
||||
* @returns {Promise<integer>} Query builder. After promise is resolved, returns count of deleted rows
|
||||
*/
|
||||
deleteById(id: number | string, trx?) {
|
||||
return this.deleteBy(
|
||||
{
|
||||
[this.idColumn]: id,
|
||||
},
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given entries in the array on the specific field.
|
||||
* @param {string} field -
|
||||
* @param {number|string} values -
|
||||
*/
|
||||
deleteWhereIn(field: string, values: (string | number)[], trx) {
|
||||
return this.model.query(trx).whereIn(field, values).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string|number[]} values
|
||||
*/
|
||||
deleteWhereIdIn(values: (string | number)[], trx?) {
|
||||
return this.deleteWhereIn(this.idColumn, values, trx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary relation graphs can be upserted (insert + update + delete)
|
||||
* using the upsertGraph method.
|
||||
* @param graph
|
||||
* @param options
|
||||
*/
|
||||
upsertGraph(graph, options) {
|
||||
// Keep the input grpah immutable
|
||||
const graphCloned = cloneDeep(graph);
|
||||
return this.model.query().upsertGraph(graphCloned, options);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} whereAttributes
|
||||
* @param {string} field
|
||||
* @param amount
|
||||
*/
|
||||
changeNumber(whereAttributes, field: string, amount: number, trx) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
|
||||
return this.model
|
||||
.query(trx)
|
||||
.where(whereAttributes)
|
||||
[changeMethod](field, Math.abs(amount));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// import { CachableRepository } from './CachableRepository';
|
||||
import { EntityRepository } from './EntityRepository';
|
||||
|
||||
export class TenantRepository extends EntityRepository {
|
||||
|
||||
}
|
||||
4
packages/server-nest/src/common/types/Discount.ts
Normal file
4
packages/server-nest/src/common/types/Discount.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum DiscountType {
|
||||
Percentage = 'percentage',
|
||||
Amount = 'amount',
|
||||
}
|
||||
16
packages/server-nest/src/common/types/Features.ts
Normal file
16
packages/server-nest/src/common/types/Features.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export enum Features {
|
||||
WAREHOUSES = 'warehouses',
|
||||
BRANCHES = 'branches',
|
||||
BankSyncing = 'BankSyncing',
|
||||
}
|
||||
|
||||
export interface IFeatureAllItem {
|
||||
name: string;
|
||||
isAccessible: boolean;
|
||||
defaultAccessible: boolean;
|
||||
}
|
||||
|
||||
export interface IFeatureConfiugration {
|
||||
name: string;
|
||||
defaultValue?: boolean;
|
||||
}
|
||||
230
packages/server-nest/src/constants/accounts.ts
Normal file
230
packages/server-nest/src/constants/accounts.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
export const ACCOUNT_TYPE = {
|
||||
CASH: 'cash',
|
||||
BANK: 'bank',
|
||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||
INVENTORY: 'inventory',
|
||||
OTHER_CURRENT_ASSET: 'other-current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'none-current-asset',
|
||||
|
||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||
CREDIT_CARD: 'credit-card',
|
||||
TAX_PAYABLE: 'tax-payable',
|
||||
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
INCOME: 'income',
|
||||
OTHER_INCOME: 'other-income',
|
||||
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
||||
EXPENSE: 'expense',
|
||||
OTHER_EXPENSE: 'other-expense',
|
||||
};
|
||||
|
||||
export const ACCOUNT_PARENT_TYPE = {
|
||||
CURRENT_ASSET: 'current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'non-current-asset',
|
||||
|
||||
CURRENT_LIABILITY: 'current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expense',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_ROOT_TYPE = {
|
||||
ASSET: 'asset',
|
||||
LIABILITY: 'liability',
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expense',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_NORMAL = {
|
||||
CREDIT: 'credit',
|
||||
DEBIT: 'debit',
|
||||
};
|
||||
export const ACCOUNT_TYPES = [
|
||||
{
|
||||
label: 'Cash',
|
||||
key: ACCOUNT_TYPE.CASH,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Bank',
|
||||
key: ACCOUNT_TYPE.BANK,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Receivable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Inventory',
|
||||
key: ACCOUNT_TYPE.INVENTORY,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Asset',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Fixed Asset',
|
||||
key: ACCOUNT_TYPE.FIXED_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Asset',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Payable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Credit Card',
|
||||
key: ACCOUNT_TYPE.CREDIT_CARD,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Tax Payable',
|
||||
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Liability',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Long Term Liability',
|
||||
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Liability',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Equity',
|
||||
key: ACCOUNT_TYPE.EQUITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Income',
|
||||
key: ACCOUNT_TYPE.INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Income',
|
||||
key: ACCOUNT_TYPE.OTHER_INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Cost of Goods Sold',
|
||||
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Expense',
|
||||
key: ACCOUNT_TYPE.EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Expense',
|
||||
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const getAccountsSupportsMultiCurrency = () => {
|
||||
return ACCOUNT_TYPES.filter((account) => account.multiCurrency);
|
||||
};
|
||||
7
packages/server-nest/src/constants/data-types.ts
Normal file
7
packages/server-nest/src/constants/data-types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const DATATYPES_LENGTH = {
|
||||
STRING: 255,
|
||||
TEXT: 65535,
|
||||
INT_10: 4294967295,
|
||||
DECIMAL_13_3: 9999999999.999,
|
||||
DECIMAL_15_5: 999999999999.999,
|
||||
};
|
||||
241
packages/server-nest/src/constants/metable-options.ts
Normal file
241
packages/server-nest/src/constants/metable-options.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
// import { getTransactionsLockingSettingsSchema } from '@/api/controllers/TransactionsLocking/utils';
|
||||
|
||||
export const SettingsOptions = {
|
||||
organization: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
base_currency: {
|
||||
type: 'string',
|
||||
},
|
||||
industry: {
|
||||
type: 'string',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
},
|
||||
fiscal_year: {
|
||||
type: 'string',
|
||||
},
|
||||
financial_date_start: {
|
||||
type: 'string',
|
||||
},
|
||||
language: {
|
||||
type: 'string',
|
||||
},
|
||||
time_zone: {
|
||||
type: 'string',
|
||||
},
|
||||
date_format: {
|
||||
type: 'string',
|
||||
},
|
||||
accounting_basis: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
manual_journals: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
bill_payments: {
|
||||
withdrawal_account: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
sales_estimates: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
customer_notes: {
|
||||
type: 'string',
|
||||
},
|
||||
terms_conditions: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
sales_receipts: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
preferred_deposit_account: {
|
||||
type: 'number',
|
||||
},
|
||||
receipt_message: {
|
||||
type: 'string',
|
||||
},
|
||||
terms_conditions: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
sales_invoices: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
customer_notes: {
|
||||
type: 'string',
|
||||
},
|
||||
terms_conditions: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
payment_receives: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
preferred_deposit_account: {
|
||||
type: 'number',
|
||||
},
|
||||
preferred_advance_deposit: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
items: {
|
||||
preferred_sell_account: {
|
||||
type: 'number',
|
||||
},
|
||||
preferred_cost_account: {
|
||||
type: 'number',
|
||||
},
|
||||
preferred_inventory_account: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
expenses: {
|
||||
preferred_payment_account: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
inventory: {
|
||||
cost_compute_running: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
account_code_required: {
|
||||
type: 'boolean',
|
||||
},
|
||||
account_code_unique: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
cashflow: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
credit_note: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
customer_notes: {
|
||||
type: 'string',
|
||||
},
|
||||
terms_conditions: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
vendor_credit: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
warehouse_transfers: {
|
||||
next_number: {
|
||||
type: 'string',
|
||||
},
|
||||
number_prefix: {
|
||||
type: 'string',
|
||||
},
|
||||
auto_increment: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
'sms-notification': {
|
||||
'sms-notification-enable.sale-invoice-details': {
|
||||
type: 'boolean',
|
||||
},
|
||||
'sms-notification-enable.sale-invoice-reminder': {
|
||||
type: 'boolean',
|
||||
},
|
||||
'sms-notification-enable.sale-estimate-details': {
|
||||
type: 'boolean',
|
||||
},
|
||||
'sms-notification-enable.sale-receipt-details': {
|
||||
type: 'boolean',
|
||||
},
|
||||
'sms-notification-enable.payment-receive-details': {
|
||||
type: 'boolean',
|
||||
},
|
||||
'sms-notification-enable.customer-balance': {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
'transactions-locking': {
|
||||
'locking-type': {
|
||||
type: 'string',
|
||||
},
|
||||
// ...getTransactionsLockingSettingsSchema([
|
||||
// 'all',
|
||||
// 'sales',
|
||||
// 'purchases',
|
||||
// 'financial',
|
||||
// ]),
|
||||
},
|
||||
features: {
|
||||
'multi-warehouses': {
|
||||
type: 'boolean',
|
||||
},
|
||||
'multi-branches': {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
};
|
||||
21
packages/server-nest/src/i18n/en/test.json
Normal file
21
packages/server-nest/src/i18n/en/test.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"HELLO": "Hello",
|
||||
"PRODUCT": {
|
||||
"NEW": "New Product: {name}"
|
||||
},
|
||||
"ENGLISH": "English",
|
||||
"ARRAY": ["ONE", "TWO", "THREE"],
|
||||
"cat": "Cat",
|
||||
"cat_name": "Cat: {name}",
|
||||
"set-up-password": {
|
||||
"heading": "Hello, {username}",
|
||||
"title": "Forgot password",
|
||||
"followLink": "Please follow the link to set up your password"
|
||||
},
|
||||
"day_interval": {
|
||||
"one": "Every day",
|
||||
"other": "Every {count} days",
|
||||
"zero": "Never"
|
||||
},
|
||||
"nested": "We go shopping: $t(test.day_interval, {{\"count\": {count} }})"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ExcludeNullInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(map((value) => (value === null ? '' : value)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalPrefixInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
request.url = `/api${request.url}`;
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
return data;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
21
packages/server-nest/src/interceptors/user-ip.interceptor.ts
Normal file
21
packages/server-nest/src/interceptors/user-ip.interceptor.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class UserIpInterceptor implements NestInterceptor {
|
||||
constructor(private readonly cls: ClsService) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const userIp = request.connection.remoteAddress;
|
||||
this.cls.set('ip', userIp);
|
||||
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
174
packages/server-nest/src/interfaces/Account.ts
Normal file
174
packages/server-nest/src/interfaces/Account.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export interface IAccountDTO {
|
||||
name: string;
|
||||
code: string;
|
||||
description: string;
|
||||
accountType: string;
|
||||
parentAccountId?: number;
|
||||
active: boolean;
|
||||
bankBalance?: number;
|
||||
accountMask?: string;
|
||||
}
|
||||
|
||||
export interface IAccountCreateDTO extends IAccountDTO {
|
||||
currencyCode?: string;
|
||||
plaidAccountId?: string;
|
||||
plaidItemId?: string;
|
||||
}
|
||||
|
||||
export interface IAccountEditDTO extends IAccountDTO {}
|
||||
|
||||
export interface IAccount {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
code: string;
|
||||
index: number;
|
||||
description: string;
|
||||
accountType: string;
|
||||
parentAccountId: number;
|
||||
active: boolean;
|
||||
predefined: boolean;
|
||||
amount: number;
|
||||
currencyCode: string;
|
||||
transactions?: any[];
|
||||
type?: any[];
|
||||
accountNormal: string;
|
||||
accountParentType: string;
|
||||
bankBalance: string;
|
||||
plaidItemId: number | null;
|
||||
lastFeedsUpdatedAt: Date;
|
||||
}
|
||||
|
||||
export enum AccountNormal {
|
||||
DEBIT = 'debit',
|
||||
CREDIT = 'credit',
|
||||
}
|
||||
|
||||
export interface IAccountsTransactionsFilter {
|
||||
accountId?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface IAccountTransaction {
|
||||
id?: number;
|
||||
|
||||
credit: number;
|
||||
debit: number;
|
||||
currencyCode: string;
|
||||
exchangeRate: number;
|
||||
|
||||
accountId: number;
|
||||
contactId?: number | null;
|
||||
date: string | Date;
|
||||
|
||||
referenceType: string;
|
||||
referenceTypeFormatted: string;
|
||||
referenceId: number;
|
||||
|
||||
referenceNumber?: string;
|
||||
|
||||
transactionNumber?: string;
|
||||
transactionType?: string;
|
||||
|
||||
note?: string;
|
||||
|
||||
index: number;
|
||||
indexGroup?: number;
|
||||
|
||||
costable?: boolean;
|
||||
|
||||
userId?: number;
|
||||
itemId?: number;
|
||||
branchId?: number;
|
||||
projectId?: number;
|
||||
|
||||
account?: IAccount;
|
||||
|
||||
taxRateId?: number;
|
||||
taxRate?: number;
|
||||
}
|
||||
export interface IAccountResponse extends IAccount {}
|
||||
|
||||
export enum IAccountsStructureType {
|
||||
Tree = 'tree',
|
||||
Flat = 'flat',
|
||||
}
|
||||
|
||||
export interface IAccountsFilter {
|
||||
stringifiedFilterRoles?: string;
|
||||
onlyInactive: boolean;
|
||||
structure?: IAccountsStructureType;
|
||||
}
|
||||
|
||||
export interface IAccountType {
|
||||
label: string;
|
||||
key: string;
|
||||
normal: string;
|
||||
rootType: string;
|
||||
childType: string;
|
||||
balanceSheet: boolean;
|
||||
incomeSheet: boolean;
|
||||
}
|
||||
|
||||
export interface IAccountsTypesService {
|
||||
getAccountsTypes(tenantId: number): Promise<IAccountType>;
|
||||
}
|
||||
|
||||
export interface IAccountEventCreatingPayload {
|
||||
tenantId: number;
|
||||
accountDTO: any;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IAccountEventCreatedPayload {
|
||||
tenantId: number;
|
||||
account: IAccount;
|
||||
accountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventEditedPayload {
|
||||
tenantId: number;
|
||||
account: IAccount;
|
||||
oldAccount: IAccount;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventDeletedPayload {
|
||||
tenantId: number;
|
||||
accountId: number;
|
||||
oldAccount: IAccount;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventDeletePayload {
|
||||
trx: Knex.Transaction;
|
||||
oldAccount: IAccount;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export interface IAccountEventActivatedPayload {
|
||||
tenantId: number;
|
||||
accountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum AccountAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
TransactionsLocking = 'TransactionsLocking',
|
||||
}
|
||||
|
||||
export enum TaxRateAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
}
|
||||
|
||||
export interface CreateAccountParams {
|
||||
ignoreUniqueName: boolean;
|
||||
}
|
||||
165
packages/server-nest/src/interfaces/Item.ts
Normal file
165
packages/server-nest/src/interfaces/Item.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Knex } from 'knex';
|
||||
import { Item } from '@/modules/Items/models/Item';
|
||||
// import { AbilitySubject } from '@/interfaces';
|
||||
// import { IFilterRole } from '@/interfaces/DynamicFilter';
|
||||
|
||||
export interface IItem {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
code: string;
|
||||
|
||||
sellable: boolean;
|
||||
purchasable: boolean;
|
||||
|
||||
costPrice: number;
|
||||
sellPrice: number;
|
||||
currencyCode: string;
|
||||
|
||||
costAccountId: number;
|
||||
sellAccountId: number;
|
||||
inventoryAccountId: number;
|
||||
|
||||
sellDescription: string;
|
||||
purchaseDescription: string;
|
||||
|
||||
sellTaxRateId: number;
|
||||
purchaseTaxRateId: number;
|
||||
|
||||
quantityOnHand: number;
|
||||
|
||||
note: string;
|
||||
active: boolean;
|
||||
|
||||
categoryId: number;
|
||||
userId: number;
|
||||
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export class IItemDTO {
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
|
||||
@ApiProperty()
|
||||
code: string;
|
||||
|
||||
@ApiProperty()
|
||||
sellable: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
purchasable: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
costPrice: number;
|
||||
|
||||
@ApiProperty()
|
||||
sellPrice: number;
|
||||
|
||||
@ApiProperty()
|
||||
currencyCode: string;
|
||||
|
||||
@ApiProperty()
|
||||
costAccountId: number;
|
||||
|
||||
@ApiProperty()
|
||||
sellAccountId: number;
|
||||
|
||||
@ApiProperty()
|
||||
inventoryAccountId: number;
|
||||
|
||||
@ApiProperty()
|
||||
sellDescription: string;
|
||||
|
||||
@ApiProperty()
|
||||
purchaseDescription: string;
|
||||
|
||||
@ApiProperty()
|
||||
sellTaxRateId: number;
|
||||
|
||||
@ApiProperty()
|
||||
purchaseTaxRateId: number;
|
||||
|
||||
@ApiProperty()
|
||||
quantityOnHand: number;
|
||||
|
||||
@ApiProperty()
|
||||
note: string;
|
||||
|
||||
@ApiProperty()
|
||||
active: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
categoryId: number;
|
||||
}
|
||||
|
||||
export interface IItemCreateDTO extends IItemDTO {}
|
||||
export interface IItemEditDTO extends IItemDTO {}
|
||||
|
||||
// export interface IItemsService {
|
||||
// getItem(tenantId: number, itemId: number): Promise<IItem>;
|
||||
// deleteItem(tenantId: number, itemId: number): Promise<void>;
|
||||
// editItem(tenantId: number, itemId: number, itemDTO: IItemDTO): Promise<IItem>;
|
||||
// newItem(tenantId: number, itemDTO: IItemDTO): Promise<IItem>;
|
||||
// itemsList(
|
||||
// tenantId: number,
|
||||
// itemsFilter: IItemsFilter,
|
||||
// ): Promise<{ items: IItem[] }>;
|
||||
// }
|
||||
|
||||
// export interface IItemsFilter extends IDynamicListFilterDTO {
|
||||
// stringifiedFilterRoles?: string;
|
||||
// page: number;
|
||||
// pageSize: number;
|
||||
// inactiveMode: boolean;
|
||||
// viewSlug?: string;
|
||||
// }
|
||||
|
||||
// export interface IItemsAutoCompleteFilter {
|
||||
// limit: number;
|
||||
// keyword: string;
|
||||
// filterRoles?: IFilterRole[];
|
||||
// columnSortBy: string;
|
||||
// sortOrder: string;
|
||||
// }
|
||||
|
||||
export interface IItemEventCreatedPayload {
|
||||
// tenantId: number;
|
||||
item: Item;
|
||||
itemId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IItemEventEditedPayload {
|
||||
item: Item;
|
||||
oldItem: Item;
|
||||
itemId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IItemEventDeletingPayload {
|
||||
// tenantId: number;
|
||||
trx: Knex.Transaction;
|
||||
oldItem: Item;
|
||||
}
|
||||
|
||||
export interface IItemEventDeletedPayload {
|
||||
// tenantId: number;
|
||||
itemId: number;
|
||||
oldItem: Item;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum ItemAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
}
|
||||
|
||||
// export type ItemAbility = [ItemAction, AbilitySubject.Item];
|
||||
193
packages/server-nest/src/interfaces/Model.ts
Normal file
193
packages/server-nest/src/interfaces/Model.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
export interface IModel {
|
||||
name: string;
|
||||
tableName: string;
|
||||
fields: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface IFilterMeta {
|
||||
sortOrder: string;
|
||||
sortBy: string;
|
||||
}
|
||||
|
||||
export interface IPaginationMeta {
|
||||
pageSize: number;
|
||||
page: number;
|
||||
}
|
||||
|
||||
export interface IModelMetaDefaultSort {
|
||||
sortOrder: ISortOrder;
|
||||
sortField: string;
|
||||
}
|
||||
|
||||
export type IModelColumnType =
|
||||
| 'text'
|
||||
| 'number'
|
||||
| 'enumeration'
|
||||
| 'boolean'
|
||||
| 'relation';
|
||||
|
||||
export type ISortOrder = 'DESC' | 'ASC';
|
||||
|
||||
export interface IModelMetaFieldCommon {
|
||||
name: string;
|
||||
column: string;
|
||||
columnable?: boolean;
|
||||
customQuery?: Function;
|
||||
required?: boolean;
|
||||
importHint?: string;
|
||||
importableRelationLabel?: string;
|
||||
order?: number;
|
||||
unique?: number;
|
||||
dataTransferObjectKey?: string;
|
||||
}
|
||||
|
||||
export interface IModelMetaFieldText {
|
||||
fieldType: 'text';
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
}
|
||||
export interface IModelMetaFieldBoolean {
|
||||
fieldType: 'boolean';
|
||||
}
|
||||
export interface IModelMetaFieldNumber {
|
||||
fieldType: 'number';
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
export interface IModelMetaFieldDate {
|
||||
fieldType: 'date';
|
||||
}
|
||||
export interface IModelMetaFieldUrl {
|
||||
fieldType: 'url';
|
||||
}
|
||||
export type IModelMetaField = IModelMetaFieldCommon &
|
||||
(
|
||||
| IModelMetaFieldText
|
||||
| IModelMetaFieldNumber
|
||||
| IModelMetaFieldBoolean
|
||||
| IModelMetaFieldDate
|
||||
| IModelMetaFieldUrl
|
||||
| IModelMetaEnumerationField
|
||||
| IModelMetaRelationField
|
||||
| IModelMetaCollectionField
|
||||
);
|
||||
|
||||
export interface IModelMetaEnumerationOption {
|
||||
key: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface IModelMetaEnumerationField {
|
||||
fieldType: 'enumeration';
|
||||
options: IModelMetaEnumerationOption[];
|
||||
}
|
||||
|
||||
export interface IModelMetaRelationFieldCommon {
|
||||
fieldType: 'relation';
|
||||
}
|
||||
|
||||
export interface IModelMetaRelationEnumerationField {
|
||||
relationType: 'enumeration';
|
||||
relationKey: string;
|
||||
relationEntityLabel: string;
|
||||
relationEntityKey: string;
|
||||
}
|
||||
|
||||
export interface IModelMetaFieldWithFields {
|
||||
fields: IModelMetaFieldCommon2 &
|
||||
(
|
||||
| IModelMetaFieldText
|
||||
| IModelMetaFieldNumber
|
||||
| IModelMetaFieldBoolean
|
||||
| IModelMetaFieldDate
|
||||
| IModelMetaFieldUrl
|
||||
| IModelMetaEnumerationField
|
||||
| IModelMetaRelationField
|
||||
);
|
||||
}
|
||||
|
||||
interface IModelMetaCollectionObjectField extends IModelMetaFieldWithFields {
|
||||
collectionOf: 'object';
|
||||
}
|
||||
|
||||
export interface IModelMetaCollectionFieldCommon {
|
||||
fieldType: 'collection';
|
||||
collectionMinLength?: number;
|
||||
collectionMaxLength?: number;
|
||||
}
|
||||
|
||||
export type IModelMetaCollectionField = IModelMetaCollectionFieldCommon &
|
||||
IModelMetaCollectionObjectField;
|
||||
|
||||
export type IModelMetaRelationField = IModelMetaRelationFieldCommon &
|
||||
IModelMetaRelationEnumerationField;
|
||||
|
||||
interface IModelPrintMeta {
|
||||
pageTitle: string;
|
||||
}
|
||||
|
||||
export interface IModelMeta {
|
||||
defaultFilterField: string;
|
||||
defaultSort: IModelMetaDefaultSort;
|
||||
|
||||
exportable?: boolean;
|
||||
exportFlattenOn?: string;
|
||||
|
||||
importable?: boolean;
|
||||
importAggregator?: string;
|
||||
importAggregateOn?: string;
|
||||
importAggregateBy?: string;
|
||||
|
||||
print?: IModelPrintMeta;
|
||||
|
||||
fields: Record<string, IModelMetaField>;
|
||||
fields2: Record<string, IModelMetaField2>;
|
||||
columns: Record<string, IModelMetaColumn>;
|
||||
}
|
||||
|
||||
// ----
|
||||
export interface IModelMetaFieldCommon2 {
|
||||
name: string;
|
||||
required?: boolean;
|
||||
importHint?: string;
|
||||
order?: number;
|
||||
unique?: number;
|
||||
features?: Array<any>;
|
||||
}
|
||||
|
||||
export interface IModelMetaRelationField2 {
|
||||
fieldType: 'relation';
|
||||
relationModel: string;
|
||||
importableRelationLabel: string | string[];
|
||||
}
|
||||
|
||||
export type IModelMetaField2 = IModelMetaFieldCommon2 &
|
||||
(
|
||||
| IModelMetaFieldText
|
||||
| IModelMetaFieldNumber
|
||||
| IModelMetaFieldBoolean
|
||||
| IModelMetaFieldDate
|
||||
| IModelMetaFieldUrl
|
||||
| IModelMetaEnumerationField
|
||||
| IModelMetaRelationField2
|
||||
| IModelMetaCollectionField
|
||||
);
|
||||
|
||||
export interface ImodelMetaColumnMeta {
|
||||
name: string;
|
||||
accessor?: string;
|
||||
exportable?: boolean;
|
||||
}
|
||||
|
||||
interface IModelMetaColumnText {
|
||||
type: 'text;';
|
||||
}
|
||||
|
||||
interface IModelMetaColumnCollection {
|
||||
type: 'collection';
|
||||
collectionOf: 'object';
|
||||
columns: { [key: string]: ImodelMetaColumnMeta & IModelMetaColumnText };
|
||||
}
|
||||
|
||||
export type IModelMetaColumn = ImodelMetaColumnMeta &
|
||||
(IModelMetaColumnText | IModelMetaColumnCollection);
|
||||
8
packages/server-nest/src/interfaces/SubscriptionPlan.ts
Normal file
8
packages/server-nest/src/interfaces/SubscriptionPlan.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface SubscriptionPayload {
|
||||
lemonSqueezyId?: string;
|
||||
}
|
||||
|
||||
export enum SubscriptionPaymentStatus {
|
||||
Succeed = 'succeed',
|
||||
Failed = 'failed',
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { get } from 'lodash';
|
||||
import { ACCOUNT_TYPES } from '@/constants/accounts';
|
||||
|
||||
export class AccountTypesUtils {
|
||||
/**
|
||||
* Retrieve account types list.
|
||||
*/
|
||||
static getList() {
|
||||
return ACCOUNT_TYPES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the given root type.
|
||||
* @param {string} rootType -
|
||||
* @return {string}
|
||||
*/
|
||||
static getTypesByRootType(rootType: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.rootType === rootType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type by the given account type key.
|
||||
* @param {string} key
|
||||
* @param {string} accessor
|
||||
*/
|
||||
static getType(key: string, accessor?: string) {
|
||||
const type = ACCOUNT_TYPES.find((type) => type.key === key);
|
||||
|
||||
if (accessor) {
|
||||
return get(type, accessor);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the parent account type.
|
||||
* @param {string} parentType
|
||||
*/
|
||||
static getTypesByParentType(parentType: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.parentType === parentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the given account normal.
|
||||
* @param {string} normal
|
||||
*/
|
||||
static getTypesByNormal(normal: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.normal === normal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the root type equals the account type.
|
||||
* @param {string} key
|
||||
* @param {string} rootType
|
||||
*/
|
||||
static isRootTypeEqualsKey(key: string, rootType: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
const isRootType = type.rootType === rootType;
|
||||
|
||||
return isType && isRootType;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the parent account type equals the account type key.
|
||||
* @param {string} key - Account type key.
|
||||
* @param {string} parentType - Account parent type.
|
||||
*/
|
||||
static isParentTypeEqualsKey(key: string, parentType: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
const isParentType = type.parentType === parentType;
|
||||
|
||||
return isType && isParentType;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether account type has balance sheet.
|
||||
* @param {string} key - Account type key.
|
||||
*
|
||||
*/
|
||||
static isTypeBalanceSheet(key: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
return isType && type.balanceSheet;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether account type has profit/loss sheet.
|
||||
* @param {string} key - Account type key.
|
||||
*/
|
||||
static isTypePLSheet(key: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
return isType && type.incomeSheet;
|
||||
});
|
||||
}
|
||||
}
|
||||
26
packages/server-nest/src/libs/chromiumly/Chromiumly.ts
Normal file
26
packages/server-nest/src/libs/chromiumly/Chromiumly.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ChromiumRoute, LibreOfficeRoute, PdfEngineRoute } from './_types';
|
||||
|
||||
export class Chromiumly {
|
||||
public static readonly GOTENBERG_ENDPOINT = process.env.GOTENBERG_URL || '';
|
||||
|
||||
public static readonly CHROMIUM_PATH = 'forms/chromium/convert';
|
||||
public static readonly PDF_ENGINES_PATH = 'forms/pdfengines';
|
||||
public static readonly LIBRE_OFFICE_PATH = 'forms/libreoffice';
|
||||
|
||||
public static readonly GOTENBERG_DOCS_ENDPOINT =
|
||||
process.env.GOTENBERG_DOCS_URL || '';
|
||||
|
||||
public static readonly CHROMIUM_ROUTES = {
|
||||
url: ChromiumRoute.URL,
|
||||
html: ChromiumRoute.HTML,
|
||||
markdown: ChromiumRoute.MARKDOWN,
|
||||
};
|
||||
|
||||
public static readonly PDF_ENGINE_ROUTES = {
|
||||
merge: PdfEngineRoute.MERGE,
|
||||
};
|
||||
|
||||
public static readonly LIBRE_OFFICE_ROUTES = {
|
||||
convert: LibreOfficeRoute.CONVERT,
|
||||
};
|
||||
}
|
||||
66
packages/server-nest/src/libs/chromiumly/ConvertUtils.ts
Normal file
66
packages/server-nest/src/libs/chromiumly/ConvertUtils.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import FormData from 'form-data';
|
||||
import { GotenbergUtils } from './GotenbergUtils';
|
||||
import { PageProperties } from './_types';
|
||||
|
||||
export class ConverterUtils {
|
||||
public static injectPageProperties(
|
||||
data: FormData,
|
||||
pageProperties: PageProperties
|
||||
): void {
|
||||
if (pageProperties.size) {
|
||||
GotenbergUtils.assert(
|
||||
pageProperties.size.width >= 1.0 && pageProperties.size.height >= 1.5,
|
||||
'size is smaller than the minimum printing requirements (i.e. 1.0 x 1.5 in)'
|
||||
);
|
||||
|
||||
data.append('paperWidth', pageProperties.size.width);
|
||||
data.append('paperHeight', pageProperties.size.height);
|
||||
}
|
||||
if (pageProperties.margins) {
|
||||
GotenbergUtils.assert(
|
||||
pageProperties.margins.top >= 0 &&
|
||||
pageProperties.margins.bottom >= 0 &&
|
||||
pageProperties.margins.left >= 0 &&
|
||||
pageProperties.margins.left >= 0,
|
||||
'negative margins are not allowed'
|
||||
);
|
||||
data.append('marginTop', pageProperties.margins.top);
|
||||
data.append('marginBottom', pageProperties.margins.bottom);
|
||||
data.append('marginLeft', pageProperties.margins.left);
|
||||
data.append('marginRight', pageProperties.margins.right);
|
||||
}
|
||||
if (pageProperties.preferCssPageSize) {
|
||||
data.append(
|
||||
'preferCssPageSize',
|
||||
String(pageProperties.preferCssPageSize)
|
||||
);
|
||||
}
|
||||
if (pageProperties.printBackground) {
|
||||
data.append('printBackground', String(pageProperties.printBackground));
|
||||
}
|
||||
if (pageProperties.landscape) {
|
||||
data.append('landscape', String(pageProperties.landscape));
|
||||
}
|
||||
if (pageProperties.scale) {
|
||||
GotenbergUtils.assert(
|
||||
pageProperties.scale >= 0.1 && pageProperties.scale <= 2.0,
|
||||
'scale is outside of [0.1 - 2] range'
|
||||
);
|
||||
data.append('scale', pageProperties.scale);
|
||||
}
|
||||
|
||||
if (pageProperties.nativePageRanges) {
|
||||
GotenbergUtils.assert(
|
||||
pageProperties.nativePageRanges.from > 0 &&
|
||||
pageProperties.nativePageRanges.to > 0 &&
|
||||
pageProperties.nativePageRanges.to >=
|
||||
pageProperties.nativePageRanges.from,
|
||||
'page ranges syntax error'
|
||||
);
|
||||
data.append(
|
||||
'nativePageRanges',
|
||||
`${pageProperties.nativePageRanges.from}-${pageProperties.nativePageRanges.to}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
packages/server-nest/src/libs/chromiumly/Converter.ts
Normal file
10
packages/server-nest/src/libs/chromiumly/Converter.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Chromiumly } from './Chromiumly';
|
||||
import { ChromiumRoute } from './_types';
|
||||
|
||||
export abstract class Converter {
|
||||
readonly endpoint: string;
|
||||
|
||||
constructor(route: ChromiumRoute) {
|
||||
this.endpoint = `${Chromiumly.GOTENBERG_ENDPOINT}/${Chromiumly.CHROMIUM_PATH}/${Chromiumly.CHROMIUM_ROUTES[route]}`;
|
||||
}
|
||||
}
|
||||
24
packages/server-nest/src/libs/chromiumly/GotenbergUtils.ts
Normal file
24
packages/server-nest/src/libs/chromiumly/GotenbergUtils.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import FormData from 'form-data';
|
||||
import Axios from 'axios';
|
||||
|
||||
export class GotenbergUtils {
|
||||
public static assert(condition: boolean, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static async fetch(endpoint: string, data: FormData): Promise<Buffer> {
|
||||
try {
|
||||
const response = await Axios.post(endpoint, data, {
|
||||
headers: {
|
||||
...data.getHeaders(),
|
||||
},
|
||||
responseType: 'arraybuffer', // This ensures you get a Buffer bac
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
packages/server-nest/src/libs/chromiumly/HTMLConvert.ts
Normal file
38
packages/server-nest/src/libs/chromiumly/HTMLConvert.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { constants, createReadStream, PathLike, promises } from 'fs';
|
||||
import FormData from 'form-data';
|
||||
import { GotenbergUtils } from './GotenbergUtils';
|
||||
import { IConverter, PageProperties } from './_types';
|
||||
import { PdfFormat, ChromiumRoute } from './_types';
|
||||
import { ConverterUtils } from './ConvertUtils';
|
||||
import { Converter } from './Converter';
|
||||
|
||||
export class HtmlConverter extends Converter implements IConverter {
|
||||
constructor() {
|
||||
super(ChromiumRoute.HTML);
|
||||
}
|
||||
|
||||
async convert({
|
||||
html,
|
||||
properties,
|
||||
pdfFormat,
|
||||
}: {
|
||||
html: PathLike;
|
||||
properties?: PageProperties;
|
||||
pdfFormat?: PdfFormat;
|
||||
}): Promise<Buffer> {
|
||||
try {
|
||||
await promises.access(html, constants.R_OK);
|
||||
const data = new FormData();
|
||||
if (pdfFormat) {
|
||||
data.append('pdfFormat', pdfFormat);
|
||||
}
|
||||
data.append('index.html', createReadStream(html));
|
||||
if (properties) {
|
||||
ConverterUtils.injectPageProperties(data, properties);
|
||||
}
|
||||
return GotenbergUtils.fetch(this.endpoint, data);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
packages/server-nest/src/libs/chromiumly/UrlConvert.ts
Normal file
38
packages/server-nest/src/libs/chromiumly/UrlConvert.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import FormData from 'form-data';
|
||||
import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types';
|
||||
import { ConverterUtils } from './ConvertUtils';
|
||||
import { Converter } from './Converter';
|
||||
import { GotenbergUtils } from './GotenbergUtils';
|
||||
|
||||
export class UrlConverter extends Converter implements IConverter {
|
||||
constructor() {
|
||||
super(ChromiumRoute.URL);
|
||||
}
|
||||
|
||||
async convert({
|
||||
url,
|
||||
properties,
|
||||
pdfFormat,
|
||||
}: {
|
||||
url: string;
|
||||
properties?: PageProperties;
|
||||
pdfFormat?: PdfFormat;
|
||||
}): Promise<Buffer> {
|
||||
try {
|
||||
const _url = new URL(url);
|
||||
const data = new FormData();
|
||||
|
||||
if (pdfFormat) {
|
||||
data.append('pdfFormat', pdfFormat);
|
||||
}
|
||||
data.append('url', _url.href);
|
||||
|
||||
if (properties) {
|
||||
ConverterUtils.injectPageProperties(data, properties);
|
||||
}
|
||||
return GotenbergUtils.fetch(this.endpoint, data);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
packages/server-nest/src/libs/chromiumly/_types.ts
Normal file
51
packages/server-nest/src/libs/chromiumly/_types.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { PathLike } from 'fs';
|
||||
|
||||
export type PageSize = {
|
||||
width: number; // Paper width, in inches (default 8.5)
|
||||
height: number; //Paper height, in inches (default 11)
|
||||
};
|
||||
|
||||
export type PageMargins = {
|
||||
top: number; // Top margin, in inches (default 0.39)
|
||||
bottom: number; // Bottom margin, in inches (default 0.39)
|
||||
left: number; // Left margin, in inches (default 0.39)
|
||||
right: number; // Right margin, in inches (default 0.39)
|
||||
};
|
||||
|
||||
export type PageProperties = {
|
||||
size?: PageSize;
|
||||
margins?: PageMargins;
|
||||
preferCssPageSize?: boolean; // Define whether to prefer page size as defined by CSS (default false)
|
||||
printBackground?: boolean; // Print the background graphics (default false)
|
||||
landscape?: boolean; // Set the paper orientation to landscape (default false)
|
||||
scale?: number; // The scale of the page rendering (default 1.0)
|
||||
nativePageRanges?: { from: number; to: number }; // Page ranges to print
|
||||
};
|
||||
|
||||
export interface IConverter {
|
||||
convert({
|
||||
...args
|
||||
}: {
|
||||
[x: string]: string | PathLike | PageProperties | PdfFormat;
|
||||
}): Promise<Buffer>;
|
||||
}
|
||||
|
||||
export enum PdfFormat {
|
||||
A_1a = 'PDF/A-1a',
|
||||
A_2b = 'PDF/A-2b',
|
||||
A_3b = 'PDF/A-3b',
|
||||
}
|
||||
|
||||
export enum ChromiumRoute {
|
||||
URL = 'url',
|
||||
HTML = 'html',
|
||||
MARKDOWN = 'markdown',
|
||||
}
|
||||
|
||||
export enum PdfEngineRoute {
|
||||
MERGE = 'merge',
|
||||
}
|
||||
|
||||
export enum LibreOfficeRoute {
|
||||
CONVERT = 'convert',
|
||||
}
|
||||
350
packages/server-nest/src/libs/dependency-graph/index.ts
Normal file
350
packages/server-nest/src/libs/dependency-graph/index.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* A simple dependency graph
|
||||
*/
|
||||
/**
|
||||
* Helper for creating a Topological Sort using Depth-First-Search on a set of edges.
|
||||
*
|
||||
* Detects cycles and throws an Error if one is detected (unless the "circular"
|
||||
* parameter is "true" in which case it ignores them).
|
||||
*
|
||||
* @param edges The set of edges to DFS through
|
||||
* @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges)
|
||||
* @param result An array in which the results will be populated
|
||||
* @param circular A boolean to allow circular dependencies
|
||||
*/
|
||||
function createDFS(edges, leavesOnly, result, circular) {
|
||||
var visited = {};
|
||||
return function (start) {
|
||||
if (visited[start]) {
|
||||
return;
|
||||
}
|
||||
var inCurrentPath = {};
|
||||
var currentPath = [];
|
||||
var todo = []; // used as a stack
|
||||
todo.push({ node: start, processed: false });
|
||||
while (todo.length > 0) {
|
||||
var current = todo[todo.length - 1]; // peek at the todo stack
|
||||
var processed = current.processed;
|
||||
var node = current.node;
|
||||
if (!processed) {
|
||||
// Haven't visited edges yet (visiting phase)
|
||||
if (visited[node]) {
|
||||
todo.pop();
|
||||
continue;
|
||||
} else if (inCurrentPath[node]) {
|
||||
// It's not a DAG
|
||||
if (circular) {
|
||||
todo.pop();
|
||||
// If we're tolerating cycles, don't revisit the node
|
||||
continue;
|
||||
}
|
||||
currentPath.push(node);
|
||||
throw new DepGraphCycleError(currentPath);
|
||||
}
|
||||
|
||||
inCurrentPath[node] = true;
|
||||
currentPath.push(node);
|
||||
var nodeEdges = edges[node];
|
||||
// (push edges onto the todo stack in reverse order to be order-compatible with the old DFS implementation)
|
||||
for (var i = nodeEdges.length - 1; i >= 0; i--) {
|
||||
todo.push({ node: nodeEdges[i], processed: false });
|
||||
}
|
||||
current.processed = true;
|
||||
} else {
|
||||
// Have visited edges (stack unrolling phase)
|
||||
todo.pop();
|
||||
currentPath.pop();
|
||||
inCurrentPath[node] = false;
|
||||
visited[node] = true;
|
||||
if (!leavesOnly || edges[node].length === 0) {
|
||||
result.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple Dependency Graph
|
||||
*/
|
||||
var DepGraph = (DepGraph = function DepGraph(opts) {
|
||||
this.nodes = {}; // Node -> Node/Data (treated like a Set)
|
||||
this.outgoingEdges = {}; // Node -> [Dependency Node]
|
||||
this.incomingEdges = {}; // Node -> [Dependant Node]
|
||||
this.circular = opts && !!opts.circular; // Allows circular deps
|
||||
});
|
||||
|
||||
DepGraph.fromArray = (
|
||||
items,
|
||||
options = { itemId: 'id', parentItemId: 'parent_id' }
|
||||
) => {
|
||||
const depGraph = new DepGraph();
|
||||
|
||||
items.forEach((item) => {
|
||||
depGraph.addNode(item[options.itemId], item);
|
||||
});
|
||||
items.forEach((item) => {
|
||||
if (item[options.parentItemId]) {
|
||||
depGraph.addDependency(item[options.parentItemId], item[options.itemId]);
|
||||
}
|
||||
});
|
||||
return depGraph;
|
||||
};
|
||||
|
||||
DepGraph.prototype = {
|
||||
/**
|
||||
* The number of nodes in the graph.
|
||||
*/
|
||||
size: function () {
|
||||
return Object.keys(this.nodes).length;
|
||||
},
|
||||
/**
|
||||
* Add a node to the dependency graph. If a node already exists, this method will do nothing.
|
||||
*/
|
||||
addNode: function (node, data) {
|
||||
if (!this.hasNode(node)) {
|
||||
// Checking the arguments length allows the user to add a node with undefined data
|
||||
if (arguments.length === 2) {
|
||||
this.nodes[node] = data;
|
||||
} else {
|
||||
this.nodes[node] = node;
|
||||
}
|
||||
this.outgoingEdges[node] = [];
|
||||
this.incomingEdges[node] = [];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Remove a node from the dependency graph. If a node does not exist, this method will do nothing.
|
||||
*/
|
||||
removeNode: function (node) {
|
||||
if (this.hasNode(node)) {
|
||||
delete this.nodes[node];
|
||||
delete this.outgoingEdges[node];
|
||||
delete this.incomingEdges[node];
|
||||
[this.incomingEdges, this.outgoingEdges].forEach(function (edgeList) {
|
||||
Object.keys(edgeList).forEach(function (key) {
|
||||
var idx = edgeList[key].indexOf(node);
|
||||
if (idx >= 0) {
|
||||
edgeList[key].splice(idx, 1);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Check if a node exists in the graph
|
||||
*/
|
||||
hasNode: function (node) {
|
||||
return this.nodes.hasOwnProperty(node);
|
||||
},
|
||||
/**
|
||||
* Get the data associated with a node name
|
||||
*/
|
||||
getNodeData: function (node) {
|
||||
if (this.hasNode(node)) {
|
||||
return this.nodes[node];
|
||||
} else {
|
||||
throw new Error('Node does not exist: ' + node);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the associated data for a given node name. If the node does not exist, this method will throw an error
|
||||
*/
|
||||
setNodeData: function (node, data) {
|
||||
if (this.hasNode(node)) {
|
||||
this.nodes[node] = data;
|
||||
} else {
|
||||
throw new Error('Node does not exist: ' + node);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a dependency between two nodes. If either of the nodes does not exist,
|
||||
* an Error will be thrown.
|
||||
*/
|
||||
addDependency: function (from, to) {
|
||||
if (!this.hasNode(from)) {
|
||||
throw new Error('Node does not exist: ' + from);
|
||||
}
|
||||
if (!this.hasNode(to)) {
|
||||
throw new Error('Node does not exist: ' + to);
|
||||
}
|
||||
if (this.outgoingEdges[from].indexOf(to) === -1) {
|
||||
this.outgoingEdges[from].push(to);
|
||||
}
|
||||
if (this.incomingEdges[to].indexOf(from) === -1) {
|
||||
this.incomingEdges[to].push(from);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Remove a dependency between two nodes.
|
||||
*/
|
||||
removeDependency: function (from, to) {
|
||||
var idx;
|
||||
if (this.hasNode(from)) {
|
||||
idx = this.outgoingEdges[from].indexOf(to);
|
||||
if (idx >= 0) {
|
||||
this.outgoingEdges[from].splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasNode(to)) {
|
||||
idx = this.incomingEdges[to].indexOf(from);
|
||||
if (idx >= 0) {
|
||||
this.incomingEdges[to].splice(idx, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return a clone of the dependency graph. If any custom data is attached
|
||||
* to the nodes, it will only be shallow copied.
|
||||
*/
|
||||
clone: function () {
|
||||
var source = this;
|
||||
var result = new DepGraph();
|
||||
var keys = Object.keys(source.nodes);
|
||||
keys.forEach(function (n) {
|
||||
result.nodes[n] = source.nodes[n];
|
||||
result.outgoingEdges[n] = source.outgoingEdges[n].slice(0);
|
||||
result.incomingEdges[n] = source.incomingEdges[n].slice(0);
|
||||
});
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* Get an array containing the nodes that the specified node depends on (transitively).
|
||||
*
|
||||
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
||||
*
|
||||
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned
|
||||
* in the array.
|
||||
*/
|
||||
dependenciesOf: function (node, leavesOnly) {
|
||||
if (this.hasNode(node)) {
|
||||
var result = [];
|
||||
var DFS = createDFS(
|
||||
this.outgoingEdges,
|
||||
leavesOnly,
|
||||
result,
|
||||
this.circular
|
||||
);
|
||||
DFS(node);
|
||||
var idx = result.indexOf(node);
|
||||
if (idx >= 0) {
|
||||
result.splice(idx, 1);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Error('Node does not exist: ' + node);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* get an array containing the nodes that depend on the specified node (transitively).
|
||||
*
|
||||
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
||||
*
|
||||
* If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
|
||||
*/
|
||||
dependantsOf: function (node, leavesOnly) {
|
||||
if (this.hasNode(node)) {
|
||||
var result = [];
|
||||
var DFS = createDFS(
|
||||
this.incomingEdges,
|
||||
leavesOnly,
|
||||
result,
|
||||
this.circular
|
||||
);
|
||||
DFS(node);
|
||||
var idx = result.indexOf(node);
|
||||
if (idx >= 0) {
|
||||
result.splice(idx, 1);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Error('Node does not exist: ' + node);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Construct the overall processing order for the dependency graph.
|
||||
*
|
||||
* Throws an Error if the graph has a cycle.
|
||||
*
|
||||
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned.
|
||||
*/
|
||||
overallOrder: function (leavesOnly) {
|
||||
var self = this;
|
||||
var result = [];
|
||||
var keys = Object.keys(this.nodes);
|
||||
if (keys.length === 0) {
|
||||
return result; // Empty graph
|
||||
} else {
|
||||
if (!this.circular) {
|
||||
// Look for cycles - we run the DFS starting at all the nodes in case there
|
||||
// are several disconnected subgraphs inside this dependency graph.
|
||||
var CycleDFS = createDFS(this.outgoingEdges, false, [], this.circular);
|
||||
keys.forEach(function (n) {
|
||||
CycleDFS(n);
|
||||
});
|
||||
}
|
||||
|
||||
var DFS = createDFS(
|
||||
this.outgoingEdges,
|
||||
leavesOnly,
|
||||
result,
|
||||
this.circular
|
||||
);
|
||||
// Find all potential starting points (nodes with nothing depending on them) an
|
||||
// run a DFS starting at these points to get the order
|
||||
keys
|
||||
.filter(function (node) {
|
||||
return self.incomingEdges[node].length === 0;
|
||||
})
|
||||
.forEach(function (n) {
|
||||
DFS(n);
|
||||
});
|
||||
|
||||
// If we're allowing cycles - we need to run the DFS against any remaining
|
||||
// nodes that did not end up in the initial result (as they are part of a
|
||||
// subgraph that does not have a clear starting point)
|
||||
if (this.circular) {
|
||||
keys
|
||||
.filter(function (node) {
|
||||
return result.indexOf(node) === -1;
|
||||
})
|
||||
.forEach(function (n) {
|
||||
DFS(n);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
mapNodes(mapper) {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Cycle error, including the path of the cycle.
|
||||
*/
|
||||
var DepGraphCycleError = (exports.DepGraphCycleError = function (cyclePath) {
|
||||
var message = 'Dependency Cycle Found: ' + cyclePath.join(' -> ');
|
||||
var instance = new Error(message);
|
||||
instance.cyclePath = cyclePath;
|
||||
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(instance, DepGraphCycleError);
|
||||
}
|
||||
return instance;
|
||||
});
|
||||
DepGraphCycleError.prototype = Object.create(Error.prototype, {
|
||||
constructor: {
|
||||
value: Error,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
Object.setPrototypeOf(DepGraphCycleError, Error);
|
||||
|
||||
export default DepGraph;
|
||||
172
packages/server-nest/src/libs/logic-evaluation/Lexer.js
Normal file
172
packages/server-nest/src/libs/logic-evaluation/Lexer.js
Normal file
@@ -0,0 +1,172 @@
|
||||
|
||||
const OperationType = {
|
||||
LOGIC: 'LOGIC',
|
||||
STRING: 'STRING',
|
||||
COMPARISON: 'COMPARISON',
|
||||
MATH: 'MATH',
|
||||
};
|
||||
|
||||
export class Lexer {
|
||||
// operation table
|
||||
static get optable() {
|
||||
return {
|
||||
'=': OperationType.LOGIC,
|
||||
'&': OperationType.LOGIC,
|
||||
'|': OperationType.LOGIC,
|
||||
'?': OperationType.LOGIC,
|
||||
':': OperationType.LOGIC,
|
||||
|
||||
'\'': OperationType.STRING,
|
||||
'"': OperationType.STRING,
|
||||
|
||||
'!': OperationType.COMPARISON,
|
||||
'>': OperationType.COMPARISON,
|
||||
'<': OperationType.COMPARISON,
|
||||
|
||||
'(': OperationType.MATH,
|
||||
')': OperationType.MATH,
|
||||
'+': OperationType.MATH,
|
||||
'-': OperationType.MATH,
|
||||
'*': OperationType.MATH,
|
||||
'/': OperationType.MATH,
|
||||
'%': OperationType.MATH,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {*} expression -
|
||||
*/
|
||||
constructor(expression) {
|
||||
this.currentIndex = 0;
|
||||
this.input = expression;
|
||||
this.tokenList = [];
|
||||
}
|
||||
|
||||
getTokens() {
|
||||
let tok;
|
||||
do {
|
||||
// read current token, so step should be -1
|
||||
tok = this.pickNext(-1);
|
||||
const pos = this.currentIndex;
|
||||
switch (Lexer.optable[tok]) {
|
||||
case OperationType.LOGIC:
|
||||
// == && || ===
|
||||
this.readLogicOpt(tok);
|
||||
break;
|
||||
|
||||
case OperationType.STRING:
|
||||
this.readString(tok);
|
||||
break;
|
||||
|
||||
case OperationType.COMPARISON:
|
||||
this.readCompare(tok);
|
||||
break;
|
||||
|
||||
case OperationType.MATH:
|
||||
this.receiveToken();
|
||||
break;
|
||||
|
||||
default:
|
||||
this.readValue(tok);
|
||||
}
|
||||
|
||||
// if the pos not changed, this loop will go into a infinite loop, every step of while loop,
|
||||
// we must move the pos forward
|
||||
// so here we should throw error, for example `1 & 2`
|
||||
if (pos === this.currentIndex && tok !== undefined) {
|
||||
const err = new Error(`unkonw token ${tok} from input string ${this.input}`);
|
||||
err.name = 'UnknowToken';
|
||||
throw err;
|
||||
}
|
||||
} while (tok !== undefined)
|
||||
|
||||
return this.tokenList;
|
||||
}
|
||||
|
||||
/**
|
||||
* read next token, the index param can set next step, default go foward 1 step
|
||||
*
|
||||
* @param index next postion
|
||||
*/
|
||||
pickNext(index = 0) {
|
||||
return this.input[index + this.currentIndex + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store token into result tokenList, and move the pos index
|
||||
*
|
||||
* @param index
|
||||
*/
|
||||
receiveToken(index = 1) {
|
||||
const tok = this.input.slice(this.currentIndex, this.currentIndex + index).trim();
|
||||
// skip empty string
|
||||
if (tok) {
|
||||
this.tokenList.push(tok);
|
||||
}
|
||||
|
||||
this.currentIndex += index;
|
||||
}
|
||||
|
||||
// ' or "
|
||||
readString(tok) {
|
||||
let next;
|
||||
let index = 0;
|
||||
do {
|
||||
next = this.pickNext(index);
|
||||
index += 1;
|
||||
} while (next !== tok && next !== undefined);
|
||||
this.receiveToken(index + 1);
|
||||
}
|
||||
|
||||
// > or < or >= or <= or !==
|
||||
// tok in (>, <, !)
|
||||
readCompare(tok) {
|
||||
if (this.pickNext() !== '=') {
|
||||
this.receiveToken(1);
|
||||
return;
|
||||
}
|
||||
// !==
|
||||
if (tok === '!' && this.pickNext(1) === '=') {
|
||||
this.receiveToken(3);
|
||||
return;
|
||||
}
|
||||
this.receiveToken(2);
|
||||
}
|
||||
|
||||
// === or ==
|
||||
// && ||
|
||||
readLogicOpt(tok) {
|
||||
if (this.pickNext() === tok) {
|
||||
// ===
|
||||
if (tok === '=' && this.pickNext(1) === tok) {
|
||||
return this.receiveToken(3);
|
||||
}
|
||||
// == && ||
|
||||
return this.receiveToken(2);
|
||||
}
|
||||
// handle as &&
|
||||
// a ? b : c is equal to a && b || c
|
||||
if (tok === '?' || tok === ':') {
|
||||
return this.receiveToken(1);
|
||||
}
|
||||
}
|
||||
|
||||
readValue(tok) {
|
||||
if (!tok) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
while (!Lexer.optable[tok] && tok !== undefined) {
|
||||
tok = this.pickNext(index);
|
||||
index += 1;
|
||||
}
|
||||
this.receiveToken(index);
|
||||
}
|
||||
}
|
||||
|
||||
export default function token(expression) {
|
||||
const lexer = new Lexer(expression);
|
||||
return lexer.getTokens();
|
||||
}
|
||||
159
packages/server-nest/src/libs/logic-evaluation/Parser.js
Normal file
159
packages/server-nest/src/libs/logic-evaluation/Parser.js
Normal file
@@ -0,0 +1,159 @@
|
||||
export const OPERATION = {
|
||||
'!': 5,
|
||||
'*': 4,
|
||||
'/': 4,
|
||||
'%': 4,
|
||||
'+': 3,
|
||||
'-': 3,
|
||||
'>': 2,
|
||||
'<': 2,
|
||||
'>=': 2,
|
||||
'<=': 2,
|
||||
'===': 2,
|
||||
'!==': 2,
|
||||
'==': 2,
|
||||
'!=': 2,
|
||||
'&&': 1,
|
||||
'||': 1,
|
||||
'?': 1,
|
||||
':': 1,
|
||||
};
|
||||
|
||||
// export interface Node {
|
||||
// left: Node | string | null;
|
||||
// right: Node | string | null;
|
||||
// operation: string;
|
||||
// grouped?: boolean;
|
||||
// };
|
||||
|
||||
export default class Parser {
|
||||
|
||||
constructor(token) {
|
||||
this.index = -1;
|
||||
this.blockLevel = 0;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {Node | string} =-
|
||||
*/
|
||||
parse() {
|
||||
let tok;
|
||||
let root = {
|
||||
left: null,
|
||||
right: null,
|
||||
operation: null,
|
||||
};
|
||||
|
||||
do {
|
||||
tok = this.parseStatement();
|
||||
|
||||
if (tok === null || tok === undefined) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (root.left === null) {
|
||||
root.left = tok;
|
||||
root.operation = this.nextToken();
|
||||
|
||||
if (!root.operation) {
|
||||
return tok;
|
||||
}
|
||||
|
||||
root.right = this.parseStatement();
|
||||
} else {
|
||||
if (typeof tok !== 'string') {
|
||||
throw new Error('operation must be string, but get ' + JSON.stringify(tok));
|
||||
}
|
||||
root = this.addNode(tok, this.parseStatement(), root);
|
||||
}
|
||||
} while (tok);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
nextToken() {
|
||||
this.index += 1;
|
||||
return this.token[this.index];
|
||||
}
|
||||
|
||||
prevToken() {
|
||||
return this.token[this.index - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} operation
|
||||
* @param {Node|String|null} right
|
||||
* @param {Node} root
|
||||
*/
|
||||
addNode(operation, right, root) {
|
||||
let pre = root;
|
||||
|
||||
if (this.compare(pre.operation, operation) < 0 && !pre.grouped) {
|
||||
|
||||
while (pre.right !== null &&
|
||||
typeof pre.right !== 'string' &&
|
||||
this.compare(pre.right.operation, operation) < 0 && !pre.right.grouped) {
|
||||
pre = pre.right;
|
||||
}
|
||||
|
||||
pre.right = {
|
||||
operation,
|
||||
left: pre.right,
|
||||
right,
|
||||
};
|
||||
return root;
|
||||
}
|
||||
return {
|
||||
left: pre,
|
||||
right,
|
||||
operation,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} a
|
||||
* @param {String} b
|
||||
*/
|
||||
compare(a, b) {
|
||||
if (!OPERATION.hasOwnProperty(a) || !OPERATION.hasOwnProperty(b)) {
|
||||
throw new Error(`unknow operation ${a} or ${b}`);
|
||||
}
|
||||
return OPERATION[a] - OPERATION[b];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string | Node | null
|
||||
*/
|
||||
parseStatement() {
|
||||
const token = this.nextToken();
|
||||
if (token === '(') {
|
||||
this.blockLevel += 1;
|
||||
const node = this.parse();
|
||||
this.blockLevel -= 1;
|
||||
|
||||
if (typeof node !== 'string') {
|
||||
node.grouped = true;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
if (token === ')') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token === '!') {
|
||||
return { left: null, operation: token, right: this.parseStatement() }
|
||||
}
|
||||
|
||||
// 3 > -12 or -12 + 10
|
||||
if (token === '-' && (OPERATION[this.prevToken()] > 0 || this.prevToken() === undefined)) {
|
||||
return { left: '0', operation: token, right: this.parseStatement(), grouped: true };
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { OPERATION } from './Parser';
|
||||
|
||||
export default class QueryParser {
|
||||
|
||||
constructor(tree, queries) {
|
||||
this.tree = tree;
|
||||
this.queries = queries;
|
||||
this.query = null;
|
||||
}
|
||||
|
||||
setQuery(query) {
|
||||
this.query = query.clone();
|
||||
}
|
||||
|
||||
parse() {
|
||||
return this.parseNode(this.tree);
|
||||
}
|
||||
|
||||
parseNode(node) {
|
||||
if (typeof node === 'string') {
|
||||
const nodeQuery = this.getQuery(node);
|
||||
return (query) => { nodeQuery(query); };
|
||||
}
|
||||
if (OPERATION[node.operation] === undefined) {
|
||||
throw new Error(`unknow expression ${node.operation}`);
|
||||
}
|
||||
const leftQuery = this.getQuery(node.left);
|
||||
const rightQuery = this.getQuery(node.right);
|
||||
|
||||
switch (node.operation) {
|
||||
case '&&':
|
||||
case 'AND':
|
||||
default:
|
||||
return (nodeQuery) => nodeQuery.where((query) => {
|
||||
query.where((q) => { leftQuery(q); });
|
||||
query.andWhere((q) => { rightQuery(q); });
|
||||
});
|
||||
case '||':
|
||||
case 'OR':
|
||||
return (nodeQuery) => nodeQuery.where((query) => {
|
||||
query.where((q) => { leftQuery(q); });
|
||||
query.orWhere((q) => { rightQuery(q); });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getQuery(node) {
|
||||
if (typeof node !== 'string' && node !== null) {
|
||||
return this.parseNode(node);
|
||||
}
|
||||
const value = parseFloat(node);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
if (typeof this.queries[node] === 'undefined') {
|
||||
throw new Error(`unknow query under index ${node}`);
|
||||
}
|
||||
return this.queries[node];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
29
packages/server-nest/src/main.ts
Normal file
29
packages/server-nest/src/main.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { ClsMiddleware } from 'nestjs-cls';
|
||||
import './utils/moment-mysql';
|
||||
import { AppModule } from './modules/App/App.module';
|
||||
import { ServiceErrorFilter } from './common/filters/service-error.filter';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.setGlobalPrefix('/api');
|
||||
|
||||
// create and mount the middleware manually here
|
||||
app.use(new ClsMiddleware({}).use);
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Bigcapital')
|
||||
.setDescription('Financial accounting software')
|
||||
.setVersion('1.0')
|
||||
.addTag('cats')
|
||||
.build();
|
||||
|
||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('swagger', app, documentFactory);
|
||||
|
||||
app.useGlobalFilters(new ServiceErrorFilter());
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
11
packages/server-nest/src/middleware/logger.middleware.ts
Normal file
11
packages/server-nest/src/middleware/logger.middleware.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: NextFunction) {
|
||||
// @ts-expect-error
|
||||
req.test = 'test';
|
||||
next();
|
||||
}
|
||||
}
|
||||
43
packages/server-nest/src/models/Model.ts
Normal file
43
packages/server-nest/src/models/Model.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { QueryBuilder, Model } from 'objection';
|
||||
|
||||
interface PaginationResult<M extends Model> {
|
||||
results: M[];
|
||||
pagination: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type PaginationQueryBuilderType<M extends Model> = QueryBuilder<
|
||||
M,
|
||||
PaginationResult<M>
|
||||
>;
|
||||
|
||||
class PaginationQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<
|
||||
M,
|
||||
R
|
||||
> {
|
||||
pagination(page: number, pageSize: number): PaginationQueryBuilderType<M> {
|
||||
const query = super.page(page, pageSize);
|
||||
|
||||
return query.runAfter(({ results, total }) => {
|
||||
return {
|
||||
results,
|
||||
pagination: {
|
||||
total,
|
||||
page: page + 1,
|
||||
pageSize,
|
||||
},
|
||||
};
|
||||
}) as unknown as PaginationQueryBuilderType<M>;
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseModel extends Model {
|
||||
public readonly id: number;
|
||||
public readonly tableName: string;
|
||||
|
||||
QueryBuilderType!: PaginationQueryBuilder<this>;
|
||||
static QueryBuilder = PaginationQueryBuilder;
|
||||
}
|
||||
130
packages/server-nest/src/modules/Accounts/Account.transformer.ts
Normal file
130
packages/server-nest/src/modules/Accounts/Account.transformer.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Transformer } from '../Transformer/Transformer';
|
||||
import { Account } from './models/Account.model';
|
||||
import { flatToNestedArray } from '@/utils/flat-to-nested-array';
|
||||
import { assocDepthLevelToObjectTree } from '@/utils/assoc-depth-level-to-object-tree';
|
||||
import { nestedArrayToFlatten } from '@/utils/nested-array-to-flatten';
|
||||
import { IAccountsStructureType } from './Accounts.types';
|
||||
|
||||
export class AccountTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'accountTypeLabel',
|
||||
'accountNormalFormatted',
|
||||
'formattedAmount',
|
||||
'flattenName',
|
||||
'bankBalanceFormatted',
|
||||
'lastFeedsUpdatedAtFormatted',
|
||||
'isFeedsPaused',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['plaidItem'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the flatten name with all dependants accounts names.
|
||||
* @param {IAccount} account -
|
||||
* @returns {string}
|
||||
*/
|
||||
public flattenName = (account: Account): string => {
|
||||
const parentDependantsIds = this.options.accountsGraph.dependantsOf(
|
||||
account.id,
|
||||
);
|
||||
const prefixAccounts = parentDependantsIds.map((dependId) => {
|
||||
const node = this.options.accountsGraph.getNodeData(dependId);
|
||||
return `${node.name}: `;
|
||||
});
|
||||
return `${prefixAccounts}${account.name}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted account amount.
|
||||
* @param {IAccount} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (account: Account): string => {
|
||||
return this.formatNumber(account.amount, {
|
||||
currencyCode: account.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted bank balance.
|
||||
* @param {Account} account
|
||||
* @returns {string}
|
||||
*/
|
||||
protected bankBalanceFormatted = (account: Account): string => {
|
||||
return this.formatNumber(account.bankBalance, {
|
||||
currencyCode: account.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted last feeds update at.
|
||||
* @param {IAccount} account
|
||||
* @returns {string}
|
||||
*/
|
||||
protected lastFeedsUpdatedAtFormatted = (account: Account): string => {
|
||||
return account.lastFeedsUpdatedAt
|
||||
? this.formatDate(account.lastFeedsUpdatedAt)
|
||||
: '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the bank account connection is paused.
|
||||
* @param account
|
||||
* @returns {boolean}
|
||||
*/
|
||||
protected isFeedsPaused = (account: Account): boolean => {
|
||||
// return account.plaidItem?.isPaused || false;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted account type label.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected accountTypeLabel = (account: Account): string => {
|
||||
return this.context.i18n.t(account.accountTypeLabel);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves formatted account normal.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected accountNormalFormatted = (account: Account): string => {
|
||||
return this.context.i18n.t(account.accountNormalFormatted);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the accounts collection to flat or nested array.
|
||||
* @param {IAccount[]}
|
||||
* @returns {IAccount[]}
|
||||
*/
|
||||
protected postCollectionTransform = (accounts: Account[]) => {
|
||||
// Transfom the flatten to accounts tree.
|
||||
const transformed = flatToNestedArray(accounts, {
|
||||
id: 'id',
|
||||
parentId: 'parentAccountId',
|
||||
});
|
||||
// Associate `accountLevel` attr to indicate object depth.
|
||||
const transformed2 = assocDepthLevelToObjectTree(
|
||||
transformed,
|
||||
1,
|
||||
'accountLevel',
|
||||
);
|
||||
return this.options.structure === IAccountsStructureType.Flat
|
||||
? nestedArrayToFlatten(transformed2)
|
||||
: transformed2;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Transformer } from '../Transformer/Transformer';
|
||||
import { AccountTransaction } from './models/AccountTransaction.model';
|
||||
|
||||
export class AccountTransactionTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'date',
|
||||
'formattedDate',
|
||||
'transactionType',
|
||||
'transactionId',
|
||||
'transactionTypeFormatted',
|
||||
'credit',
|
||||
'debit',
|
||||
'formattedCredit',
|
||||
'formattedDebit',
|
||||
'fcCredit',
|
||||
'fcDebit',
|
||||
'formattedFcCredit',
|
||||
'formattedFcDebit',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude all attributes of the model.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted date.
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedDate(transaction: AccountTransaction) {
|
||||
return this.formatDate(transaction.date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted transaction type.
|
||||
* @returns {string}
|
||||
*/
|
||||
public transactionTypeFormatted(transaction: AccountTransaction) {
|
||||
return this.context.i18n.t(transaction.referenceTypeFormatted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the tranasction type.
|
||||
* @returns {string}
|
||||
*/
|
||||
public transactionType(transaction: AccountTransaction) {
|
||||
return transaction.referenceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the transaction id.
|
||||
* @returns {number}
|
||||
*/
|
||||
public transactionId(transaction: AccountTransaction) {
|
||||
return transaction.referenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCredit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(transaction.credit, {
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDebit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(transaction.debit, {
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the foreign credit amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
protected fcCredit(transaction: AccountTransaction) {
|
||||
return transaction.credit * transaction.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the foreign debit amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
protected fcDebit(transaction: AccountTransaction) {
|
||||
return transaction.debit * transaction.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted foreign credit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedFcCredit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(this.fcCredit(transaction), {
|
||||
currencyCode: transaction.currencyCode,
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted foreign debit amount.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedFcDebit(transaction: AccountTransaction) {
|
||||
return this.formatMoney(this.fcDebit(transaction), {
|
||||
currencyCode: transaction.currencyCode,
|
||||
excerptZero: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
637
packages/server-nest/src/modules/Accounts/Accounts.constants.ts
Normal file
637
packages/server-nest/src/modules/Accounts/Accounts.constants.ts
Normal file
@@ -0,0 +1,637 @@
|
||||
export const OtherExpensesAccount = {
|
||||
name: 'Other Expenses',
|
||||
slug: 'other-expenses',
|
||||
account_type: 'other-expense',
|
||||
code: '40011',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
};
|
||||
|
||||
export const TaxPayableAccount = {
|
||||
name: 'Tax Payable',
|
||||
slug: 'tax-payable',
|
||||
account_type: 'tax-payable',
|
||||
code: '20006',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
};
|
||||
|
||||
export const UnearnedRevenueAccount = {
|
||||
name: 'Unearned Revenue',
|
||||
slug: 'unearned-revenue',
|
||||
account_type: 'other-current-liability',
|
||||
parent_account_id: null,
|
||||
code: '50005',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const PrepardExpenses = {
|
||||
name: 'Prepaid Expenses',
|
||||
slug: 'prepaid-expenses',
|
||||
account_type: 'other-current-asset',
|
||||
parent_account_id: null,
|
||||
code: '100010',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const StripeClearingAccount = {
|
||||
name: 'Stripe Clearing',
|
||||
slug: 'stripe-clearing',
|
||||
account_type: 'other-current-asset',
|
||||
parent_account_id: null,
|
||||
code: '100020',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const DiscountExpenseAccount = {
|
||||
name: 'Discount',
|
||||
slug: 'discount',
|
||||
account_type: 'other-income',
|
||||
code: '40008',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const PurchaseDiscountAccount = {
|
||||
name: 'Purchase Discount',
|
||||
slug: 'purchase-discount',
|
||||
account_type: 'other-expense',
|
||||
code: '40009',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const OtherChargesAccount = {
|
||||
name: 'Other Charges',
|
||||
slug: 'other-charges',
|
||||
account_type: 'other-income',
|
||||
code: '40010',
|
||||
active: true,
|
||||
index: 1,
|
||||
predefined: true,
|
||||
};
|
||||
|
||||
export const SeedAccounts = [
|
||||
{
|
||||
name: 'Bank Account',
|
||||
slug: 'bank-account',
|
||||
account_type: 'bank',
|
||||
code: '10001',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Saving Bank Account',
|
||||
slug: 'saving-bank-account',
|
||||
account_type: 'bank',
|
||||
code: '10002',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Undeposited Funds',
|
||||
slug: 'undeposited-funds',
|
||||
account_type: 'cash',
|
||||
code: '10003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Petty Cash',
|
||||
slug: 'petty-cash',
|
||||
account_type: 'cash',
|
||||
code: '10004',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Computer Equipment',
|
||||
slug: 'computer-equipment',
|
||||
code: '10005',
|
||||
account_type: 'fixed-asset',
|
||||
predefined: 0,
|
||||
parent_account_id: null,
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Office Equipment',
|
||||
slug: 'office-equipment',
|
||||
code: '10006',
|
||||
account_type: 'fixed-asset',
|
||||
predefined: 0,
|
||||
parent_account_id: null,
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Accounts Receivable (A/R)',
|
||||
slug: 'accounts-receivable',
|
||||
account_type: 'accounts-receivable',
|
||||
code: '10007',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Inventory Asset',
|
||||
slug: 'inventory-asset',
|
||||
code: '10008',
|
||||
account_type: 'inventory',
|
||||
predefined: 1,
|
||||
parent_account_id: null,
|
||||
index: 1,
|
||||
active: 1,
|
||||
description:
|
||||
'An account that holds valuation of products or goods that available for sale.',
|
||||
},
|
||||
|
||||
// Libilities
|
||||
{
|
||||
name: 'Accounts Payable (A/P)',
|
||||
slug: 'accounts-payable',
|
||||
account_type: 'accounts-payable',
|
||||
parent_account_id: null,
|
||||
code: '20001',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Owner A Drawings',
|
||||
slug: 'owner-drawings',
|
||||
account_type: 'other-current-liability',
|
||||
parent_account_id: null,
|
||||
code: '20002',
|
||||
description: 'Withdrawals by the owners.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Loan',
|
||||
slug: 'owner-drawings',
|
||||
account_type: 'other-current-liability',
|
||||
code: '20003',
|
||||
description: 'Money that has been borrowed from a creditor.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Opening Balance Liabilities',
|
||||
slug: 'opening-balance-liabilities',
|
||||
account_type: 'other-current-liability',
|
||||
code: '20004',
|
||||
description:
|
||||
'This account will hold the difference in the debits and credits entered during the opening balance..',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Revenue Received in Advance',
|
||||
slug: 'revenue-received-in-advance',
|
||||
account_type: 'other-current-liability',
|
||||
parent_account_id: null,
|
||||
code: '20005',
|
||||
description: 'When customers pay in advance for products/services.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
TaxPayableAccount,
|
||||
|
||||
// Equity
|
||||
{
|
||||
name: 'Retained Earnings',
|
||||
slug: 'retained-earnings',
|
||||
account_type: 'equity',
|
||||
code: '30001',
|
||||
description:
|
||||
'Retained earnings tracks net income from previous fiscal years.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Opening Balance Equity',
|
||||
slug: 'opening-balance-equity',
|
||||
account_type: 'equity',
|
||||
code: '30002',
|
||||
description:
|
||||
'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: "Owner's Equity",
|
||||
slug: 'owner-equity',
|
||||
account_type: 'equity',
|
||||
code: '30003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: `Drawings`,
|
||||
slug: 'drawings',
|
||||
account_type: 'equity',
|
||||
code: '30003',
|
||||
description:
|
||||
'Goods purchased with the intention of selling these to customers',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
|
||||
// Expenses
|
||||
OtherExpensesAccount,
|
||||
{
|
||||
name: 'Other Expenses',
|
||||
slug: 'other-expenses',
|
||||
account_type: 'other-expense',
|
||||
parent_account_id: null,
|
||||
code: '40001',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Cost of Goods Sold',
|
||||
slug: 'cost-of-goods-sold',
|
||||
account_type: 'cost-of-goods-sold',
|
||||
parent_account_id: null,
|
||||
code: '40002',
|
||||
description: 'Tracks the direct cost of the goods sold.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Office expenses',
|
||||
slug: 'office-expenses',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Rent',
|
||||
slug: 'rent',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40004',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Exchange Gain or Loss',
|
||||
slug: 'exchange-grain-loss',
|
||||
account_type: 'other-expense',
|
||||
parent_account_id: null,
|
||||
code: '40005',
|
||||
description: 'Tracks the gain and losses of the exchange differences.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Bank Fees and Charges',
|
||||
slug: 'bank-fees-and-charges',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40006',
|
||||
description:
|
||||
'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
{
|
||||
name: 'Depreciation Expense',
|
||||
slug: 'depreciation-expense',
|
||||
account_type: 'expense',
|
||||
parent_account_id: null,
|
||||
code: '40007',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
|
||||
// Income
|
||||
{
|
||||
name: 'Sales of Product Income',
|
||||
slug: 'sales-of-product-income',
|
||||
account_type: 'income',
|
||||
predefined: 1,
|
||||
parent_account_id: null,
|
||||
code: '50001',
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Sales of Service Income',
|
||||
slug: 'sales-of-service-income',
|
||||
account_type: 'income',
|
||||
predefined: 0,
|
||||
parent_account_id: null,
|
||||
code: '50002',
|
||||
index: 1,
|
||||
active: 1,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
name: 'Uncategorized Income',
|
||||
slug: 'uncategorized-income',
|
||||
account_type: 'income',
|
||||
parent_account_id: null,
|
||||
code: '50003',
|
||||
description: '',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 1,
|
||||
},
|
||||
{
|
||||
name: 'Other Income',
|
||||
slug: 'other-income',
|
||||
account_type: 'other-income',
|
||||
parent_account_id: null,
|
||||
code: '50004',
|
||||
description:
|
||||
'The income activities are not associated to the core business.',
|
||||
active: 1,
|
||||
index: 1,
|
||||
predefined: 0,
|
||||
},
|
||||
UnearnedRevenueAccount,
|
||||
PrepardExpenses,
|
||||
DiscountExpenseAccount,
|
||||
PurchaseDiscountAccount,
|
||||
OtherChargesAccount,
|
||||
];
|
||||
|
||||
export const ACCOUNT_TYPE = {
|
||||
CASH: 'cash',
|
||||
BANK: 'bank',
|
||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||
INVENTORY: 'inventory',
|
||||
OTHER_CURRENT_ASSET: 'other-current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'none-current-asset',
|
||||
|
||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||
CREDIT_CARD: 'credit-card',
|
||||
TAX_PAYABLE: 'tax-payable',
|
||||
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
INCOME: 'income',
|
||||
OTHER_INCOME: 'other-income',
|
||||
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
||||
EXPENSE: 'expense',
|
||||
OTHER_EXPENSE: 'other-expense',
|
||||
};
|
||||
|
||||
export const ACCOUNT_PARENT_TYPE = {
|
||||
CURRENT_ASSET: 'current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'non-current-asset',
|
||||
|
||||
CURRENT_LIABILITY: 'current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expense',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_ROOT_TYPE = {
|
||||
ASSET: 'asset',
|
||||
LIABILITY: 'liability',
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expense',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_NORMAL = {
|
||||
CREDIT: 'credit',
|
||||
DEBIT: 'debit',
|
||||
};
|
||||
export const ACCOUNT_TYPES = [
|
||||
{
|
||||
label: 'Cash',
|
||||
key: ACCOUNT_TYPE.CASH,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Bank',
|
||||
key: ACCOUNT_TYPE.BANK,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Receivable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Inventory',
|
||||
key: ACCOUNT_TYPE.INVENTORY,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Asset',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Fixed Asset',
|
||||
key: ACCOUNT_TYPE.FIXED_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Asset',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Payable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Credit Card',
|
||||
key: ACCOUNT_TYPE.CREDIT_CARD,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
multiCurrency: true,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Tax Payable',
|
||||
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Liability',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Long Term Liability',
|
||||
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Liability',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Equity',
|
||||
key: ACCOUNT_TYPE.EQUITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Income',
|
||||
key: ACCOUNT_TYPE.INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Income',
|
||||
key: ACCOUNT_TYPE.OTHER_INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Cost of Goods Sold',
|
||||
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Expense',
|
||||
key: ACCOUNT_TYPE.EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Expense',
|
||||
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const getAccountsSupportsMultiCurrency = () => {
|
||||
return ACCOUNT_TYPES.filter((account) => account.multiCurrency);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
Delete,
|
||||
Get,
|
||||
Query,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { AccountsApplication } from './AccountsApplication.service';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
||||
// import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
||||
// import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
|
||||
|
||||
@Controller('accounts')
|
||||
@PublicRoute()
|
||||
export class AccountsController {
|
||||
constructor(private readonly accountsApplication: AccountsApplication) {}
|
||||
|
||||
@Post()
|
||||
async createAccount(@Body() accountDTO: CreateAccountDTO) {
|
||||
return this.accountsApplication.createAccount(accountDTO);
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
async editAccount(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() accountDTO: EditAccountDTO,
|
||||
) {
|
||||
return this.accountsApplication.editAccount(id, accountDTO);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async deleteAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.deleteAccount(id);
|
||||
}
|
||||
|
||||
@Post(':id/activate')
|
||||
async activateAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.activateAccount(id);
|
||||
}
|
||||
|
||||
@Post(':id/inactivate')
|
||||
async inactivateAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.inactivateAccount(id);
|
||||
}
|
||||
|
||||
@Get('types')
|
||||
async getAccountTypes() {
|
||||
return this.accountsApplication.getAccountTypes();
|
||||
}
|
||||
|
||||
@Get('transactions')
|
||||
async getAccountTransactions(@Query() filter: IAccountsTransactionsFilter) {
|
||||
return this.accountsApplication.getAccountsTransactions(filter);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async getAccount(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.accountsApplication.getAccount(id);
|
||||
}
|
||||
|
||||
@Get()
|
||||
async getAccounts(@Query() filter: IAccountsFilter) {
|
||||
return this.accountsApplication.getAccounts(filter);
|
||||
}
|
||||
|
||||
}
|
||||
42
packages/server-nest/src/modules/Accounts/Accounts.module.ts
Normal file
42
packages/server-nest/src/modules/Accounts/Accounts.module.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||
import { AccountsController } from './Accounts.controller';
|
||||
import { AccountsApplication } from './AccountsApplication.service';
|
||||
import { CreateAccountService } from './CreateAccount.service';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { EditAccount } from './EditAccount.service';
|
||||
import { DeleteAccount } from './DeleteAccount.service';
|
||||
import { GetAccount } from './GetAccount.service';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { ActivateAccount } from './ActivateAccount.service';
|
||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { BankAccount } from '../BankingTransactions/models/BankAccount';
|
||||
// import { GetAccountsService } from './GetAccounts.service';
|
||||
|
||||
const models = [RegisterTenancyModel(BankAccount)];
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule],
|
||||
controllers: [AccountsController],
|
||||
providers: [
|
||||
AccountsApplication,
|
||||
CreateAccountService,
|
||||
TenancyContext,
|
||||
CommandAccountValidators,
|
||||
AccountRepository,
|
||||
EditAccount,
|
||||
DeleteAccount,
|
||||
GetAccount,
|
||||
TransformerInjectable,
|
||||
ActivateAccount,
|
||||
GetAccountTypesService,
|
||||
GetAccountTransactionsService,
|
||||
...models,
|
||||
],
|
||||
exports: [AccountRepository, CreateAccountService, ...models],
|
||||
})
|
||||
export class AccountsModule {}
|
||||
91
packages/server-nest/src/modules/Accounts/Accounts.types.ts
Normal file
91
packages/server-nest/src/modules/Accounts/Accounts.types.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Account } from './models/Account.model';
|
||||
// import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
|
||||
|
||||
export enum AccountNormal {
|
||||
DEBIT = 'debit',
|
||||
CREDIT = 'credit',
|
||||
}
|
||||
|
||||
export interface IAccountsTransactionsFilter {
|
||||
accountId?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export enum IAccountsStructureType {
|
||||
Tree = 'tree',
|
||||
Flat = 'flat',
|
||||
}
|
||||
|
||||
// export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||
// }
|
||||
export interface IAccountsFilter {
|
||||
onlyInactive: boolean;
|
||||
structure?: IAccountsStructureType;
|
||||
}
|
||||
export interface IAccountType {
|
||||
label: string;
|
||||
key: string;
|
||||
normal: string;
|
||||
rootType: string;
|
||||
childType: string;
|
||||
balanceSheet: boolean;
|
||||
incomeSheet: boolean;
|
||||
}
|
||||
|
||||
export interface IAccountsTypesService {
|
||||
getAccountsTypes(): Promise<IAccountType>;
|
||||
}
|
||||
|
||||
export interface IAccountEventCreatingPayload {
|
||||
accountDTO: any;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IAccountEventCreatedPayload {
|
||||
account: Account;
|
||||
accountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventEditedPayload {
|
||||
account: Account;
|
||||
oldAccount: Account;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventDeletedPayload {
|
||||
accountId: number;
|
||||
oldAccount: Account;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IAccountEventDeletePayload {
|
||||
trx: Knex.Transaction;
|
||||
oldAccount: Account;
|
||||
}
|
||||
|
||||
export interface IAccountEventActivatedPayload {
|
||||
accountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum AccountAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
TransactionsLocking = 'TransactionsLocking',
|
||||
}
|
||||
|
||||
export enum TaxRateAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
}
|
||||
|
||||
export interface CreateAccountParams {
|
||||
ignoreUniqueName: boolean;
|
||||
}
|
||||
|
||||
export interface IGetAccountTransactionPOJO {}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateAccountService } from './CreateAccount.service';
|
||||
import { DeleteAccount } from './DeleteAccount.service';
|
||||
import { EditAccount } from './EditAccount.service';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
import { Account } from './models/Account.model';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
import { GetAccount } from './GetAccount.service';
|
||||
import { ActivateAccount } from './ActivateAccount.service';
|
||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||
import {
|
||||
IAccountsFilter,
|
||||
IAccountsTransactionsFilter,
|
||||
IGetAccountTransactionPOJO,
|
||||
} from './Accounts.types';
|
||||
import { GetAccountsService } from './GetAccounts.service';
|
||||
import { IFilterMeta } from '@/interfaces/Model';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsApplication {
|
||||
constructor(
|
||||
private readonly createAccountService: CreateAccountService,
|
||||
private readonly editAccountService: EditAccount,
|
||||
private readonly deleteAccountService: DeleteAccount,
|
||||
private readonly activateAccountService: ActivateAccount,
|
||||
private readonly getAccountTypesService: GetAccountTypesService,
|
||||
private readonly getAccountService: GetAccount,
|
||||
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
||||
private readonly getAccountsService: GetAccountsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new account.
|
||||
* @param {number} tenantId
|
||||
* @param {IAccountCreateDTO} accountDTO
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public createAccount = (
|
||||
accountDTO: CreateAccountDTO,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<Account> => {
|
||||
return this.createAccountService.createAccount(accountDTO, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteAccount = (accountId: number) => {
|
||||
return this.deleteAccountService.deleteAccount(accountId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits the given account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {IAccountEditDTO} accountDTO
|
||||
* @returns
|
||||
*/
|
||||
public editAccount = (accountId: number, accountDTO: EditAccountDTO) => {
|
||||
return this.editAccountService.editAccount(accountId, accountDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate the given account.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
public activateAccount = (accountId: number) => {
|
||||
return this.activateAccountService.activateAccount(accountId, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inactivate the given account.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
public inactivateAccount = (accountId: number) => {
|
||||
return this.activateAccountService.activateAccount(accountId, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the account details.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} accountId - Account id.
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public getAccount = (accountId: number) => {
|
||||
return this.getAccountService.getAccount(accountId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all account types.
|
||||
* @returns {Promise<IAccountType[]>}
|
||||
*/
|
||||
public getAccountTypes = () => {
|
||||
return this.getAccountTypesService.getAccountsTypes();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the accounts list.
|
||||
* @param {IAccountsFilter} filterDTO - Filter DTO.
|
||||
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
*/
|
||||
public getAccounts = (
|
||||
filterDTO: IAccountsFilter,
|
||||
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> => {
|
||||
return this.getAccountsService.getAccountsList(filterDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given account transactions.
|
||||
* @param {IAccountsTransactionsFilter} filter
|
||||
* @returns {Promise<IGetAccountTransactionPOJO[]>}
|
||||
*/
|
||||
public getAccountsTransactions = (
|
||||
filter: IAccountsTransactionsFilter,
|
||||
): Promise<IGetAccountTransactionPOJO[]> => {
|
||||
return this.getAccountTransactionsService.getAccountsTransactions(filter);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import { AccountsApplication } from './AccountsApplication.service';
|
||||
// import { Exportable } from '../Export/Exportable';
|
||||
// import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||
// import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
||||
|
||||
// @Service()
|
||||
// export class AccountsExportable extends Exportable {
|
||||
// @Inject()
|
||||
// private accountsApplication: AccountsApplication;
|
||||
|
||||
// /**
|
||||
// * Retrieves the accounts data to exportable sheet.
|
||||
// * @param {number} tenantId
|
||||
// * @returns
|
||||
// */
|
||||
// public exportable(tenantId: number, query: IAccountsFilter) {
|
||||
// const parsedQuery = {
|
||||
// sortOrder: 'desc',
|
||||
// columnSortBy: 'created_at',
|
||||
// inactiveMode: false,
|
||||
// ...query,
|
||||
// structure: IAccountsStructureType.Flat,
|
||||
// pageSize: EXPORT_SIZE_LIMIT,
|
||||
// page: 1,
|
||||
// } as IAccountsFilter;
|
||||
|
||||
// return this.accountsApplication
|
||||
// .getAccounts(tenantId, parsedQuery)
|
||||
// .then((output) => output.accounts);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,50 @@
|
||||
export const AccountsSampleData = [
|
||||
{
|
||||
'Account Name': 'Utilities Expense',
|
||||
'Account Code': 9000,
|
||||
Type: 'Expense',
|
||||
Description: 'Omnis voluptatum consequatur.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Unearned Revenue',
|
||||
'Account Code': 9010,
|
||||
Type: 'Long Term Liability',
|
||||
Description: 'Autem odit voluptas nihil unde.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Long-Term Debt',
|
||||
'Account Code': 9020,
|
||||
Type: 'Long Term Liability',
|
||||
Description: 'In voluptas cumque exercitationem.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Salaries and Wages Expense',
|
||||
'Account Code': 9030,
|
||||
Type: 'Expense',
|
||||
Description: 'Assumenda aspernatur soluta aliquid perspiciatis quasi.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Rental Income',
|
||||
'Account Code': 9040,
|
||||
Type: 'Income',
|
||||
Description: 'Omnis possimus amet occaecati inventore.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
{
|
||||
'Account Name': 'Paypal',
|
||||
'Account Code': 9050,
|
||||
Type: 'Bank',
|
||||
Description: 'In voluptas cumque exercitationem.',
|
||||
Active: 'T',
|
||||
'Currency Code': '',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,45 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import { Knex } from 'knex';
|
||||
// import { IAccountCreateDTO } from '@/interfaces';
|
||||
// import { CreateAccount } from './CreateAccount.service';
|
||||
// import { Importable } from '../Import/Importable';
|
||||
// import { AccountsSampleData } from './AccountsImportable.SampleData';
|
||||
|
||||
// @Service()
|
||||
// export class AccountsImportable extends Importable {
|
||||
// @Inject()
|
||||
// private createAccountService: CreateAccount;
|
||||
|
||||
// /**
|
||||
// * Importing to account service.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IAccountCreateDTO} createAccountDTO
|
||||
// * @returns
|
||||
// */
|
||||
// public importable(
|
||||
// tenantId: number,
|
||||
// createAccountDTO: IAccountCreateDTO,
|
||||
// trx?: Knex.Transaction
|
||||
// ) {
|
||||
// return this.createAccountService.createAccount(
|
||||
// tenantId,
|
||||
// createAccountDTO,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Concurrrency controlling of the importing process.
|
||||
// * @returns {number}
|
||||
// */
|
||||
// public get concurrency() {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieves the sample data that used to download accounts sample sheet.
|
||||
// */
|
||||
// public sampleData(): any[] {
|
||||
// return AccountsSampleData;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { IAccountEventActivatedPayload } from './Accounts.types';
|
||||
import { Account } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class ActivateAccount {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Activates/Inactivates the given account.
|
||||
* @param {number} accountId
|
||||
* @param {boolean} activate
|
||||
*/
|
||||
public activateAccount = async (accountId: number, activate?: boolean) => {
|
||||
// Retrieve the given account or throw not found error.
|
||||
const oldAccount = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Get all children accounts.
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
const dependenciesAccounts = accountsGraph.dependenciesOf(accountId);
|
||||
|
||||
const patchAccountsIds = [...dependenciesAccounts, accountId];
|
||||
|
||||
// Activate account and associated transactions under unit-of-work environment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Activate and inactivate the given accounts ids.
|
||||
activate
|
||||
? await this.accountRepository.activateByIds(patchAccountsIds, trx)
|
||||
: await this.accountRepository.inactivateByIds(patchAccountsIds, trx);
|
||||
|
||||
// Triggers `onAccountActivated` event.
|
||||
this.eventEmitter.emitAsync(events.accounts.onActivated, {
|
||||
accountId,
|
||||
trx,
|
||||
} as IAccountEventActivatedPayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// @ts-nocheck
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
// import { IAccountDTO, IAccount, IAccountCreateDTO } from './Accounts.types';
|
||||
// import AccountTypesUtils from '@/lib/AccountTypes';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { ERRORS, MAX_ACCOUNTS_CHART_DEPTH } from './constants';
|
||||
import { Account } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { AccountTypesUtils } from './utils/AccountType.utils';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class CommandAccountValidators {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Throws error if the account was prefined.
|
||||
* @param {Account} account
|
||||
*/
|
||||
public throwErrorIfAccountPredefined(account: Account) {
|
||||
if (account.predefined) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_PREDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff account type between new and old account, throw service error
|
||||
* if they have different account type.
|
||||
* @param {Account|CreateAccountDTO|EditAccountDTO} oldAccount
|
||||
* @param {Account|CreateAccountDTO|EditAccountDTO} newAccount
|
||||
*/
|
||||
public async isAccountTypeChangedOrThrowError(
|
||||
oldAccount: Account | CreateAccountDTO | EditAccountDTO,
|
||||
newAccount: Account | CreateAccountDTO | EditAccountDTO,
|
||||
) {
|
||||
if (oldAccount.accountType !== newAccount.accountType) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type or throws service error.
|
||||
* @param {string} accountTypeKey -
|
||||
* @return {IAccountType}
|
||||
*/
|
||||
public getAccountTypeOrThrowError(accountTypeKey: string) {
|
||||
const accountType = AccountTypesUtils.getType(accountTypeKey);
|
||||
|
||||
if (!accountType) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_FOUND);
|
||||
}
|
||||
return accountType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve parent account or throw service error.
|
||||
* @param {number} accountId - Account id.
|
||||
* @param {number} notAccountId - Ignore the account id.
|
||||
*/
|
||||
public async getParentAccountOrThrowError(
|
||||
accountId: number,
|
||||
notAccountId?: number,
|
||||
) {
|
||||
const parentAccount = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (!parentAccount) {
|
||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
return parentAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws error if the account type was not unique on the storage.
|
||||
* @param {string} accountCode - Account code.
|
||||
* @param {number} notAccountId - Ignore the account id.
|
||||
*/
|
||||
public async isAccountCodeUniqueOrThrowError(
|
||||
accountCode: string,
|
||||
notAccountId?: number,
|
||||
) {
|
||||
const account = await this.accountModel
|
||||
.query()
|
||||
.where('code', accountCode)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (account.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
|
||||
'Account code is not unique.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the account name uniquiness.
|
||||
* @param {string} accountName - Account name.
|
||||
* @param {number} notAccountId - Ignore the account id.
|
||||
*/
|
||||
public async validateAccountNameUniquiness(
|
||||
accountName: string,
|
||||
notAccountId?: number,
|
||||
) {
|
||||
const foundAccount = await this.accountModel
|
||||
.query()
|
||||
.findOne('name', accountName)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (foundAccount) {
|
||||
throw new ServiceError(
|
||||
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
|
||||
'Account name is not unique.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given account type supports multi-currency.
|
||||
* @param {CreateAccountDTO | EditAccountDTO} accountDTO -
|
||||
*/
|
||||
public validateAccountTypeSupportCurrency = (
|
||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||
baseCurrency: string,
|
||||
) => {
|
||||
// Can't continue to validate the type has multi-currency feature
|
||||
// if the given currency equals the base currency or not assigned.
|
||||
if (accountDTO.currencyCode === baseCurrency || !accountDTO.currencyCode) {
|
||||
return;
|
||||
}
|
||||
const meta = AccountTypesUtils.getType(accountDTO.accountType);
|
||||
|
||||
// Throw error if the account type does not support multi-currency.
|
||||
if (!meta?.multiCurrency) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the account DTO currency code whether equals the currency code of
|
||||
* parent account.
|
||||
* @param {CreateAccountDTO | EditAccountDTO} accountDTO
|
||||
* @param {Account} parentAccount
|
||||
* @param {string} baseCurrency -
|
||||
* @throws {ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT)}
|
||||
*/
|
||||
public validateCurrentSameParentAccount = (
|
||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||
parentAccount: Account,
|
||||
baseCurrency: string,
|
||||
) => {
|
||||
// If the account DTO currency not assigned and the parent account has no base currency.
|
||||
if (
|
||||
!accountDTO.currencyCode &&
|
||||
parentAccount.currencyCode !== baseCurrency
|
||||
) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
||||
}
|
||||
// If the account DTO is assigned and not equals the currency code of parent account.
|
||||
if (
|
||||
accountDTO.currencyCode &&
|
||||
parentAccount.currencyCode !== accountDTO.currencyCode
|
||||
) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws service error if parent account has different type.
|
||||
* @param {IAccountDTO} accountDTO
|
||||
* @param {IAccount} parentAccount
|
||||
*/
|
||||
public throwErrorIfParentHasDiffType(
|
||||
accountDTO: CreateAccountDTO | EditAccountDTO,
|
||||
parentAccount: Account,
|
||||
) {
|
||||
if (accountDTO.accountType !== parentAccount.accountType) {
|
||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_HAS_DIFFERENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account of throw service error in case account not found.
|
||||
* @param {number} accountId
|
||||
* @return {IAccount}
|
||||
*/
|
||||
public async getAccountOrThrowError(accountId: number) {
|
||||
const account = await this.accountRepository.findOneById(accountId);
|
||||
|
||||
if (!account) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the max depth level of accounts chart.
|
||||
* @param {number} parentAccountId - Parent account id.
|
||||
*/
|
||||
public async validateMaxParentAccountDepthLevels(parentAccountId: number) {
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
const parentDependantsIds = accountsGraph.dependantsOf(parentAccountId);
|
||||
|
||||
if (parentDependantsIds.length >= MAX_ACCOUNTS_CHART_DEPTH) {
|
||||
throw new ServiceError(ERRORS.PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsInt,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
IsBoolean,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateAccountDTO {
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(6)
|
||||
code?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
currencyCode?: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
accountType: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
parentAccountId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidAccountId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidItemId?: string;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { kebabCase } from 'lodash';
|
||||
import { Knex } from 'knex';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import {
|
||||
// IAccount,
|
||||
// IAccountEventCreatedPayload,
|
||||
// IAccountCreateDTO,
|
||||
IAccountEventCreatingPayload,
|
||||
CreateAccountParams,
|
||||
} from './Accounts.types';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { Account } from './models/Account.model';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CreateAccountService {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly validator: CommandAccountValidators,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the account creation.
|
||||
* @param {CreateAccountDTO} accountDTO
|
||||
*/
|
||||
private authorize = async (
|
||||
accountDTO: CreateAccountDTO,
|
||||
baseCurrency: string,
|
||||
params?: CreateAccountParams,
|
||||
) => {
|
||||
// Validate account name uniquiness.
|
||||
if (!params.ignoreUniqueName) {
|
||||
await this.validator.validateAccountNameUniquiness(accountDTO.name);
|
||||
}
|
||||
// Validate the account code uniquiness.
|
||||
if (accountDTO.code) {
|
||||
await this.validator.isAccountCodeUniqueOrThrowError(accountDTO.code);
|
||||
}
|
||||
// Retrieve the account type meta or throw service error if not found.
|
||||
this.validator.getAccountTypeOrThrowError(accountDTO.accountType);
|
||||
|
||||
// Ingore the parent account validation if not presented.
|
||||
if (accountDTO.parentAccountId) {
|
||||
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
||||
accountDTO.parentAccountId,
|
||||
);
|
||||
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
|
||||
// Inherit active status from parent account.
|
||||
accountDTO.active = parentAccount.active;
|
||||
|
||||
// Validate should currency code be the same currency of parent account.
|
||||
this.validator.validateCurrentSameParentAccount(
|
||||
accountDTO,
|
||||
parentAccount,
|
||||
baseCurrency,
|
||||
);
|
||||
// Validates the max depth level of accounts chart.
|
||||
await this.validator.validateMaxParentAccountDepthLevels(
|
||||
accountDTO.parentAccountId,
|
||||
);
|
||||
}
|
||||
// Validates the given account type supports the multi-currency.
|
||||
this.validator.validateAccountTypeSupportCurrency(accountDTO, baseCurrency);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the create account DTO to input model.
|
||||
* @param {IAccountCreateDTO} createAccountDTO
|
||||
*/
|
||||
private transformDTOToModel = (
|
||||
createAccountDTO: CreateAccountDTO,
|
||||
baseCurrency: string,
|
||||
) => {
|
||||
return {
|
||||
...createAccountDTO,
|
||||
slug: kebabCase(createAccountDTO.name),
|
||||
currencyCode: createAccountDTO.currencyCode || baseCurrency,
|
||||
|
||||
// Mark the account is Plaid owner since Plaid item/account is defined on creating.
|
||||
isSyncingOwner: Boolean(
|
||||
createAccountDTO.plaidAccountId || createAccountDTO.plaidItemId,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new account on the storage.
|
||||
* @param {IAccountCreateDTO} accountDTO
|
||||
* @returns {Promise<IAccount>}
|
||||
*/
|
||||
public createAccount = async (
|
||||
accountDTO: CreateAccountDTO,
|
||||
trx?: Knex.Transaction,
|
||||
params: CreateAccountParams = { ignoreUniqueName: false },
|
||||
): Promise<Account> => {
|
||||
// Retrieves the given tenant metadata.
|
||||
const tenant = await this.tenancyContext.getTenant(true);
|
||||
|
||||
// Authorize the account creation.
|
||||
await this.authorize(accountDTO, tenant.metadata.baseCurrency, params);
|
||||
|
||||
// Transformes the DTO to model.
|
||||
const accountInputModel = this.transformDTOToModel(
|
||||
accountDTO,
|
||||
tenant.metadata.baseCurrency,
|
||||
);
|
||||
// Creates a new account with associated transactions under unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onAccountCreating` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onCreating, {
|
||||
accountDTO,
|
||||
trx,
|
||||
} as IAccountEventCreatingPayload);
|
||||
|
||||
// Inserts account to the storage.
|
||||
const account = await this.accountModel.query().insert({
|
||||
...accountInputModel,
|
||||
});
|
||||
// Triggers `onAccountCreated` event.
|
||||
// await this.eventEmitter.emitAsync(events.accounts.onCreated, {
|
||||
// account,
|
||||
// accountId: account.id,
|
||||
// trx,
|
||||
// } as IAccountEventCreatedPayload);
|
||||
|
||||
return account;
|
||||
}, trx);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
// import { IAccountEventDeletedPayload } from '@/interfaces';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { Account } from './models/Account.model';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { IAccountEventDeletedPayload } from './Accounts.types';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteAccount {
|
||||
constructor(
|
||||
@Inject(Account.name) private accountModel: typeof Account,
|
||||
private eventEmitter: EventEmitter2,
|
||||
private uow: UnitOfWork,
|
||||
private validator: CommandAccountValidators,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize account delete.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
private authorize = async (accountId: number, oldAccount: Account) => {
|
||||
// Throw error if the account was predefined.
|
||||
this.validator.throwErrorIfAccountPredefined(oldAccount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unlink the given parent account with children accounts.
|
||||
* @param {number|number[]} parentAccountId -
|
||||
*/
|
||||
private async unassociateChildrenAccountsFromParent(
|
||||
parentAccountId: number | number[],
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const accountsIds = Array.isArray(parentAccountId)
|
||||
? parentAccountId
|
||||
: [parentAccountId];
|
||||
|
||||
await this.accountModel
|
||||
.query(trx)
|
||||
.whereIn('parent_account_id', accountsIds)
|
||||
.patch({ parent_account_id: null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the account from the storage.
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public deleteAccount = async (accountId: number): Promise<void> => {
|
||||
// Retrieve account or not found service error.
|
||||
const oldAccount = await this.accountModel.query().findById(accountId);
|
||||
|
||||
// Authorize before delete account.
|
||||
await this.authorize(accountId, oldAccount);
|
||||
|
||||
// Deletes the account and associated transactions under UOW environment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onAccountDelete` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onDelete, {
|
||||
trx,
|
||||
oldAccount,
|
||||
} as IAccountEventDeletedPayload);
|
||||
|
||||
// Unlink the parent account from children accounts.
|
||||
await this.unassociateChildrenAccountsFromParent(accountId, trx);
|
||||
|
||||
// Deletes account by the given id.
|
||||
await this.accountModel.query(trx).deleteById(accountId);
|
||||
|
||||
// Triggers `onAccountDeleted` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onDeleted, {
|
||||
accountId,
|
||||
oldAccount,
|
||||
trx,
|
||||
} as IAccountEventDeletedPayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
34
packages/server-nest/src/modules/Accounts/EditAccount.dto.ts
Normal file
34
packages/server-nest/src/modules/Accounts/EditAccount.dto.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsInt,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class EditAccountDTO {
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(6)
|
||||
code?: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
|
||||
accountType: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
parentAccountId?: number;
|
||||
}
|
||||
100
packages/server-nest/src/modules/Accounts/EditAccount.service.ts
Normal file
100
packages/server-nest/src/modules/Accounts/EditAccount.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { CommandAccountValidators } from './CommandAccountValidators.service';
|
||||
import { Account } from './models/Account.model';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
|
||||
@Injectable()
|
||||
export class EditAccount {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly validator: CommandAccountValidators,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the account editing.
|
||||
* @param {number} accountId
|
||||
* @param {IAccountEditDTO} accountDTO
|
||||
* @param {IAccount} oldAccount -
|
||||
*/
|
||||
private authorize = async (
|
||||
accountId: number,
|
||||
accountDTO: EditAccountDTO,
|
||||
oldAccount: Account,
|
||||
) => {
|
||||
// Validate account name uniquiness.
|
||||
await this.validator.validateAccountNameUniquiness(
|
||||
accountDTO.name,
|
||||
accountId,
|
||||
);
|
||||
// Validate the account type should be not mutated.
|
||||
await this.validator.isAccountTypeChangedOrThrowError(
|
||||
oldAccount,
|
||||
accountDTO,
|
||||
);
|
||||
// Validate the account code not exists on the storage.
|
||||
if (accountDTO.code && accountDTO.code !== oldAccount.code) {
|
||||
await this.validator.isAccountCodeUniqueOrThrowError(
|
||||
accountDTO.code,
|
||||
oldAccount.id,
|
||||
);
|
||||
}
|
||||
// Retrieve the parent account of throw not found service error.
|
||||
if (accountDTO.parentAccountId) {
|
||||
const parentAccount = await this.validator.getParentAccountOrThrowError(
|
||||
accountDTO.parentAccountId,
|
||||
oldAccount.id,
|
||||
);
|
||||
this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits details of the given account.
|
||||
* @param {number} accountId
|
||||
* @param {IAccountDTO} accountDTO
|
||||
*/
|
||||
public async editAccount(
|
||||
accountId: number,
|
||||
accountDTO: EditAccountDTO,
|
||||
): Promise<Account> {
|
||||
// Retrieve the old account or throw not found service error.
|
||||
const oldAccount = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Authorize the account editing.
|
||||
await this.authorize(accountId, accountDTO, oldAccount);
|
||||
|
||||
// Edits account and associated transactions under unit-of-work environment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onAccountEditing` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onEditing, {
|
||||
oldAccount,
|
||||
accountDTO,
|
||||
});
|
||||
// Update the account on the storage.
|
||||
const account = await this.accountModel
|
||||
.query(trx)
|
||||
.findById(accountId)
|
||||
.updateAndFetch({ ...accountDTO });
|
||||
|
||||
// Triggers `onAccountEdited` event.
|
||||
// await this.eventEmitter.emitAsync(events.accounts.onEdited, {
|
||||
// account,
|
||||
// oldAccount,
|
||||
// trx,
|
||||
// } as IAccountEventEditedPayload);
|
||||
|
||||
return account;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { AccountTransformer } from './Account.transformer';
|
||||
import { Account } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class GetAccount {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the given account details.
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public getAccount = async (accountId: number) => {
|
||||
// Find the given account or throw not found error.
|
||||
const account = await this.accountModel
|
||||
.query()
|
||||
.findById(accountId)
|
||||
.withGraphFetched('plaidItem')
|
||||
.throwIfNotFound();
|
||||
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
|
||||
// Transforms the account model to POJO.
|
||||
const transformed = await this.transformer.transform(
|
||||
account,
|
||||
new AccountTransformer(),
|
||||
{ accountsGraph },
|
||||
);
|
||||
const eventPayload = { accountId };
|
||||
|
||||
// Triggers `onAccountViewed` event.
|
||||
await this.eventEmitter.emitAsync(events.accounts.onViewed, eventPayload);
|
||||
|
||||
return transformed;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
IAccountsTransactionsFilter,
|
||||
IGetAccountTransactionPOJO,
|
||||
} from './Accounts.types';
|
||||
import { AccountTransactionTransformer } from './AccountTransaction.transformer';
|
||||
import { AccountTransaction } from './models/AccountTransaction.model';
|
||||
import { Account } from './models/Account.model';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
|
||||
@Injectable()
|
||||
export class GetAccountTransactionsService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(AccountTransaction.name)
|
||||
private readonly accountTransaction: typeof AccountTransaction,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly account: typeof Account,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the accounts transactions.
|
||||
* @param {IAccountsTransactionsFilter} filter -
|
||||
*/
|
||||
public getAccountsTransactions = async (
|
||||
filter: IAccountsTransactionsFilter,
|
||||
): Promise<IGetAccountTransactionPOJO[]> => {
|
||||
// Retrieve the given account or throw not found error.
|
||||
if (filter.accountId) {
|
||||
await this.account.query().findById(filter.accountId).throwIfNotFound();
|
||||
}
|
||||
const transactions = await this.accountTransaction
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
query.orderBy('date', 'DESC');
|
||||
|
||||
if (filter.accountId) {
|
||||
query.where('account_id', filter.accountId);
|
||||
}
|
||||
query.withGraphFetched('account');
|
||||
query.withGraphFetched('contact');
|
||||
query.limit(filter.limit || 50);
|
||||
});
|
||||
// Transform the account transaction.
|
||||
return this.transformer.transform(
|
||||
transactions,
|
||||
new AccountTransactionTransformer(),
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// import { IAccountType } from './Accounts.types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AccountTypesUtils } from './utils/AccountType.utils';
|
||||
|
||||
@Injectable()
|
||||
export class GetAccountTypesService {
|
||||
/**
|
||||
* Retrieve all accounts types.
|
||||
* @param {number} tenantId -
|
||||
* @return {IAccountType}
|
||||
*/
|
||||
public getAccountsTypes() {
|
||||
const accountTypes = AccountTypesUtils.getList();
|
||||
|
||||
return accountTypes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as R from 'ramda';
|
||||
import { IAccountsFilter } from './Accounts.types';
|
||||
import { DynamicListService } from '../DynamicListing/DynamicList.service';
|
||||
import { AccountTransformer } from './Account.transformer';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { Account } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { IFilterMeta } from '@/interfaces/Model';
|
||||
|
||||
@Injectable()
|
||||
export class GetAccountsService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformerService: TransformerInjectable,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve accounts datatable list.
|
||||
* @param {IAccountsFilter} accountsFilter
|
||||
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
*/
|
||||
public async getAccountsList(
|
||||
filterDTO: IAccountsFilter,
|
||||
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> {
|
||||
// Parses the stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
this.accountModel,
|
||||
filter,
|
||||
);
|
||||
// Retrieve accounts model based on the given query.
|
||||
const accounts = await this.accountModel.query().onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
});
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
|
||||
// Retrieves the transformed accounts collection.
|
||||
const transformedAccounts = await this.transformerService.transform(
|
||||
accounts,
|
||||
new AccountTransformer(),
|
||||
{ accountsGraph, structure: filterDTO.structure },
|
||||
);
|
||||
|
||||
return {
|
||||
accounts: transformedAccounts,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsees accounts list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
// @Service()
|
||||
// export class MutateBaseCurrencyAccounts {
|
||||
// @Inject()
|
||||
// tenancy: HasTenancyService;
|
||||
|
||||
// /**
|
||||
// * Mutates the all accounts or the organziation.
|
||||
// * @param {number} tenantId
|
||||
// * @param {string} currencyCode
|
||||
// */
|
||||
// public mutateAllAccountsCurrency = async (
|
||||
// tenantId: number,
|
||||
// currencyCode: string
|
||||
// ) => {
|
||||
// const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
// await Account.query().update({ currencyCode });
|
||||
// };
|
||||
// }
|
||||
103
packages/server-nest/src/modules/Accounts/constants.ts
Normal file
103
packages/server-nest/src/modules/Accounts/constants.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
export const ERRORS = {
|
||||
ACCOUNT_NOT_FOUND: 'account_not_found',
|
||||
ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found',
|
||||
PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found',
|
||||
ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique',
|
||||
ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue',
|
||||
PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type',
|
||||
ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed',
|
||||
ACCOUNT_PREDEFINED: 'account_predefined',
|
||||
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
||||
PREDEFINED_ACCOUNTS: 'predefined_accounts',
|
||||
ACCOUNTS_HAVE_TRANSACTIONS: 'accounts_have_transactions',
|
||||
CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE:
|
||||
'close_account_and_to_account_not_same_type',
|
||||
ACCOUNTS_NOT_FOUND: 'accounts_not_found',
|
||||
ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY:
|
||||
'ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY',
|
||||
ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT:
|
||||
'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT',
|
||||
PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL:
|
||||
'PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL',
|
||||
};
|
||||
|
||||
// Default views columns.
|
||||
export const DEFAULT_VIEW_COLUMNS = [
|
||||
{ key: 'name', label: 'Account name' },
|
||||
{ key: 'code', label: 'Account code' },
|
||||
{ key: 'account_type_label', label: 'Account type' },
|
||||
{ key: 'account_normal', label: 'Account normal' },
|
||||
{ key: 'amount', label: 'Balance' },
|
||||
{ key: 'currencyCode', label: 'Currency' },
|
||||
];
|
||||
|
||||
export const MAX_ACCOUNTS_CHART_DEPTH = 5;
|
||||
|
||||
// Accounts default views.
|
||||
export const DEFAULT_VIEWS = [
|
||||
{
|
||||
name: 'Assets',
|
||||
slug: 'assets',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{ index: 1, fieldKey: 'root_type', comparator: 'equals', value: 'asset' },
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Liabilities',
|
||||
slug: 'liabilities',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'liability',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Equity',
|
||||
slug: 'equity',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'equity',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Income',
|
||||
slug: 'income',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'income',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Expenses',
|
||||
slug: 'expenses',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
fieldKey: 'root_type',
|
||||
index: 1,
|
||||
comparator: 'equals',
|
||||
value: 'expense',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,458 @@
|
||||
/* eslint-disable global-require */
|
||||
// import { mixin, Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import DependencyGraph from '@/libs/dependency-graph';
|
||||
import {
|
||||
ACCOUNT_TYPES,
|
||||
getAccountsSupportsMultiCurrency,
|
||||
} from '@/constants/accounts';
|
||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
// import { SearchableModel } from '@/modules/Search/SearchableMdel';
|
||||
// import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
|
||||
// import { ModelSettings } from '@/modules/Settings/ModelSettings';
|
||||
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
||||
import { Model } from 'objection';
|
||||
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||
// import AccountSettings from './Account.Settings';
|
||||
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
|
||||
// import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder';
|
||||
// import { flatToNestedArray } from 'utils';
|
||||
|
||||
// @ts-expect-error
|
||||
// export class Account extends mixin(TenantModel, [
|
||||
// ModelSettings,
|
||||
// CustomViewBaseModel,
|
||||
// SearchableModel,
|
||||
// ]) {
|
||||
|
||||
export class Account extends TenantModel {
|
||||
public name!: string;
|
||||
public slug!: string;
|
||||
public code!: string;
|
||||
public index!: number;
|
||||
public accountType!: string;
|
||||
public predefined!: boolean;
|
||||
public currencyCode!: string;
|
||||
public active!: boolean;
|
||||
public bankBalance!: number;
|
||||
public lastFeedsUpdatedAt!: string | null;
|
||||
public amount!: number;
|
||||
public plaidItemId!: number;
|
||||
|
||||
public plaidItem!: PlaidItem;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'accountTypeLabel',
|
||||
'accountParentType',
|
||||
'accountRootType',
|
||||
'accountNormal',
|
||||
'accountNormalFormatted',
|
||||
'isBalanceSheetAccount',
|
||||
'isPLSheet',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Account normal.
|
||||
*/
|
||||
get accountNormal() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'normal');
|
||||
}
|
||||
|
||||
get accountNormalFormatted() {
|
||||
const paris = {
|
||||
credit: 'Credit',
|
||||
debit: 'Debit',
|
||||
};
|
||||
return paris[this.accountNormal] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type label.
|
||||
*/
|
||||
get accountTypeLabel() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account parent type.
|
||||
*/
|
||||
get accountParentType() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'parentType');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account root type.
|
||||
*/
|
||||
get accountRootType() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'rootType');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether the account is balance sheet account.
|
||||
*/
|
||||
get isBalanceSheetAccount() {
|
||||
return this.isBalanceSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether the account is profit/loss sheet account.
|
||||
*/
|
||||
get isPLSheet() {
|
||||
return this.isProfitLossSheet();
|
||||
}
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
const TABLE_NAME = Account.tableName;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('accounts.active', !active);
|
||||
},
|
||||
|
||||
filterAccounts(query, accountIds) {
|
||||
if (accountIds.length > 0) {
|
||||
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
||||
}
|
||||
},
|
||||
filterAccountTypes(query, typesIds) {
|
||||
if (typesIds.length > 0) {
|
||||
query.whereIn('account_types.account_type_id', typesIds);
|
||||
}
|
||||
},
|
||||
viewRolesBuilder(query, conditionals, expression) {
|
||||
// buildFilterQuery(Account.tableName, conditionals, expression)(query);
|
||||
},
|
||||
sortColumnBuilder(query, columnKey, direction) {
|
||||
// buildSortColumnQuery(Account.tableName, columnKey, direction)(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by root type.
|
||||
*/
|
||||
filterByRootType(query, rootType) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.rootType === rootType,
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by account normal
|
||||
*/
|
||||
filterByAccountNormal(query, accountNormal) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.normal === accountNormal,
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds account by the given slug.
|
||||
* @param {*} query
|
||||
* @param {*} slug
|
||||
*/
|
||||
findBySlug(query, slug) {
|
||||
query.where('slug', slug).first();
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} baseCyrrency
|
||||
*/
|
||||
preventMutateBaseCurrency(query) {
|
||||
const accountsTypes = getAccountsSupportsMultiCurrency();
|
||||
const accountsTypesKeys = accountsTypes.map((type) => type.key);
|
||||
|
||||
query
|
||||
.whereIn('accountType', accountsTypesKeys)
|
||||
.where('seededAt', null)
|
||||
.first();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { AccountTransaction } = require('./AccountTransaction.model');
|
||||
const { Item } = require('../../Items/models/Item');
|
||||
// const InventoryAdjustment = require('models/InventoryAdjustment');
|
||||
// const ManualJournalEntry = require('models/ManualJournalEntry');
|
||||
// const Expense = require('models/Expense');
|
||||
// const ExpenseEntry = require('models/ExpenseCategory');
|
||||
// const ItemEntry = require('models/ItemEntry');
|
||||
// const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
|
||||
const { PlaidItem } = require('../../BankingPlaid/models/PlaidItem');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Account model may has many transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'accounts_transactions.accountId',
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Account may has many items as cost account.
|
||||
*/
|
||||
itemsCostAccount: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Item,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'items.costAccountId',
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Account may has many items as sell account.
|
||||
*/
|
||||
itemsSellAccount: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Item,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'items.sellAccountId',
|
||||
},
|
||||
},
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// inventoryAdjustments: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: InventoryAdjustment.default,
|
||||
// join: {
|
||||
// from: 'accounts.id',
|
||||
// to: 'inventory_adjustments.adjustmentAccountId',
|
||||
// },
|
||||
// },
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// manualJournalEntries: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: ManualJournalEntry.default,
|
||||
// join: {
|
||||
// from: 'accounts.id',
|
||||
// to: 'manual_journals_entries.accountId',
|
||||
// },
|
||||
// },
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// expensePayments: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: Expense.default,
|
||||
// join: {
|
||||
// from: 'accounts.id',
|
||||
// to: 'expenses_transactions.paymentAccountId',
|
||||
// },
|
||||
// },
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// expenseEntries: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: ExpenseEntry.default,
|
||||
// join: {
|
||||
// from: 'accounts.id',
|
||||
// to: 'expense_transaction_categories.expenseAccountId',
|
||||
// },
|
||||
// },
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// entriesCostAccount: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: ItemEntry.default,
|
||||
// join: {
|
||||
// from: 'accounts.id',
|
||||
// to: 'items_entries.costAccountId',
|
||||
// },
|
||||
// },
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// entriesSellAccount: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: ItemEntry.default,
|
||||
// join: {
|
||||
// from: 'accounts.id',
|
||||
// to: 'items_entries.sellAccountId',
|
||||
// },
|
||||
// },
|
||||
// /**
|
||||
// * Associated uncategorized transactions.
|
||||
// */
|
||||
// uncategorizedTransactions: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: UncategorizedTransaction.default,
|
||||
// join: {
|
||||
// from: 'accounts.id',
|
||||
// to: 'uncategorized_cashflow_transactions.accountId',
|
||||
// },
|
||||
// filter: (query) => {
|
||||
// query.where('categorized', false);
|
||||
// },
|
||||
// },
|
||||
/**
|
||||
* Account model may belongs to a Plaid item.
|
||||
*/
|
||||
plaidItem: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PlaidItem,
|
||||
join: {
|
||||
from: 'accounts.plaidItemId',
|
||||
to: 'plaid_items.plaidItemId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the given type equals the account type.
|
||||
* @param {string} accountType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAccountType(accountType) {
|
||||
const types = castArray(accountType);
|
||||
return types.indexOf(this.accountType) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the given root type equals the account type.
|
||||
* @param {string} rootType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isRootType(rootType) {
|
||||
return AccountTypesUtils.isRootTypeEqualsKey(this.accountType, rootType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the given parent type equals the account type.
|
||||
* @param {string} parentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isParentType(parentType) {
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||
this.accountType,
|
||||
parentType,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the account is balance sheet account.
|
||||
* @return {boolean}
|
||||
*/
|
||||
isBalanceSheet() {
|
||||
return AccountTypesUtils.isTypeBalanceSheet(this.accountType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the account is profit/loss account.
|
||||
* @return {boolean}
|
||||
*/
|
||||
isProfitLossSheet() {
|
||||
return AccountTypesUtils.isTypePLSheet(this.accountType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the account is income statement account
|
||||
* @return {boolean}
|
||||
*/
|
||||
isIncomeSheet() {
|
||||
return this.isProfitLossSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts flatten accounts list to nested array.
|
||||
* @param {Array} accounts
|
||||
* @param {Object} options
|
||||
*/
|
||||
static toNestedArray(accounts, options = { children: 'children' }) {
|
||||
// return flatToNestedArray(accounts, {
|
||||
// id: 'id',
|
||||
// parentId: 'parentAccountId',
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the accounts list to depenedency graph structure.
|
||||
* @param {IAccount[]} accounts
|
||||
*/
|
||||
static toDependencyGraph(accounts) {
|
||||
return DependencyGraph.fromArray(accounts, {
|
||||
itemId: 'id',
|
||||
parentItemId: 'parentAccountId',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
// static get meta() {
|
||||
// return AccountSettings;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
// static get defaultViews() {
|
||||
// return DEFAULT_VIEWS;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import moment, { unitOfTime } from 'moment';
|
||||
import { isEmpty, castArray } from 'lodash';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { Account } from './Account.model';
|
||||
// import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||
|
||||
export class AccountTransaction extends BaseModel {
|
||||
referenceType: string;
|
||||
referenceId: number;
|
||||
accountId: number;
|
||||
contactId: number;
|
||||
credit: number;
|
||||
debit: number;
|
||||
exchangeRate: number;
|
||||
taxRate: number;
|
||||
date: Date | string;
|
||||
transactionType: string;
|
||||
currencyCode: string;
|
||||
referenceTypeFormatted: string;
|
||||
transactionNumber!: string;
|
||||
referenceNumber!: string;
|
||||
note!: string;
|
||||
|
||||
index!: number;
|
||||
indexGroup!: number;
|
||||
|
||||
taxRateId!: number;
|
||||
|
||||
branchId!: number;
|
||||
userId!: number;
|
||||
itemId!: number;
|
||||
projectId!: number;
|
||||
account: Account;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['referenceTypeFormatted', 'creditLocal', 'debitLocal'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit amount in base currency.
|
||||
* @return {number}
|
||||
*/
|
||||
get creditLocal() {
|
||||
return this.credit * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the debit amount in base currency.
|
||||
* @return {number}
|
||||
*/
|
||||
get debitLocal() {
|
||||
return this.debit * this.exchangeRate;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Retrieve formatted reference type.
|
||||
// * @return {string}
|
||||
// */
|
||||
// get referenceTypeFormatted() {
|
||||
// return getTransactionTypeLabel(this.referenceType, this.transactionType);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters accounts by the given ids.
|
||||
* @param {Query} query
|
||||
* @param {number[]} accountsIds
|
||||
*/
|
||||
filterAccounts(query, accountsIds) {
|
||||
if (Array.isArray(accountsIds) && accountsIds.length > 0) {
|
||||
query.whereIn('account_id', accountsIds);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the transaction types.
|
||||
* @param {Query} query
|
||||
* @param {string[]} types
|
||||
*/
|
||||
filterTransactionTypes(query, types) {
|
||||
if (Array.isArray(types) && types.length > 0) {
|
||||
query.whereIn('reference_type', types);
|
||||
} else if (typeof types === 'string') {
|
||||
query.where('reference_type', types);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the date range.
|
||||
* @param {Query} query
|
||||
* @param {moment.MomentInput} startDate
|
||||
* @param {moment.MomentInput} endDate
|
||||
* @param {unitOfTime.StartOf} type
|
||||
*/
|
||||
filterDateRange(
|
||||
query,
|
||||
startDate: moment.MomentInput,
|
||||
endDate: moment.MomentInput,
|
||||
type: unitOfTime.StartOf = 'day',
|
||||
) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
if (startDate) {
|
||||
query.where('date', '>=', fromDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('date', '<=', toDate);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the amount range.
|
||||
* @param {Query} query
|
||||
* @param {number} fromAmount
|
||||
* @param {number} toAmount
|
||||
*/
|
||||
filterAmountRange(query, fromAmount, toAmount) {
|
||||
if (fromAmount) {
|
||||
query.andWhere((q) => {
|
||||
q.where('credit', '>=', fromAmount);
|
||||
q.orWhere('debit', '>=', fromAmount);
|
||||
});
|
||||
}
|
||||
if (toAmount) {
|
||||
query.andWhere((q) => {
|
||||
q.where('credit', '<=', toAmount);
|
||||
q.orWhere('debit', '<=', toAmount);
|
||||
});
|
||||
}
|
||||
},
|
||||
sumationCreditDebit(query) {
|
||||
query.select(['accountId']);
|
||||
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
query.groupBy('account_id');
|
||||
},
|
||||
filterContactType(query, contactType) {
|
||||
query.where('contact_type', contactType);
|
||||
},
|
||||
filterContactIds(query, contactIds) {
|
||||
query.whereIn('contact_id', contactIds);
|
||||
},
|
||||
openingBalance(query, fromDate) {
|
||||
query.modify('filterDateRange', null, fromDate);
|
||||
query.modify('sumationCreditDebit');
|
||||
},
|
||||
closingBalance(query, toDate) {
|
||||
query.modify('filterDateRange', null, toDate);
|
||||
query.modify('sumationCreditDebit');
|
||||
},
|
||||
contactsOpeningBalance(
|
||||
query,
|
||||
openingDate,
|
||||
receivableAccounts,
|
||||
customersIds,
|
||||
) {
|
||||
// Filter by date.
|
||||
query.modify('filterDateRange', null, openingDate);
|
||||
|
||||
// Filter by customers.
|
||||
query.whereNot('contactId', null);
|
||||
query.whereIn('accountId', castArray(receivableAccounts));
|
||||
|
||||
if (!isEmpty(customersIds)) {
|
||||
query.whereIn('contactId', castArray(customersIds));
|
||||
}
|
||||
// Group by the contact transactions.
|
||||
query.groupBy('contactId');
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
query.select('contactId');
|
||||
},
|
||||
creditDebitSummation(query) {
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
},
|
||||
groupByDateFormat(query, groupType = 'month') {
|
||||
const groupBy = {
|
||||
day: '%Y-%m-%d',
|
||||
month: '%Y-%m',
|
||||
year: '%Y',
|
||||
};
|
||||
const dateFormat = groupBy[groupType];
|
||||
|
||||
query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date'));
|
||||
query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`);
|
||||
},
|
||||
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branchId', formattedBranchesIds);
|
||||
},
|
||||
|
||||
filterByProjects(query, projectsIds) {
|
||||
const formattedProjectsIds = castArray(projectsIds);
|
||||
|
||||
query.whereIn('projectId', formattedProjectsIds);
|
||||
},
|
||||
|
||||
filterByReference(query, referenceId: number, referenceType: string) {
|
||||
query.where('reference_id', referenceId);
|
||||
query.where('reference_type', referenceType);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { Account } = require('./Account.model');
|
||||
const { Contact } = require('../../Contacts/models/Contact');
|
||||
|
||||
return {
|
||||
account: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'accounts_transactions.accountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact,
|
||||
join: {
|
||||
from: 'accounts_transactions.contactId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { TenantRepository } from '@/common/repository/TenantRepository';
|
||||
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
|
||||
import { Account } from '../models/Account.model';
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import {
|
||||
DiscountExpenseAccount,
|
||||
OtherChargesAccount,
|
||||
OtherExpensesAccount,
|
||||
PrepardExpenses,
|
||||
PurchaseDiscountAccount,
|
||||
StripeClearingAccount,
|
||||
TaxPayableAccount,
|
||||
UnearnedRevenueAccount,
|
||||
} from '../Accounts.constants';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class AccountRepository extends TenantRepository {
|
||||
constructor(
|
||||
private readonly i18n: I18nService,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
|
||||
@Inject(TENANCY_DB_CONNECTION)
|
||||
private readonly tenantDBKnex: Knex,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
get model(): typeof Account {
|
||||
return Account.bindKnex(this.tenantDBKnex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts dependency graph.
|
||||
* @param {string} withRelation
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns {}
|
||||
*/
|
||||
public async getDependencyGraph(
|
||||
withRelation?: string,
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const accounts = await this.all(withRelation, trx);
|
||||
|
||||
return this.model.toDependencyGraph(accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account by slug.
|
||||
* @param {string} slug
|
||||
* @return {Promise<IAccount>}
|
||||
*/
|
||||
public findBySlug(slug: string) {
|
||||
return this.findOne({ slug });
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Changes account balance.
|
||||
// * @param {number} accountId
|
||||
// * @param {number} amount
|
||||
// * @return {Promise<void>}
|
||||
// */
|
||||
// async balanceChange(accountId: number, amount: number): Promise<void> {
|
||||
// const method: string = amount < 0 ? 'decrement' : 'increment';
|
||||
|
||||
// await this.model.query().where('id', accountId)[method]('amount', amount);
|
||||
// this.flushCache();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
activateById(userId: number): Promise<number> {
|
||||
return super.update({ active: 1 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
inactivateById(userId: number): Promise<number> {
|
||||
return super.update({ active: 0 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async activateByIds(userIds: number[], trx): Promise<number> {
|
||||
const results = await this.model
|
||||
.query(trx)
|
||||
.whereIn('id', userIds)
|
||||
.patch({ active: true });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async inactivateByIds(userIds: number[], trx): Promise<number> {
|
||||
const results = await this.model
|
||||
.query(trx)
|
||||
.whereIn('id', userIds)
|
||||
.patch({ active: false });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} currencyCode
|
||||
* @param extraAttrs
|
||||
* @param trx
|
||||
* @returns
|
||||
*/
|
||||
findOrCreateAccountReceivable = async (
|
||||
currencyCode: string = '',
|
||||
extraAttrs = {},
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.onBuild((query) => {
|
||||
if (currencyCode) {
|
||||
query.where('currencyCode', currencyCode);
|
||||
}
|
||||
query.where('accountType', 'accounts-receivable');
|
||||
})
|
||||
.first();
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
name: this.i18n.t('account.accounts_receivable.currency', {
|
||||
args: { currency: currencyCode },
|
||||
}),
|
||||
accountType: 'accounts-receivable',
|
||||
currencyCode,
|
||||
active: 1,
|
||||
...extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find or create tax payable account.
|
||||
* @param {Record<string, string>}extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
async findOrCreateTaxPayable(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: TaxPayableAccount.slug, ...extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...TaxPayableAccount,
|
||||
...extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
findOrCreateAccountsPayable = async (
|
||||
currencyCode: string = '',
|
||||
extraAttrs = {},
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.onBuild((query) => {
|
||||
if (currencyCode) {
|
||||
query.where('currencyCode', currencyCode);
|
||||
}
|
||||
query.where('accountType', 'accounts-payable');
|
||||
})
|
||||
.first();
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
name: this.i18n.t('account.accounts_payable.currency', {
|
||||
args: { currency: currencyCode },
|
||||
}),
|
||||
accountType: 'accounts-payable',
|
||||
currencyCode,
|
||||
active: 1,
|
||||
...extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds or creates the unearned revenue.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreateUnearnedRevenue(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
const _extraAttrs = {
|
||||
currencyCode: tenantMeta.baseCurrency,
|
||||
...extraAttrs,
|
||||
};
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...UnearnedRevenueAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds or creates the prepard expenses account.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreatePrepardExpenses(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
const _extraAttrs = {
|
||||
currencyCode: tenantMeta.baseCurrency,
|
||||
...extraAttrs,
|
||||
};
|
||||
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...PrepardExpenses,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds or creates the stripe clearing account.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreateStripeClearing(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
const _extraAttrs = {
|
||||
currencyCode: tenantMeta.baseCurrency,
|
||||
...extraAttrs,
|
||||
};
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: StripeClearingAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...StripeClearingAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds or creates the discount expense account.
|
||||
* @param {Record<string, string>} extraAttrs
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns
|
||||
*/
|
||||
public async findOrCreateDiscountAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
const _extraAttrs = {
|
||||
currencyCode: tenantMeta.baseCurrency,
|
||||
...extraAttrs,
|
||||
};
|
||||
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: DiscountExpenseAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...DiscountExpenseAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async findOrCreatePurchaseDiscountAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
const _extraAttrs = {
|
||||
currencyCode: tenantMeta.baseCurrency,
|
||||
...extraAttrs,
|
||||
};
|
||||
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: PurchaseDiscountAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...PurchaseDiscountAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async findOrCreateOtherChargesAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
const _extraAttrs = {
|
||||
currencyCode: tenantMeta.baseCurrency,
|
||||
...extraAttrs,
|
||||
};
|
||||
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: OtherChargesAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...OtherChargesAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async findOrCreateOtherExpensesAccount(
|
||||
extraAttrs: Record<string, string> = {},
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
const _extraAttrs = {
|
||||
currencyCode: tenantMeta.baseCurrency,
|
||||
...extraAttrs,
|
||||
};
|
||||
let result = await this.model
|
||||
.query(trx)
|
||||
.findOne({ slug: OtherExpensesAccount.slug, ..._extraAttrs });
|
||||
|
||||
if (!result) {
|
||||
result = await this.model.query(trx).insertAndFetch({
|
||||
...OtherExpensesAccount,
|
||||
..._extraAttrs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { MutateBaseCurrencyAccounts } from '../MutateBaseCurrencyAccounts';
|
||||
|
||||
// @Service()
|
||||
// export class MutateBaseCurrencyAccountsSubscriber {
|
||||
// @Inject()
|
||||
// public mutateBaseCurrencyAccounts: MutateBaseCurrencyAccounts;
|
||||
|
||||
// /**
|
||||
// * Attaches the events with handles.
|
||||
// * @param bus
|
||||
// */
|
||||
// attach(bus) {
|
||||
// bus.subscribe(
|
||||
// events.organization.baseCurrencyUpdated,
|
||||
// this.updateAccountsCurrencyOnBaseCurrencyMutated
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Updates the all accounts currency once the base currency
|
||||
// * of the organization is mutated.
|
||||
// */
|
||||
// private updateAccountsCurrencyOnBaseCurrencyMutated = async ({
|
||||
// tenantId,
|
||||
// organizationDTO,
|
||||
// }) => {
|
||||
// await this.mutateBaseCurrencyAccounts.mutateAllAccountsCurrency(
|
||||
// tenantId,
|
||||
// organizationDTO.baseCurrency
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
@@ -0,0 +1,101 @@
|
||||
import { get } from 'lodash';
|
||||
import { ACCOUNT_TYPES } from '../Accounts.constants';
|
||||
|
||||
export class AccountTypesUtils {
|
||||
/**
|
||||
* Retrieve account types list.
|
||||
*/
|
||||
static getList() {
|
||||
return ACCOUNT_TYPES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the given root type.
|
||||
* @param {string} rootType -
|
||||
* @return {string}
|
||||
*/
|
||||
static getTypesByRootType(rootType: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.rootType === rootType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type by the given account type key.
|
||||
* @param {string} key
|
||||
* @param {string} accessor
|
||||
*/
|
||||
static getType(key: string, accessor?: string) {
|
||||
const type = ACCOUNT_TYPES.find((type) => type.key === key);
|
||||
|
||||
if (accessor) {
|
||||
return get(type, accessor);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the parent account type.
|
||||
* @param {string} parentType
|
||||
*/
|
||||
static getTypesByParentType(parentType: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.parentType === parentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts types by the given account normal.
|
||||
* @param {string} normal
|
||||
*/
|
||||
static getTypesByNormal(normal: string) {
|
||||
return ACCOUNT_TYPES.filter((type) => type.normal === normal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the root type equals the account type.
|
||||
* @param {string} key
|
||||
* @param {string} rootType
|
||||
*/
|
||||
static isRootTypeEqualsKey(key: string, rootType: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
const isRootType = type.rootType === rootType;
|
||||
|
||||
return isType && isRootType;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the parent account type equals the account type key.
|
||||
* @param {string} key - Account type key.
|
||||
* @param {string} parentType - Account parent type.
|
||||
*/
|
||||
static isParentTypeEqualsKey(key: string, parentType: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
const isParentType = type.parentType === parentType;
|
||||
|
||||
return isType && isParentType;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether account type has balance sheet.
|
||||
* @param {string} key - Account type key.
|
||||
*
|
||||
*/
|
||||
static isTypeBalanceSheet(key: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
return isType && type.balanceSheet;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether account type has profit/loss sheet.
|
||||
* @param {string} key - Account type key.
|
||||
*/
|
||||
static isTypePLSheet(key: string): boolean {
|
||||
return ACCOUNT_TYPES.some((type) => {
|
||||
const isType = type.key === key;
|
||||
return isType && type.incomeSheet;
|
||||
});
|
||||
}
|
||||
}
|
||||
22
packages/server-nest/src/modules/App/App.controller.spec.ts
Normal file
22
packages/server-nest/src/modules/App/App.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './App.controller';
|
||||
import { AppService } from './App.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
12
packages/server-nest/src/modules/App/App.controller.ts
Normal file
12
packages/server-nest/src/modules/App/App.controller.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './App.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
192
packages/server-nest/src/modules/App/App.module.ts
Normal file
192
packages/server-nest/src/modules/App/App.module.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
AcceptLanguageResolver,
|
||||
CookieResolver,
|
||||
HeaderResolver,
|
||||
I18nModule,
|
||||
QueryResolver,
|
||||
} from 'nestjs-i18n';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ClsModule } from 'nestjs-cls';
|
||||
import { AppController } from './App.controller';
|
||||
import { AppService } from './App.service';
|
||||
import { ItemsModule } from '../Items/items.module';
|
||||
import { config } from '../../common/config';
|
||||
import { SystemDatabaseModule } from '../System/SystemDB/SystemDB.module';
|
||||
import { SystemModelsModule } from '../System/SystemModels/SystemModels.module';
|
||||
import { JwtStrategy } from '../Auth/Jwt.strategy';
|
||||
import { jwtConstants } from '../Auth/Auth.constants';
|
||||
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||
import { TenancyModelsModule } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { LoggerMiddleware } from '@/middleware/logger.middleware';
|
||||
import { ExcludeNullInterceptor } from '@/interceptors/ExcludeNull.interceptor';
|
||||
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
||||
import { JwtAuthGuard } from '../Auth/Jwt.guard';
|
||||
import { UserIpInterceptor } from '@/interceptors/user-ip.interceptor';
|
||||
import { TenancyGlobalMiddleware } from '../Tenancy/TenancyGlobal.middleware';
|
||||
import { TransformerModule } from '../Transformer/Transformer.module';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
import { ExpensesModule } from '../Expenses/Expenses.module';
|
||||
import { ItemCategoryModule } from '../ItemCategories/ItemCategory.module';
|
||||
import { TaxRatesModule } from '../TaxRates/TaxRate.module';
|
||||
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
|
||||
import { BranchesModule } from '../Branches/Branches.module';
|
||||
import { WarehousesModule } from '../Warehouses/Warehouses.module';
|
||||
import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor';
|
||||
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
|
||||
import { CustomersModule } from '../Customers/Customers.module';
|
||||
import { VendorsModule } from '../Vendors/Vendors.module';
|
||||
import { SaleEstimatesModule } from '../SaleEstimates/SaleEstimates.module';
|
||||
import { BillsModule } from '../Bills/Bills.module';
|
||||
import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
||||
import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
|
||||
import { ManualJournalsModule } from '../ManualJournals/ManualJournals.module';
|
||||
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
|
||||
import { VendorCreditsModule } from '../VendorCredit/VendorCredits.module';
|
||||
import { VendorCreditApplyBillsModule } from '../VendorCreditsApplyBills/VendorCreditApplyBills.module';
|
||||
import { VendorCreditsRefundModule } from '../VendorCreditsRefund/VendorCreditsRefund.module';
|
||||
import { CreditNoteRefundsModule } from '../CreditNoteRefunds/CreditNoteRefunds.module';
|
||||
import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
|
||||
import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { BankRulesModule } from '../BankRules/BankRules.module';
|
||||
import { BankAccountsModule } from '../BankingAccounts/BankAccounts.module';
|
||||
import { BankingTransactionsExcludeModule } from '../BankingTransactionsExclude/BankingTransactionsExclude.module';
|
||||
import { BankingTransactionsRegonizeModule } from '../BankingTranasctionsRegonize/BankingTransactionsRegonize.module';
|
||||
import { BankingMatchingModule } from '../BankingMatching/BankingMatching.module';
|
||||
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||
import { TransactionsLockingModule } from '../TransactionsLocking/TransactionsLocking.module';
|
||||
import { SettingsModule } from '../Settings/Settings.module';
|
||||
import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module';
|
||||
import { PostHogModule } from '../EventsTracker/postHog.module';
|
||||
import { EventTrackerModule } from '../EventsTracker/EventTracker.module';
|
||||
import { MailModule } from '../Mail/Mail.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: '.env',
|
||||
load: config,
|
||||
isGlobal: true,
|
||||
}),
|
||||
SystemDatabaseModule,
|
||||
SystemModelsModule,
|
||||
EventEmitterModule.forRoot(),
|
||||
I18nModule.forRootAsync({
|
||||
useFactory: () => ({
|
||||
fallbackLanguage: 'en',
|
||||
loaderOptions: {
|
||||
path: join(__dirname, '/../../i18n/'),
|
||||
watch: true,
|
||||
},
|
||||
}),
|
||||
resolvers: [
|
||||
new QueryResolver(),
|
||||
new HeaderResolver(),
|
||||
new CookieResolver(),
|
||||
AcceptLanguageResolver,
|
||||
],
|
||||
}),
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: jwtConstants.secret,
|
||||
signOptions: { expiresIn: '60s' },
|
||||
}),
|
||||
BullModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
connection: {
|
||||
host: configService.get('QUEUE_HOST'),
|
||||
port: configService.get('QUEUE_PORT'),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
ClsModule.forRoot({
|
||||
global: true,
|
||||
middleware: {
|
||||
mount: true,
|
||||
setup: (cls, req: Request, res: Response) => {
|
||||
cls.set('organizationId', req.headers['organization-id']);
|
||||
cls.set('userId', 1);
|
||||
},
|
||||
},
|
||||
}),
|
||||
TenancyDatabaseModule,
|
||||
TenancyModelsModule,
|
||||
ChromiumlyTenancyModule,
|
||||
TransformerModule,
|
||||
MailModule,
|
||||
ItemsModule,
|
||||
ItemCategoryModule,
|
||||
AccountsModule,
|
||||
ExpensesModule,
|
||||
TaxRatesModule,
|
||||
PdfTemplatesModule,
|
||||
BranchesModule,
|
||||
WarehousesModule,
|
||||
CustomersModule,
|
||||
VendorsModule,
|
||||
SaleInvoicesModule,
|
||||
SaleEstimatesModule,
|
||||
SaleReceiptsModule,
|
||||
BillsModule,
|
||||
ManualJournalsModule,
|
||||
CreditNotesModule,
|
||||
VendorCreditsModule,
|
||||
VendorCreditApplyBillsModule,
|
||||
VendorCreditsRefundModule,
|
||||
CreditNoteRefundsModule,
|
||||
BillPaymentsModule,
|
||||
PaymentsReceivedModule,
|
||||
LedgerModule,
|
||||
BankAccountsModule,
|
||||
BankRulesModule,
|
||||
BankingTransactionsModule,
|
||||
BankingTransactionsExcludeModule,
|
||||
BankingTransactionsRegonizeModule,
|
||||
BankingMatchingModule,
|
||||
TransactionsLockingModule,
|
||||
SettingsModule,
|
||||
InventoryAdjustmentsModule,
|
||||
PostHogModule,
|
||||
EventTrackerModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: SerializeInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: JwtAuthGuard,
|
||||
},
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: UserIpInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: ExcludeNullInterceptor,
|
||||
},
|
||||
AppService,
|
||||
JwtStrategy,
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply(LoggerMiddleware)
|
||||
.forRoutes({ path: '*', method: RequestMethod.ALL });
|
||||
|
||||
consumer
|
||||
.apply(TenancyGlobalMiddleware)
|
||||
.forRoutes({ path: '*', method: RequestMethod.ALL });
|
||||
}
|
||||
}
|
||||
24
packages/server-nest/src/modules/App/App.service.ts
Normal file
24
packages/server-nest/src/modules/App/App.service.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
// configService: ConfigService;
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
getHello(): string {
|
||||
console.log(this.configService.get('DATABASE_PORT'));
|
||||
const payload = {};
|
||||
|
||||
const accessToken = this.jwtService.sign(payload);
|
||||
|
||||
console.log(accessToken);
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Transformer } from "../Transformer/Transformer";
|
||||
|
||||
export class AttachmentTransformer extends Transformer {
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['id', 'createdAt'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Includeded attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface AttachmentLinkDTO {
|
||||
key: string;
|
||||
}
|
||||
4
packages/server-nest/src/modules/Auth/Auth.constants.ts
Normal file
4
packages/server-nest/src/modules/Auth/Auth.constants.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const jwtConstants = {
|
||||
secret:
|
||||
'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.',
|
||||
};
|
||||
5
packages/server-nest/src/modules/Auth/Auth.interfaces.ts
Normal file
5
packages/server-nest/src/modules/Auth/Auth.interfaces.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface IAuthSignedInEventPayload {}
|
||||
|
||||
export interface IAuthSigningInEventPayload {}
|
||||
|
||||
export interface IAuthSignInPOJO {}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export class AuthApplication {
|
||||
|
||||
}
|
||||
32
packages/server-nest/src/modules/Auth/Jwt.guard.ts
Normal file
32
packages/server-nest/src/modules/Auth/Jwt.guard.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
Scope,
|
||||
SetMetadata,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const PublicRoute = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
private readonly cls: ClsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
canActivate(context: ExecutionContext) {
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
return super.canActivate(context);
|
||||
}
|
||||
}
|
||||
19
packages/server-nest/src/modules/Auth/Jwt.strategy.ts
Normal file
19
packages/server-nest/src/modules/Auth/Jwt.strategy.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { jwtConstants } from './Auth.constants';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: jwtConstants.secret,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
return { userId: payload.sub, username: payload.username };
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user