Compare commits
1176 Commits
exit-early
...
form-row-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f233bc218 | ||
|
|
448f4b94af | ||
|
|
7c58fb93a9 | ||
|
|
80e4e4a40e | ||
|
|
7161d56ae2 | ||
|
|
1e85c7b575 | ||
|
|
03130f0947 | ||
|
|
4812e25e5c | ||
|
|
e6c49da11c | ||
|
|
6f3ee1914a | ||
|
|
9232ee781e | ||
|
|
3d675d375c | ||
|
|
d2edc77197 | ||
|
|
53716cbe90 | ||
|
|
8ce3fe0df0 | ||
|
|
affee0a990 | ||
|
|
6d5f515f48 | ||
|
|
d96844498f | ||
|
|
08609d4f56 | ||
|
|
be344c440f | ||
|
|
96cc2c9ee9 | ||
|
|
fb1a89442c | ||
|
|
359e0197bf | ||
|
|
6dbb836a01 | ||
|
|
3426afe5a8 | ||
|
|
4bbf923eb6 | ||
|
|
e2c3480194 | ||
|
|
73159076f6 | ||
|
|
90d040573d | ||
|
|
155481a442 | ||
|
|
2f31bfc5fe | ||
|
|
8d0c88dc74 | ||
|
|
07256fd833 | ||
|
|
776cd43a58 | ||
|
|
acb5309aab | ||
|
|
c68f81db3c | ||
|
|
ac8e341b37 | ||
|
|
36122b3966 | ||
|
|
79bcf472f0 | ||
|
|
55d86da846 | ||
|
|
e4f8c1ba3f | ||
|
|
c36236b7dc | ||
|
|
63994333d0 | ||
|
|
da4c7d8934 | ||
|
|
186721eca0 | ||
|
|
f53d939c86 | ||
|
|
23e6909708 | ||
|
|
cf421fe1c1 | ||
|
|
4d80e806e4 | ||
|
|
60a7b7f7ff | ||
|
|
90263eab06 | ||
|
|
9d8f251fc4 | ||
|
|
2b4986571c | ||
|
|
890d13bd52 | ||
|
|
e698e71137 | ||
|
|
d064a5530a | ||
|
|
ab4fbf6c19 | ||
|
|
728afa8361 | ||
|
|
b77019c16e | ||
|
|
6703448b80 | ||
|
|
776ba19a1f | ||
|
|
1f499e0d44 | ||
|
|
0a6eb61103 | ||
|
|
32a2eed5ec | ||
|
|
40a70d39d0 | ||
|
|
5697054e98 | ||
|
|
def5969e1c | ||
|
|
78a418630d | ||
|
|
6d76e7b2d4 | ||
|
|
f052c8b44c | ||
|
|
0e6991d56d | ||
|
|
06eab5f8a4 | ||
|
|
4a481e79c4 | ||
|
|
ee4abbcbaa | ||
|
|
dcc82d742f | ||
|
|
19cb2089d7 | ||
|
|
04923b06b0 | ||
|
|
e16755d491 | ||
|
|
742b0769a4 | ||
|
|
df68dca9dc | ||
|
|
4a5bf78d58 | ||
|
|
7947237489 | ||
|
|
1115205164 | ||
|
|
d5d01136c4 | ||
|
|
3d47277614 | ||
|
|
b937bea04f | ||
|
|
fff14632bc | ||
|
|
9bdf1a620f | ||
|
|
60099aa989 | ||
|
|
f3976e5dd8 | ||
|
|
d7d6893304 | ||
|
|
99549ce805 | ||
|
|
7eb15fe04d | ||
|
|
e2019a13ab | ||
|
|
4b7a06761a | ||
|
|
8f4a1f5801 | ||
|
|
891bec9cdb | ||
|
|
c5252ea583 | ||
|
|
82d553c180 | ||
|
|
71e34355b9 | ||
|
|
2bad8c72e4 | ||
|
|
6134ca01ac | ||
|
|
be8193ebff | ||
|
|
430ee46645 | ||
|
|
76fbbf29e8 | ||
|
|
3108159d95 | ||
|
|
f282a1ead7 | ||
|
|
324bc4957d | ||
|
|
c3b5c4dfae | ||
|
|
4ab5d97e86 | ||
|
|
b4696ef11e | ||
|
|
294ffb72a4 | ||
|
|
8c534d29d3 | ||
|
|
d6cb262f9d | ||
|
|
5211e2ae20 | ||
|
|
90832fd1ad | ||
|
|
889cbc69e1 | ||
|
|
15948370d4 | ||
|
|
63c5177b37 | ||
|
|
4fbfaf6b9f | ||
|
|
9e68497b63 | ||
|
|
0b9e13bf1e | ||
|
|
485d343e0f | ||
|
|
07f0e8a3be | ||
|
|
cc5afb1cd8 | ||
|
|
c69f1c0890 | ||
|
|
a8733bdedf | ||
|
|
4222e4eb51 | ||
|
|
5db4441f5c | ||
|
|
2bcfe97211 | ||
|
|
4e74c97c84 | ||
|
|
71a46c9bd6 | ||
|
|
1a7c7fdebf | ||
|
|
65ff3d414a | ||
|
|
9fa38b70c8 | ||
|
|
470172f53f | ||
|
|
81261d9e36 | ||
|
|
3ab2e20119 | ||
|
|
b773d576ea | ||
|
|
23feb64b5a | ||
|
|
87fe9d9d3d | ||
|
|
b1ef3f51cb | ||
|
|
e1b6488f8e | ||
|
|
a472dede2b | ||
|
|
263cc3f7a1 | ||
|
|
598612d4bf | ||
|
|
a07d83e583 | ||
|
|
d355812433 | ||
|
|
1afc14f5ab | ||
|
|
8947b667ae | ||
|
|
6b41796d44 | ||
|
|
6efe8eb55b | ||
|
|
4c8f8918e8 | ||
|
|
4cb5bb1855 | ||
|
|
eb007e025a | ||
|
|
18aefa9dee | ||
|
|
2ec540bd36 | ||
|
|
1374ec408a | ||
|
|
8f8fed2b79 | ||
|
|
a1e6f01fe9 | ||
|
|
d7496f22e5 | ||
|
|
7de25a1c37 | ||
|
|
78da89340c | ||
|
|
60606115fe | ||
|
|
57b49fc31c | ||
|
|
6e90c8f6e6 | ||
|
|
a7ff2595a5 | ||
|
|
1edea6abef | ||
|
|
a3bd58bda6 | ||
|
|
3339d1999f | ||
|
|
df3e8ec0f3 | ||
|
|
2dbec867d9 | ||
|
|
3fc651d659 | ||
|
|
b91d23023d | ||
|
|
cc234c60b8 | ||
|
|
f1883c8004 | ||
|
|
79f6ddc8ee | ||
|
|
89f439a18d | ||
|
|
79eb5bfad9 | ||
|
|
3a36b7dafd | ||
|
|
1fe2fd9891 | ||
|
|
5fdb999ece | ||
|
|
5a38d9c2b6 | ||
|
|
b1c7dc6cbb | ||
|
|
b21b2ac41c | ||
|
|
1396c597ef | ||
|
|
46af72ee4e | ||
|
|
e3e8f553c8 | ||
|
|
549928d3d1 | ||
|
|
abefec9628 | ||
|
|
b483eeded4 | ||
|
|
fab85dafa8 | ||
|
|
955faed919 | ||
|
|
3ff516180d | ||
|
|
0c8ca6d6b0 | ||
|
|
a5de077e04 | ||
|
|
c0a99d6b52 | ||
|
|
a1e65cd897 | ||
|
|
5d65f1ffc5 | ||
|
|
b7193a06fd | ||
|
|
cba090f8eb | ||
|
|
0d6baa1081 | ||
|
|
bc60d796a3 | ||
|
|
85a208526b | ||
|
|
c3ac0a750d | ||
|
|
0f111127a5 | ||
|
|
cd266a6bef | ||
|
|
0f4945621c | ||
|
|
9a477f227b | ||
|
|
3c81257325 | ||
|
|
218fe9ebdc | ||
|
|
24bb45ab97 | ||
|
|
85f39de540 | ||
|
|
5cc3277e2d | ||
|
|
d5ef7f3204 | ||
|
|
cb47d01f51 | ||
|
|
ee499c1385 | ||
|
|
4ae4af5c10 | ||
|
|
b0f5fe7e25 | ||
|
|
fd32585efc | ||
|
|
c675bb7252 | ||
|
|
e65d11d71e | ||
|
|
c2680334f1 | ||
|
|
1217a02ec1 | ||
|
|
143a091f45 | ||
|
|
49ecc93dbe | ||
|
|
1735fb6bed | ||
|
|
8fefc11b4d | ||
|
|
1a3b22171c | ||
|
|
5e773be260 | ||
|
|
c317a1dc8b | ||
|
|
9401ffc83c | ||
|
|
479a852446 | ||
|
|
9188fb03f0 | ||
|
|
36bb91cbc3 | ||
|
|
56e5ab8bc6 | ||
|
|
607eb6ca03 | ||
|
|
ad745cc84b | ||
|
|
5a13d5ea6f | ||
|
|
0b065eb7fe | ||
|
|
e2c0e4bc66 | ||
|
|
655b0e6778 | ||
|
|
cd785c9fc3 | ||
|
|
23885f5166 | ||
|
|
44ef39e419 | ||
|
|
e05a0ef565 | ||
|
|
0ea9c0647f | ||
|
|
00b9ba2b75 | ||
|
|
f1e3bc9531 | ||
|
|
7360e093b2 | ||
|
|
5f639bc24f | ||
|
|
884d2a9552 | ||
|
|
fd8a8b29b1 | ||
|
|
4e1ef40c05 | ||
|
|
00af0ddff5 | ||
|
|
2467e823a5 | ||
|
|
224642fcb5 | ||
|
|
59518ca2c5 | ||
|
|
dfb7c73069 | ||
|
|
70eccceab3 | ||
|
|
3f69b70367 | ||
|
|
94a0a2f8be | ||
|
|
7e23596ab8 | ||
|
|
3c58a5dd3d | ||
|
|
51789ccbf3 | ||
|
|
6ae4a9aa1a | ||
|
|
0aebd669b2 | ||
|
|
2a92c4899d | ||
|
|
530089895a | ||
|
|
ae8289fc8c | ||
|
|
90f4dfb48b | ||
|
|
ec2eddf538 | ||
|
|
44d31d4b39 | ||
|
|
4934e7666c | ||
|
|
35bf0d020e | ||
|
|
4934dc85ac | ||
|
|
5ceb50d7e5 | ||
|
|
ae7ccbb7bd | ||
|
|
1cd9fc47aa | ||
|
|
013c50607a | ||
|
|
d7bf9b7f2e | ||
|
|
4702fdddc6 | ||
|
|
dd06a530c0 | ||
|
|
c36125dc95 | ||
|
|
ae43f93d0a | ||
|
|
8918b17f77 | ||
|
|
dfd05e8b5b | ||
|
|
3daa6dd051 | ||
|
|
14e43192e6 | ||
|
|
6cf88b1792 | ||
|
|
6b9839367f | ||
|
|
34fcf5d616 | ||
|
|
1cf3c74e67 | ||
|
|
16b57b931e | ||
|
|
3457e7d617 | ||
|
|
edbe8001e6 | ||
|
|
71644c1cbe | ||
|
|
03fd8df8bd | ||
|
|
71d622b6dd | ||
|
|
689d5a2d58 | ||
|
|
b2e9eb866c | ||
|
|
c8b7782d1d | ||
|
|
673f936689 | ||
|
|
2ca0d39e51 | ||
|
|
908c8bc397 | ||
|
|
93082e1e87 | ||
|
|
ef0a6aa25e | ||
|
|
b9f4dc1e9d | ||
|
|
90afec864e | ||
|
|
b082fb6692 | ||
|
|
4bbbd786cd | ||
|
|
0c1b2a54e7 | ||
|
|
a6ded20ede | ||
|
|
9b96314371 | ||
|
|
85e16ecd51 | ||
|
|
2ac36cdfd6 | ||
|
|
e40c532354 | ||
|
|
9404dff79c | ||
|
|
55b324d8d6 | ||
|
|
3ed2e2d79e | ||
|
|
3152d9eadd | ||
|
|
f1266ab5d6 | ||
|
|
e70f1408aa | ||
|
|
664e3984e3 | ||
|
|
665c13e238 | ||
|
|
8a667b20c2 | ||
|
|
3693241292 | ||
|
|
a384245368 | ||
|
|
3c3acff79b | ||
|
|
e15de83a95 | ||
|
|
636fccbf97 | ||
|
|
7d8ed399a8 | ||
|
|
272385db6c | ||
|
|
9334b8df47 | ||
|
|
e0bc2ae86f | ||
|
|
2f019bb033 | ||
|
|
291be64aa0 | ||
|
|
75c83236ff | ||
|
|
72be171917 | ||
|
|
4ddee4ac40 | ||
|
|
43cd0d7eb3 | ||
|
|
6b693e2644 | ||
|
|
eeea69d8f2 | ||
|
|
72cf921a4b | ||
|
|
32882f81e7 | ||
|
|
36f5099932 | ||
|
|
bec88a0441 | ||
|
|
6e67e3a8a0 | ||
|
|
4dc3c30354 | ||
|
|
947ccf911d | ||
|
|
06f313febe | ||
|
|
b387136b8f | ||
|
|
31614c5da1 | ||
|
|
146b5a3085 | ||
|
|
397cc1754a | ||
|
|
ff1297cac5 | ||
|
|
8af3cf4056 | ||
|
|
9edec9e212 | ||
|
|
be4362c59a | ||
|
|
8461b147de | ||
|
|
82bdd43168 | ||
|
|
533d82d4d8 | ||
|
|
6f990dd1de | ||
|
|
be848598e3 | ||
|
|
62a58fa23b | ||
|
|
7d0742054f | ||
|
|
dcf7e83507 | ||
|
|
407c2bf0c8 | ||
|
|
c46227ee94 | ||
|
|
d8171eb056 | ||
|
|
c67ca500db | ||
|
|
0081e7b731 | ||
|
|
4e6483d3ed | ||
|
|
0ddf0002c4 | ||
|
|
ad0daf33b9 | ||
|
|
c614c44d4c | ||
|
|
8a46579588 | ||
|
|
fb9fb9c097 | ||
|
|
d9399534ce | ||
|
|
17a749bbed | ||
|
|
0b60c6a939 | ||
|
|
25ce63f00b | ||
|
|
2462bc05b3 | ||
|
|
c3748da0b1 | ||
|
|
90c242a441 | ||
|
|
52239a88b5 | ||
|
|
7a3596c86d | ||
|
|
ac8a9e38f0 | ||
|
|
5c08f3a27e | ||
|
|
2dc11a84bf | ||
|
|
2960ea15f5 | ||
|
|
17aab4c490 | ||
|
|
59d0f0d292 | ||
|
|
27d13a113a | ||
|
|
c58e999fbb | ||
|
|
a02a96d5c4 | ||
|
|
47e9e4704d | ||
|
|
b2ad9d404e | ||
|
|
925d48640d | ||
|
|
5216dd75bf | ||
|
|
028b4e7b79 | ||
|
|
b8b45d2d81 | ||
|
|
4b2b2cb68e | ||
|
|
625a46a2c2 | ||
|
|
be4ace293e | ||
|
|
ebc1e27c22 | ||
|
|
764b363bbc | ||
|
|
705474dc14 | ||
|
|
e639d7726b | ||
|
|
357e85d358 | ||
|
|
9da9166442 | ||
|
|
8ea339f0ef | ||
|
|
e29b0aa6a4 | ||
|
|
d2157868f2 | ||
|
|
89b36ba63f | ||
|
|
1d3dfa1fa4 | ||
|
|
89cfafd933 | ||
|
|
ca567eec8a | ||
|
|
75cfcb83aa | ||
|
|
41da31c379 | ||
|
|
e81f63f46b | ||
|
|
ade03e4827 | ||
|
|
63a4d1ad33 | ||
|
|
33a4c88c3a | ||
|
|
69c5dbfc23 | ||
|
|
cf1bccfd65 | ||
|
|
99acf018f1 | ||
|
|
f04d6f37e5 | ||
|
|
1f79776b8f | ||
|
|
11e5f851f0 | ||
|
|
4ca1db8a1b | ||
|
|
14b829aa30 | ||
|
|
384652b3df | ||
|
|
469069b471 | ||
|
|
9db65c6ae9 | ||
|
|
1346e33e99 | ||
|
|
ab9cc447aa | ||
|
|
cb63c12d2f | ||
|
|
fe9e0444b4 | ||
|
|
6ce0fd20ce | ||
|
|
a18957dbe9 | ||
|
|
13d5b724ee | ||
|
|
b383cd9493 | ||
|
|
c7d8203da9 | ||
|
|
96b5c1d8e1 | ||
|
|
882ee80424 | ||
|
|
e977771fe4 | ||
|
|
4339e4552e | ||
|
|
9bca5912d9 | ||
|
|
b54d222943 | ||
|
|
23756ba1c7 | ||
|
|
e4e613550a | ||
|
|
d1207444db | ||
|
|
06f060161d | ||
|
|
73e0628124 | ||
|
|
7393c4170b | ||
|
|
73e185bf9d | ||
|
|
0bad75b263 | ||
|
|
74b98083e2 | ||
|
|
9034b5ec11 | ||
|
|
77153c3e78 | ||
|
|
927f557672 | ||
|
|
86fb089901 | ||
|
|
630ea05e17 | ||
|
|
7df5196083 | ||
|
|
01f7b5d709 | ||
|
|
07227887f6 | ||
|
|
ec47ee3573 | ||
|
|
13d3b103f1 | ||
|
|
7062962cc8 | ||
|
|
fde447846a | ||
|
|
319cb1bd1e | ||
|
|
58cda5ae6d | ||
|
|
251a3db880 | ||
|
|
30b6dcd767 | ||
|
|
05f6622912 | ||
|
|
36183ac19d | ||
|
|
f6a823e0a8 | ||
|
|
312353551d | ||
|
|
4bdfd0e115 | ||
|
|
fd5c9cee38 | ||
|
|
84bf71802c | ||
|
|
786b20708e | ||
|
|
35739c2eef | ||
|
|
1914a71623 | ||
|
|
dcc53886d9 | ||
|
|
21ef87ef09 | ||
|
|
0e957cad84 | ||
|
|
b67f808da9 | ||
|
|
ad69447b53 | ||
|
|
b4614df88c | ||
|
|
7171247cdc | ||
|
|
6d0084f108 | ||
|
|
29359f42ae | ||
|
|
cf875bf872 | ||
|
|
13c0d335d3 | ||
|
|
ceb33409b5 | ||
|
|
83597d4a8b | ||
|
|
81eefc5448 | ||
|
|
082bc3ece4 | ||
|
|
b2406b61fb | ||
|
|
15698d7694 | ||
|
|
990cd82f97 | ||
|
|
c87829b3e8 | ||
|
|
6799c41d65 | ||
|
|
80c059be58 | ||
|
|
aa3f896538 | ||
|
|
85c728f313 | ||
|
|
d8c17a8a5e | ||
|
|
50e210b2db | ||
|
|
850939367c | ||
|
|
bf4fef9bf7 | ||
|
|
d5175961a4 | ||
|
|
ba3fb8cd66 | ||
|
|
e7e1d6a232 | ||
|
|
712345f3a0 | ||
|
|
54c9bc3dcb | ||
|
|
e796c0da4a | ||
|
|
cf8ff0f43e | ||
|
|
e67ce23a7c | ||
|
|
d9fb7dc754 | ||
|
|
a66bb95a81 | ||
|
|
2249dad9d7 | ||
|
|
c66fa33b2e | ||
|
|
5c4fa630ae | ||
|
|
56eebb9db4 | ||
|
|
ede74ad24e | ||
|
|
d9773f107e | ||
|
|
27542a8f91 | ||
|
|
5dc07b94aa | ||
|
|
e09112f46a | ||
|
|
d7acf721ae | ||
|
|
50a17a82b6 | ||
|
|
eff5232828 | ||
|
|
7eb032d646 | ||
|
|
3eb29b1cdb | ||
|
|
e065f22f8e | ||
|
|
c1b4ba1f85 | ||
|
|
eeaec471f0 | ||
|
|
0d3c8678d8 | ||
|
|
bbddf5f95b | ||
|
|
3b8c8b3af9 | ||
|
|
84753aa13f | ||
|
|
90b84451d8 | ||
|
|
54c8ae41cc | ||
|
|
7d32b1a724 | ||
|
|
69ffd63ca6 | ||
|
|
4857c19eb6 | ||
|
|
d535e23da0 | ||
|
|
30e02544ab | ||
|
|
ee53925bd2 | ||
|
|
c9961f63b4 | ||
|
|
40495b8a17 | ||
|
|
b1de98f05d | ||
|
|
6bc9a82a7a | ||
|
|
09e843a800 | ||
|
|
6504ee37bd | ||
|
|
082bff2fa8 | ||
|
|
ab7bd86336 | ||
|
|
eada0b0bb5 | ||
|
|
f221f9f22a | ||
|
|
6731e44a0d | ||
|
|
acc37045e4 | ||
|
|
a7c5899c16 | ||
|
|
80b02635a9 | ||
|
|
f90de5ec67 | ||
|
|
9a3e046530 | ||
|
|
7f56e461fe | ||
|
|
1da37e0d38 | ||
|
|
0004d4936c | ||
|
|
7a6fdc4e0a | ||
|
|
2eb727bd0c | ||
|
|
57af507170 | ||
|
|
e37f87465c | ||
|
|
324070f345 | ||
|
|
e1aa843b6d | ||
|
|
e652a7fd61 | ||
|
|
2397bfbad0 | ||
|
|
7e2bc8e452 | ||
|
|
00e8fd0483 | ||
|
|
6d8bf2c665 | ||
|
|
77b79dbd95 | ||
|
|
72466f1aab | ||
|
|
dafc6c5136 | ||
|
|
6901deccbf | ||
|
|
5a9c906eb9 | ||
|
|
b95b60b49e | ||
|
|
14408ef18f | ||
|
|
c790147a5c | ||
|
|
6b87c90e02 | ||
|
|
80c39c5ef3 | ||
|
|
2b4d5222eb | ||
|
|
9604ecebad | ||
|
|
9a3e84d84c | ||
|
|
0d67970a45 | ||
|
|
913b9f0c40 | ||
|
|
610a5745f0 | ||
|
|
dff12324c6 | ||
|
|
f340390fc8 | ||
|
|
643960c829 | ||
|
|
be81e74921 | ||
|
|
1737018325 | ||
|
|
2bee8729e4 | ||
|
|
5d03038734 | ||
|
|
75b11de0f4 | ||
|
|
484d5ba76e | ||
|
|
c5bede8594 | ||
|
|
798685d0b8 | ||
|
|
cd9ea6ae3b | ||
|
|
cb7654ae90 | ||
|
|
113b762ec7 | ||
|
|
78704d8b85 | ||
|
|
1109db76fe | ||
|
|
b1b390febf | ||
|
|
be451fa0c0 | ||
|
|
1fa553c785 | ||
|
|
905f61371d | ||
|
|
7da5210a01 | ||
|
|
18172d3896 | ||
|
|
c28e78b9e2 | ||
|
|
e7827a3847 | ||
|
|
039564e74c | ||
|
|
e164595a0f | ||
|
|
d29e09a3ff | ||
|
|
db9f85e9da | ||
|
|
27022954b1 | ||
|
|
30362c924f | ||
|
|
bf63b15b46 | ||
|
|
19aea4bd6c | ||
|
|
090890e9c6 | ||
|
|
00c394345a | ||
|
|
605022a9e3 | ||
|
|
b06c58fe7b | ||
|
|
f5c8b3eb04 | ||
|
|
739980aa09 | ||
|
|
afde5943e3 | ||
|
|
32300cb42c | ||
|
|
dffcb62fa1 | ||
|
|
de3b1697c8 | ||
|
|
8c668b72b7 | ||
|
|
a18fb10b5a | ||
|
|
52140dbe06 | ||
|
|
db5bb1928e | ||
|
|
65b66beb07 | ||
|
|
c83504b4e7 | ||
|
|
cd2e7ee31d | ||
|
|
c3a0a0415a | ||
|
|
709f4672b7 | ||
|
|
b54ecd4da0 | ||
|
|
e6c030b050 | ||
|
|
7bd3a791a1 | ||
|
|
87a7e3501b | ||
|
|
b9cfc03b4f | ||
|
|
daefec3013 | ||
|
|
131327a64d | ||
|
|
183a9742c4 | ||
|
|
77d002a158 | ||
|
|
94699893ac | ||
|
|
9f81989bdd | ||
|
|
15abe36c53 | ||
|
|
3094e007ee | ||
|
|
eb259aee22 | ||
|
|
04b83f8176 | ||
|
|
c05c8defb9 | ||
|
|
bf5668a42e | ||
|
|
335ab3f064 | ||
|
|
ec310bc8fb | ||
|
|
db477421b2 | ||
|
|
30a9496cf5 | ||
|
|
6cefa0d0b3 | ||
|
|
9284984265 | ||
|
|
53b96168a9 | ||
|
|
eadce51f10 | ||
|
|
7dd493da35 | ||
|
|
b3c583b6dc | ||
|
|
560bd6da92 | ||
|
|
28abeab31d | ||
|
|
a5824ccc5f | ||
|
|
830a7964a4 | ||
|
|
12a649ec4b | ||
|
|
35b79e4d14 | ||
|
|
751dad7f2e | ||
|
|
b08d86220a | ||
|
|
3a27ecc475 | ||
|
|
da6fab5d43 | ||
|
|
ca95b29cd6 | ||
|
|
c5c68e9dd5 | ||
|
|
44fbde26fa | ||
|
|
6e2bcd6aa9 | ||
|
|
b1359c3277 | ||
|
|
9c0202e5ce | ||
|
|
39ef353073 | ||
|
|
7b5d90dd81 | ||
|
|
0ba8f5cc5a | ||
|
|
d1129081df | ||
|
|
315a812df5 | ||
|
|
6fb9e2c38e | ||
|
|
cfc979acf0 | ||
|
|
d7407d70a3 | ||
|
|
8ccd2e97a8 | ||
|
|
988204619f | ||
|
|
cad6cc3007 | ||
|
|
eebc2ab8be | ||
|
|
b303875f1d | ||
|
|
d5cc61f378 | ||
|
|
0d7ec43262 | ||
|
|
d3747f4daa | ||
|
|
af695e7dc8 | ||
|
|
1edbfd87df | ||
|
|
454be01a6c | ||
|
|
b65b3151ee | ||
|
|
745fc515f1 | ||
|
|
715b9c1182 | ||
|
|
1d24b7985b | ||
|
|
526bb2c650 | ||
|
|
c450c0ddb8 | ||
|
|
95be847d87 | ||
|
|
c1a6546eba | ||
|
|
648c25a0a7 | ||
|
|
f2ec7f2975 | ||
|
|
f518af6d61 | ||
|
|
13a0f49f5f | ||
|
|
b11c6a5c06 | ||
|
|
5822e4e692 | ||
|
|
199eefafa1 | ||
|
|
c5b58f9ecc | ||
|
|
6b68fe4de6 | ||
|
|
3461bbfdb3 | ||
|
|
51f6927076 | ||
|
|
4298aad008 | ||
|
|
823c67400d | ||
|
|
3160d1064d | ||
|
|
1d88cf443f | ||
|
|
7b6c0c3a40 | ||
|
|
fdb0651bf4 | ||
|
|
c39d484611 | ||
|
|
c42996429f | ||
|
|
a091baf5a6 | ||
|
|
128bdf500a | ||
|
|
918426a2fa | ||
|
|
c076b37c9b | ||
|
|
7c58bfa282 | ||
|
|
0caaba156d | ||
|
|
c22575812d | ||
|
|
0ede4da816 | ||
|
|
98e23ff92e | ||
|
|
00a17cd55e | ||
|
|
643d44af22 | ||
|
|
b934f43db0 | ||
|
|
c7bdad649a | ||
|
|
18c2508d2f | ||
|
|
4dcfd8b353 | ||
|
|
726116574d | ||
|
|
27f02014ca | ||
|
|
f80f1acaa7 | ||
|
|
39d5ffeceb | ||
|
|
48ba7eed3e | ||
|
|
f39afe5a65 | ||
|
|
35b358d336 | ||
|
|
ae109be631 | ||
|
|
3f7ed73395 | ||
|
|
7612ee6b08 | ||
|
|
2ed2b0101a | ||
|
|
5ca9d31964 | ||
|
|
2fcd8cd261 | ||
|
|
0ffa47a2c6 | ||
|
|
9caa240fdb | ||
|
|
3ffb73a516 | ||
|
|
cc1132be87 | ||
|
|
1c31f126ef | ||
|
|
d8eaf2676f | ||
|
|
e203d4dee3 | ||
|
|
b47d773e13 | ||
|
|
a8d0a4a95d | ||
|
|
3fb0804cef | ||
|
|
6811ebcd52 | ||
|
|
4fe7bfb851 | ||
|
|
fb60985d03 | ||
|
|
8f575923cf | ||
|
|
0ecfd02649 | ||
|
|
420aaf4f61 | ||
|
|
0c35f213e1 | ||
|
|
f68813af13 | ||
|
|
37a90d0ce9 | ||
|
|
02f1291e8f | ||
|
|
92e4f6b5d9 | ||
|
|
7b7738fbcc | ||
|
|
3101212c49 | ||
|
|
3f0ac103a1 | ||
|
|
59bd6ca360 | ||
|
|
22fe9a786e | ||
|
|
31197604a3 | ||
|
|
d2ee8de9ac | ||
|
|
03d3fb6a5f | ||
|
|
e33b1b6c90 | ||
|
|
30520297e8 | ||
|
|
78ca1d1335 | ||
|
|
6159ee8c2c | ||
|
|
5cd5392958 | ||
|
|
0dcdfc5d14 | ||
|
|
d0e068f1c0 | ||
|
|
f42a2d7457 | ||
|
|
d29619b67c | ||
|
|
f5235cb835 | ||
|
|
ee830e0cb4 | ||
|
|
0cd3be003d | ||
|
|
c93e35ec77 | ||
|
|
9538a76232 | ||
|
|
05876bb124 | ||
|
|
8bcd5a6d2a | ||
|
|
a36afbcb25 | ||
|
|
ebd8d085cf | ||
|
|
505148b024 | ||
|
|
3eefeec4ce | ||
|
|
b61419c1ce | ||
|
|
f590fcffbc | ||
|
|
e87e924ac2 | ||
|
|
90f261bab6 | ||
|
|
f7dfb09a4d | ||
|
|
3135917127 | ||
|
|
52afa3d36d | ||
|
|
242aa60e04 | ||
|
|
7a3c2c27ff | ||
|
|
5d124360c2 | ||
|
|
365d7448d5 | ||
|
|
9a0102c723 | ||
|
|
2f77f2cb2b | ||
|
|
528e3a2106 | ||
|
|
032a664d4c | ||
|
|
aac1864c9b | ||
|
|
e3477f3306 | ||
|
|
6620a4f87b | ||
|
|
c0e9dff5bf | ||
|
|
2d961c435a | ||
|
|
7c95f03166 | ||
|
|
31e5c13b50 | ||
|
|
4a9fe4f981 | ||
|
|
4fcc5587ee | ||
|
|
6ca49a20ce | ||
|
|
28f293fdc1 | ||
|
|
b3e7619adc | ||
|
|
6e56d56137 | ||
|
|
2528f6a07b | ||
|
|
423d07c919 | ||
|
|
cc608de4bf | ||
|
|
f999a68608 | ||
|
|
db78a9f18f | ||
|
|
816039f48e | ||
|
|
ae240bae6d | ||
|
|
9e30c69e6d | ||
|
|
43c7de9049 | ||
|
|
7e51c5db81 | ||
|
|
0ee3c45e7b | ||
|
|
981e69929c | ||
|
|
553ab8851a | ||
|
|
1eae5d12fc | ||
|
|
8863208333 | ||
|
|
5f38a74a72 | ||
|
|
fe15dacb1f | ||
|
|
c2d44cf2f2 | ||
|
|
7f1bdb6f34 | ||
|
|
7cdfaa93ec | ||
|
|
59ccc70303 | ||
|
|
f1584b722d | ||
|
|
b0305e12d2 | ||
|
|
4d8c5a86a4 | ||
|
|
58f76b5c99 | ||
|
|
7c4ee632cf | ||
|
|
b6b0f716eb | ||
|
|
bd0e04ed15 | ||
|
|
8599981d44 | ||
|
|
6fc6e95c67 | ||
|
|
43b585bde8 | ||
|
|
710f89291f | ||
|
|
5f835aa009 | ||
|
|
d5ca543719 | ||
|
|
4c6249eb9e | ||
|
|
016900bad8 | ||
|
|
2e8ae33761 | ||
|
|
2b8ea9a233 | ||
|
|
0d325060da | ||
|
|
1a6e98e18f | ||
|
|
97e34595f6 | ||
|
|
a179d5234b | ||
|
|
64c6121fdb | ||
|
|
08d8954a85 | ||
|
|
4f20955d0d | ||
|
|
3a703c8bcf | ||
|
|
ccbffa086b | ||
|
|
07ee4be840 | ||
|
|
4cc9b2d312 | ||
|
|
24dddae1d1 | ||
|
|
ad0165d085 | ||
|
|
39dc38c5d1 | ||
|
|
046ce19dbb | ||
|
|
33263f5a93 | ||
|
|
8ef8e76300 | ||
|
|
73cfdae9e7 | ||
|
|
54a3e41281 | ||
|
|
d59ba6da84 | ||
|
|
1b28b06934 | ||
|
|
ff3e69a56c | ||
|
|
6b975a5fb4 | ||
|
|
3a02b15124 | ||
|
|
5f73d81935 | ||
|
|
002b5c0f6f | ||
|
|
8086842570 | ||
|
|
51f2d5a664 | ||
|
|
db63ad1cf4 | ||
|
|
b74b76de75 | ||
|
|
a1b1498106 | ||
|
|
1158851ea7 | ||
|
|
e1ab9e959e | ||
|
|
2bbac3ae9d | ||
|
|
e889b1d5e5 | ||
|
|
233bf856f4 | ||
|
|
bca843e06c | ||
|
|
30a79a1278 | ||
|
|
0f92dee2c4 | ||
|
|
f04efede15 | ||
|
|
f0dfdf6720 | ||
|
|
e26d731382 | ||
|
|
d684d3e559 | ||
|
|
47c54cb998 | ||
|
|
592cb2b3ec | ||
|
|
f5a7871a2e | ||
|
|
ec411fa0db | ||
|
|
5da79cd5ca | ||
|
|
a850a9bb83 | ||
|
|
13c971b171 | ||
|
|
1cee7e43ed | ||
|
|
5e81c63d6e | ||
|
|
479b7a3f94 | ||
|
|
f7cfee77c9 | ||
|
|
65a8126a13 | ||
|
|
a39bc102d5 | ||
|
|
81d930c4d2 | ||
|
|
6839623061 | ||
|
|
7de2809d42 | ||
|
|
98ec6b6886 | ||
|
|
04827f00cc | ||
|
|
660bfc6578 | ||
|
|
1152cd5537 | ||
|
|
17456482d6 | ||
|
|
a0431e1912 | ||
|
|
f30e8497b2 | ||
|
|
06495bc45d | ||
|
|
26067916b3 | ||
|
|
c36ee4852b | ||
|
|
2cb992ad44 | ||
|
|
ee5aac8008 | ||
|
|
083b7be6c0 | ||
|
|
e24854558f | ||
|
|
e4314cf426 | ||
|
|
4106e4e45c | ||
|
|
05f143db2b | ||
|
|
64aeaeeeea | ||
|
|
61db37ab0d | ||
|
|
f9c4d921e7 | ||
|
|
ca099df573 | ||
|
|
28b584b8bc | ||
|
|
70449e694d | ||
|
|
8395ea552d | ||
|
|
dc66452633 | ||
|
|
53ce44ac91 | ||
|
|
c7c3243bbc | ||
|
|
8bdd77d33d | ||
|
|
acd7d0db3a | ||
|
|
2bfadb8a3c | ||
|
|
0912e4af7b | ||
|
|
5aa5c48018 | ||
|
|
8cdd998f79 | ||
|
|
050d4d6b25 | ||
|
|
366cd11238 | ||
|
|
58d6443331 | ||
|
|
101b8afb56 | ||
|
|
5df5c47945 | ||
|
|
a04740ba86 | ||
|
|
425ad93ac5 | ||
|
|
b0067fee51 | ||
|
|
732c3dae89 | ||
|
|
d45bd67cae | ||
|
|
9200de5032 | ||
|
|
8a44144c20 | ||
|
|
ee82c70582 | ||
|
|
c87e8e606b | ||
|
|
37a50dd953 | ||
|
|
a2669a3084 | ||
|
|
77da22f4dd | ||
|
|
7830ffe202 | ||
|
|
1c9e20d59f | ||
|
|
3fbbff5a47 | ||
|
|
c22efc2c3d | ||
|
|
8c0281bf70 | ||
|
|
320edac286 | ||
|
|
d49878371d | ||
|
|
d2575a5d9b | ||
|
|
ea6cf72580 | ||
|
|
2118155b37 | ||
|
|
ba4f5bb71f | ||
|
|
d5a74a5a8b | ||
|
|
23be1df360 | ||
|
|
b5c1a1da4c | ||
|
|
c11e784f51 | ||
|
|
06f51c8f9c | ||
|
|
181bcbbda6 | ||
|
|
d008ead6a4 | ||
|
|
75924be958 | ||
|
|
b1a6e3f8a2 | ||
|
|
06712a6041 | ||
|
|
720a4bc4a2 | ||
|
|
7fd93645b3 | ||
|
|
62b16339a9 | ||
|
|
9a2f1a36ba | ||
|
|
fcbfbca6d0 | ||
|
|
f2bca9491c | ||
|
|
b48f309ab6 | ||
|
|
0b1be3e63b | ||
|
|
95cc4d3a73 | ||
|
|
497eeeb2e0 | ||
|
|
4be21ca249 | ||
|
|
e8598e214e | ||
|
|
54b1d65e3c | ||
|
|
f7648496d3 | ||
|
|
59a57c7197 | ||
|
|
5659b26827 | ||
|
|
ee4443aaf0 | ||
|
|
839dcad358 | ||
|
|
d67933ab49 | ||
|
|
0eb3f6b952 | ||
|
|
68b0f80fce | ||
|
|
93489529a3 | ||
|
|
511be74e74 | ||
|
|
bdee067803 | ||
|
|
32156cace3 | ||
|
|
30688114be | ||
|
|
34088bcc17 | ||
|
|
07835766cc | ||
|
|
8c129c10af | ||
|
|
251851ec6a | ||
|
|
049a669186 | ||
|
|
d29f13bae9 | ||
|
|
c758355df9 | ||
|
|
79d97a83af | ||
|
|
85bd47c240 | ||
|
|
473ead9616 | ||
|
|
cf2850933c | ||
|
|
ff2564c57a | ||
|
|
91d3848246 | ||
|
|
c031f0b45e | ||
|
|
fdbb9568ae | ||
|
|
d817883459 | ||
|
|
12255979ac | ||
|
|
366b61850b | ||
|
|
89be6bd183 | ||
|
|
e120331a2c | ||
|
|
cb8a212d96 | ||
|
|
7aec431ac5 | ||
|
|
d19681dea1 | ||
|
|
63ce2a14fe | ||
|
|
f435ebb110 | ||
|
|
843f001bf6 | ||
|
|
bf2299daf8 | ||
|
|
164930d0dd | ||
|
|
387dbac809 | ||
|
|
3b661e5a99 | ||
|
|
90c1c0e655 | ||
|
|
21d8e7695b | ||
|
|
0d28165c04 | ||
|
|
ee31bfbcd4 | ||
|
|
1acc452cfe | ||
|
|
1375e1feee | ||
|
|
2187adf59a | ||
|
|
0dcb315d9d | ||
|
|
327ccbd0c9 | ||
|
|
f571d400e6 | ||
|
|
293aa52335 | ||
|
|
ca178ae9a7 | ||
|
|
d0c810e418 | ||
|
|
d496d2caeb | ||
|
|
e70b75c350 | ||
|
|
a50befeda5 | ||
|
|
e2616e8039 | ||
|
|
904266debe | ||
|
|
72d5783795 | ||
|
|
d699fb1473 | ||
|
|
fab1a6c33a | ||
|
|
be73c30194 | ||
|
|
451646fe4f | ||
|
|
decc919991 | ||
|
|
e0b4005921 | ||
|
|
3ef36e7534 | ||
|
|
1949e1e1e9 | ||
|
|
3358382358 | ||
|
|
3eca3ecd75 | ||
|
|
140c6b91b0 | ||
|
|
a5315ec240 | ||
|
|
93f1656e0b | ||
|
|
f1d006c236 | ||
|
|
b0b5a96694 | ||
|
|
7dbe9a85f4 | ||
|
|
d2c39528d5 | ||
|
|
0420543c94 | ||
|
|
aae0db902b | ||
|
|
88dae7cef7 | ||
|
|
e5cb17e934 | ||
|
|
9d609805f2 | ||
|
|
e2b9ca8254 | ||
|
|
e16a2fe8af | ||
|
|
22d61a533d | ||
|
|
af408bb45f | ||
|
|
24bfbc06f0 | ||
|
|
5eb9f353b5 | ||
|
|
473ce15f47 | ||
|
|
eb9cfbaed6 | ||
|
|
faeb037ff9 | ||
|
|
07602f697d | ||
|
|
11abb0fdb1 | ||
|
|
deeb2fa543 | ||
|
|
ef8d5ff11e | ||
|
|
91f3e07b83 | ||
|
|
c29bdbdacb | ||
|
|
a20d104d2f | ||
|
|
a61dd8ac17 | ||
|
|
7ee9a690ea | ||
|
|
5ba94c6c41 | ||
|
|
9fa855c837 | ||
|
|
9251007574 | ||
|
|
cc73b984cb | ||
|
|
548ef97c32 | ||
|
|
ed8a486726 | ||
|
|
1ab0911fc8 | ||
|
|
bdbaea7294 | ||
|
|
5cfd1f6fb2 | ||
|
|
5eda67381f | ||
|
|
2c8b8bfaf2 | ||
|
|
8f3159751a | ||
|
|
4b05e55b29 | ||
|
|
3d3c13fcd0 | ||
|
|
88e1d8a8cf | ||
|
|
e007db34e2 | ||
|
|
f9f06d2c02 | ||
|
|
234f7d00c8 | ||
|
|
9924553da5 | ||
|
|
df38d7e3ed | ||
|
|
44dd061619 | ||
|
|
7603a932b1 | ||
|
|
138e7acc13 | ||
|
|
e863d3e7e5 | ||
|
|
c8e401f5ed | ||
|
|
3ba20a8e28 | ||
|
|
ebae63752f | ||
|
|
8bc73901cf | ||
|
|
b4f70d9244 | ||
|
|
21e9f2bba3 | ||
|
|
881f4e3d6a | ||
|
|
b141945add | ||
|
|
1c67d6802d | ||
|
|
5da8c86ec7 | ||
|
|
10a2d59ec1 | ||
|
|
34e8360b10 | ||
|
|
ca259ee4c3 | ||
|
|
2143952a1e | ||
|
|
6bab6e7151 | ||
|
|
d217c2e295 | ||
|
|
52bf0faaa5 | ||
|
|
3f3f2bfc61 | ||
|
|
f050864fb4 | ||
|
|
db11fc35f4 | ||
|
|
f47a2b10c0 | ||
|
|
344b4e7d60 | ||
|
|
7a23372489 | ||
|
|
9da15a8e58 | ||
|
|
50e0e4a07b | ||
|
|
5e1562ae4c | ||
|
|
8a0ed49623 |
@@ -3206,7 +3206,8 @@
|
|||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
|
||||||
"profile": "https://github.com/swift2512",
|
"profile": "https://github.com/swift2512",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug",
|
||||||
|
"code"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ APP_ALLOW_INSECURE_HOSTS=false
|
|||||||
GOOGLE_MAPS_API=
|
GOOGLE_MAPS_API=
|
||||||
LDAP_MEM_LIM=500M
|
LDAP_MEM_LIM=500M
|
||||||
LDAP_TIME_LIM=600
|
LDAP_TIME_LIM=600
|
||||||
|
BACKUP_TIME_LIMIT=600
|
||||||
IMPORT_TIME_LIMIT=600
|
IMPORT_TIME_LIMIT=600
|
||||||
IMPORT_MEMORY_LIMIT=500M
|
IMPORT_MEMORY_LIMIT=500M
|
||||||
REPORT_TIME_LIMIT=12000
|
REPORT_TIME_LIMIT=12000
|
||||||
|
|||||||
163
.github/ISSUE_TEMPLATE/Bug-Report.yml
vendored
Normal file
163
.github/ISSUE_TEMPLATE/Bug-Report.yml
vendored
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: File a bug report.
|
||||||
|
title: "[Bug]: "
|
||||||
|
projects: ["grokability/snipe-it"]
|
||||||
|
type: bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report! Most issues are documented in the [Snipe-IT repository's issues](https://github.com/grokability/snipe-it/issues) or in the official [Common Issues section of the Documentation](https://snipe-it.readme.io/docs/common-issues#/) and are due to the following:
|
||||||
|
|
||||||
|
- `.env` misconfiguration
|
||||||
|
- [Server Permissions](https://snipe-it.readme.io/docs/debugging-permissions#/)
|
||||||
|
- [Database Migrations](https://snipe-it.readme.io/docs/database-issues#run-migrations)
|
||||||
|
|
||||||
|
Please make sure you've checked these resources before submitting a new issue. If you find an existing issue, please add your context to it instead of opening a new issue. If your issue is more of a question, consider [opening a new discussion](https://github.com/grokability/snipe-it/discussions) or [pop by our Discord](https://discord.gg/yZFtShAcKk) instead of creating an issue.
|
||||||
|
|
||||||
|
**Please write your bug report in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
|
||||||
|
|
||||||
|
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Snipe-IT Version
|
||||||
|
description: What version of Snipe-IT are you seeing this issue on? You can find the version number in the footer of any page in Snipe-IT.
|
||||||
|
placeholder: ex. v8.3.2 - build 19577 (master)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: php-version
|
||||||
|
attributes:
|
||||||
|
label: PHP Version
|
||||||
|
description: What version of PHP are you running? You can find the version of PHP your webserver is running in the `Admin Settings` section in the footer, and the cli version by running `php -v` via command line .
|
||||||
|
placeholder: ex. v8.3.1 (web), PHP 8.4.12 (cli)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: composer-version
|
||||||
|
attributes:
|
||||||
|
label: Composer Version
|
||||||
|
description: What version of composer are you running? You can find the version number by running `composer --version`.
|
||||||
|
placeholder: ex. 2.8.10
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: db-version
|
||||||
|
attributes:
|
||||||
|
label: MySQL/MariaDB version
|
||||||
|
description: What database are you using, and what version?
|
||||||
|
placeholder: ex. MySQL 5.7
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: install-method
|
||||||
|
attributes:
|
||||||
|
label: How did you install Snipe-IT?
|
||||||
|
options:
|
||||||
|
- Git install
|
||||||
|
- Manual install (downloading zip/tar.gz)
|
||||||
|
- Docker
|
||||||
|
- install.sh
|
||||||
|
- Hosted by Grokability
|
||||||
|
- Other
|
||||||
|
- Not sure
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: upgrade-or-fresh
|
||||||
|
attributes:
|
||||||
|
label: Is this a fresh install or an upgrade?
|
||||||
|
options:
|
||||||
|
- Fresh install
|
||||||
|
- Upgrade
|
||||||
|
- NA
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
placeholder: Tell us what you see! (Be nice!)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: browsers
|
||||||
|
attributes:
|
||||||
|
label: What browsers are you seeing the problem on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Firefox
|
||||||
|
- Chrome
|
||||||
|
- Safari
|
||||||
|
- Microsoft Edge
|
||||||
|
- Other
|
||||||
|
- type: dropdown
|
||||||
|
id: on-demo
|
||||||
|
attributes:
|
||||||
|
label: Can you reproduce this on the public demo?
|
||||||
|
description: You can check this at https://demo.snipeitapp.com.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
- N/A
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: fmcs
|
||||||
|
attributes:
|
||||||
|
label: Do you have full multiple company support enabled?
|
||||||
|
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: fmcs-location
|
||||||
|
attributes:
|
||||||
|
label: If you have full multiple company support enabled, do you have location scoping to company enabled?
|
||||||
|
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
- I do not have full multiple company support enabled
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: server-logs
|
||||||
|
attributes:
|
||||||
|
label: Application log output
|
||||||
|
description: Please copy and paste any relevant log output from `storage/logs/laravel.log`. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: browser-logs
|
||||||
|
attributes:
|
||||||
|
label: Browser console output
|
||||||
|
description: Please copy and paste any relevant log output from your browser console. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: checkboxes
|
||||||
|
id: common-issues
|
||||||
|
attributes:
|
||||||
|
label: Common Issues
|
||||||
|
description: Please make sure you have done the following before submitting your issue.
|
||||||
|
options:
|
||||||
|
- label: I have searched this repo for existing issues related to my issue (including closed issues)
|
||||||
|
required: true
|
||||||
|
- label: My APP_URL is set correctly in my .env file (including http or https and no trailing slash)
|
||||||
|
required: true
|
||||||
|
- label: I have searched the official Snipe-IT documentation and have checked the Common Issues documentation (where applicable)
|
||||||
|
required: true
|
||||||
|
- label: I have run database migrations (where applicable).
|
||||||
|
required: true
|
||||||
|
- label: I have attached screenshots and/or videos of the issue (where applicable)
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
38
.github/ISSUE_TEMPLATE/Feature-Request.yml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/Feature-Request.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Request a new feature.
|
||||||
|
title: "[Feature]: "
|
||||||
|
projects: ["grokability/snipe-it"]
|
||||||
|
type: feature
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this feature request! Please make sure to search the existing issues in this repository to see if your feature has already been requested, and feel free to add your context to any existing requests.
|
||||||
|
|
||||||
|
**Please write your issue in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
|
||||||
|
|
||||||
|
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Snipe-IT Version
|
||||||
|
description: What version of Snipe-IT are you currently running? You can find the version number in the footer of any page in Snipe-IT.
|
||||||
|
placeholder: ex. v8.3.1 - build 19577 (master)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: feature-description
|
||||||
|
attributes:
|
||||||
|
label: How can we help?
|
||||||
|
description: Let us know in detail what feature you'd like to see added. While we can't promise to implement every feature request, we do read every one and take them into consideration when planning future releases.
|
||||||
|
placeholder: Tell us what you'd like to see in Snipe-IT! (Be nice!)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
6
.github/workflows/SA-codeql.yml
vendored
6
.github/workflows/SA-codeql.yml
vendored
@@ -30,10 +30,10 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v4
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|||||||
2
.github/workflows/codacy-analysis.yml
vendored
2
.github/workflows/codacy-analysis.yml
vendored
@@ -52,6 +52,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the SARIF file generated in the previous step
|
# Upload the SARIF file generated in the previous step
|
||||||
- name: Upload SARIF results file
|
- name: Upload SARIF results file
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
uses: github/codeql-action/upload-sarif@v4
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
# pull-requests: write
|
# pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
debug-only: true
|
debug-only: true
|
||||||
ascending: true
|
ascending: true
|
||||||
|
|||||||
12
.github/workflows/tests-mysql.yml
vendored
12
.github/workflows/tests-mysql.yml
vendored
@@ -76,4 +76,16 @@ jobs:
|
|||||||
DB_DATABASE: snipeit
|
DB_DATABASE: snipeit
|
||||||
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
12
.github/workflows/tests-postgres.yml
vendored
12
.github/workflows/tests-postgres.yml
vendored
@@ -75,4 +75,16 @@ jobs:
|
|||||||
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
||||||
DB_USERNAME: snipeit
|
DB_USERNAME: snipeit
|
||||||
DB_PASSWORD: password
|
DB_PASSWORD: password
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
12
.github/workflows/tests-sqlite.yml
vendored
12
.github/workflows/tests-sqlite.yml
vendored
@@ -61,4 +61,16 @@ jobs:
|
|||||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||||
env:
|
env:
|
||||||
DB_CONNECTION: sqlite
|
DB_CONNECTION: sqlite
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||||||
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=swift2512 "Code") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
|
||||||
|
|||||||
59
app/Actions/Categories/DestroyCategoryAction.php
Normal file
59
app/Actions/Categories/DestroyCategoryAction.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Categories;
|
||||||
|
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Category;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class DestroyCategoryAction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws ItemStillHasAssets
|
||||||
|
* @throws ItemStillHasAssetModels
|
||||||
|
* @throws ItemStillHasComponents
|
||||||
|
* @throws ItemStillHasAccessories
|
||||||
|
* @throws ItemStillHasLicenses
|
||||||
|
* @throws ItemStillHasConsumables
|
||||||
|
*/
|
||||||
|
static function run(Category $category): bool
|
||||||
|
{
|
||||||
|
$category->loadCount([
|
||||||
|
'assets as assets_count',
|
||||||
|
'accessories as accessories_count',
|
||||||
|
'consumables as consumables_count',
|
||||||
|
'components as components_count',
|
||||||
|
'licenses as licenses_count',
|
||||||
|
'models as models_count'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($category->assets_count > 0) {
|
||||||
|
throw new ItemStillHasAssets($category);
|
||||||
|
}
|
||||||
|
if ($category->accessories_count > 0) {
|
||||||
|
throw new ItemStillHasAccessories($category);
|
||||||
|
}
|
||||||
|
if ($category->consumables_count > 0) {
|
||||||
|
throw new ItemStillHasConsumables($category);
|
||||||
|
}
|
||||||
|
if ($category->components_count > 0) {
|
||||||
|
throw new ItemStillHasComponents($category);
|
||||||
|
}
|
||||||
|
if ($category->licenses_count > 0) {
|
||||||
|
throw new ItemStillHasLicenses($category);
|
||||||
|
}
|
||||||
|
if ($category->models_count > 0) {
|
||||||
|
throw new ItemStillHasAssetModels($category);
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::disk('public')->delete('categories'.'/'.$category->image);
|
||||||
|
$category->delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
app/Actions/Manufacturers/DeleteManufacturerAction.php
Normal file
63
app/Actions/Manufacturers/DeleteManufacturerAction.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Manufacturers;
|
||||||
|
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Manufacturer;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class DeleteManufacturerAction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws ItemStillHasAssets
|
||||||
|
* @throws ItemStillHasComponents
|
||||||
|
* @throws ItemStillHasAccessories
|
||||||
|
* @throws ItemStillHasLicenses
|
||||||
|
* @throws ItemStillHasConsumables
|
||||||
|
*/
|
||||||
|
static function run(Manufacturer $manufacturer): bool
|
||||||
|
{
|
||||||
|
$manufacturer->loadCount([
|
||||||
|
'assets as assets_count',
|
||||||
|
'accessories as accessories_count',
|
||||||
|
'consumables as consumables_count',
|
||||||
|
'components as components_count',
|
||||||
|
'licenses as licenses_count',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($manufacturer->assets_count > 0) {
|
||||||
|
throw new ItemStillHasAssets($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->accessories_count > 0) {
|
||||||
|
throw new ItemStillHasAccessories($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->consumables_count > 0) {
|
||||||
|
throw new ItemStillHasConsumables($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->components_count > 0) {
|
||||||
|
throw new ItemStillHasComponents($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->licenses_count > 0) {
|
||||||
|
throw new ItemStillHasLicenses($manufacturer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($manufacturer->image) {
|
||||||
|
try {
|
||||||
|
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::info($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$manufacturer->delete();
|
||||||
|
//dd($manufacturer);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
72
app/Actions/Suppliers/DestroySupplierAction.php
Normal file
72
app/Actions/Suppliers/DestroySupplierAction.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Suppliers;
|
||||||
|
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class DestroySupplierAction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws ItemStillHasLicenses
|
||||||
|
* @throws ItemStillHasAssets
|
||||||
|
* @throws ItemStillHasMaintenances
|
||||||
|
* @throws ItemStillHasAccessories
|
||||||
|
* @throws ItemStillHasConsumables
|
||||||
|
* @throws ItemStillHasComponents
|
||||||
|
*/
|
||||||
|
static function run(Supplier $supplier): bool
|
||||||
|
{
|
||||||
|
$supplier->loadCount([
|
||||||
|
'maintenances as maintenances_count',
|
||||||
|
'assets as assets_count',
|
||||||
|
'licenses as licenses_count',
|
||||||
|
'accessories as accessories_count',
|
||||||
|
'consumables as consumables_count',
|
||||||
|
'components as components_count',
|
||||||
|
]);
|
||||||
|
if ($supplier->assets_count > 0) {
|
||||||
|
throw new ItemStillHasAssets($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->maintenances_count > 0) {
|
||||||
|
throw new ItemStillHasMaintenances($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->licenses_count > 0) {
|
||||||
|
throw new ItemStillHasLicenses($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->accessories_count > 0) {
|
||||||
|
throw new ItemStillHasAccessories($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->consumables_count > 0) {
|
||||||
|
throw new ItemStillHasConsumables($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->components_count > 0) {
|
||||||
|
throw new ItemStillHasComponents($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->image) {
|
||||||
|
try {
|
||||||
|
Storage::disk('public')->delete('suppliers/'.$supplier->image);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::info($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$supplier->delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -317,9 +317,21 @@ class LdapSync extends Command
|
|||||||
if($ldap_map["jobtitle"] != null){
|
if($ldap_map["jobtitle"] != null){
|
||||||
$user->jobtitle = $item['jobtitle'];
|
$user->jobtitle = $item['jobtitle'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["address"] != null){
|
||||||
|
$user->address = $item['address'];
|
||||||
|
}
|
||||||
|
if($ldap_map["city"] != null){
|
||||||
|
$user->city = $item['city'];
|
||||||
|
}
|
||||||
|
if($ldap_map["state"] != null){
|
||||||
|
$user->state = $item['state'];
|
||||||
|
}
|
||||||
if($ldap_map["country"] != null){
|
if($ldap_map["country"] != null){
|
||||||
$user->country = $item['country'];
|
$user->country = $item['country'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["zip"] != null){
|
||||||
|
$user->zip = $item['zip'];
|
||||||
|
}
|
||||||
if($ldap_map["dept"] != null){
|
if($ldap_map["dept"] != null){
|
||||||
$user->department_id = $department->id;
|
$user->department_id = $department->id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Symfony\Component\Console\Helper\ProgressIndicator;
|
use Symfony\Component\Console\Helper\ProgressIndicator;
|
||||||
|
|
||||||
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
|
|
||||||
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ObjectImportCommand
|
* Class ObjectImportCommand
|
||||||
@@ -52,6 +50,9 @@ class ObjectImportCommand extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
|
||||||
|
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
|
||||||
|
|
||||||
$this->progressIndicator = new ProgressIndicator($this->output);
|
$this->progressIndicator = new ProgressIndicator($this->output);
|
||||||
|
|
||||||
$filename = $this->argument('filename');
|
$filename = $this->argument('filename');
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ class PaveIt extends Command
|
|||||||
'migrations',
|
'migrations',
|
||||||
'settings',
|
'settings',
|
||||||
'users',
|
'users',
|
||||||
|
'telescope_entries',
|
||||||
|
'telescope_entries_tags',
|
||||||
|
'telescope_monitoring',
|
||||||
];
|
];
|
||||||
|
|
||||||
// We only need to find out what these are so we can nuke these columns on the assets table.
|
// We only need to find out what these are so we can nuke these columns on the assets table.
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Purge extends Command
|
|||||||
$maintenances = 0;
|
$maintenances = 0;
|
||||||
|
|
||||||
foreach ($assets as $asset) {
|
foreach ($assets as $asset) {
|
||||||
$this->info('- Asset "'.$asset->present()->name().'" deleted.');
|
$this->info('- Asset "'.$asset->display_name.'" deleted.');
|
||||||
$asset_assoc += $asset->assetlog()->count();
|
$asset_assoc += $asset->assetlog()->count();
|
||||||
$asset->assetlog()->forceDelete();
|
$asset->assetlog()->forceDelete();
|
||||||
$maintenances += $asset->maintenances()->count();
|
$maintenances += $asset->maintenances()->count();
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Actionlog;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class RemoveInvalidUploadDeleteActionLogItems extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'snipeit:remove-invalid-upload-delete-action-log-items';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Permanently remove invalid "upload deleted" action log items that have a null filename. This command can potentially result in deleted files being "resurrected" in the UI.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$invalidLogs = Actionlog::query()
|
||||||
|
->where('action_type', 'upload deleted')
|
||||||
|
->whereNull('filename')
|
||||||
|
->withTrashed()
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->info("{$invalidLogs->count()} invalid log items found.");
|
||||||
|
|
||||||
|
if ($invalidLogs->count() === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->table(['ID', 'Action Type', 'Item Type', 'Item ID', 'Created At', 'Deleted At'], $invalidLogs->map(fn($log) => [
|
||||||
|
$log->id,
|
||||||
|
$log->action_type,
|
||||||
|
$log->item_type,
|
||||||
|
$log->item_id,
|
||||||
|
$log->created_at,
|
||||||
|
$log->deleted_at,
|
||||||
|
])->toArray());
|
||||||
|
|
||||||
|
if ($this->confirm("Do you wish to remove {$invalidLogs->count()} log items?")) {
|
||||||
|
$invalidLogs->each(fn($log) => $log->forceDelete());
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use enshrined\svgSanitize\Sanitizer;
|
||||||
|
|
||||||
class SQLStreamer {
|
class SQLStreamer {
|
||||||
private $input;
|
private $input;
|
||||||
@@ -242,9 +243,10 @@ class RestoreFromBackup extends Command
|
|||||||
|
|
||||||
$private_dirs = [
|
$private_dirs = [
|
||||||
'storage/private_uploads/accessories',
|
'storage/private_uploads/accessories',
|
||||||
'storage/private_uploads/assetmodels',
|
'storage/private_uploads/assetmodels' => 'storage/private_uploads/models', //this was changed from assetmodels => models Aug 10 2025
|
||||||
'storage/private_uploads/maintenances',
|
'storage/private_uploads/asset_maintenances' => 'storage/private_uploads/maintenances', //this was changed from asset_maintenances => maintenances Aug 10 2025
|
||||||
'storage/private_uploads/models',
|
'storage/private_uploads/maintenances', //but let 'maintenances' take precedence
|
||||||
|
'storage/private_uploads/models', //and let 'models' take precedence
|
||||||
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
|
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
|
||||||
'storage/private_uploads/audits',
|
'storage/private_uploads/audits',
|
||||||
'storage/private_uploads/components',
|
'storage/private_uploads/components',
|
||||||
@@ -262,7 +264,7 @@ class RestoreFromBackup extends Command
|
|||||||
];
|
];
|
||||||
$public_dirs = [
|
$public_dirs = [
|
||||||
'public/uploads/accessories',
|
'public/uploads/accessories',
|
||||||
'public/uploads/assetmodels',
|
// 'public/uploads/assetmodels' => 'public/uploads/models', //according to git, this was _never_ a thing... (see below)
|
||||||
'public/uploads/maintenances',
|
'public/uploads/maintenances',
|
||||||
'public/uploads/assets', // these are asset _pictures_, not asset files
|
'public/uploads/assets', // these are asset _pictures_, not asset files
|
||||||
'public/uploads/avatars',
|
'public/uploads/avatars',
|
||||||
@@ -273,7 +275,7 @@ class RestoreFromBackup extends Command
|
|||||||
'public/uploads/departments',
|
'public/uploads/departments',
|
||||||
'public/uploads/locations',
|
'public/uploads/locations',
|
||||||
'public/uploads/manufacturers',
|
'public/uploads/manufacturers',
|
||||||
'public/uploads/models',
|
'public/uploads/models', // ...it's been this way for 9 years (as of late 2025)
|
||||||
'public/uploads/suppliers',
|
'public/uploads/suppliers',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -286,8 +288,6 @@ class RestoreFromBackup extends Command
|
|||||||
'public/uploads/favicon-uploaded.*',
|
'public/uploads/favicon-uploaded.*',
|
||||||
];
|
];
|
||||||
|
|
||||||
$all_files = $private_dirs + $public_dirs;
|
|
||||||
|
|
||||||
$sqlfiles = [];
|
$sqlfiles = [];
|
||||||
$sqlfile_indices = [];
|
$sqlfile_indices = [];
|
||||||
|
|
||||||
@@ -295,6 +295,20 @@ class RestoreFromBackup extends Command
|
|||||||
$boring_files = [];
|
$boring_files = [];
|
||||||
$unsafe_files = [];
|
$unsafe_files = [];
|
||||||
|
|
||||||
|
$good_extensions = config('filesystems.allowed_upload_extensions_array');
|
||||||
|
|
||||||
|
$private_extensions = array_merge($good_extensions, ["csv", "key"]); //add csv, and 'key'
|
||||||
|
$public_extensions = array_diff($good_extensions, ["xml"]); //remove xml
|
||||||
|
|
||||||
|
$sanitizer = new Sanitizer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: I _hate_ the "continue 3" thing we keep doing here
|
||||||
|
* I think a better approach might be to have the "each file" stuff be in a method on this class, and the
|
||||||
|
* boring_files and interesting_files be properties on it that we fill out. Then, in that method, we could
|
||||||
|
* just do a 'return' once the file is actually handled (yay or nay). We could also start to break out some of
|
||||||
|
* the _other_ things that we do into their own methods too? But I don't care about that as much.
|
||||||
|
*/
|
||||||
for ($i = 0; $i < $za->numFiles; $i++) {
|
for ($i = 0; $i < $za->numFiles; $i++) {
|
||||||
$stat_results = $za->statIndex($i);
|
$stat_results = $za->statIndex($i);
|
||||||
// echo "index: $i\n";
|
// echo "index: $i\n";
|
||||||
@@ -309,7 +323,7 @@ class RestoreFromBackup extends Command
|
|||||||
// skip macOS resource fork files (?!?!?!)
|
// skip macOS resource fork files (?!?!?!)
|
||||||
if (strpos($raw_path, '__MACOSX') !== false && strpos($raw_path, '._') !== false) {
|
if (strpos($raw_path, '__MACOSX') !== false && strpos($raw_path, '._') !== false) {
|
||||||
//print "SKIPPING macOS Resource fork file: $raw_path\n";
|
//print "SKIPPING macOS Resource fork file: $raw_path\n";
|
||||||
$boring_files[] = $raw_path;
|
// $boring_files[] = $raw_path; //stop adding this to the boring files list; it's just confusing
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
|
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
|
||||||
@@ -318,44 +332,70 @@ class RestoreFromBackup extends Command
|
|||||||
$sqlfile_indices[] = $i;
|
$sqlfile_indices[] = $i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ($raw_path[-1] == '/') {
|
||||||
|
//last character is '/' - this is a directory, and we don't need it, and we don't need to warn about it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_array(basename($raw_path), [".gitkeep", ".gitignore", ".DS_Store"])) {
|
||||||
|
//skip these boring files silently without reporting on them; they're stupid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
foreach (array_merge($private_dirs, $public_dirs) as $dir) {
|
foreach (['public' => $public_dirs, 'private' => $private_dirs] as $purpose => $dirs) {
|
||||||
$last_pos = strrpos($raw_path, $dir . '/');
|
$allowed_extensions = match ($purpose) {
|
||||||
if ($last_pos !== false) {
|
'public' => $public_extensions,
|
||||||
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
'private' => $private_extensions,
|
||||||
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
|
};
|
||||||
$interesting_files[$raw_path] = ['dest' => $dir, 'index' => $i];
|
foreach ($dirs as $dir => $destdir) {
|
||||||
continue 2;
|
if (is_int($dir)) {
|
||||||
if ($last_pos + strlen($dir) + 1 == strlen($raw_path)) {
|
$dir = $destdir;
|
||||||
// we don't care about that; we just want files with the appropriate prefix
|
}
|
||||||
//print("FOUND THE EXACT DIRECTORY: $dir AT: $raw_path!!!\n");
|
$last_pos = strrpos($raw_path, $dir . '/');
|
||||||
|
if ($last_pos !== false) {
|
||||||
|
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
||||||
|
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
|
||||||
|
//the CSV bit, below, is because we store CSV files as "blahcsv" - without an extension
|
||||||
|
if (!in_array($extension, $allowed_extensions) && !($dir == "storage/private_uploads/imports" && substr($raw_path, -3) == "csv" && $extension == "")) {
|
||||||
|
$unsafe_files[] = $raw_path;
|
||||||
|
Log::debug($raw_path . ' from directory ' . $dir . ' is being skipped');
|
||||||
|
} else {
|
||||||
|
if ($dir != $destdir) {
|
||||||
|
Log::debug("Getting ready to save file $raw_path to new directory $destdir");
|
||||||
|
}
|
||||||
|
$interesting_files[$raw_path] = ['dest' => $destdir, 'index' => $i];
|
||||||
|
}
|
||||||
|
continue 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$good_extensions = config('filesystems.allowed_upload_extensions_array');
|
foreach (['public' => $public_files, 'private' => $private_files] as $purpose => $files) {
|
||||||
|
$allowed_extensions = match ($purpose) {
|
||||||
foreach (array_merge($private_files, $public_files) as $file) {
|
'public' => $public_extensions,
|
||||||
$has_wildcard = (strpos($file, '*') !== false);
|
'private' => $private_extensions,
|
||||||
if ($has_wildcard) {
|
};
|
||||||
$file = substr($file, 0, -1); //trim last character (which should be the wildcard)
|
foreach ($files as $file) {
|
||||||
}
|
$has_wildcard = (strpos($file, '*') !== false);
|
||||||
$last_pos = strrpos($raw_path, $file); // no trailing slash!
|
if ($has_wildcard) {
|
||||||
if ($last_pos !== false) {
|
$file = substr($file, 0, -1); //trim last character (which should be the wildcard)
|
||||||
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
|
|
||||||
if (!in_array($extension, $good_extensions)) {
|
|
||||||
// gathering potentially unsafe files here to return at exit
|
|
||||||
$unsafe_files[] = $raw_path;
|
|
||||||
Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
|
|
||||||
$boring_files[] = $raw_path;
|
|
||||||
continue 2;
|
|
||||||
}
|
}
|
||||||
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
$last_pos = strrpos($raw_path, $file); // no trailing slash!
|
||||||
//no wildcards found in $file, process 'normally'
|
if ($last_pos !== false) {
|
||||||
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { //again, no trailing slash. or this is a wildcard and we just take it.
|
if (!in_array($extension, $allowed_extensions)) {
|
||||||
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
|
// gathering potentially unsafe files here to return at exit
|
||||||
$interesting_files[$raw_path] = ['dest' => dirname($file), 'index' => $i];
|
$unsafe_files[] = $raw_path;
|
||||||
continue 2;
|
Log::debug('Potentially unsafe file ' . $raw_path . ' is being skipped');
|
||||||
|
$boring_files[] = $raw_path;
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
|
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
||||||
|
//no wildcards found in $file, process 'normally'
|
||||||
|
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { //again, no trailing slash. or this is a wildcard and we just take it.
|
||||||
|
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
|
||||||
|
$interesting_files[$raw_path] = ['dest' => dirname($file), 'index' => $i];
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,18 +532,25 @@ class RestoreFromBackup extends Command
|
|||||||
}
|
}
|
||||||
foreach ($interesting_files as $pretty_file_name => $file_details) {
|
foreach ($interesting_files as $pretty_file_name => $file_details) {
|
||||||
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
|
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
|
||||||
$fp = $za->getStream($ugly_file_name);
|
$migrated_file_name = $file_details['dest'] . '/' . basename($pretty_file_name);
|
||||||
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
|
if (strcasecmp(substr($pretty_file_name, -4), ".svg") === 0) {
|
||||||
if (!is_dir($file_details['dest'])) {
|
$svg_contents = $za->getFromIndex($file_details['index']);
|
||||||
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
|
$cleaned_svg = $sanitizer->sanitize($svg_contents);
|
||||||
|
file_put_contents($migrated_file_name, $cleaned_svg);
|
||||||
|
} else {
|
||||||
|
$fp = $za->getStream($ugly_file_name);
|
||||||
|
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
|
||||||
|
if (!is_dir($file_details['dest'])) {
|
||||||
|
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
|
||||||
|
}
|
||||||
|
$migrated_file = fopen($migrated_file_name, 'w');
|
||||||
|
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
|
||||||
|
fwrite($migrated_file, $buffer);
|
||||||
|
}
|
||||||
|
fclose($migrated_file);
|
||||||
|
fclose($fp);
|
||||||
|
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
|
||||||
}
|
}
|
||||||
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
|
|
||||||
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
|
|
||||||
fwrite($migrated_file, $buffer);
|
|
||||||
}
|
|
||||||
fclose($migrated_file);
|
|
||||||
fclose($fp);
|
|
||||||
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
|
|
||||||
if ($bar) {
|
if ($bar) {
|
||||||
$bar->advance();
|
$bar->advance();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
use App\Mail\ExpiringAssetsMail;
|
use App\Mail\ExpiringAssetsMail;
|
||||||
use App\Mail\ExpiringLicenseMail;
|
use App\Mail\ExpiringLicenseMail;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
@@ -52,19 +53,73 @@ class SendExpirationAlerts extends Command
|
|||||||
->filter(fn($item) => !empty($item))
|
->filter(fn($item) => !empty($item))
|
||||||
->all();
|
->all();
|
||||||
// Expiring Assets
|
// Expiring Assets
|
||||||
$assets = Asset::getExpiringWarrantee($alert_interval);
|
$assets = Asset::getExpiringWarrantyOrEol($alert_interval);
|
||||||
|
|
||||||
if ($assets->count() > 0) {
|
if ($assets->count() > 0) {
|
||||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
|
|
||||||
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
|
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
[
|
||||||
|
trans('general.id'),
|
||||||
|
trans('admin/hardware/form.tag'),
|
||||||
|
trans('admin/hardware/form.model'),
|
||||||
|
trans('general.model_no'),
|
||||||
|
trans('general.purchase_date'),
|
||||||
|
trans('admin/hardware/form.eol_rate'),
|
||||||
|
trans('admin/hardware/form.eol_date'),
|
||||||
|
trans('admin/hardware/form.warranty_expires'),
|
||||||
|
],
|
||||||
|
$assets->map(fn($item) =>
|
||||||
|
[
|
||||||
|
trans('general.id') => $item->id,
|
||||||
|
trans('admin/hardware/form.tag') => $item->asset_tag,
|
||||||
|
trans('admin/hardware/form.model') => $item->model->name,
|
||||||
|
trans('general.model_no') => $item->model->model_number,
|
||||||
|
trans('general.purchase_date') => $item->purchase_date_formatted,
|
||||||
|
trans('admin/hardware/form.eol_rate') => $item->model->eol,
|
||||||
|
trans('admin/hardware/form.eol_date') => $item->eol_date ? $item->eol_formatted_date .' ('.$item->eol_diff_for_humans.')' : '',
|
||||||
|
trans('admin/hardware/form.warranty_expires') => $item->warranty_expires ? $item->warranty_expires_formatted_date .' ('.$item->warranty_expires_diff_for_humans.')' : '',
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expiring licenses
|
// Expiring licenses
|
||||||
$licenses = License::getExpiringLicenses($alert_interval);
|
$licenses = License::query()->ExpiringLicenses($alert_interval)
|
||||||
|
->with('manufacturer','category')
|
||||||
|
->orderBy('expiration_date', 'ASC')
|
||||||
|
->orderBy('termination_date', 'ASC')
|
||||||
|
->get();
|
||||||
if ($licenses->count() > 0) {
|
if ($licenses->count() > 0) {
|
||||||
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
|
|
||||||
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
|
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
[
|
||||||
|
trans('general.id'),
|
||||||
|
trans('general.name'),
|
||||||
|
trans('general.purchase_date'),
|
||||||
|
trans('admin/licenses/form.expiration'),
|
||||||
|
trans('mail.expires'),
|
||||||
|
trans('admin/licenses/form.termination_date'),
|
||||||
|
trans('mail.terminates')],
|
||||||
|
$licenses->map(fn($item) => [
|
||||||
|
trans('general.id') => $item->id,
|
||||||
|
trans('general.name') => $item->name,
|
||||||
|
trans('general.purchase_date') => $item->purchase_date_formatted,
|
||||||
|
trans('admin/licenses/form.expiration') => $item->expires_formatted_date,
|
||||||
|
trans('mail.expires') => $item->expires_formatted_date ? $item->expires_diff_for_humans : '',
|
||||||
|
trans('admin/licenses/form.termination_date') => $item->terminates_formatted_date,
|
||||||
|
trans('mail.terminates') => $item->terminates_diff_for_humans
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send a message even if the count is 0
|
||||||
|
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
|
||||||
|
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if ($settings->alert_email == '') {
|
if ($settings->alert_email == '') {
|
||||||
$this->error('Could not send email. No alert email configured in settings');
|
$this->error('Could not send email. No alert email configured in settings');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class SendUpcomingAuditReport extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'snipeit:upcoming-audits';
|
protected $signature = 'snipeit:upcoming-audits {--with-output : Display the results in a table in your console in addition to sending the email}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -47,21 +47,69 @@ class SendUpcomingAuditReport extends Command
|
|||||||
$today = Carbon::now();
|
$today = Carbon::now();
|
||||||
$interval_date = $today->copy()->addDays($interval);
|
$interval_date = $today->copy()->addDays($interval);
|
||||||
|
|
||||||
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
|
$assets_query = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'asc')->with('supplier');
|
||||||
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
|
$asset_count = $assets_query->count();
|
||||||
|
$this->info(number_format($asset_count) . ' assets must be audited on or before ' . $interval_date);
|
||||||
|
if (!$this->option('with-output')) {
|
||||||
if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
|
$this->info('Run this command with the --with-output option to see the full list in the console.');
|
||||||
// Send a rollup to the admin, if settings dictate
|
|
||||||
$recipients = collect(explode(',', $settings->alert_email))
|
|
||||||
->map(fn($item) => trim($item))
|
|
||||||
->filter(fn($item) => !empty($item))
|
|
||||||
->all();
|
|
||||||
|
|
||||||
|
|
||||||
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
|
|
||||||
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($asset_count > 0) {
|
||||||
|
|
||||||
|
$assets_for_email = $assets_query->limit(30)->get();
|
||||||
|
|
||||||
|
// Send a rollup to the admin, if settings dictate
|
||||||
|
if ($settings->alert_email != '') {
|
||||||
|
|
||||||
|
$recipients = collect(explode(',', $settings->alert_email))
|
||||||
|
->map(fn($item) => trim($item))
|
||||||
|
->filter(fn($item) => !empty($item))
|
||||||
|
->all();
|
||||||
|
|
||||||
|
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets_for_email, $settings->audit_warning_days, $asset_count));
|
||||||
|
$this->info('Audit notification sent to: ' . $settings->alert_email);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->info('There is no admin alert email set so no email will be sent.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ($this->option('with-output')) {
|
||||||
|
|
||||||
|
|
||||||
|
// Get the full list if the user wants output in the console
|
||||||
|
$assets_for_output = $assets_query->limit(null)->get();
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
[
|
||||||
|
trans('general.id'),
|
||||||
|
trans('general.name'),
|
||||||
|
trans('general.last_audit'),
|
||||||
|
trans('general.next_audit_date'),
|
||||||
|
trans('mail.Days'),
|
||||||
|
trans('mail.supplier'),
|
||||||
|
trans('mail.assigned_to'),
|
||||||
|
|
||||||
|
],
|
||||||
|
$assets_for_output->map(fn($item) => [
|
||||||
|
trans('general.id') => $item->id,
|
||||||
|
trans('general.name') => $item->display_name,
|
||||||
|
trans('general.last_audit') => $item->last_audit_formatted_date,
|
||||||
|
trans('general.next_audit_date') => $item->next_audit_formatted_date,
|
||||||
|
trans('mail.Days') => round($item->next_audit_diff_in_days),
|
||||||
|
trans('mail.supplier') => $item->supplier ? $item->supplier->name : '',
|
||||||
|
trans('mail.assigned_to') => $item->assignedTo ? $item->assignedTo->display_name : '',
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->info('There are no assets due for audit in the next ' . $interval . ' days.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class SystemBackup extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
ini_set('max_execution_time', env('BACKUP_TIME_LIMIT', 600)); //600 seconds = 10 minutes
|
||||||
|
|
||||||
if ($this->option('filename')) {
|
if ($this->option('filename')) {
|
||||||
$filename = $this->option('filename');
|
$filename = $this->option('filename');
|
||||||
|
|
||||||
|
|||||||
10
app/Exceptions/ItemStillHasAccessories.php
Normal file
10
app/Exceptions/ItemStillHasAccessories.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasAccessories extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasAssetModels.php
Normal file
10
app/Exceptions/ItemStillHasAssetModels.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasAssetModels extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
9
app/Exceptions/ItemStillHasAssets.php
Normal file
9
app/Exceptions/ItemStillHasAssets.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasAssets extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
}
|
||||||
14
app/Exceptions/ItemStillHasChildren.php
Normal file
14
app/Exceptions/ItemStillHasChildren.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasChildren extends Exception
|
||||||
|
{
|
||||||
|
//public function __construct($message, $code = 0, Exception $previous = null, $parent, $children)
|
||||||
|
//{
|
||||||
|
// trans()
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasComponents.php
Normal file
10
app/Exceptions/ItemStillHasComponents.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasComponents extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasConsumables.php
Normal file
10
app/Exceptions/ItemStillHasConsumables.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasConsumables extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasLicenses.php
Normal file
10
app/Exceptions/ItemStillHasLicenses.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasLicenses extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasMaintenances.php
Normal file
10
app/Exceptions/ItemStillHasMaintenances.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasMaintenances extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -95,7 +95,7 @@ class Helper
|
|||||||
$Parsedown->setSafeMode(true);
|
$Parsedown->setSafeMode(true);
|
||||||
|
|
||||||
if ($str) {
|
if ($str) {
|
||||||
return $Parsedown->text($str);
|
return $Parsedown->text(strip_tags($str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class Helper
|
|||||||
$Parsedown->setSafeMode(true);
|
$Parsedown->setSafeMode(true);
|
||||||
|
|
||||||
if ($str) {
|
if ($str) {
|
||||||
return $Parsedown->line($str);
|
return $Parsedown->line(strip_tags($str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +435,34 @@ class Helper
|
|||||||
return $colors[$index];
|
return $colors[$index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string has any RTL characters
|
||||||
|
* @param $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function hasRtl($string) {
|
||||||
|
$rtlChar = '/[\x{0590}-\x{083F}]|[\x{08A0}-\x{08FF}]|[\x{FB1D}-\x{FDFF}]|[\x{FE70}-\x{FEFF}]/u';
|
||||||
|
return preg_match($rtlChar, $string) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is chinese, japanese or korean language
|
||||||
|
public static function isCjk($string) {
|
||||||
|
return Helper::isChinese($string) || Helper::isJapanese($string) || Helper::isKorean($string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isChinese($string) {
|
||||||
|
return preg_match("/\p{Han}+/u", $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isJapanese($string) {
|
||||||
|
return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isKorean($string) {
|
||||||
|
return preg_match('/[\x{3130}-\x{318F}\x{AC00}-\x{D7AF}]/u', $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
||||||
*
|
*
|
||||||
@@ -1706,5 +1734,5 @@ class Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $mismatched;
|
return $mismatched;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class IconHelper
|
|||||||
case 'clone':
|
case 'clone':
|
||||||
return 'far fa-clone';
|
return 'far fa-clone';
|
||||||
case 'delete':
|
case 'delete':
|
||||||
|
case 'upload deleted':
|
||||||
return 'fas fa-trash';
|
return 'fas fa-trash';
|
||||||
case 'create':
|
case 'create':
|
||||||
return 'fa-solid fa-plus';
|
return 'fa-solid fa-plus';
|
||||||
@@ -39,6 +40,8 @@ class IconHelper
|
|||||||
return 'fa-solid fa-trash-arrow-up';
|
return 'fa-solid fa-trash-arrow-up';
|
||||||
case 'external-link':
|
case 'external-link':
|
||||||
return 'fa fa-external-link';
|
return 'fa fa-external-link';
|
||||||
|
case 'link':
|
||||||
|
return 'fa fa-link';
|
||||||
case 'email':
|
case 'email':
|
||||||
return 'fa-regular fa-envelope';
|
return 'fa-regular fa-envelope';
|
||||||
case 'phone':
|
case 'phone':
|
||||||
@@ -139,7 +142,7 @@ class IconHelper
|
|||||||
case 'more-files':
|
case 'more-files':
|
||||||
return 'fa-solid fa-laptop-file';
|
return 'fa-solid fa-laptop-file';
|
||||||
case 'maintenances':
|
case 'maintenances':
|
||||||
return 'fas fa-wrench';
|
return 'fa-solid fa-screwdriver-wrench';
|
||||||
case 'seats':
|
case 'seats':
|
||||||
return 'far fa-list-alt';
|
return 'far fa-list-alt';
|
||||||
case 'globe-us':
|
case 'globe-us':
|
||||||
@@ -194,6 +197,10 @@ class IconHelper
|
|||||||
case 'note':
|
case 'note':
|
||||||
case 'notes':
|
case 'notes':
|
||||||
return 'fas fa-sticky-note';
|
return 'fas fa-sticky-note';
|
||||||
|
case 'tip':
|
||||||
|
return 'fa-solid fa-lightbulb';
|
||||||
|
case 'highlight':
|
||||||
|
return 'fa-solid fa-highlighter';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class AccessoryCheckoutController extends Controller
|
|||||||
$this->authorize('checkout', $accessory);
|
$this->authorize('checkout', $accessory);
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
||||||
|
|
||||||
|
|||||||
@@ -8,33 +8,23 @@ use App\Events\ItemAccepted;
|
|||||||
use App\Events\ItemDeclined;
|
use App\Events\ItemDeclined;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Mail\CheckoutAcceptanceResponseMail;
|
use App\Mail\CheckoutAcceptanceResponseMail;
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Asset;
|
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Contracts\Acceptable;
|
use App\Models\Contracts\Acceptable;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\AssetModel;
|
|
||||||
use App\Models\Accessory;
|
|
||||||
use App\Models\License;
|
|
||||||
use App\Models\Component;
|
|
||||||
use App\Models\Consumable;
|
|
||||||
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
||||||
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
|
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
|
||||||
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Http\Controllers\SettingsController;
|
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use \Illuminate\Contracts\View\View;
|
use \Illuminate\Contracts\View\View;
|
||||||
use \Illuminate\Http\RedirectResponse;
|
use \Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
|
||||||
class AcceptanceController extends Controller
|
class AcceptanceController extends Controller
|
||||||
{
|
{
|
||||||
@@ -85,6 +75,10 @@ class AcceptanceController extends Controller
|
|||||||
public function store(Request $request, $id) : RedirectResponse
|
public function store(Request $request, $id) : RedirectResponse
|
||||||
{
|
{
|
||||||
$acceptance = CheckoutAcceptance::find($id);
|
$acceptance = CheckoutAcceptance::find($id);
|
||||||
|
$assigned_user = User::find($acceptance->assigned_to_id);
|
||||||
|
$settings = Setting::getSettings();
|
||||||
|
$sig_filename='';
|
||||||
|
|
||||||
|
|
||||||
if (is_null($acceptance)) {
|
if (is_null($acceptance)) {
|
||||||
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||||
@@ -107,140 +101,80 @@ class AcceptanceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the signature and save it
|
* Check for the signature directory
|
||||||
*/
|
*/
|
||||||
if (! Storage::exists('private_uploads/signatures')) {
|
if (! Storage::exists('private_uploads/signatures')) {
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
Storage::makeDirectory('private_uploads/signatures', 775);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for the eula-pdfs directory
|
||||||
|
*/
|
||||||
|
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
||||||
|
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
||||||
|
}
|
||||||
|
|
||||||
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
||||||
$display_model = '';
|
|
||||||
$pdf_view_route = '';
|
// If signatures are required, make sure we have one
|
||||||
$pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
if (Setting::getSettings()->require_accept_signature == '1') {
|
||||||
$sig_filename='';
|
|
||||||
|
// The item was accepted, check for a signature
|
||||||
|
if ($request->filled('signature_output')) {
|
||||||
|
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
||||||
|
$data_uri = $request->input('signature_output');
|
||||||
|
$encoded_image = explode(',', $data_uri);
|
||||||
|
$decoded_image = base64_decode($encoded_image[1]);
|
||||||
|
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
||||||
|
|
||||||
|
// No image data is present, kick them back.
|
||||||
|
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
||||||
|
} else {
|
||||||
|
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Convert PDF logo to base64 for TCPDF
|
||||||
|
// This is needed for TCPDF to properly embed the image if it's a png and the cache isn't writable
|
||||||
|
$encoded_logo = null;
|
||||||
|
if ($settings->acceptance_pdf_logo) {
|
||||||
|
$encoded_logo = base64_encode(file_get_contents(public_path() . '/uploads/' . $settings->acceptance_pdf_logo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the data array ready for the notifications and PDF generation
|
||||||
|
$data = [
|
||||||
|
'item_tag' => $item->asset_tag,
|
||||||
|
'item_name' => $item->name, // this handles licenses seats, which don't have a 'name' field
|
||||||
|
'item_model' => $item->model?->name,
|
||||||
|
'item_serial' => $item->serial,
|
||||||
|
'item_status' => $item->assetstatus?->name,
|
||||||
|
'eula' => $item->getEula(),
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'check_out_date' => Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false),
|
||||||
|
'accepted_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
|
||||||
|
'declined_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
|
||||||
|
'assigned_to' => $assigned_user->display_name,
|
||||||
|
'email' => $assigned_user->email,
|
||||||
|
'employee_num' => $assigned_user->employee_num,
|
||||||
|
'site_name' => $settings->site_name,
|
||||||
|
'company_name' => $item->company?->name?? $settings->site_name,
|
||||||
|
'signature' => (($sig_filename && array_key_exists('1', $encoded_image))) ? $encoded_image[1] : null,
|
||||||
|
'logo' => ($encoded_logo) ?? null,
|
||||||
|
'date_settings' => $settings->date_display_format,
|
||||||
|
'qty' => $acceptance->qty ?? 1,
|
||||||
|
];
|
||||||
|
|
||||||
if ($request->input('asset_acceptance') == 'accepted') {
|
if ($request->input('asset_acceptance') == 'accepted') {
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for the eula-pdfs directory
|
|
||||||
*/
|
|
||||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
||||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Setting::getSettings()->require_accept_signature == '1') {
|
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
||||||
|
|
||||||
// Check if the signature directory exists, if not create it
|
|
||||||
if (!Storage::exists('private_uploads/signatures')) {
|
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item was accepted, check for a signature
|
// Generate the PDF content
|
||||||
if ($request->filled('signature_output')) {
|
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
|
||||||
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
|
||||||
$data_uri = $request->input('signature_output');
|
|
||||||
$encoded_image = explode(',', $data_uri);
|
|
||||||
$decoded_image = base64_decode($encoded_image[1]);
|
|
||||||
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
|
||||||
|
|
||||||
// No image data is present, kick them back.
|
|
||||||
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
|
||||||
} else {
|
|
||||||
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$assigned_user = User::find($acceptance->assigned_to_id);
|
|
||||||
// this is horrible
|
|
||||||
switch($acceptance->checkoutable_type){
|
|
||||||
case 'App\Models\Asset':
|
|
||||||
$pdf_view_route ='account.accept.accept-asset-eula';
|
|
||||||
$asset_model = AssetModel::find($item->model_id);
|
|
||||||
if (!$asset_model) {
|
|
||||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
|
||||||
}
|
|
||||||
$display_model = $asset_model->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Accessory':
|
|
||||||
$pdf_view_route ='account.accept.accept-accessory-eula';
|
|
||||||
$accessory = Accessory::find($item->id);
|
|
||||||
$display_model = $accessory->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\LicenseSeat':
|
|
||||||
$pdf_view_route ='account.accept.accept-license-eula';
|
|
||||||
$license = License::find($item->license_id);
|
|
||||||
$display_model = $license->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Component':
|
|
||||||
$pdf_view_route ='account.accept.accept-component-eula';
|
|
||||||
$component = Component::find($item->id);
|
|
||||||
$display_model = $component->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Consumable':
|
|
||||||
$pdf_view_route ='account.accept.accept-consumable-eula';
|
|
||||||
$consumable = Consumable::find($item->id);
|
|
||||||
$display_model = $consumable->name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
|
|
||||||
// $pdf_view_route ='account.accept.accept-asset-eula';
|
|
||||||
// $asset_model = AssetModel::find($item->model_id);
|
|
||||||
// $display_model = $asset_model->name;
|
|
||||||
// $assigned_to = User::find($item->assigned_to)->present()->fullName;
|
|
||||||
//
|
|
||||||
// } elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
|
|
||||||
// $pdf_view_route ='account.accept.accept-accessory-eula';
|
|
||||||
// $accessory = Accessory::find($item->id);
|
|
||||||
// $display_model = $accessory->name;
|
|
||||||
// $assigned_to = User::find($item->assignedTo);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gather the data for the PDF. We fire this whether there is a signature required or not,
|
|
||||||
* since we want the moment-in-time proof of what the EULA was when they accepted it.
|
|
||||||
*/
|
|
||||||
$branding_settings = SettingsController::getPDFBranding();
|
|
||||||
|
|
||||||
$path_logo = "";
|
|
||||||
|
|
||||||
// Check for the PDF logo path and use that, otherwise use the regular logo path
|
|
||||||
if (!is_null($branding_settings->acceptance_pdf_logo)) {
|
|
||||||
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
|
|
||||||
} elseif (!is_null($branding_settings->logo)) {
|
|
||||||
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'item_tag' => $item->asset_tag,
|
|
||||||
'item_model' => $display_model,
|
|
||||||
'item_serial' => $item->serial,
|
|
||||||
'item_status' => $item->assetstatus?->name,
|
|
||||||
'eula' => $item->getEula(),
|
|
||||||
'note' => $request->input('note'),
|
|
||||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
|
|
||||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
|
|
||||||
'assigned_to' => $assigned_user->present()->fullName,
|
|
||||||
'company_name' => $branding_settings->site_name,
|
|
||||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
|
||||||
'logo' => $path_logo,
|
|
||||||
'date_settings' => $branding_settings->date_display_format,
|
|
||||||
'admin' => auth()->user()->present()?->fullName,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($pdf_view_route!='') {
|
|
||||||
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
|
||||||
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
||||||
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Log the acceptance
|
||||||
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
||||||
|
|
||||||
// Send the PDF to the signing user
|
// Send the PDF to the signing user
|
||||||
@@ -248,15 +182,14 @@ class AcceptanceController extends Controller
|
|||||||
|
|
||||||
// Add the attachment for the signing user into the $data array
|
// Add the attachment for the signing user into the $data array
|
||||||
$data['file'] = $pdf_filename;
|
$data['file'] = $pdf_filename;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$assigned_user->notify(new AcceptanceAssetAcceptedToUserNotification($data));
|
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($assigned_user->locale));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
|
$acceptance->notify((new AcceptanceAssetAcceptedNotification($data))->locale(Setting::getSettings()->locale));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
@@ -264,95 +197,21 @@ class AcceptanceController extends Controller
|
|||||||
|
|
||||||
$return_msg = trans('admin/users/message.accepted');
|
$return_msg = trans('admin/users/message.accepted');
|
||||||
|
|
||||||
|
// Item was declined
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
/**
|
for ($i = 0; $i < ($acceptance->qty ?? 1); $i++) {
|
||||||
* Check for the eula-pdfs directory
|
$acceptance->decline($sig_filename, $request->input('note'));
|
||||||
*/
|
|
||||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
||||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->require_accept_signature == '1') {
|
|
||||||
|
|
||||||
// Check if the signature directory exists, if not create it
|
|
||||||
if (!Storage::exists('private_uploads/signatures')) {
|
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item was accepted, check for a signature
|
|
||||||
if ($request->filled('signature_output')) {
|
|
||||||
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
|
||||||
$data_uri = $request->input('signature_output');
|
|
||||||
$encoded_image = explode(',', $data_uri);
|
|
||||||
$decoded_image = base64_decode($encoded_image[1]);
|
|
||||||
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
|
||||||
|
|
||||||
// No image data is present, kick them back.
|
|
||||||
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
|
||||||
} else {
|
|
||||||
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the data to send the declined notification
|
|
||||||
$branding_settings = SettingsController::getPDFBranding();
|
|
||||||
|
|
||||||
// This is the most horriblest
|
|
||||||
switch($acceptance->checkoutable_type){
|
|
||||||
case 'App\Models\Asset':
|
|
||||||
$asset_model = AssetModel::find($item->model_id);
|
|
||||||
$display_model = $asset_model->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Accessory':
|
|
||||||
$accessory = Accessory::find($item->id);
|
|
||||||
$display_model = $accessory->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\LicenseSeat':
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Component':
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Consumable':
|
|
||||||
$consumable = Consumable::find($item->id);
|
|
||||||
$display_model = $consumable->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'item_tag' => $item->asset_tag,
|
|
||||||
'item_model' => $display_model,
|
|
||||||
'item_serial' => $item->serial,
|
|
||||||
'item_status' => $item->assetstatus?->name,
|
|
||||||
'note' => $request->input('note'),
|
|
||||||
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
|
|
||||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
|
||||||
'assigned_to' => $assigned_to,
|
|
||||||
'company_name' => $branding_settings->site_name,
|
|
||||||
'date_settings' => $branding_settings->date_display_format,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($pdf_view_route!='') {
|
|
||||||
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
|
||||||
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
||||||
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
||||||
}
|
|
||||||
|
|
||||||
$acceptance->decline($sig_filename, $request->input('note'));
|
|
||||||
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
||||||
Log::debug('New event acceptance.');
|
Log::debug('New event acceptance.');
|
||||||
event(new CheckoutDeclined($acceptance));
|
event(new CheckoutDeclined($acceptance));
|
||||||
$return_msg = trans('admin/users/message.declined');
|
$return_msg = trans('admin/users/message.declined');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send an email notification if one is requested
|
||||||
if ($acceptance->alert_on_response_id) {
|
if ($acceptance->alert_on_response_id) {
|
||||||
try {
|
try {
|
||||||
$recipient = User::find($acceptance->alert_on_response_id);
|
$recipient = User::find($acceptance->alert_on_response_id);
|
||||||
@@ -371,9 +230,10 @@ class AcceptanceController extends Controller
|
|||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->to('account/accept')->with('success', $return_msg);
|
return redirect()->to('account/accept')->with('success', $return_msg);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
|
use App\Models\Actionlog;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use \Illuminate\Http\Response;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use \Illuminate\Http\Response;
|
||||||
|
|
||||||
class ActionlogController extends Controller
|
class ActionlogController extends Controller
|
||||||
{
|
{
|
||||||
public function displaySig($filename) : RedirectResponse | Response | bool
|
public function displaySig($filename) : RedirectResponse | Response | bool
|
||||||
@@ -39,17 +41,29 @@ class ActionlogController extends Controller
|
|||||||
|
|
||||||
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('view', \App\Models\Asset::class);
|
|
||||||
|
|
||||||
if (config('filesystems.default') == 's3_private') {
|
if ($actionlog = Actionlog::where('filename', $filename)->with('user')->with('target')->firstOrFail()) {
|
||||||
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
|
|
||||||
|
$this->authorize('view', $actionlog->target);
|
||||||
|
$this->authorize('view', $actionlog->user);
|
||||||
|
|
||||||
|
|
||||||
|
if (config('filesystems.default') == 's3_private') {
|
||||||
|
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/' . $filename, now()->addMinutes(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Storage::exists('private_uploads/eula-pdfs/' . $filename)) {
|
||||||
|
|
||||||
|
if (request()->input('inline') == 'true') {
|
||||||
|
return response()->file(config('app.private_uploads') . '/eula-pdfs/' . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->download(config('app.private_uploads') . '/eula-pdfs/' . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
|
return redirect()->back()->with('error', trans('general.record_not_found'));
|
||||||
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,15 @@ class AccessoriesController extends Controller
|
|||||||
'notes',
|
'notes',
|
||||||
'checkouts_count',
|
'checkouts_count',
|
||||||
'qty',
|
'qty',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'category',
|
||||||
|
'supplier',
|
||||||
|
'manufacturer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@@ -61,10 +70,23 @@ class AccessoriesController extends Controller
|
|||||||
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
|
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
|
||||||
->withCount('checkouts as checkouts_count');
|
->withCount('checkouts as checkouts_count');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
$filter = [];
|
||||||
$accessories = $accessories->TextSearch($request->input('search'));
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$accessories->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$accessories->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('company_id')) {
|
if ($request->filled('company_id')) {
|
||||||
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
|
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
|
||||||
}
|
}
|
||||||
@@ -288,32 +310,42 @@ class AccessoriesController extends Controller
|
|||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
$accessory_checkout->created_by = auth()->id();
|
$accessory_checkout->created_by = auth()->id();
|
||||||
$accessory_checkout->save();
|
$accessory_checkout->save();
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'accessory_id' => $accessory->id,
|
||||||
|
'assigned_to' => $target->id,
|
||||||
|
'assigned_type' => $target::class,
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'created_by' => auth()->id(),
|
||||||
|
'pivot' => $accessory_checkout->id,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this value to be able to pass the qty through to the event
|
// Set this value to be able to pass the qty through to the event
|
||||||
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkout.success')));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check in the item so that it can be checked out again to someone else
|
* Check in the item so that it can be checked out again to someone else
|
||||||
*
|
*
|
||||||
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param int $accessoryUserId
|
* @param int $accessoryUserId
|
||||||
* @param string $backto
|
* @param string $backto
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return JsonResponse
|
||||||
|
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
||||||
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @internal param int $accessoryId
|
* @internal param int $accessoryId
|
||||||
*/
|
*/
|
||||||
public function checkin(Request $request, $accessoryUserId = null)
|
public function checkin(Request $request, $accessoryUserId = null)
|
||||||
{
|
{
|
||||||
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
|
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist', ['id' => $accessoryUserId])));
|
||||||
}
|
}
|
||||||
|
|
||||||
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
||||||
@@ -327,7 +359,14 @@ class AccessoriesController extends Controller
|
|||||||
$user = User::find($accessory_checkout->assigned_to);
|
$user = User::find($accessory_checkout->assigned_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
|
$payload = [
|
||||||
|
'accessory_id' => $accessory->id,
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'created_by' => auth()->id(),
|
||||||
|
'pivot' => $accessory_checkout->id,
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkin.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
|
||||||
|
|||||||
@@ -46,10 +46,20 @@ class AssetModelsController extends Controller
|
|||||||
'manufacturer',
|
'manufacturer',
|
||||||
'requestable',
|
'requestable',
|
||||||
'assets_count',
|
'assets_count',
|
||||||
|
'assets_assigned_count',
|
||||||
|
'assets_archived_count',
|
||||||
|
'remaining',
|
||||||
'category',
|
'category',
|
||||||
'fieldset',
|
'fieldset',
|
||||||
'deleted_at',
|
'deleted_at',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
|
'require_serial',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'manufacturer',
|
||||||
|
'category',
|
||||||
];
|
];
|
||||||
|
|
||||||
$assetmodels = AssetModel::select([
|
$assetmodels = AssetModel::select([
|
||||||
@@ -69,9 +79,31 @@ class AssetModelsController extends Controller
|
|||||||
'models.fieldset_id',
|
'models.fieldset_id',
|
||||||
'models.deleted_at',
|
'models.deleted_at',
|
||||||
'models.updated_at',
|
'models.updated_at',
|
||||||
|
'models.require_serial'
|
||||||
])
|
])
|
||||||
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
||||||
->withCount('assets as assets_count');
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('availableAssets as remaining')
|
||||||
|
->withCount('assignedAssets as assets_assigned_count')
|
||||||
|
->withCount('archivedAssets as assets_archived_count');
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$assetmodels->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$assetmodels->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->input('status')=='deleted') {
|
if ($request->input('status')=='deleted') {
|
||||||
$assetmodels->onlyTrashed();
|
$assetmodels->onlyTrashed();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Events\CheckoutableCheckedIn;
|
|||||||
use App\Http\Requests\StoreAssetRequest;
|
use App\Http\Requests\StoreAssetRequest;
|
||||||
use App\Http\Requests\UpdateAssetRequest;
|
use App\Http\Requests\UpdateAssetRequest;
|
||||||
use App\Http\Traits\MigratesLegacyAssetLocations;
|
use App\Http\Traits\MigratesLegacyAssetLocations;
|
||||||
|
use App\Http\Transformers\ComponentsTransformer;
|
||||||
use App\Models\AccessoryCheckout;
|
use App\Models\AccessoryCheckout;
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\LicenseSeat;
|
use App\Models\LicenseSeat;
|
||||||
@@ -115,6 +116,22 @@ class AssetsController extends Controller
|
|||||||
'asset_eol_date',
|
'asset_eol_date',
|
||||||
'requestable',
|
'requestable',
|
||||||
'jobtitle',
|
'jobtitle',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'model',
|
||||||
|
'location',
|
||||||
|
'rtd_location',
|
||||||
|
'category',
|
||||||
|
'status_label',
|
||||||
|
'manufacturer',
|
||||||
|
'supplier',
|
||||||
|
'jobtitle',
|
||||||
|
'assigned_to',
|
||||||
|
'created_by',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
||||||
@@ -131,6 +148,7 @@ class AssetsController extends Controller
|
|||||||
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
return in_array($key, $allowed_columns);
|
return in_array($key, $allowed_columns);
|
||||||
}, ARRAY_FILTER_USE_KEY);
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$assets = Asset::select('assets.*')
|
$assets = Asset::select('assets.*')
|
||||||
@@ -165,7 +183,7 @@ class AssetsController extends Controller
|
|||||||
// Search custom fields by column name
|
// Search custom fields by column name
|
||||||
foreach ($all_custom_fields as $field) {
|
foreach ($all_custom_fields as $field) {
|
||||||
if ($request->filled($field->db_column_name()) && $field->db_column_name()) {
|
if ($request->filled($field->db_column_name()) && $field->db_column_name()) {
|
||||||
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
|
$assets->where('assets.'.$field->db_column_name(), '=', $request->input($field->db_column_name()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1290,9 +1308,19 @@ class AssetsController extends Controller
|
|||||||
|
|
||||||
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
|
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
|
||||||
{
|
{
|
||||||
|
$this->authorize('view', Asset::class);
|
||||||
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
return [];
|
$query = Asset::where([
|
||||||
// to do
|
'assigned_to' => $asset->id,
|
||||||
|
'assigned_type' => Asset::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$total = $query->count();
|
||||||
|
|
||||||
|
$assets = $query->applyOffsetAndLimit($total)->get();
|
||||||
|
|
||||||
|
return (new AssetsTransformer)->transformAssets($assets, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
|
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
|
||||||
@@ -1312,6 +1340,18 @@ class AssetsController extends Controller
|
|||||||
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
|
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function assignedComponents(Request $request, Asset $asset): JsonResponse|array
|
||||||
|
{
|
||||||
|
$this->authorize('view', Asset::class);
|
||||||
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
|
$asset->loadCount('components');
|
||||||
|
$total = $asset->components_count;
|
||||||
|
|
||||||
|
$components = $asset->load(['components' => fn($query) => $query->applyOffsetAndLimit($total)])->components;
|
||||||
|
|
||||||
|
return (new ComponentsTransformer)->transformComponents($components, $total);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate asset labels by tag
|
* Generate asset labels by tag
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Categories\DestroyCategoryAction;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Transformers\CategoriesTransformer;
|
use App\Http\Transformers\CategoriesTransformer;
|
||||||
@@ -38,6 +40,8 @@ class CategoriesController extends Controller
|
|||||||
'consumables_count',
|
'consumables_count',
|
||||||
'components_count',
|
'components_count',
|
||||||
'licenses_count',
|
'licenses_count',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
'image',
|
'image',
|
||||||
'notes',
|
'notes',
|
||||||
];
|
];
|
||||||
@@ -59,6 +63,23 @@ class CategoriesController extends Controller
|
|||||||
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
|
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
|
||||||
|
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$categories->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$categories->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This checks to see if we should override the Admin Setting to show archived assets in list.
|
* This checks to see if we should override the Admin Setting to show archived assets in list.
|
||||||
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
|
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
|
||||||
@@ -72,10 +93,6 @@ class CategoriesController extends Controller
|
|||||||
$categories = $categories->withCount('showableAssets as assets_count');
|
$categories = $categories->withCount('showableAssets as assets_count');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
|
||||||
$categories = $categories->TextSearch($request->input('search'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->filled('name')) {
|
if ($request->filled('name')) {
|
||||||
$categories->where('name', '=', $request->input('name'));
|
$categories->where('name', '=', $request->input('name'));
|
||||||
}
|
}
|
||||||
@@ -209,17 +226,21 @@ class CategoriesController extends Controller
|
|||||||
* @param int $id
|
* @param int $id
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy($id) : JsonResponse
|
public function destroy(Category $category): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Category::class);
|
$this->authorize('delete', Category::class);
|
||||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
|
try {
|
||||||
|
DestroyCategoryAction::run(category: $category);
|
||||||
if (! $category->isDeletable()) {
|
} catch (ItemStillHasChildren $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>$category->category_type]))
|
Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['asset_type' => $category->category_type]))
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return response()->json(
|
||||||
|
Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$category->delete();
|
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,16 +45,40 @@ class ComponentsController extends Controller
|
|||||||
'qty',
|
'qty',
|
||||||
'image',
|
'image',
|
||||||
'notes',
|
'notes',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'category',
|
||||||
|
'manufacturer',
|
||||||
|
'supplier',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$components = Component::select('components.*')
|
$components = Component::select('components.*')
|
||||||
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
|
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
|
||||||
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
|
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
$filter = [];
|
||||||
$components = $components->TextSearch($request->input('search'));
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$components->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$components->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('name')) {
|
if ($request->filled('name')) {
|
||||||
$components->where('name', '=', $request->input('name'));
|
$components->where('name', '=', $request->input('name'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,53 @@ class ConsumablesController extends Controller
|
|||||||
$consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
|
$consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
|
||||||
->withCount('users as consumables_users_count');
|
->withCount('users as consumables_users_count');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
|
||||||
$consumables = $consumables->TextSearch(e($request->input('search')));
|
// These must match a column on the consumables table directly.
|
||||||
|
$allowed_columns = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'order_number',
|
||||||
|
'min_amt',
|
||||||
|
'purchase_date',
|
||||||
|
'purchase_cost',
|
||||||
|
'company',
|
||||||
|
'category',
|
||||||
|
'model_number',
|
||||||
|
'item_no',
|
||||||
|
'manufacturer',
|
||||||
|
'location',
|
||||||
|
'qty',
|
||||||
|
'image',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'category',
|
||||||
|
'supplier',
|
||||||
|
'manufacturer',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$consumables->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$consumables->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('name')) {
|
if ($request->filled('name')) {
|
||||||
$consumables->where('name', '=', $request->input('name'));
|
$consumables->where('name', '=', $request->input('name'));
|
||||||
}
|
}
|
||||||
@@ -96,25 +139,6 @@ class ConsumablesController extends Controller
|
|||||||
$consumables = $consumables->OrderByCreatedBy($order);
|
$consumables = $consumables->OrderByCreatedBy($order);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
|
|
||||||
// These must match a column on the consumables table directly.
|
|
||||||
$allowed_columns = [
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'order_number',
|
|
||||||
'min_amt',
|
|
||||||
'purchase_date',
|
|
||||||
'purchase_cost',
|
|
||||||
'company',
|
|
||||||
'category',
|
|
||||||
'model_number',
|
|
||||||
'item_no',
|
|
||||||
'manufacturer',
|
|
||||||
'location',
|
|
||||||
'qty',
|
|
||||||
'image'
|
|
||||||
];
|
|
||||||
|
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||||
$consumables = $consumables->orderBy($sort, $order);
|
$consumables = $consumables->orderBy($sort, $order);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class ImportController extends Controller
|
|||||||
if (function_exists('iconv')) {
|
if (function_exists('iconv')) {
|
||||||
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
||||||
$encoding = $detector->getEncoding($file_contents);
|
$encoding = $detector->getEncoding($file_contents);
|
||||||
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
|
\Log::debug("Discovered encoding: $encoding in uploaded CSV");
|
||||||
$reader = null;
|
$reader = null;
|
||||||
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
||||||
$transliterated = false;
|
$transliterated = false;
|
||||||
@@ -103,7 +103,7 @@ class ImportController extends Controller
|
|||||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$import->header_row = $reader->fetchOne(0);
|
$import->header_row = $reader->nth(0);
|
||||||
} catch (JsonEncodingException $e) {
|
} catch (JsonEncodingException $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse(
|
Helper::formatStandardApiResponse(
|
||||||
@@ -136,7 +136,7 @@ class ImportController extends Controller
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Grab the first row to display via ajax as the user picks fields
|
// Grab the first row to display via ajax as the user picks fields
|
||||||
$import->first_row = $reader->fetchOne(1);
|
$import->first_row = $reader->nth(1);
|
||||||
} catch (JsonEncodingException $e) {
|
} catch (JsonEncodingException $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse(
|
Helper::formatStandardApiResponse(
|
||||||
|
|||||||
@@ -128,7 +128,9 @@ class LicenseSeatsController extends Controller
|
|||||||
// nothing to update
|
// nothing to update
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||||
}
|
}
|
||||||
|
if( $touched && $licenseSeat->unreassignable_seat) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.checkout.unavailable')));
|
||||||
|
}
|
||||||
// the logging functions expect only one "target". if both asset and user are present in the request,
|
// the logging functions expect only one "target". if both asset and user are present in the request,
|
||||||
// we simply let assets take precedence over users...
|
// we simply let assets take precedence over users...
|
||||||
if ($licenseSeat->isDirty('assigned_to')) {
|
if ($licenseSeat->isDirty('assigned_to')) {
|
||||||
@@ -145,7 +147,11 @@ class LicenseSeatsController extends Controller
|
|||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
|
|
||||||
if ($is_checkin) {
|
if ($is_checkin) {
|
||||||
$licenseSeat->logCheckin($target, $request->input('notes'));
|
if(!$licenseSeat->license->reassignable){
|
||||||
|
$licenseSeat->unreassignable_seat = true;
|
||||||
|
$licenseSeat->save();
|
||||||
|
}
|
||||||
|
$licenseSeat->logCheckin($target, $licenseSeat->notes);
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Http\Transformers\LicensesTransformer;
|
use App\Http\Transformers\LicensesTransformer;
|
||||||
use App\Http\Transformers\SelectlistTransformer;
|
use App\Http\Transformers\SelectlistTransformer;
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
|
use App\Models\Setting;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@@ -25,6 +26,15 @@ class LicensesController extends Controller
|
|||||||
$this->authorize('view', License::class);
|
$this->authorize('view', License::class);
|
||||||
|
|
||||||
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
|
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
|
||||||
|
$settings = Setting::getSettings();
|
||||||
|
|
||||||
|
if ($request->input('status')=='inactive') {
|
||||||
|
$licenses->ExpiredLicenses();
|
||||||
|
} elseif ($request->input('status')=='expiring') {
|
||||||
|
$licenses->ExpiringLicenses($settings->alert_interval);
|
||||||
|
} else {
|
||||||
|
$licenses->ActiveLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('company_id')) {
|
if ($request->filled('company_id')) {
|
||||||
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
|
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
|
||||||
@@ -94,6 +104,8 @@ class LicensesController extends Controller
|
|||||||
$licenses->onlyTrashed();
|
$licenses->onlyTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
|
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|||||||
@@ -37,10 +37,14 @@ class LocationsController extends Controller
|
|||||||
'address',
|
'address',
|
||||||
'address2',
|
'address2',
|
||||||
'assets_count',
|
'assets_count',
|
||||||
'assets_count',
|
'assigned_assets_count',
|
||||||
|
'rtd_assets_count',
|
||||||
|
'accessories_count',
|
||||||
'assigned_accessories_count',
|
'assigned_accessories_count',
|
||||||
'assigned_assets_count',
|
'components_count',
|
||||||
'assigned_assets_count',
|
'consumables_count',
|
||||||
|
'users_count',
|
||||||
|
'children_count',
|
||||||
'city',
|
'city',
|
||||||
'country',
|
'country',
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -54,7 +58,6 @@ class LocationsController extends Controller
|
|||||||
'rtd_assets_count',
|
'rtd_assets_count',
|
||||||
'state',
|
'state',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
'users_count',
|
|
||||||
'zip',
|
'zip',
|
||||||
'notes',
|
'notes',
|
||||||
];
|
];
|
||||||
@@ -79,8 +82,9 @@ class LocationsController extends Controller
|
|||||||
'locations.currency',
|
'locations.currency',
|
||||||
'locations.company_id',
|
'locations.company_id',
|
||||||
'locations.notes',
|
'locations.notes',
|
||||||
|
'locations.created_by',
|
||||||
|
'locations.deleted_at',
|
||||||
])
|
])
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
->withCount('assignedAccessories as assigned_accessories_count')
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
@@ -88,6 +92,8 @@ class LocationsController extends Controller
|
|||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->with('adminuser');
|
->with('adminuser');
|
||||||
|
|
||||||
// Only scope locations if the setting is enabled
|
// Only scope locations if the setting is enabled
|
||||||
@@ -131,6 +137,14 @@ class LocationsController extends Controller
|
|||||||
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('parent_id')) {
|
||||||
|
$locations->where('locations.parent_id', '=', $request->input('parent_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('status') == 'deleted') {
|
||||||
|
$locations->onlyTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
@@ -224,8 +238,13 @@ class LocationsController extends Controller
|
|||||||
])
|
])
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
return (new LocationsTransformer)->transformLocation($location);
|
return (new LocationsTransformer)->transformLocation($location);
|
||||||
@@ -320,11 +339,15 @@ class LocationsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('delete', Location::class);
|
$this->authorize('delete', Location::class);
|
||||||
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||||
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
->withCount('accessories as accessories_count')
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
if (! $location->isDeletable()) {
|
if (! $location->isDeletable()) {
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ class MaintenancesController extends Controller
|
|||||||
$maintenances->where('maintenances.created_by', '=', $request->input('created_by'));
|
$maintenances->where('maintenances.created_by', '=', $request->input('created_by'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('url')) {
|
||||||
|
$maintenances->where('maintenances.url', '=', $request->input('url'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('asset_maintenance_type')) {
|
if ($request->filled('asset_maintenance_type')) {
|
||||||
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
|
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Manufacturers\DeleteManufacturerAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Transformers\ManufacturersTransformer;
|
use App\Http\Transformers\ManufacturersTransformer;
|
||||||
@@ -184,19 +191,19 @@ class ManufacturersController extends Controller
|
|||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
* @param int $id
|
* @param int $id
|
||||||
*/
|
*/
|
||||||
public function destroy($id) : JsonResponse
|
public function destroy(Manufacturer $manufacturer): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Manufacturer::class);
|
|
||||||
$manufacturer = Manufacturer::findOrFail($id);
|
|
||||||
$this->authorize('delete', $manufacturer);
|
$this->authorize('delete', $manufacturer);
|
||||||
|
try {
|
||||||
if ($manufacturer->isDeletable()) {
|
DeleteManufacturerAction::run($manufacturer);
|
||||||
$manufacturer->delete();
|
} catch (ItemStillHasChildren $e) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')])));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.assoc_users')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class ProfileController extends Controller
|
|||||||
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
||||||
$assets = [
|
$assets = [
|
||||||
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
||||||
'name' => e($checkoutRequest->itemRequested()->present()->name()),
|
'name' => e($checkoutRequest->itemRequested()->display_name),
|
||||||
'type' => e($checkoutRequest->itemType()),
|
'type' => e($checkoutRequest->itemType()),
|
||||||
'qty' => (int) $checkoutRequest->quantity,
|
'qty' => (int) $checkoutRequest->quantity,
|
||||||
'location' => ($checkoutRequest->location()) ? e($checkoutRequest->location()->name) : null,
|
'location' => ($checkoutRequest->location()) ? e($checkoutRequest->location()->name) : null,
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Suppliers\DestroySupplierAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Transformers\SelectlistTransformer;
|
use App\Http\Transformers\SelectlistTransformer;
|
||||||
@@ -191,27 +198,40 @@ class SuppliersController extends Controller
|
|||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
* @param int $id
|
* @param int $id
|
||||||
*/
|
*/
|
||||||
public function destroy($id) : JsonResponse
|
public function destroy(Supplier $supplier): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Supplier::class);
|
|
||||||
$supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
|
|
||||||
$this->authorize('delete', $supplier);
|
$this->authorize('delete', $supplier);
|
||||||
|
try {
|
||||||
|
DestroySupplierAction::run(supplier: $supplier);
|
||||||
if ($supplier->assets_count > 0) {
|
} catch (ItemStillHasAssets $e) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_assets', [
|
||||||
|
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasMaintenances $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_maintenances', [
|
||||||
|
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_licenses', [
|
||||||
|
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_accessories', [
|
||||||
|
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_consumables', [
|
||||||
|
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_components', [
|
||||||
|
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($supplier->maintenances_count > 0) {
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count])));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($supplier->licenses_count > 0) {
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count])));
|
|
||||||
}
|
|
||||||
|
|
||||||
$supplier->delete();
|
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/suppliers/message.delete.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/suppliers/message.delete.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -51,11 +51,7 @@ class UploadedFilesController extends Controller
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
$uploads = Actionlog::select('action_logs.*')
|
$uploads = self::$map_object_type[$object_type]::withTrashed()->find($id)->uploads()
|
||||||
->whereNotNull('filename')
|
|
||||||
->where('item_type', self::$map_object_type[$object_type])
|
|
||||||
->where('item_id', $object->id)
|
|
||||||
->where('action_type', '=', 'uploaded')
|
|
||||||
->with('adminuser');
|
->with('adminuser');
|
||||||
|
|
||||||
$offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset'));
|
||||||
@@ -96,7 +92,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -144,7 +140,7 @@ class UploadedFilesController extends Controller
|
|||||||
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||||
{
|
{
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -188,7 +184,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('update', self::$map_object_type[$object_type]);
|
$this->authorize('update', self::$map_object_type[$object_type]);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -197,8 +193,12 @@ class UploadedFilesController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
// Check for the file
|
// Check for the file
|
||||||
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
|
$log = Actionlog::query()
|
||||||
->where('item_id', $object->id)->first();
|
->where('id', $file_id)
|
||||||
|
->where('action_type', 'uploaded')
|
||||||
|
->where('item_type', self::$map_object_type[$object_type])
|
||||||
|
->where('item_id', $object->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
if ($log) {
|
if ($log) {
|
||||||
// Check the file actually exists, and delete it
|
// Check the file actually exists, and delete it
|
||||||
@@ -206,7 +206,7 @@ class UploadedFilesController extends Controller
|
|||||||
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
||||||
}
|
}
|
||||||
// Delete the record of the file
|
// Delete the record of the file
|
||||||
if ($log->delete()) {
|
if ($log->logUploadDelete($object, $log->filename)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,4 +217,4 @@ class UploadedFilesController extends Controller
|
|||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use App\Models\Consumable;
|
|||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\CurrentInventory;
|
use App\Notifications\CurrentInventory;
|
||||||
|
use App\Notifications\WelcomeNotification;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -102,9 +103,75 @@ class UsersController extends Controller
|
|||||||
'managedLocations as manages_locations_count'
|
'managedLocations as manages_locations_count'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$allowed_columns =
|
||||||
|
[
|
||||||
|
'last_name',
|
||||||
|
'first_name',
|
||||||
|
'display_name',
|
||||||
|
'email',
|
||||||
|
'jobtitle',
|
||||||
|
'username',
|
||||||
|
'employee_num',
|
||||||
|
'groups',
|
||||||
|
'activated',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'two_factor_enrolled',
|
||||||
|
'two_factor_optin',
|
||||||
|
'last_login',
|
||||||
|
'assets_count',
|
||||||
|
'licenses_count',
|
||||||
|
'consumables_count',
|
||||||
|
'accessories_count',
|
||||||
|
'manages_users_count',
|
||||||
|
'manages_locations_count',
|
||||||
|
'phone',
|
||||||
|
'mobile',
|
||||||
|
'address',
|
||||||
|
'city',
|
||||||
|
'state',
|
||||||
|
'country',
|
||||||
|
'zip',
|
||||||
|
'id',
|
||||||
|
'ldap_import',
|
||||||
|
'two_factor_optin',
|
||||||
|
'two_factor_enrolled',
|
||||||
|
'remote',
|
||||||
|
'vip',
|
||||||
|
'start_date',
|
||||||
|
'end_date',
|
||||||
|
'autoassign_licenses',
|
||||||
|
'website',
|
||||||
|
'locale',
|
||||||
|
'notes',
|
||||||
|
'employee_num',
|
||||||
|
|
||||||
if ($request->filled('search') != '') {
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
$users = $users->TextSearch($request->input('search'));
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'department',
|
||||||
|
'manager',
|
||||||
|
'created_by',
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$users->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$users->TextSearch($request->input('search'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('activated')) {
|
if ($request->filled('activated')) {
|
||||||
@@ -285,49 +352,6 @@ class UsersController extends Controller
|
|||||||
$users->orderBy('first_name', $order);
|
$users->orderBy('first_name', $order);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$allowed_columns =
|
|
||||||
[
|
|
||||||
'last_name',
|
|
||||||
'first_name',
|
|
||||||
'display_name',
|
|
||||||
'email',
|
|
||||||
'jobtitle',
|
|
||||||
'username',
|
|
||||||
'employee_num',
|
|
||||||
'groups',
|
|
||||||
'activated',
|
|
||||||
'created_at',
|
|
||||||
'updated_at',
|
|
||||||
'two_factor_enrolled',
|
|
||||||
'two_factor_optin',
|
|
||||||
'last_login',
|
|
||||||
'assets_count',
|
|
||||||
'licenses_count',
|
|
||||||
'consumables_count',
|
|
||||||
'accessories_count',
|
|
||||||
'manages_users_count',
|
|
||||||
'manages_locations_count',
|
|
||||||
'phone',
|
|
||||||
'mobile',
|
|
||||||
'address',
|
|
||||||
'city',
|
|
||||||
'state',
|
|
||||||
'country',
|
|
||||||
'zip',
|
|
||||||
'id',
|
|
||||||
'ldap_import',
|
|
||||||
'two_factor_optin',
|
|
||||||
'two_factor_enrolled',
|
|
||||||
'remote',
|
|
||||||
'vip',
|
|
||||||
'start_date',
|
|
||||||
'end_date',
|
|
||||||
'autoassign_licenses',
|
|
||||||
'website',
|
|
||||||
'locale',
|
|
||||||
'notes',
|
|
||||||
];
|
|
||||||
|
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
|
||||||
$users = $users->orderBy($sort, $order);
|
$users = $users->orderBy($sort, $order);
|
||||||
break;
|
break;
|
||||||
@@ -434,9 +458,20 @@ class UsersController extends Controller
|
|||||||
$user->password = $user->noPassword();
|
$user->password = $user->noPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
|
|
||||||
|
if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user->notify(new WelcomeNotification($user));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Could not send welcome notification for user: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('groups')) {
|
if ($request->filled('groups')) {
|
||||||
$user->groups()->sync($request->input('groups'));
|
$user->groups()->sync($request->input('groups'));
|
||||||
} else {
|
} else {
|
||||||
@@ -548,7 +583,7 @@ class UsersController extends Controller
|
|||||||
Asset::where('assigned_type', User::class)
|
Asset::where('assigned_type', User::class)
|
||||||
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
||||||
}
|
}
|
||||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class AssetModelsController extends Controller
|
|||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
$model->created_by = auth()->id();
|
$model->created_by = auth()->id();
|
||||||
$model->requestable = $request->has('requestable');
|
$model->requestable = $request->has('requestable');
|
||||||
|
$model->require_serial = $request->input('require_serial', 0);
|
||||||
|
|
||||||
if ($request->input('fieldset_id') != '') {
|
if ($request->input('fieldset_id') != '') {
|
||||||
$model->fieldset_id = $request->input('fieldset_id');
|
$model->fieldset_id = $request->input('fieldset_id');
|
||||||
@@ -155,7 +156,7 @@ class AssetModelsController extends Controller
|
|||||||
$model->category_id = $request->input('category_id');
|
$model->category_id = $request->input('category_id');
|
||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
$model->requestable = $request->input('requestable', '0');
|
$model->requestable = $request->input('requestable', '0');
|
||||||
|
$model->require_serial = $request->input('require_serial', 0);
|
||||||
$model->fieldset_id = $request->input('fieldset_id');
|
$model->fieldset_id = $request->input('fieldset_id');
|
||||||
|
|
||||||
if ($model->save()) {
|
if ($model->save()) {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class AssetCheckoutController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
|
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if the asset exists
|
// Check if the asset exists
|
||||||
if (! $asset = Asset::find($assetId)) {
|
if (! $asset = Asset::find($assetId)) {
|
||||||
@@ -81,6 +83,7 @@ class AssetCheckoutController extends Controller
|
|||||||
$admin = auth()->user();
|
$admin = auth()->user();
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
$asset = $this->updateAssetLocation($asset, $target);
|
$asset = $this->updateAssetLocation($asset, $target);
|
||||||
|
|
||||||
|
|||||||
@@ -110,17 +110,35 @@ class AssetsController extends Controller
|
|||||||
// This is only necessary on create, not update, since bulk editing is handled
|
// This is only necessary on create, not update, since bulk editing is handled
|
||||||
// differently
|
// differently
|
||||||
$asset_tags = $request->input('asset_tags');
|
$asset_tags = $request->input('asset_tags');
|
||||||
|
$model = AssetModel::find($request->input('model_id'));
|
||||||
|
$serial_errors = [];
|
||||||
|
$serials = $request->input('serials');
|
||||||
|
|
||||||
$settings = Setting::getSettings();
|
$settings = Setting::getSettings();
|
||||||
|
|
||||||
|
//Validate required serial based on model setting
|
||||||
|
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
|
||||||
|
if ($model && $model->require_serial === 1 && empty($serials[$a])) {
|
||||||
|
$serial_errors["serials.$a"] = trans('admin/hardware/form.serial_required', ['number' => $a]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($serial_errors)) {
|
||||||
|
return redirect()->back()
|
||||||
|
->withInput()
|
||||||
|
->withErrors($serial_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$asset = null;
|
||||||
|
$companyId = Company::getIdForCurrentUser($request->input('company_id'));
|
||||||
$successes = [];
|
$successes = [];
|
||||||
$failures = [];
|
$failures = [];
|
||||||
$serials = $request->input('serials');
|
|
||||||
$asset = null;
|
|
||||||
|
|
||||||
for ($a = 1; $a <= count($asset_tags); $a++) {
|
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
|
||||||
$asset = new Asset();
|
$asset = new Asset();
|
||||||
$asset->model()->associate(AssetModel::find($request->input('model_id')));
|
|
||||||
|
$asset->model()->associate($model);
|
||||||
$asset->name = $request->input('name');
|
$asset->name = $request->input('name');
|
||||||
|
|
||||||
// Check for a corresponding serial
|
// Check for a corresponding serial
|
||||||
@@ -132,7 +150,7 @@ class AssetsController extends Controller
|
|||||||
$asset->asset_tag = $asset_tags[$a];
|
$asset->asset_tag = $asset_tags[$a];
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
$asset->company_id = $companyId;
|
||||||
$asset->model_id = $request->input('model_id');
|
$asset->model_id = $request->input('model_id');
|
||||||
$asset->order_number = $request->input('order_number');
|
$asset->order_number = $request->input('order_number');
|
||||||
$asset->notes = $request->input('notes');
|
$asset->notes = $request->input('notes');
|
||||||
@@ -172,7 +190,6 @@ class AssetsController extends Controller
|
|||||||
|
|
||||||
// Update custom fields in the database.
|
// Update custom fields in the database.
|
||||||
// Validation for these fields is handled through the AssetRequest form request
|
// Validation for these fields is handled through the AssetRequest form request
|
||||||
$model = AssetModel::find($request->get('model_id'));
|
|
||||||
|
|
||||||
if (($model) && ($model->fieldset)) {
|
if (($model) && ($model->fieldset)) {
|
||||||
foreach ($model->fieldset->fields as $field) {
|
foreach ($model->fieldset->fields as $field) {
|
||||||
@@ -346,7 +363,7 @@ class AssetsController extends Controller
|
|||||||
$asset->purchase_cost = $request->input('purchase_cost', null);
|
$asset->purchase_cost = $request->input('purchase_cost', null);
|
||||||
$asset->purchase_date = $request->input('purchase_date', null);
|
$asset->purchase_date = $request->input('purchase_date', null);
|
||||||
$asset->next_audit_date = $request->input('next_audit_date', null);
|
$asset->next_audit_date = $request->input('next_audit_date', null);
|
||||||
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
|
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model?->eol > 0)) {
|
||||||
$asset->purchase_date = $request->input('purchase_date', null);
|
$asset->purchase_date = $request->input('purchase_date', null);
|
||||||
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
|
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
|
||||||
$asset->eol_explicit = false;
|
$asset->eol_explicit = false;
|
||||||
@@ -362,7 +379,7 @@ class AssetsController extends Controller
|
|||||||
} else {
|
} else {
|
||||||
$asset->eol_explicit = true;
|
$asset->eol_explicit = true;
|
||||||
}
|
}
|
||||||
} elseif (!$request->filled('asset_eol_date') && (($asset->model->eol) == 0)) {
|
} elseif (!$request->filled('asset_eol_date') && (($asset->model?->eol) == 0)) {
|
||||||
$asset->asset_eol_date = null;
|
$asset->asset_eol_date = null;
|
||||||
$asset->eol_explicit = false;
|
$asset->eol_explicit = false;
|
||||||
}
|
}
|
||||||
@@ -381,6 +398,7 @@ class AssetsController extends Controller
|
|||||||
$asset->assigned_to = null;
|
$asset->assigned_to = null;
|
||||||
$asset->assigned_type = null;
|
$asset->assigned_type = null;
|
||||||
$asset->accepted = null;
|
$asset->accepted = null;
|
||||||
|
$asset->last_checkin = now();
|
||||||
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues));
|
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +471,13 @@ class AssetsController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
//Validate required serial based on model setting
|
||||||
|
if ($model && $model->require_serial === 1 && empty($serial[1])) {
|
||||||
|
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||||
|
->with('warning', trans('admin/hardware/form.serial_required_post_model_update', [
|
||||||
|
'asset_model' => $model->name
|
||||||
|
]));
|
||||||
|
}
|
||||||
if ($asset->save()) {
|
if ($asset->save()) {
|
||||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||||
->with('success', trans('admin/hardware/message.update.success'));
|
->with('success', trans('admin/hardware/message.update.success'));
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class BulkAssetsController extends Controller
|
|||||||
$modelNames = [];
|
$modelNames = [];
|
||||||
|
|
||||||
foreach($models as $model) {
|
foreach($models as $model) {
|
||||||
$modelNames[] = $model->model->name;
|
$modelNames[] = $model->model?->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('bulk_actions')) {
|
if ($request->filled('bulk_actions')) {
|
||||||
@@ -240,10 +240,6 @@ class BulkAssetsController extends Controller
|
|||||||
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
|
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
|
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
|
||||||
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||||
@@ -274,6 +270,7 @@ class BulkAssetsController extends Controller
|
|||||||
|| ($request->filled('company_id'))
|
|| ($request->filled('company_id'))
|
||||||
|| ($request->filled('status_id'))
|
|| ($request->filled('status_id'))
|
||||||
|| ($request->filled('model_id'))
|
|| ($request->filled('model_id'))
|
||||||
|
|| ($request->filled('notes'))
|
||||||
|| ($request->filled('next_audit_date'))
|
|| ($request->filled('next_audit_date'))
|
||||||
|| ($request->filled('asset_eol_date'))
|
|| ($request->filled('asset_eol_date'))
|
||||||
|| ($request->filled('null_name'))
|
|| ($request->filled('null_name'))
|
||||||
@@ -470,7 +467,7 @@ class BulkAssetsController extends Controller
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Does the model have a fieldset?
|
// Does the model have a fieldset?
|
||||||
if ($asset->model->fieldset) {
|
if ($asset->model?->fieldset) {
|
||||||
foreach ($asset->model->fieldset->fields as $field) {
|
foreach ($asset->model->fieldset->fields as $field) {
|
||||||
|
|
||||||
// null custom fields
|
// null custom fields
|
||||||
@@ -621,9 +618,25 @@ class BulkAssetsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('checkout', Asset::class);
|
$this->authorize('checkout', Asset::class);
|
||||||
|
|
||||||
|
$alreadyAssigned = collect();
|
||||||
|
|
||||||
|
if (old('selected_assets') && is_array(old('selected_assets'))) {
|
||||||
|
$assets = Asset::findMany(old('selected_assets'));
|
||||||
|
|
||||||
|
[$assignable, $alreadyAssigned] = $assets->partition(function (Asset $asset) {
|
||||||
|
return !$asset->assigned_to;
|
||||||
|
});
|
||||||
|
|
||||||
|
session()->flashInput(['selected_assets' => $assignable->pluck('id')->values()->toArray()]);
|
||||||
|
}
|
||||||
|
|
||||||
$do_not_change = ['' => trans('general.do_not_change')];
|
$do_not_change = ['' => trans('general.do_not_change')];
|
||||||
$status_label_list = $do_not_change + Helper::deployableStatusLabelList();
|
$status_label_list = $do_not_change + Helper::deployableStatusLabelList();
|
||||||
return view('hardware/bulk-checkout')->with('statusLabel_list', $status_label_list);
|
|
||||||
|
return view('hardware/bulk-checkout', [
|
||||||
|
'statusLabel_list' => $status_label_list,
|
||||||
|
'removed_assets' => $alreadyAssigned,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -637,6 +650,7 @@ class BulkAssetsController extends Controller
|
|||||||
$admin = auth()->user();
|
$admin = auth()->user();
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
if (! is_array($request->get('selected_assets'))) {
|
if (! is_array($request->get('selected_assets'))) {
|
||||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||||
@@ -646,6 +660,30 @@ class BulkAssetsController extends Controller
|
|||||||
|
|
||||||
$assets = Asset::findOrFail($asset_ids);
|
$assets = Asset::findOrFail($asset_ids);
|
||||||
|
|
||||||
|
// Prevent checking out assets that are already checked out
|
||||||
|
if ($assets->pluck('assigned_to')->unique()->filter()->isNotEmpty()) {
|
||||||
|
// re-add the asset ids so the assets select is re-populated
|
||||||
|
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
||||||
|
|
||||||
|
return redirect(route('hardware.bulkcheckout.show'))
|
||||||
|
->with('error', trans('general.error_assets_already_checked_out'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent checking out assets across companies if FMCS enabled
|
||||||
|
if (Setting::getSettings()->full_multiple_companies_support && $target->company_id) {
|
||||||
|
$company_ids = $assets->pluck('company_id')->unique();
|
||||||
|
|
||||||
|
// if there is more than one unique company id or the singular company id does not match
|
||||||
|
// then the checkout is invalid
|
||||||
|
if ($company_ids->count() > 1 || $company_ids->first() != $target->company_id) {
|
||||||
|
// re-add the asset ids so the assets select is re-populated
|
||||||
|
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
||||||
|
|
||||||
|
return redirect(route('hardware.bulkcheckout.show'))
|
||||||
|
->with('error', trans('general.error_user_company_multiple'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request('checkout_to_type') == 'asset') {
|
if (request('checkout_to_type') == 'asset') {
|
||||||
foreach ($asset_ids as $asset_id) {
|
foreach ($asset_ids as $asset_id) {
|
||||||
if ($target->id == $asset_id) {
|
if ($target->id == $asset_id) {
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ class BulkAssetModelsController extends Controller
|
|||||||
$update_array['min_amt'] = $request->input('min_amt');
|
$update_array['min_amt'] = $request->input('min_amt');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('require_serial')) {
|
||||||
|
$update_array['require_serial'] = $request->input('require_serial');
|
||||||
|
}
|
||||||
|
|
||||||
if (count($update_array) > 0) {
|
if (count($update_array) > 0) {
|
||||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||||
|
|||||||
59
app/Http/Controllers/BulkCategoriesController.php
Normal file
59
app/Http/Controllers/BulkCategoriesController.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Categories\DestroyCategoryAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Category;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class BulkCategoriesController extends Controller
|
||||||
|
{
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', Category::class);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$success_count = 0;
|
||||||
|
|
||||||
|
foreach ($request->ids as $id) {
|
||||||
|
$category = Category::find($id);
|
||||||
|
if (is_null($category)) {
|
||||||
|
$errors[] = trans('admin/categories/message.does_not_exist');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DestroyCategoryAction::run(category: $category);
|
||||||
|
$success_count++;
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasAssetModels) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_asset_models_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasAssets) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasComponents) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasConsumables) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasLicenses) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
$errors[] = trans('general.something_went_wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
if ($success_count > 0) {
|
||||||
|
return redirect()->route('categories.index')->with('success', trans_choice('admin/categories/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
|
||||||
|
}
|
||||||
|
return redirect()->route('categories.index')->with('multi_error_messages', $errors);
|
||||||
|
} else {
|
||||||
|
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.bulk_success'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/Http/Controllers/BulkManufacturersController.php
Normal file
57
app/Http/Controllers/BulkManufacturersController.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Manufacturers\DeleteManufacturerAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Manufacturer;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class BulkManufacturersController extends Controller
|
||||||
|
{
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', Manufacturer::class);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$success_count = 0;
|
||||||
|
foreach ($request->ids as $id) {
|
||||||
|
$manufacturer = Manufacturer::find($id);
|
||||||
|
if (is_null($manufacturer)) {
|
||||||
|
$errors[] = trans('admin/manufacturers/message.does_not_exist');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DeleteManufacturerAction::run(manufacturer: $manufacturer);
|
||||||
|
$success_count++;
|
||||||
|
} catch (ItemStillHasAssets $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_accessories_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
$errors[] = trans('general.something_went_wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
if ($success_count > 0) {
|
||||||
|
return redirect()->route('manufacturers.index')->with('success', trans_choice('admin/manufacturers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
|
||||||
|
}
|
||||||
|
return redirect()->route('manufacturers.index')->with('multi_error_messages', $errors);
|
||||||
|
} else {
|
||||||
|
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.bulk_success'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
app/Http/Controllers/BulkSuppliersController.php
Normal file
58
app/Http/Controllers/BulkSuppliersController.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Suppliers\DestroySupplierAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class BulkSuppliersController extends Controller
|
||||||
|
{
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', Supplier::class);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$success_count = 0;
|
||||||
|
|
||||||
|
foreach ($request->ids as $id) {
|
||||||
|
$supplier = Supplier::find($id);
|
||||||
|
if (is_null($supplier)) {
|
||||||
|
$errors[] = trans('admin/suppliers/message.delete.not_found');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DestroySupplierAction::run(supplier: $supplier);
|
||||||
|
} catch (ItemStillHasAssets $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets', ['asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasMaintenances $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_accessories', ['accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_consumables', ['consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_components', ['components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
$errors[] = trans('general.something_went_wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
if ($success_count > 0) {
|
||||||
|
return redirect()->route('suppliers.index')->with('success', trans_choice('admin/suppliers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
|
||||||
|
}
|
||||||
|
return redirect()->route('suppliers.index')->with('multi_error_messages', $errors);
|
||||||
|
} else {
|
||||||
|
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.bulk_success'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Categories\DestroyCategoryAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
@@ -143,20 +151,18 @@ class CategoriesController extends Controller
|
|||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @param int $categoryId
|
* @param int $categoryId
|
||||||
*/
|
*/
|
||||||
public function destroy($categoryId) : RedirectResponse
|
public function destroy(Category $category): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Category::class);
|
$this->authorize('delete', Category::class);
|
||||||
// Check if the category exists
|
try {
|
||||||
if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) {
|
DestroyCategoryAction::run($category);
|
||||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
|
} catch (ItemStillHasChildren $e) {
|
||||||
|
return redirect()->route('categories.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.category')]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.delete.error'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $category->isDeletable()) {
|
|
||||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=> $category->category_type]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage::disk('public')->delete('categories'.'/'.$category->image);
|
|
||||||
$category->delete();
|
|
||||||
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
|
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,13 +102,15 @@ class ComponentCheckoutController extends Controller
|
|||||||
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
|
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$component->checkout_qty = $request->input('assigned_qty');
|
||||||
|
|
||||||
// Update the component data
|
// Update the component data
|
||||||
$component->asset_id = $request->input('asset_id');
|
$component->asset_id = $request->input('asset_id');
|
||||||
$component->assets()->attach($component->id, [
|
$component->assets()->attach($component->id, [
|
||||||
'component_id' => $component->id,
|
'component_id' => $component->id,
|
||||||
'created_by' => auth()->user()->id,
|
'created_by' => auth()->user()->id,
|
||||||
'created_at' => date('Y-m-d H:i:s'),
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
'assigned_qty' => $request->input('assigned_qty'),
|
'assigned_qty' => $component->checkout_qty,
|
||||||
'asset_id' => $request->input('asset_id'),
|
'asset_id' => $request->input('asset_id'),
|
||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ abstract class Controller extends BaseController
|
|||||||
'accessories' => Accessory::class,
|
'accessories' => Accessory::class,
|
||||||
'maintenances' => Maintenance::class,
|
'maintenances' => Maintenance::class,
|
||||||
'assets' => Asset::class,
|
'assets' => Asset::class,
|
||||||
|
'audits' => Asset::class,
|
||||||
'components' => Component::class,
|
'components' => Component::class,
|
||||||
'consumables' => Consumable::class,
|
'consumables' => Consumable::class,
|
||||||
'hardware' => Asset::class,
|
'hardware' => Asset::class,
|
||||||
@@ -58,6 +59,7 @@ abstract class Controller extends BaseController
|
|||||||
'accessories' => 'private_uploads/accessories/',
|
'accessories' => 'private_uploads/accessories/',
|
||||||
'maintenances' => 'private_uploads/maintenances/',
|
'maintenances' => 'private_uploads/maintenances/',
|
||||||
'assets' => 'private_uploads/assets/',
|
'assets' => 'private_uploads/assets/',
|
||||||
|
'audits' => 'private_uploads/audits/',
|
||||||
'components' => 'private_uploads/components/',
|
'components' => 'private_uploads/components/',
|
||||||
'consumables' => 'private_uploads/consumables/',
|
'consumables' => 'private_uploads/consumables/',
|
||||||
'hardware' => 'private_uploads/assets/',
|
'hardware' => 'private_uploads/assets/',
|
||||||
@@ -71,6 +73,7 @@ abstract class Controller extends BaseController
|
|||||||
'accessories' => 'accessory',
|
'accessories' => 'accessory',
|
||||||
'maintenances' => 'maintenance',
|
'maintenances' => 'maintenance',
|
||||||
'assets' => 'asset',
|
'assets' => 'asset',
|
||||||
|
'audits' => 'audits',
|
||||||
'components' => 'component',
|
'components' => 'component',
|
||||||
'consumables' => 'consumable',
|
'consumables' => 'consumable',
|
||||||
'hardware' => 'asset',
|
'hardware' => 'asset',
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class GroupsController extends Controller
|
|||||||
$group->notes = $request->input('notes');
|
$group->notes = $request->input('notes');
|
||||||
|
|
||||||
if ($group->save()) {
|
if ($group->save()) {
|
||||||
|
$group->users()->sync($request->input('associated_users'));
|
||||||
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
|
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +89,10 @@ class GroupsController extends Controller
|
|||||||
$groupPermissions = [];
|
$groupPermissions = [];
|
||||||
}
|
}
|
||||||
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
|
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
|
||||||
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
|
$associated_users = $group->users()->get();
|
||||||
|
|
||||||
|
//dd($associated_users->toArray());
|
||||||
|
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'))->with('associated_users', $associated_users);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,8 +109,10 @@ class GroupsController extends Controller
|
|||||||
$group->permissions = json_encode($request->input('permission'));
|
$group->permissions = json_encode($request->input('permission'));
|
||||||
$group->notes = $request->input('notes');
|
$group->notes = $request->input('notes');
|
||||||
|
|
||||||
|
|
||||||
if (! config('app.lock_passwords')) {
|
if (! config('app.lock_passwords')) {
|
||||||
if ($group->save()) {
|
if ($group->save()) {
|
||||||
|
$group->users()->sync($request->input('associated_users'));
|
||||||
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.update'));
|
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.update'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,12 +64,7 @@ class LicenseCheckinController extends Controller
|
|||||||
|
|
||||||
$this->authorize('checkout', $license);
|
$this->authorize('checkout', $license);
|
||||||
|
|
||||||
if (! $license->reassignable) {
|
|
||||||
// Not allowed to checkin
|
|
||||||
Session::flash('error', trans('admin/licenses/message.checkin.not_reassignable') . '.');
|
|
||||||
|
|
||||||
return redirect()->back()->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare the rules for the form validation
|
// Declare the rules for the form validation
|
||||||
$rules = [
|
$rules = [
|
||||||
@@ -98,6 +93,9 @@ class LicenseCheckinController extends Controller
|
|||||||
$licenseSeat->assigned_to = null;
|
$licenseSeat->assigned_to = null;
|
||||||
$licenseSeat->asset_id = null;
|
$licenseSeat->asset_id = null;
|
||||||
$licenseSeat->notes = $request->input('notes');
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
if (! $licenseSeat->license->reassignable) {
|
||||||
|
$licenseSeat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
|
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||||
if ($request->get('redirect_option') === 'target'){
|
if ($request->get('redirect_option') === 'target'){
|
||||||
@@ -106,7 +104,7 @@ class LicenseCheckinController extends Controller
|
|||||||
|
|
||||||
// Was the asset updated?
|
// Was the asset updated?
|
||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
|
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $licenseSeat->notes));
|
||||||
|
|
||||||
|
|
||||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||||
@@ -132,21 +130,17 @@ class LicenseCheckinController extends Controller
|
|||||||
$license = License::findOrFail($licenseId);
|
$license = License::findOrFail($licenseId);
|
||||||
$this->authorize('checkin', $license);
|
$this->authorize('checkin', $license);
|
||||||
|
|
||||||
if (! $license->reassignable) {
|
|
||||||
// Not allowed to checkin
|
|
||||||
Session::flash('error', 'License not reassignable.');
|
|
||||||
|
|
||||||
return redirect()->back()->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
||||||
->whereNotNull('assigned_to')
|
->whereNotNull('assigned_to')
|
||||||
->with('user')
|
->with('user', 'license')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$license = $licenseSeatsByUser->first()?->license;
|
||||||
foreach ($licenseSeatsByUser as $user_seat) {
|
foreach ($licenseSeatsByUser as $user_seat) {
|
||||||
$user_seat->assigned_to = null;
|
$user_seat->assigned_to = null;
|
||||||
|
if ($license && ! $license->reassignable) {
|
||||||
|
$user_seat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
if ($user_seat->save()) {
|
if ($user_seat->save()) {
|
||||||
Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
|
Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
|
||||||
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
||||||
@@ -159,9 +153,12 @@ class LicenseCheckinController extends Controller
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
|
$license = $licenseSeatsByAsset->first()?->license;
|
||||||
foreach ($licenseSeatsByAsset as $asset_seat) {
|
foreach ($licenseSeatsByAsset as $asset_seat) {
|
||||||
$asset_seat->asset_id = null;
|
$asset_seat->asset_id = null;
|
||||||
|
if ($license && ! $license->reassignable) {
|
||||||
|
$asset_seat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
if ($asset_seat->save()) {
|
if ($asset_seat->save()) {
|
||||||
Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
|
Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
|
||||||
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @param $id
|
* @param $id
|
||||||
* @return \Illuminate\Contracts\View\View
|
* @return \Illuminate\Contracts\View\View |\Illuminate\Http\RedirectResponse
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function create(License $license)
|
public function create(License $license)
|
||||||
@@ -39,6 +39,16 @@ class LicenseCheckoutController extends Controller
|
|||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the license is expired or terminated
|
||||||
|
if ($license->isInactive()) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't currently allow checking out licenses to locations, so we'll reset that to user if needed
|
||||||
|
if (session()->get('checkout_to_type') == 'location') {
|
||||||
|
session()->put(['checkout_to_type' => 'user']);
|
||||||
|
}
|
||||||
|
|
||||||
// Return the checkout view
|
// Return the checkout view
|
||||||
return view('licenses/checkout', compact('license'));
|
return view('licenses/checkout', compact('license'));
|
||||||
}
|
}
|
||||||
@@ -65,22 +75,31 @@ class LicenseCheckoutController extends Controller
|
|||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->authorize('checkout', $license);
|
$this->authorize('checkout', $license);
|
||||||
|
|
||||||
|
// Make sure there is at least one available to checkout
|
||||||
|
if ($license->availCount()->count() < 1) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the license is expired or terminated
|
||||||
|
if ($license->isInactive()) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
|
||||||
|
}
|
||||||
|
|
||||||
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
||||||
$licenseSeat->created_by = auth()->id();
|
$licenseSeat->created_by = auth()->id();
|
||||||
$licenseSeat->notes = $request->input('notes');
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
|
||||||
|
|
||||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
|
||||||
|
|
||||||
if ($request->filled('asset_id')) {
|
if ($request->filled('asset_id')) {
|
||||||
|
session()->put(['checkout_to_type' => 'asset']);
|
||||||
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
||||||
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
||||||
|
|
||||||
} elseif ($request->filled('assigned_to')) {
|
} elseif ($request->filled('assigned_to')) {
|
||||||
|
session()->put(['checkout_to_type' => 'user']);
|
||||||
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
||||||
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
||||||
@@ -89,6 +108,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
if ($checkoutTarget) {
|
if ($checkoutTarget) {
|
||||||
|
|
||||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||||
->with('success', trans('admin/licenses/message.checkout.success'));
|
->with('success', trans('admin/licenses/message.checkout.success'));
|
||||||
}
|
}
|
||||||
@@ -110,6 +130,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (! $licenseSeat->license->is($license)) {
|
if (! $licenseSeat->license->is($license)) {
|
||||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,16 +245,28 @@ class LicensesController extends Controller
|
|||||||
$license = License::with('assignedusers')->find($license->id);
|
$license = License::with('assignedusers')->find($license->id);
|
||||||
|
|
||||||
$users_count = User::where('autoassign_licenses', '1')->count();
|
$users_count = User::where('autoassign_licenses', '1')->count();
|
||||||
$total_seats_count = $license->totalSeatsByLicenseID();
|
|
||||||
|
$total_seats_count = (int) $license->totalSeatsByLicenseID();
|
||||||
$available_seats_count = $license->availCount()->count();
|
$available_seats_count = $license->availCount()->count();
|
||||||
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
|
$unreassignable_seats_count = License::unReassignableCount($license);
|
||||||
|
|
||||||
|
if(!$license->reassignable){
|
||||||
|
$checkedout_seats_count = ($total_seats_count - $available_seats_count - $unreassignable_seats_count );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
|
||||||
|
}
|
||||||
|
if($license->isInactive()){
|
||||||
|
session()->flash('warning', (trans('admin/licenses/message.checkout.license_is_inactive')));
|
||||||
|
}
|
||||||
|
|
||||||
$this->authorize('view', $license);
|
$this->authorize('view', $license);
|
||||||
return view('licenses.view', compact('license'))
|
return view('licenses.view', compact('license'))
|
||||||
->with('users_count', $users_count)
|
->with('users_count', $users_count)
|
||||||
->with('total_seats_count', $total_seats_count)
|
->with('total_seats_count', $total_seats_count)
|
||||||
->with('available_seats_count', $available_seats_count)
|
->with('available_seats_count', $available_seats_count)
|
||||||
->with('checkedout_seats_count', $checkedout_seats_count);
|
->with('checkedout_seats_count', $checkedout_seats_count)
|
||||||
|
->with('unreassignable_seats_count', $unreassignable_seats_count);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,30 +189,36 @@ class LocationsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('delete', Location::class);
|
$this->authorize('delete', Location::class);
|
||||||
|
|
||||||
if (is_null($location = Location::find($locationId))) {
|
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||||
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
|
->withCount('children as children_count')
|
||||||
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
|
->find($locationId);
|
||||||
|
|
||||||
|
if (!$location) {
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($location->users()->count() > 0) {
|
if ($location->isDeletable()) {
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
|
|
||||||
} elseif ($location->children()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_child_loc'));
|
|
||||||
} elseif ($location->assets()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
|
|
||||||
} elseif ($location->assignedassets()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($location->image) {
|
if ($location->image) {
|
||||||
try {
|
try {
|
||||||
Storage::disk('public')->delete('locations/'.$location->image);
|
Storage::disk('public')->delete('locations/'.$location->image);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$location->delete();
|
||||||
|
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
|
||||||
|
} else {
|
||||||
|
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
|
||||||
}
|
}
|
||||||
$location->delete();
|
|
||||||
|
|
||||||
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,23 +253,41 @@ class LocationsController extends Controller
|
|||||||
$this->authorize('view', Location::class);
|
$this->authorize('view', Location::class);
|
||||||
|
|
||||||
if ($location = Location::where('id', $id)->first()) {
|
if ($location = Location::where('id', $id)->first()) {
|
||||||
$parent = Location::where('id', $location->parent_id)->first();
|
|
||||||
$manager = User::where('id', $location->manager_id)->first();
|
|
||||||
$company = Company::where('id', $location->company_id)->first();
|
|
||||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
|
||||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
|
|
||||||
return view('locations/print')
|
return view('locations/print')
|
||||||
->with('assets', $assets)
|
->with('assigned', false)
|
||||||
->with('users',$users)
|
->with('assets', $location->assets)
|
||||||
|
->with('assignedAssets', $location->assignedAssets)
|
||||||
|
->with('accessories', $location->accessories)
|
||||||
|
->with('assignedAccessories', $location->assignedAccessories)
|
||||||
|
->with('users',$location->users)
|
||||||
->with('location', $location)
|
->with('location', $location)
|
||||||
->with('parent', $parent)
|
->with('consumables', $location->consumables)
|
||||||
->with('manager', $manager)
|
->with('components', $location->components)
|
||||||
->with('company', $company);
|
->with('children', $location->children);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function print_all_assigned($id) : View | RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('view', Location::class);
|
||||||
|
if ($location = Location::where('id', $id)->first()) {
|
||||||
|
return view('locations/print')
|
||||||
|
->with('assigned', true)
|
||||||
|
->with('assets', $location->assets)
|
||||||
|
->with('assignedAssets', $location->assignedAssets)
|
||||||
|
->with('accessories', $location->accessories)
|
||||||
|
->with('assignedAccessories', $location->assignedAccessories)
|
||||||
|
->with('users',$location->users)
|
||||||
|
->with('location', $location)
|
||||||
|
->with('consumables', $location->consumables)
|
||||||
|
->with('components', $location->components)
|
||||||
|
->with('children', $location->children);
|
||||||
|
}
|
||||||
|
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a view that presents a form to clone a location.
|
* Returns a view that presents a form to clone a location.
|
||||||
@@ -321,33 +345,12 @@ class LocationsController extends Controller
|
|||||||
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
|
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check validation
|
|
||||||
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
|
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
||||||
|
|
||||||
}
|
}
|
||||||
public function print_all_assigned($id) : View | RedirectResponse
|
|
||||||
{
|
|
||||||
$this->authorize('view', Location::class);
|
|
||||||
if ($location = Location::where('id', $id)->first()) {
|
|
||||||
$parent = Location::where('id', $location->parent_id)->first();
|
|
||||||
$manager = User::where('id', $location->manager_id)->first();
|
|
||||||
$company = Company::where('id', $location->company_id)->first();
|
|
||||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
|
||||||
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
|
|
||||||
return view('locations/print')
|
|
||||||
->with('assets', $assets)
|
|
||||||
->with('users',$users)
|
|
||||||
->with('location', $location)
|
|
||||||
->with('parent', $parent)
|
|
||||||
->with('manager', $manager)
|
|
||||||
->with('company', $company);
|
|
||||||
}
|
|
||||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a view that allows the user to bulk delete locations
|
* Returns a view that allows the user to bulk delete locations
|
||||||
@@ -366,8 +369,12 @@ class LocationsController extends Controller
|
|||||||
$locations = Location::whereIn('id', $locations_raw_array)
|
$locations = Location::whereIn('id', $locations_raw_array)
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->withCount('users as users_count')->get();
|
->withCount('users as users_count')->get();
|
||||||
|
|
||||||
$valid_count = 0;
|
$valid_count = 0;
|
||||||
@@ -400,9 +407,13 @@ class LocationsController extends Controller
|
|||||||
$locations = Location::whereIn('id', $locations_raw_array)
|
$locations = Location::whereIn('id', $locations_raw_array)
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')->get();
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')->get();
|
||||||
|
|
||||||
$success_count = 0;
|
$success_count = 0;
|
||||||
$error_count = 0;
|
$error_count = 0;
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ class MaintenancesController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
|
|
||||||
|
\Log::error(print_r($request->input('selected_assets[]'), true));
|
||||||
|
|
||||||
|
if (!$request->filled('selected_assets')) {
|
||||||
|
return redirect()->back()->withInput()->with('error', 'No assets were selected.');
|
||||||
|
}
|
||||||
|
|
||||||
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
|
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
|
||||||
|
|
||||||
// Loop through the selected assets
|
// Loop through the selected assets
|
||||||
@@ -78,6 +84,7 @@ class MaintenancesController extends Controller
|
|||||||
$maintenance->is_warranty = $request->input('is_warranty');
|
$maintenance->is_warranty = $request->input('is_warranty');
|
||||||
$maintenance->cost = $request->input('cost');
|
$maintenance->cost = $request->input('cost');
|
||||||
$maintenance->notes = $request->input('notes');
|
$maintenance->notes = $request->input('notes');
|
||||||
|
$maintenance->url = $request->input('url');
|
||||||
|
|
||||||
// Save the asset maintenance data
|
// Save the asset maintenance data
|
||||||
$maintenance->asset_id = $asset->id;
|
$maintenance->asset_id = $asset->id;
|
||||||
@@ -152,6 +159,7 @@ class MaintenancesController extends Controller
|
|||||||
$maintenance->name = $request->input('name');
|
$maintenance->name = $request->input('name');
|
||||||
$maintenance->start_date = $request->input('start_date');
|
$maintenance->start_date = $request->input('start_date');
|
||||||
$maintenance->completion_date = $request->input('completion_date');
|
$maintenance->completion_date = $request->input('completion_date');
|
||||||
|
$maintenance->url = $request->input('url');
|
||||||
|
|
||||||
|
|
||||||
// Todo - put this in a getter/setter?
|
// Todo - put this in a getter/setter?
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Manufacturers\DeleteManufacturerAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Helpers\Helper;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\Manufacturer;
|
use App\Models\Manufacturer;
|
||||||
@@ -157,32 +165,18 @@ class ManufacturersController extends Controller
|
|||||||
* @param int $manufacturerId
|
* @param int $manufacturerId
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
*/
|
*/
|
||||||
public function destroy($manufacturerId) : RedirectResponse
|
public function destroy(Manufacturer $manufacturer): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Manufacturer::class);
|
$this->authorize('delete', $manufacturer);
|
||||||
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
|
try {
|
||||||
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.not_found'));
|
DeleteManufacturerAction::run($manufacturer);
|
||||||
|
} catch (ItemStillHasChildren $e) {
|
||||||
|
return redirect()->route('manufacturers.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return redirect()->route('manufacturers.index')->with('error', trans('general.something_went_wrong'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $manufacturer->isDeletable()) {
|
|
||||||
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.assoc_users'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($manufacturer->image) {
|
|
||||||
try {
|
|
||||||
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::info($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Soft delete the manufacturer if active, permanent delete if is already deleted
|
|
||||||
if ($manufacturer->deleted_at === null) {
|
|
||||||
$manufacturer->delete();
|
|
||||||
} else {
|
|
||||||
$manufacturer->forceDelete();
|
|
||||||
}
|
|
||||||
// Redirect to the manufacturers management page
|
|
||||||
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.success'));
|
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class ReportsController extends Controller
|
|||||||
$row[] = e($asset->serial);
|
$row[] = e($asset->serial);
|
||||||
|
|
||||||
if ($target = $asset->assignedTo) {
|
if ($target = $asset->assignedTo) {
|
||||||
$row[] = e($target->present()->name());
|
$row[] = e($target->display_name);
|
||||||
} else {
|
} else {
|
||||||
$row[] = ''; // Empty string if unassigned
|
$row[] = ''; // Empty string if unassigned
|
||||||
}
|
}
|
||||||
@@ -274,22 +274,18 @@ class ReportsController extends Controller
|
|||||||
$target_name = '';
|
$target_name = '';
|
||||||
|
|
||||||
if ($actionlog->target) {
|
if ($actionlog->target) {
|
||||||
if ($actionlog->targetType() == 'user') {
|
$target_name = $actionlog->target->display_name;
|
||||||
$target_name = $actionlog->target->display_name;
|
|
||||||
} else {
|
|
||||||
$target_name = $actionlog->target->getDisplayNameAttribute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($actionlog->item){
|
if ($actionlog->item){
|
||||||
$item_name = e($actionlog->item->getDisplayNameAttribute());
|
$item_name = e($actionlog->item->display_name);
|
||||||
} else {
|
} else {
|
||||||
$item_name = '';
|
$item_name = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$row = [
|
$row = [
|
||||||
$actionlog->created_at,
|
$actionlog->created_at,
|
||||||
($actionlog->adminuser) ? e($actionlog->adminuser->display_name) : '',
|
($actionlog->adminuser) ? $actionlog->adminuser->display_name : '',
|
||||||
$actionlog->present()->actionType(),
|
$actionlog->present()->actionType(),
|
||||||
e($actionlog->itemType()),
|
e($actionlog->itemType()),
|
||||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||||
@@ -298,10 +294,10 @@ class ReportsController extends Controller
|
|||||||
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
|
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
|
||||||
$target_name,
|
$target_name,
|
||||||
($actionlog->note) ? e($actionlog->note) : '',
|
($actionlog->note) ? e($actionlog->note) : '',
|
||||||
$actionlog->log_meta,
|
|
||||||
$actionlog->remote_ip,
|
$actionlog->remote_ip,
|
||||||
$actionlog->user_agent,
|
$actionlog->user_agent,
|
||||||
$actionlog->action_source,
|
$actionlog->action_source,
|
||||||
|
$actionlog->log_meta,
|
||||||
];
|
];
|
||||||
fputcsv($handle, $row);
|
fputcsv($handle, $row);
|
||||||
}
|
}
|
||||||
@@ -440,10 +436,8 @@ class ReportsController extends Controller
|
|||||||
// Open output stream
|
// Open output stream
|
||||||
$handle = fopen('php://output', 'w');
|
$handle = fopen('php://output', 'w');
|
||||||
stream_set_timeout($handle, 2000);
|
stream_set_timeout($handle, 2000);
|
||||||
|
|
||||||
if ($request->filled('use_bom')) {
|
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||||
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
|
|
||||||
}
|
|
||||||
|
|
||||||
$header = [];
|
$header = [];
|
||||||
|
|
||||||
@@ -689,6 +683,14 @@ class ReportsController extends Controller
|
|||||||
$assets->whereBetween('assets.purchase_date', [$request->input('purchase_start'), $request->input('purchase_end')]);
|
$assets->whereBetween('assets.purchase_date', [$request->input('purchase_start'), $request->input('purchase_end')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('purchase_cost_start')) {
|
||||||
|
if ($request->filled('purchase_cost_end')) {
|
||||||
|
$assets->whereBetween('assets.purchase_cost', [$request->input('purchase_cost_start'), $request->input('purchase_cost_end')]);
|
||||||
|
} else {
|
||||||
|
$assets->where('assets.purchase_cost', ">", $request->input('purchase_cost_start'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
|
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
|
||||||
$created_start = Carbon::parse($request->input('created_start'))->startOfDay();
|
$created_start = Carbon::parse($request->input('created_start'))->startOfDay();
|
||||||
$created_end = Carbon::parse($request->input('created_end'))->endOfDay();
|
$created_end = Carbon::parse($request->input('created_end'))->endOfDay();
|
||||||
@@ -830,7 +832,7 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('location')) {
|
if ($request->filled('location')) {
|
||||||
$row[] = ($asset->location) ? $asset->location->present()->name() : '';
|
$row[] = ($asset->location) ? $asset->location->display_name : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('location_address')) {
|
if ($request->filled('location_address')) {
|
||||||
@@ -843,7 +845,7 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('rtd_location')) {
|
if ($request->filled('rtd_location')) {
|
||||||
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->present()->name() : '';
|
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->display_name : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('rtd_location_address')) {
|
if ($request->filled('rtd_location_address')) {
|
||||||
@@ -856,8 +858,8 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('assigned_to')) {
|
if ($request->filled('assigned_to')) {
|
||||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ?? $asset->assigned->display_name;
|
$row[] = ($asset->assigned) ? $asset->assigned->display_name : '';
|
||||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : $asset->assignedType();
|
$row[] = ($asset->assigned) ? $asset->assignedType() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('username')) {
|
if ($request->filled('username')) {
|
||||||
@@ -1260,7 +1262,7 @@ class ReportsController extends Controller
|
|||||||
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
|
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
|
||||||
$row[] = str_replace(',', '', e($item['assetItem']->name));
|
$row[] = str_replace(',', '', e($item['assetItem']->name));
|
||||||
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
|
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
|
||||||
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
|
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->display_name : trans('admin/reports/general.deleted_user')));
|
||||||
$rows[] = implode(',', $row);
|
$rows[] = implode(',', $row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Suppliers\DestroySupplierAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Supplier;
|
use App\Models\Supplier;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use \Illuminate\Contracts\View\View;
|
use \Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Support\MessageBag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This controller handles all actions related to Suppliers for
|
* This controller handles all actions related to Suppliers for
|
||||||
@@ -118,30 +126,41 @@ class SuppliersController extends Controller
|
|||||||
*
|
*
|
||||||
* @param int $supplierId
|
* @param int $supplierId
|
||||||
*/
|
*/
|
||||||
public function destroy($supplierId) : RedirectResponse
|
public function destroy(Supplier $supplier): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Supplier::class);
|
$this->authorize('delete', Supplier::class);
|
||||||
if (is_null($supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) {
|
try {
|
||||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.not_found'));
|
DestroySupplierAction::run(supplier: $supplier);
|
||||||
|
} catch (ItemStillHasAssets $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_assets', [
|
||||||
|
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasMaintenances $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_maintenances', [
|
||||||
|
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_licenses', [
|
||||||
|
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_accessories', [
|
||||||
|
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_consumables', [
|
||||||
|
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_components', [
|
||||||
|
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.error'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($supplier->assets_count > 0) {
|
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.success'));
|
||||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($supplier->maintenances_count > 0) {
|
|
||||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($supplier->licenses_count > 0) {
|
|
||||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count]));
|
|
||||||
}
|
|
||||||
|
|
||||||
$supplier->delete();
|
|
||||||
|
|
||||||
return redirect()->route('suppliers.index')->with('success',
|
|
||||||
trans('admin/suppliers/message.delete.success')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,6 +173,5 @@ class SuppliersController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('view', Supplier::class);
|
$this->authorize('view', Supplier::class);
|
||||||
return view('suppliers/view', compact('supplier'));
|
return view('suppliers/view', compact('supplier'));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('update', $object);
|
$this->authorize('update', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -85,7 +85,7 @@ class UploadedFilesController extends Controller
|
|||||||
public function show($object_type, $id, $file_id) : RedirectResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
public function show($object_type, $id, $file_id) : RedirectResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||||
{
|
{
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -130,7 +130,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('update', self::$map_object_type[$object_type]);
|
$this->authorize('update', self::$map_object_type[$object_type]);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -139,7 +139,7 @@ class UploadedFilesController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
// Check for the file
|
// Check for the file
|
||||||
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
|
$log = Actionlog::where('id',$file_id)->where('item_type', self::$map_object_type[$object_type])
|
||||||
->where('item_id', $object->id)->first();
|
->where('item_id', $object->id)->first();
|
||||||
|
|
||||||
if ($log) {
|
if ($log) {
|
||||||
@@ -148,7 +148,7 @@ class UploadedFilesController extends Controller
|
|||||||
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
||||||
}
|
}
|
||||||
// Delete the record of the file
|
// Delete the record of the file
|
||||||
if ($log->delete()) {
|
if ($log->logUploadDelete($object, $log->filename)) {
|
||||||
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1));
|
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ use App\Models\Company;
|
|||||||
use App\Models\Group;
|
use App\Models\Group;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Notifications\WelcomeNotification;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
use App\Notifications\CurrentInventory;
|
use App\Notifications\CurrentInventory;
|
||||||
@@ -128,7 +130,7 @@ class UsersController extends Controller
|
|||||||
// we have to invoke the form request here to handle image uploads
|
// we have to invoke the form request here to handle image uploads
|
||||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
|
|
||||||
if($request->get('redirect_option') === 'back'){
|
if ($request->get('redirect_option') === 'back'){
|
||||||
session()->put(['redirect_option' => 'index']);
|
session()->put(['redirect_option' => 'index']);
|
||||||
} else {
|
} else {
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||||
@@ -136,6 +138,18 @@ class UsersController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
|
|
||||||
|
if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user->notify(new WelcomeNotification($user));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Could not send welcome notification for user: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('groups')) {
|
if ($request->filled('groups')) {
|
||||||
$user->groups()->sync($request->input('groups'));
|
$user->groups()->sync($request->input('groups'));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ class AssetCountForSidebar
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$total_assets = Asset::count();
|
$total_assets = Asset::AssetsForShow()->count();
|
||||||
if ($settings->show_archived_in_list != '1') {
|
|
||||||
$total_assets -= Asset::Archived()->count();
|
|
||||||
}
|
|
||||||
view()->share('total_assets', $total_assets);
|
view()->share('total_assets', $total_assets);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::debug($e);
|
Log::debug($e);
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ class CustomAssetReportRequest extends Request
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function prepareForValidation()
|
||||||
|
{
|
||||||
|
if($this->filled('purchase_cost_end') && !$this->filled('purchase_cost_start')){
|
||||||
|
$this->merge(['purchase_cost_start' => 0 ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
@@ -24,6 +33,7 @@ class CustomAssetReportRequest extends Request
|
|||||||
return [
|
return [
|
||||||
'purchase_start' => 'date|date_format:Y-m-d|nullable',
|
'purchase_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
'purchase_end' => 'date|date_format:Y-m-d|nullable',
|
'purchase_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'purchase_cost_end' => 'numeric|nullable|gte:purchase_cost_start',
|
||||||
'created_start' => 'date|date_format:Y-m-d|nullable',
|
'created_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
'created_end' => 'date|date_format:Y-m-d|nullable',
|
'created_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
|
'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class SettingsSamlRequest extends FormRequest
|
|||||||
];
|
];
|
||||||
|
|
||||||
$pkey = openssl_pkey_new([
|
$pkey = openssl_pkey_new([
|
||||||
'private_key_bits' => config('app.saml_key_size'),
|
'private_key_bits' => (int) config('app.saml_key_size'),
|
||||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class StoreAssetRequest extends ImageUploadRequest
|
|||||||
|
|
||||||
public function prepareForValidation(): void
|
public function prepareForValidation(): void
|
||||||
{
|
{
|
||||||
|
parent::prepareForValidation(); // call ImageUploadRequest thing
|
||||||
// Guard against users passing in an array for company_id instead of an integer.
|
// Guard against users passing in an array for company_id instead of an integer.
|
||||||
// If the company_id is not an integer then we simply use what was
|
// If the company_id is not an integer then we simply use what was
|
||||||
// provided to be caught by model level validation later.
|
// provided to be caught by model level validation later.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class StoreNotificationSettings extends FormRequest
|
|||||||
],
|
],
|
||||||
'alert_threshold' => 'numeric|nullable',
|
'alert_threshold' => 'numeric|nullable',
|
||||||
'alert_interval' => 'numeric|nullable|gt:0',
|
'alert_interval' => 'numeric|nullable|gt:0',
|
||||||
'audit_warning_days' => 'numeric|nullable',
|
'audit_warning_days' => 'numeric|nullable|gte:0',
|
||||||
'due_checkin_days' => 'numeric|nullable|gt:0',
|
'due_checkin_days' => 'numeric|nullable|gt:0',
|
||||||
'audit_interval' => 'numeric|nullable|gt:0',
|
'audit_interval' => 'numeric|nullable|gt:0',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Traits;
|
namespace App\Http\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -38,20 +39,13 @@ trait ConvertsBase64ToFiles
|
|||||||
if (!$base64Contents) {
|
if (!$base64Contents) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// autogenerate filenames
|
|
||||||
if ($filename == 'auto'){
|
|
||||||
$header = explode(';', $base64Contents, 2)[0];
|
|
||||||
// Grab the image type from the header while we're at it.
|
|
||||||
$filename = $key . '.' . substr($header, strpos($header, '/')+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a temporary path to store the Base64 contents
|
// Generate a temporary path to store the Base64 contents
|
||||||
$tempFilePath = tempnam(sys_get_temp_dir(), $filename);
|
$tempFilePath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
|
|
||||||
// Store the contents using a stream, or by decoding manually
|
// Store the contents using a stream, or throw an Error (which doesn't do anything?)
|
||||||
if (Str::startsWith($base64Contents, 'data:') && count(explode(',', $base64Contents)) > 1) {
|
if (Str::startsWith($base64Contents, 'data:') && count(explode(',', $base64Contents)) > 1) {
|
||||||
$source = fopen($base64Contents, 'r');
|
$source = fopen($base64Contents, 'r'); // PHP has special processing for "data:" URL's
|
||||||
$destination = fopen($tempFilePath, 'w');
|
$destination = fopen($tempFilePath, 'w');
|
||||||
|
|
||||||
stream_copy_to_stream($source, $destination);
|
stream_copy_to_stream($source, $destination);
|
||||||
@@ -59,7 +53,8 @@ trait ConvertsBase64ToFiles
|
|||||||
fclose($source);
|
fclose($source);
|
||||||
fclose($destination);
|
fclose($destination);
|
||||||
} else {
|
} else {
|
||||||
file_put_contents($tempFilePath, base64_decode($base64Contents, true));
|
// TODO - to get a better error message here, can we maybe do something with modifying the errorBag?
|
||||||
|
throw new ValidationException("Need Base64 URL starting with 'data:'"); // This doesn't actually throw?
|
||||||
}
|
}
|
||||||
|
|
||||||
$uploadedFile = new UploadedFile($tempFilePath, $filename, null, null, true);
|
$uploadedFile = new UploadedFile($tempFilePath, $filename, null, null, true);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class AccessoriesTransformer
|
|||||||
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
|
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
|
||||||
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
|
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
|
||||||
|
'total_cost' => Helper::formatCurrencyOutput($accessory->totalCostSum()),
|
||||||
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
|
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
|
||||||
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter
|
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter
|
||||||
'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
|
'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
|
||||||
|
|||||||
@@ -50,17 +50,20 @@ class ActionlogsTransformer
|
|||||||
|
|
||||||
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
$icon = $actionlog->present()->icon();
|
$icon = $actionlog->present()->icon();
|
||||||
|
|
||||||
|
if (($actionlog->filename!='') && ($actionlog->action_type!='upload deleted')) {
|
||||||
|
$icon = Helper::filetype_icon($actionlog->filename);
|
||||||
|
}
|
||||||
|
|
||||||
static $custom_fields = false;
|
static $custom_fields = false;
|
||||||
|
|
||||||
if ($custom_fields === false) {
|
if ($custom_fields === false) {
|
||||||
$custom_fields = CustomField::all();
|
$custom_fields = CustomField::all();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($actionlog->filename!='') {
|
|
||||||
$icon = Helper::filetype_icon($actionlog->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is necessary since we can't escape special characters within a JSON object
|
// This is necessary since we can't escape special characters within a JSON object
|
||||||
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
|
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
|
||||||
@@ -144,15 +147,16 @@ class ActionlogsTransformer
|
|||||||
[
|
[
|
||||||
'url' => $actionlog->uploads_file_url(),
|
'url' => $actionlog->uploads_file_url(),
|
||||||
'filename' => $actionlog->filename,
|
'filename' => $actionlog->filename,
|
||||||
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_url()),
|
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_path()),
|
||||||
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
|
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
|
||||||
|
'mediatype' => StorageHelper::getMediaType($actionlog->uploads_file_path()),
|
||||||
] : null,
|
] : null,
|
||||||
|
|
||||||
'item' => ($actionlog->item) ? [
|
'item' => ($actionlog->item) ? [
|
||||||
'id' => (int) $actionlog->item->id,
|
'id' => (int) $actionlog->item->id,
|
||||||
'name' => e($actionlog->item->display_name) ?? null,
|
'name' => e($actionlog->item->display_name) ?? null,
|
||||||
'type' => e($actionlog->itemType()),
|
'type' => e($actionlog->itemType()),
|
||||||
'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null
|
'serial' => e($actionlog->item->serial) ? e($actionlog->item->serial) : null
|
||||||
] : null,
|
] : null,
|
||||||
'location' => ($actionlog->location) ? [
|
'location' => ($actionlog->location) ? [
|
||||||
'id' => (int) $actionlog->location->id,
|
'id' => (int) $actionlog->location->id,
|
||||||
@@ -165,7 +169,7 @@ class ActionlogsTransformer
|
|||||||
'action_type' => $actionlog->present()->actionType(),
|
'action_type' => $actionlog->present()->actionType(),
|
||||||
'admin' => ($actionlog->adminuser) ? [
|
'admin' => ($actionlog->adminuser) ? [
|
||||||
'id' => (int) $actionlog->adminuser->id,
|
'id' => (int) $actionlog->adminuser->id,
|
||||||
'name' => e($actionlog->adminuser->display_name),
|
'name' => e($actionlog->adminuser->display_name) ?? null,
|
||||||
'first_name'=> e($actionlog->adminuser->first_name),
|
'first_name'=> e($actionlog->adminuser->first_name),
|
||||||
'last_name'=> e($actionlog->adminuser->last_name)
|
'last_name'=> e($actionlog->adminuser->last_name)
|
||||||
] : null,
|
] : null,
|
||||||
@@ -177,7 +181,7 @@ class ActionlogsTransformer
|
|||||||
] : null,
|
] : null,
|
||||||
'target' => ($actionlog->target) ? [
|
'target' => ($actionlog->target) ? [
|
||||||
'id' => (int) $actionlog->target->id,
|
'id' => (int) $actionlog->target->id,
|
||||||
'name' => ($actionlog->target->display_name) ?? null,
|
'name' => e($actionlog->target->display_name) ?? null,
|
||||||
'type' => e($actionlog->targetType()),
|
'type' => e($actionlog->targetType()),
|
||||||
] : null,
|
] : null,
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,15 @@ class AssetModelsTransformer
|
|||||||
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
|
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
|
||||||
'model_number' => ($assetmodel->model_number ? e($assetmodel->model_number): null),
|
'model_number' => ($assetmodel->model_number ? e($assetmodel->model_number): null),
|
||||||
'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
|
'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
|
||||||
'remaining' => (int) ($assetmodel->assets_count - $assetmodel->min_amt),
|
|
||||||
'depreciation' => ($assetmodel->depreciation) ? [
|
'depreciation' => ($assetmodel->depreciation) ? [
|
||||||
'id' => (int) $assetmodel->depreciation->id,
|
'id' => (int) $assetmodel->depreciation->id,
|
||||||
'name'=> e($assetmodel->depreciation->name),
|
'name'=> e($assetmodel->depreciation->name),
|
||||||
] : null,
|
] : null,
|
||||||
'assets_count' => (int) $assetmodel->assets_count,
|
'assets_count' => (int) $assetmodel->assets_count,
|
||||||
|
'assets_assigned_count' => (int) $assetmodel->assets_assigned_count,
|
||||||
|
'assets_archived_count' => (int) $assetmodel->assets_archived_count,
|
||||||
|
'remaining' => (int) ($assetmodel->assets_count - (int) $assetmodel->assets_assigned_count) - (int) $assetmodel->assets_archived_count,
|
||||||
'category' => ($assetmodel->category) ? [
|
'category' => ($assetmodel->category) ? [
|
||||||
'id' => (int) $assetmodel->category->id,
|
'id' => (int) $assetmodel->category->id,
|
||||||
'name'=> e($assetmodel->category->name),
|
'name'=> e($assetmodel->category->name),
|
||||||
@@ -65,6 +68,7 @@ class AssetModelsTransformer
|
|||||||
'default_fieldset_values' => $default_field_values,
|
'default_fieldset_values' => $default_field_values,
|
||||||
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
||||||
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
||||||
|
'require_serial' => ($assetmodel->require_serial == '1') ? true : false,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
|
||||||
'created_by' => ($assetmodel->adminuser) ? [
|
'created_by' => ($assetmodel->adminuser) ? [
|
||||||
'id' => (int) $assetmodel->adminuser->id,
|
'id' => (int) $assetmodel->adminuser->id,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class ComponentsTransformer
|
|||||||
'order_number' => e($component->order_number),
|
'order_number' => e($component->order_number),
|
||||||
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
|
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
|
||||||
|
'total_cost' => Helper::formatCurrencyOutput($component->totalCostSum()),
|
||||||
'remaining' => (int) $component->numRemaining(),
|
'remaining' => (int) $component->numRemaining(),
|
||||||
'company' => ($component->company) ? [
|
'company' => ($component->company) ? [
|
||||||
'id' => (int) $component->company->id,
|
'id' => (int) $component->company->id,
|
||||||
@@ -76,7 +77,7 @@ class ComponentsTransformer
|
|||||||
$array[] = [
|
$array[] = [
|
||||||
'assigned_pivot_id' => $asset->pivot->id,
|
'assigned_pivot_id' => $asset->pivot->id,
|
||||||
'id' => (int) $asset->id,
|
'id' => (int) $asset->id,
|
||||||
'name' => e($asset->model->present()->name).' '.e($asset->present()->name),
|
'name' => e($asset->model->display_name).' '.e($asset->display_name),
|
||||||
'qty' => $asset->pivot->assigned_qty,
|
'qty' => $asset->pivot->assigned_qty,
|
||||||
'note' => $asset->pivot->note,
|
'note' => $asset->pivot->note,
|
||||||
'type' => 'asset',
|
'type' => 'asset',
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class ConsumablesTransformer
|
|||||||
'remaining' => $consumable->numRemaining(),
|
'remaining' => $consumable->numRemaining(),
|
||||||
'order_number' => e($consumable->order_number),
|
'order_number' => e($consumable->order_number),
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($consumable->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($consumable->purchase_cost),
|
||||||
|
'total_cost' => Helper::formatCurrencyOutput($consumable->totalCostSum()),
|
||||||
'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'),
|
'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'),
|
||||||
'qty' => (int) $consumable->qty,
|
'qty' => (int) $consumable->qty,
|
||||||
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
|
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use App\Models\License;
|
|||||||
use App\Models\LicenseSeat;
|
use App\Models\LicenseSeat;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
class LicenseSeatsTransformer
|
class LicenseSeatsTransformer
|
||||||
{
|
{
|
||||||
public function transformLicenseSeats(Collection $seats, $total)
|
public function transformLicenseSeats(Collection $seats, $total)
|
||||||
@@ -52,6 +51,7 @@ class LicenseSeatsTransformer
|
|||||||
'reassignable' => (bool) $seat->license->reassignable,
|
'reassignable' => (bool) $seat->license->reassignable,
|
||||||
'notes' => e($seat->notes),
|
'notes' => e($seat->notes),
|
||||||
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
|
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
|
||||||
|
'disabled' => $seat->unreassignable_seat || $seat->license->isInactive(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ class LicensesTransformer
|
|||||||
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
|
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
|
||||||
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
|
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
|
||||||
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
|
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
|
||||||
|
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
|
||||||
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
|
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost),
|
||||||
'purchase_cost_numeric' => $license->purchase_cost,
|
'purchase_cost_numeric' => $license->purchase_cost,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($license->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($license->notes),
|
||||||
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
|
|
||||||
'seats' => (int) $license->seats,
|
'seats' => (int) $license->seats,
|
||||||
'free_seats_count' => (int) $license->free_seats_count,
|
'free_seats_count' => (int) $license->free_seats_count - License::unReassignableCount($license),
|
||||||
'remaining' => (int) $license->free_seats_count,
|
'remaining' => (int) $license->free_seats_count,
|
||||||
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
|
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
|
||||||
'license_name' => ($license->license_name) ? e($license->license_name) : null,
|
'license_name' => ($license->license_name) ? e($license->license_name) : null,
|
||||||
@@ -54,7 +54,7 @@ class LicensesTransformer
|
|||||||
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
|
||||||
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
|
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
|
||||||
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
|
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
|
||||||
|
'disabled' => $license->isInactive(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ class LocationsTransformer
|
|||||||
'assets_count' => (int) $location->assets_count,
|
'assets_count' => (int) $location->assets_count,
|
||||||
'rtd_assets_count' => (int) $location->rtd_assets_count,
|
'rtd_assets_count' => (int) $location->rtd_assets_count,
|
||||||
'users_count' => (int) $location->users_count,
|
'users_count' => (int) $location->users_count,
|
||||||
|
'consumables_count' => (int) $location->consumables_count,
|
||||||
|
'components_count' => (int) $location->components_count,
|
||||||
|
'children_count' => (int) $location->children_count,
|
||||||
'currency' => ($location->currency) ? e($location->currency) : null,
|
'currency' => ($location->currency) ? e($location->currency) : null,
|
||||||
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
|
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($location->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($location->notes),
|
||||||
@@ -76,12 +79,13 @@ class LocationsTransformer
|
|||||||
];
|
];
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
'update' => Gate::allows('update', Location::class) ? true : false,
|
'update' => (Gate::allows('update', Location::class) && ($location->deleted_at == '')),
|
||||||
'delete' => $location->isDeletable(),
|
'delete' => $location->isDeletable(),
|
||||||
'bulk_selectable' => [
|
'bulk_selectable' => [
|
||||||
'delete' => $location->isDeletable()
|
'delete' => $location->isDeletable()
|
||||||
],
|
],
|
||||||
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
|
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
|
||||||
|
'restore' => (Gate::allows('create', Location::class) && ($location->deleted_at != '')),
|
||||||
];
|
];
|
||||||
|
|
||||||
$array += $permissions_array;
|
$array += $permissions_array;
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class MaintenancesTransformer
|
|||||||
'id' => $assetmaintenance->supplier->id,
|
'id' => $assetmaintenance->supplier->id,
|
||||||
'name'=> e($assetmaintenance->supplier->name)
|
'name'=> e($assetmaintenance->supplier->name)
|
||||||
] : null,
|
] : null,
|
||||||
|
'url' => ($assetmaintenance->url) ? e($assetmaintenance->url) : null,
|
||||||
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
|
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
|
||||||
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
|
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
|
||||||
'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'),
|
'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Http\Transformers;
|
|||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\Asset;
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
class ProfileTransformer
|
class ProfileTransformer
|
||||||
@@ -26,7 +25,7 @@ class ProfileTransformer
|
|||||||
'id' => (int) $file->id,
|
'id' => (int) $file->id,
|
||||||
'icon' => Helper::filetype_icon($file->filename),
|
'icon' => Helper::filetype_icon($file->filename),
|
||||||
'item' => ($file->item) ? [
|
'item' => ($file->item) ? [
|
||||||
'name' => ($file->itemType()=='user') ? e($file->item->display_name) : e($file->item->getDisplayNameAttribute()),
|
'name' => $file->item->display_name ? e($file->item->display_name) : null,
|
||||||
'type' => e($file->itemType()),
|
'type' => e($file->itemType()),
|
||||||
] : null,
|
] : null,
|
||||||
'filename' => e($file->filename),
|
'filename' => e($file->filename),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class UsersTransformer
|
|||||||
public function transformUser(User $user)
|
public function transformUser(User $user)
|
||||||
{
|
{
|
||||||
|
|
||||||
$role = '';
|
$role = null;
|
||||||
if ($user->isSuperUser()) {
|
if ($user->isSuperUser()) {
|
||||||
$role = 'superadmin';
|
$role = 'superadmin';
|
||||||
} elseif ($user->isAdmin()) {
|
} elseif ($user->isAdmin()) {
|
||||||
@@ -34,7 +34,7 @@ class UsersTransformer
|
|||||||
'name' => e($user->getFullNameAttribute()) ?? null,
|
'name' => e($user->getFullNameAttribute()) ?? null,
|
||||||
'first_name' => e($user->first_name) ?? null,
|
'first_name' => e($user->first_name) ?? null,
|
||||||
'last_name' => e($user->last_name) ?? null,
|
'last_name' => e($user->last_name) ?? null,
|
||||||
'display_name' => e($user->getRawOriginal('display_name')) ?? null,
|
'display_name' => ($user->getRawOriginal('display_name')) ? e($user->getRawOriginal('display_name')) : null,
|
||||||
'username' => e($user->username) ?? null,
|
'username' => e($user->username) ?? null,
|
||||||
'remote' => ($user->remote == '1') ? true : false,
|
'remote' => ($user->remote == '1') ? true : false,
|
||||||
'locale' => ($user->locale) ? e($user->locale) : null,
|
'locale' => ($user->locale) ? e($user->locale) : null,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class AssetImporter extends ItemImporter
|
|||||||
foreach ($this->customFields as $customField) {
|
foreach ($this->customFields as $customField) {
|
||||||
$customFieldValue = $this->array_smart_custom_field_fetch($row, $customField);
|
$customFieldValue = $this->array_smart_custom_field_fetch($row, $customField);
|
||||||
|
|
||||||
if ($customFieldValue) {
|
if (!is_null($customFieldValue)) {
|
||||||
if ($customField->field_encrypted == 1) {
|
if ($customField->field_encrypted == 1) {
|
||||||
$this->item['custom_fields'][$customField->db_column_name()] = Crypt::encrypt($customFieldValue);
|
$this->item['custom_fields'][$customField->db_column_name()] = Crypt::encrypt($customFieldValue);
|
||||||
$this->log('Custom Field '.$customField->name.': '.Crypt::encrypt($customFieldValue));
|
$this->log('Custom Field '.$customField->name.': '.Crypt::encrypt($customFieldValue));
|
||||||
|
|||||||
@@ -40,11 +40,32 @@ class AssetModelImporter extends ItemImporter
|
|||||||
{
|
{
|
||||||
|
|
||||||
$editingAssetModel = false;
|
$editingAssetModel = false;
|
||||||
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
|
||||||
|
/**
|
||||||
|
* This part gets a little confusing, since folks might be importing multiple models with the same name and different model numbers for the first time
|
||||||
|
* or they might be wanting to update existing models with new model numbers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// They are not trying to update existing models, so we'll check for duplicates with model name *and* number
|
||||||
|
if (! $this->updating) {
|
||||||
|
$this->log('Finding model by name and model number: '.$this->findCsvMatch($row, 'name').' / '.$this->findCsvMatch($row, 'model_number'));
|
||||||
|
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->where('model_number', '=', $this->findCsvMatch($row, 'model_number'))->first();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($this->findCsvMatch($row, 'id')!='') {
|
||||||
|
// Override model if an ID was given
|
||||||
|
$this->log('Finding model by ID: '.$this->findCsvMatch($row, 'id'));
|
||||||
|
$assetModel = AssetModel::find($this->findCsvMatch($row, 'id'));
|
||||||
|
} else {
|
||||||
|
$this->log('Finding model by name: '.$this->findCsvMatch($row, 'name'));
|
||||||
|
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($assetModel) {
|
if ($assetModel) {
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
$this->log('A matching Model '.$this->item['name'].' already exists');
|
$this->log('A matching Model '.$this->item['name'].' already exists and we are not updating. Skipping.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +87,7 @@ class AssetModelImporter extends ItemImporter
|
|||||||
$this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset'));
|
$this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset'));
|
||||||
$this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation'));
|
$this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation'));
|
||||||
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0;
|
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0;
|
||||||
|
$this->item['require_serial'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'require_serial'))) == 1) ? 1 : 0;
|
||||||
|
|
||||||
if (!empty($this->item['category'])) {
|
if (!empty($this->item['category'])) {
|
||||||
if ($category = $this->createOrFetchCategory($this->item['category'])) {
|
if ($category = $this->createOrFetchCategory($this->item['category'])) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a supplier if a duplicate does not exist.
|
* Create a manufacturer if a duplicate does not exist.
|
||||||
* @todo Investigate how this should interact with Importer::createManufacturerIfNotExists
|
* @todo Investigate how this should interact with Importer::createManufacturerIfNotExists
|
||||||
*
|
*
|
||||||
* @author A. Gianotto
|
* @author A. Gianotto
|
||||||
@@ -39,16 +39,16 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
|
|
||||||
$editingManufacturer = false;
|
$editingManufacturer = false;
|
||||||
|
|
||||||
$supplier = Manufacturer::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
$manufacturer = Manufacturer::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
||||||
|
|
||||||
if ($this->findCsvMatch($row, 'id')!='') {
|
if ($this->findCsvMatch($row, 'id')!='') {
|
||||||
// Override supplier if an ID was given
|
// Override manufacturer if an ID was given
|
||||||
\Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id'));
|
\Log::debug('Finding manufacturer by ID: '.$this->findCsvMatch($row, 'id'));
|
||||||
$supplier = Manufacturer::find($this->findCsvMatch($row, 'id'));
|
$manufacturer = Manufacturer::find($this->findCsvMatch($row, 'id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($supplier) {
|
if ($manufacturer) {
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
$this->log('A matching Manufacturer '.$this->item['name'].' already exists');
|
$this->log('A matching Manufacturer '.$this->item['name'].' already exists');
|
||||||
return;
|
return;
|
||||||
@@ -58,8 +58,8 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
$editingManufacturer = true;
|
$editingManufacturer = true;
|
||||||
} else {
|
} else {
|
||||||
$this->log('No Matching Manufacturer, Create a new one');
|
$this->log('No Matching Manufacturer, Create a new one');
|
||||||
$supplier = new Manufacturer;
|
$manufacturer = new Manufacturer;
|
||||||
$supplier->created_by = auth()->id();
|
$manufacturer->created_by = auth()->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the records from the CSV to determine their values
|
// Pull the records from the CSV to determine their values
|
||||||
@@ -79,21 +79,21 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
|
|
||||||
|
|
||||||
if ($editingManufacturer) {
|
if ($editingManufacturer) {
|
||||||
Log::debug('Updating existing supplier');
|
Log::debug('Updating existing manufacturer');
|
||||||
$supplier->update($this->sanitizeItemForUpdating($supplier));
|
$manufacturer->update($this->sanitizeItemForUpdating($manufacturer));
|
||||||
} else {
|
} else {
|
||||||
Log::debug('Creating supplier');
|
Log::debug('Creating manufacturer');
|
||||||
$supplier->fill($this->sanitizeItemForStoring($supplier));
|
$manufacturer->fill($this->sanitizeItemForStoring($manufacturer));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($supplier->save()) {
|
if ($manufacturer->save()) {
|
||||||
$this->log('Manufacturer '.$supplier->name.' created or updated from CSV import');
|
$this->log('Manufacturer '.$manufacturer->name.' created or updated from CSV import');
|
||||||
return $supplier;
|
return $manufacturer;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log::debug($supplier->getErrors());
|
Log::debug($manufacturer->getErrors());
|
||||||
$this->logError($supplier, 'Manufacturer "'.$this->item['name'].'"');
|
$this->logError($manufacturer, 'Manufacturer "'.$this->item['name'].'"');
|
||||||
return $supplier->errors;
|
return $manufacturer->errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ class CheckoutableListener
|
|||||||
|
|
||||||
if (!empty($to)) {
|
if (!empty($to)) {
|
||||||
try {
|
try {
|
||||||
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
|
$toMail = (clone $mailable)->locale($notifiable->locale);
|
||||||
|
Mail::to(array_flatten($to))->send($toMail);
|
||||||
Log::info('Checkout Mail sent to checkout target');
|
Log::info('Checkout Mail sent to checkout target');
|
||||||
} catch (ClientException $e) {
|
} catch (ClientException $e) {
|
||||||
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
@@ -104,6 +105,16 @@ class CheckoutableListener
|
|||||||
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!empty($cc)) {
|
||||||
|
try {
|
||||||
|
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
|
||||||
|
Mail::to(array_flatten($cc))->send($ccMail);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($shouldSendWebhookNotification) {
|
if ($shouldSendWebhookNotification) {
|
||||||
@@ -178,15 +189,26 @@ class CheckoutableListener
|
|||||||
|
|
||||||
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
|
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
|
||||||
|
|
||||||
try {
|
if (!empty($to)) {
|
||||||
if (!empty($to)) {
|
try {
|
||||||
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
|
$toMail = (clone $mailable)->locale($notifiable->locale);
|
||||||
Log::info('Checkin Mail sent to CC addresses');
|
Mail::to(array_flatten($to))->send($toMail);
|
||||||
|
Log::info('Checkin Mail sent to checkin target');
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($cc)) {
|
||||||
|
try {
|
||||||
|
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
|
||||||
|
Mail::to(array_flatten($cc))->send($ccMail);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
} catch (ClientException $e) {
|
|
||||||
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +262,12 @@ class CheckoutableListener
|
|||||||
$acceptance->checkoutable()->associate($event->checkoutable);
|
$acceptance->checkoutable()->associate($event->checkoutable);
|
||||||
$acceptance->assignedTo()->associate($event->checkedOutTo);
|
$acceptance->assignedTo()->associate($event->checkedOutTo);
|
||||||
|
|
||||||
|
$acceptance->qty = 1;
|
||||||
|
|
||||||
|
if (isset($event->checkoutable->checkout_qty)) {
|
||||||
|
$acceptance->qty = $event->checkoutable->checkout_qty;
|
||||||
|
}
|
||||||
|
|
||||||
$category = $this->getCategoryFromCheckoutable($event->checkoutable);
|
$category = $this->getCategoryFromCheckoutable($event->checkoutable);
|
||||||
|
|
||||||
if ($category?->alert_on_response) {
|
if ($category?->alert_on_response) {
|
||||||
|
|||||||
@@ -403,6 +403,7 @@ class Importer extends Component
|
|||||||
|
|
||||||
|
|
||||||
$this->assetmodels_fields = [
|
$this->assetmodels_fields = [
|
||||||
|
'id' => trans('general.id'),
|
||||||
'category' => trans('general.category'),
|
'category' => trans('general.category'),
|
||||||
'eol' => trans('general.eol'),
|
'eol' => trans('general.eol'),
|
||||||
'fieldset' => trans('admin/models/general.fieldset'),
|
'fieldset' => trans('admin/models/general.fieldset'),
|
||||||
@@ -412,6 +413,7 @@ class Importer extends Component
|
|||||||
'model_number' => trans('general.model_no'),
|
'model_number' => trans('general.model_no'),
|
||||||
'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
|
'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
|
||||||
'requestable' => trans('admin/models/general.requestable'),
|
'requestable' => trans('admin/models/general.requestable'),
|
||||||
|
'require_serial' => trans('admin/hardware/general.require_serial'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -535,6 +537,10 @@ class Importer extends Component
|
|||||||
'product key',
|
'product key',
|
||||||
'key',
|
'key',
|
||||||
],
|
],
|
||||||
|
'require_serial' =>
|
||||||
|
[
|
||||||
|
'serial required',
|
||||||
|
],
|
||||||
'model_number' =>
|
'model_number' =>
|
||||||
[
|
[
|
||||||
'model',
|
'model',
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class CheckoutAccessoryMail extends Mailable
|
|||||||
|
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
from: $from,
|
from: $from,
|
||||||
subject: trans('mail.Accessory_Checkout_Notification'),
|
subject: trans_choice('mail.Accessory_Checkout_Notification', $this->checkout_qty),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,17 +83,19 @@ class CheckoutAccessoryMail extends Mailable
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function introductionLine(): string
|
private function introductionLine(): string
|
||||||
{
|
{
|
||||||
if ($this->target instanceof Location) {
|
if ($this->target instanceof Location) {
|
||||||
return trans('mail.new_item_checked_location', ['location' => $this->target->name ]);
|
return trans_choice('mail.new_item_checked_location', $this->checkout_qty, ['location' => $this->target->name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->requiresAcceptance()) {
|
if ($this->requiresAcceptance()) {
|
||||||
return trans('mail.new_item_checked_with_acceptance');
|
return trans_choice('mail.new_item_checked_with_acceptance', $this->checkout_qty);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->requiresAcceptance()) {
|
if (!$this->requiresAcceptance()) {
|
||||||
return trans('mail.new_item_checked');
|
return trans_choice('mail.new_item_checked', $this->checkout_qty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we shouldn't get here but let's send a default message just in case
|
// we shouldn't get here but let's send a default message just in case
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class CheckoutAssetMail extends Mailable
|
|||||||
$name = $this->target->assignedto?->display_name;
|
$name = $this->target->assignedto?->display_name;
|
||||||
}
|
}
|
||||||
else if($this->target instanceof Location){
|
else if($this->target instanceof Location){
|
||||||
$name = $this->target->manager->name;
|
$name = $this->target->manager?->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the item has custom fields associated with it
|
// Check if the item has custom fields associated with it
|
||||||
@@ -138,14 +138,15 @@ class CheckoutAssetMail extends Mailable
|
|||||||
private function introductionLine(): string
|
private function introductionLine(): string
|
||||||
{
|
{
|
||||||
if ($this->firstTimeSending && $this->target instanceof Location) {
|
if ($this->firstTimeSending && $this->target instanceof Location) {
|
||||||
return trans('mail.new_item_checked_location', ['location' => $this->target->name ]);
|
return trans_choice('mail.new_item_checked_location', 1, ['location' => $this->target->name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->firstTimeSending && $this->requiresAcceptance()) {
|
if ($this->firstTimeSending && $this->requiresAcceptance()) {
|
||||||
return trans('mail.new_item_checked_with_acceptance');
|
return trans_choice('mail.new_item_checked_with_acceptance', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->firstTimeSending && !$this->requiresAcceptance()) {
|
if ($this->firstTimeSending && !$this->requiresAcceptance()) {
|
||||||
return trans('mail.new_item_checked');
|
return trans_choice('mail.new_item_checked', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->firstTimeSending && $this->requiresAcceptance()) {
|
if (!$this->firstTimeSending && $this->requiresAcceptance()) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class CheckoutComponentMail extends Mailable
|
|||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
$this->target = $checkedOutTo;
|
$this->target = $checkedOutTo;
|
||||||
$this->acceptance = $acceptance;
|
$this->acceptance = $acceptance;
|
||||||
$this->qty = $component->assets->first()?->pivot?->assigned_qty;
|
$this->qty = $component->checkout_qty;
|
||||||
|
|
||||||
$this->settings = Setting::getSettings();
|
$this->settings = Setting::getSettings();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ class SendUpcomingAuditMail extends Mailable
|
|||||||
/**
|
/**
|
||||||
* Create a new message instance.
|
* Create a new message instance.
|
||||||
*/
|
*/
|
||||||
public function __construct($params, $threshold)
|
public function __construct($params, $threshold, $total)
|
||||||
{
|
{
|
||||||
$this->assets = $params;
|
$this->assets = $params;
|
||||||
$this->threshold = $threshold;
|
$this->threshold = $threshold;
|
||||||
|
$this->total = $total;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +33,7 @@ class SendUpcomingAuditMail extends Mailable
|
|||||||
|
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
from: $from,
|
from: $from,
|
||||||
subject: trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]),
|
subject: trans_choice('mail.upcoming-audits', $this->total, ['count' => $this->total, 'threshold' => $this->threshold]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ class SendUpcomingAuditMail extends Mailable
|
|||||||
with: [
|
with: [
|
||||||
'assets' => $this->assets,
|
'assets' => $this->assets,
|
||||||
'threshold' => $this->threshold,
|
'threshold' => $this->threshold,
|
||||||
|
'total' => $this->total,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user