Compare commits
773 Commits
experiment
...
use-error-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f4548e112 | ||
|
|
12fa505972 | ||
|
|
4e4930ba62 | ||
|
|
76301bc30d | ||
|
|
964e105cf9 | ||
|
|
6fd24c7e14 | ||
|
|
231bc1e2de | ||
|
|
37d04b7176 | ||
|
|
e62a802926 | ||
|
|
69e981364a | ||
|
|
abb4221539 | ||
|
|
e4ebabdaba | ||
|
|
da1e383295 | ||
|
|
b64ed254e0 | ||
|
|
ba291edc42 | ||
|
|
ec2ea955d8 | ||
|
|
c197644ba7 | ||
|
|
29b30cc5d3 | ||
|
|
6af27516dc | ||
|
|
a89f17a145 | ||
|
|
de0565f5b3 | ||
|
|
68c708bdef | ||
|
|
5581950fee | ||
|
|
58e366a063 | ||
|
|
b06c527767 | ||
|
|
26f28a862a | ||
|
|
94c981e22c | ||
|
|
e8da7e2df2 | ||
|
|
d635c86e00 | ||
|
|
406ff6984b | ||
|
|
c6ddc501c5 | ||
|
|
4839181beb | ||
|
|
63a05c89a7 | ||
|
|
385c4f69f7 | ||
|
|
486cd8c8c9 | ||
|
|
eb5d93b3c2 | ||
|
|
a5ff623484 | ||
|
|
b5a4389815 | ||
|
|
7b5b559baa | ||
|
|
67a9929745 | ||
|
|
64c4433b98 | ||
|
|
22bc088f6f | ||
|
|
d3e8e06638 | ||
|
|
1b933f7add | ||
|
|
3fe891a05b | ||
|
|
f4c5b712f4 | ||
|
|
4c8dc7818d | ||
|
|
5c43a1f87c | ||
|
|
d3b265de8e | ||
|
|
263151658f | ||
|
|
aa86e07cd2 | ||
|
|
d92fa5de65 | ||
|
|
c589140ea0 | ||
|
|
52894615ce | ||
|
|
8546bbdd65 | ||
|
|
cc2c8f76d0 | ||
|
|
1ffa69c43c | ||
|
|
f85ebd7ffd | ||
|
|
78d355f136 | ||
|
|
ec0346e4a8 | ||
|
|
fc5eb37776 | ||
|
|
1d7853cbfe | ||
|
|
af0a95be12 | ||
|
|
d67975cb62 | ||
|
|
663b2fd844 | ||
|
|
bcace9d019 | ||
|
|
b59bf3e7dc | ||
|
|
3957d670d0 | ||
|
|
833dace2b4 | ||
|
|
56e31d2303 | ||
|
|
ec365b0804 | ||
|
|
aef0ac68c3 | ||
|
|
f12f9a816f | ||
|
|
a000d6454f | ||
|
|
ca8864c061 | ||
|
|
423f4f9126 | ||
|
|
456c7d8d91 | ||
|
|
54cfe3f6e6 | ||
|
|
5e0b18104d | ||
|
|
8b5d3f7fbd | ||
|
|
744f43676d | ||
|
|
ec0b9b198f | ||
|
|
94300d81d6 | ||
|
|
bc9ea5a2ec | ||
|
|
4635a6efc3 | ||
|
|
9608414eae | ||
|
|
5efddf6f5b | ||
|
|
305dc049a4 | ||
|
|
3ac0702094 | ||
|
|
47d8e2f8b9 | ||
|
|
83dd9ce20e | ||
|
|
a8eb76fd8d | ||
|
|
cd7db5c4a8 | ||
|
|
da7313bc9d | ||
|
|
4ec361c718 | ||
|
|
738ef442fd | ||
|
|
d29b3bfb9a | ||
|
|
6fdce3c536 | ||
|
|
912bbf0e32 | ||
|
|
01c4fe6113 | ||
|
|
0fa9f57971 | ||
|
|
1ab29ec3a4 | ||
|
|
7e475a0786 | ||
|
|
26b3c62ab8 | ||
|
|
d0acf5b8a6 | ||
|
|
74fbc23823 | ||
|
|
a23dee52f2 | ||
|
|
4d03f1e110 | ||
|
|
963911f2e1 | ||
|
|
9d484077ae | ||
|
|
9858b0f37f | ||
|
|
09033b19a7 | ||
|
|
5fdeb9c413 | ||
|
|
22d3734075 | ||
|
|
eca6b03f44 | ||
|
|
bbdbec7197 | ||
|
|
6c450d1338 | ||
|
|
a8cd1027f3 | ||
|
|
d99b306ae9 | ||
|
|
74136761df | ||
|
|
f597d64339 | ||
|
|
0072f1500e | ||
|
|
64bed01a91 | ||
|
|
f6319e11e7 | ||
|
|
5d9f988df3 | ||
|
|
f8c72fb0ac | ||
|
|
886514a25f | ||
|
|
fa765667f2 | ||
|
|
434bdcd6d4 | ||
|
|
be5f3f38f8 | ||
|
|
08c3a25b39 | ||
|
|
34cd4b6244 | ||
|
|
d89c8682be | ||
|
|
6f024849e9 | ||
|
|
e048f0955f | ||
|
|
479b2b4fd3 | ||
|
|
84f14a05bd | ||
|
|
8d52fa51b1 | ||
|
|
229d8b9bf5 | ||
|
|
eb8d43a804 | ||
|
|
f82266fade | ||
|
|
b4b6f7a35f | ||
|
|
0bc995b87f | ||
|
|
59725f2031 | ||
|
|
00bc9ac806 | ||
|
|
f200960a57 | ||
|
|
c700127f1a | ||
|
|
ae2f9571b4 | ||
|
|
a77dcad336 | ||
|
|
7ace9324b4 | ||
|
|
d2e889e927 | ||
|
|
803bdb457c | ||
|
|
7c9b1f6e38 | ||
|
|
d545537a43 | ||
|
|
4d8904938d | ||
|
|
0c09f2b2c0 | ||
|
|
bffba02511 | ||
|
|
901f4df7ee | ||
|
|
9337cba340 | ||
|
|
2b0c67c263 | ||
|
|
da71f031f5 | ||
|
|
d2e585baa7 | ||
|
|
a102c085df | ||
|
|
cb40a82e79 | ||
|
|
4253acad4c | ||
|
|
7e6ff3cbe6 | ||
|
|
e7ef3bf515 | ||
|
|
a25efe53aa | ||
|
|
de059a715a | ||
|
|
318aff1ef0 | ||
|
|
e96b9b2f4f | ||
|
|
3ae4a5caf0 | ||
|
|
559f0d2f90 | ||
|
|
9b6a36c8aa | ||
|
|
1a4aebf805 | ||
|
|
6ad7100aa3 | ||
|
|
5529669884 | ||
|
|
b57283d8d1 | ||
|
|
7658f7c41d | ||
|
|
70e5e0f9df | ||
|
|
dec4691c73 | ||
|
|
48903b1402 | ||
|
|
7376c21f10 | ||
|
|
f921400579 | ||
|
|
ad6d09b6ad | ||
|
|
43b338d612 | ||
|
|
67655ad84b | ||
|
|
ea2b1b0748 | ||
|
|
856c57cb12 | ||
|
|
90584ab803 | ||
|
|
184f54a6cd | ||
|
|
f24031b974 | ||
|
|
0a7aaa54b6 | ||
|
|
3167ee91d1 | ||
|
|
e8f1190628 | ||
|
|
44f18d210e | ||
|
|
e8deecc9b4 | ||
|
|
a79fe69bf4 | ||
|
|
d49c33fbe3 | ||
|
|
de29c1d8a0 | ||
|
|
f9172594b2 | ||
|
|
94d8a547b8 | ||
|
|
b39a7c6f0c | ||
|
|
a3d847151a | ||
|
|
594f506641 | ||
|
|
dd458dfa7f | ||
|
|
b917489f00 | ||
|
|
75dfedcb82 | ||
|
|
6216b4fc0d | ||
|
|
4356cb7b9b | ||
|
|
d8df52cc8f | ||
|
|
10dc1f1368 | ||
|
|
8f7bce7aad | ||
|
|
4d66f7c93f | ||
|
|
381003eeab | ||
|
|
c3d4c821f3 | ||
|
|
d8038ac483 | ||
|
|
d268f25f16 | ||
|
|
01c69c8f8f | ||
|
|
9622e05cf5 | ||
|
|
afaf53cdfc | ||
|
|
f031309f8f | ||
|
|
20ec420ba3 | ||
|
|
a70b94e707 | ||
|
|
0d7cf55a3a | ||
|
|
008bf036b5 | ||
|
|
cc5ad456e6 | ||
|
|
3e29457094 | ||
|
|
0b3ac2a9cd | ||
|
|
0f1e5181a6 | ||
|
|
e544007185 | ||
|
|
4b690e8e1f | ||
|
|
371afde52d | ||
|
|
7a6a2e3d92 | ||
|
|
3f8ac59f4c | ||
|
|
74e7b1cfa5 | ||
|
|
ae0a6d66f3 | ||
|
|
321839e074 | ||
|
|
120cfd13c5 | ||
|
|
09f2739298 | ||
|
|
e9fc455a30 | ||
|
|
8af4126de1 | ||
|
|
5d1c48c071 | ||
|
|
3044af9410 | ||
|
|
4f0f0af83b | ||
|
|
198afee946 | ||
|
|
def0af652d | ||
|
|
20e4ba0590 | ||
|
|
72fd9977e5 | ||
|
|
10f35c682b | ||
|
|
6e84c29ce4 | ||
|
|
389a06aac5 | ||
|
|
4b96721393 | ||
|
|
e42ee0c0e1 | ||
|
|
277a0fd16a | ||
|
|
c163d6774f | ||
|
|
9e73eaf955 | ||
|
|
1b2094f4e2 | ||
|
|
ec863df007 | ||
|
|
cc3b8e0681 | ||
|
|
ff145abbe7 | ||
|
|
e6106aa7cf | ||
|
|
b45a8f4b5f | ||
|
|
c9f8a84d48 | ||
|
|
ac1543d568 | ||
|
|
9d354ca657 | ||
|
|
3f2139349e | ||
|
|
f56dd582f9 | ||
|
|
225ab47b8e | ||
|
|
4c350bd5a3 | ||
|
|
e7fb29fced | ||
|
|
a2d11b43de | ||
|
|
f80cf666b1 | ||
|
|
ce5be8ac24 | ||
|
|
1777bb4b93 | ||
|
|
5c6c655cdc | ||
|
|
60eb602156 | ||
|
|
76b114d820 | ||
|
|
ce33deed86 | ||
|
|
1e6c172f18 | ||
|
|
a1ce9b9285 | ||
|
|
6ec3693030 | ||
|
|
71729f0b16 | ||
|
|
1c229a8e08 | ||
|
|
07b1e3fb90 | ||
|
|
6294516271 | ||
|
|
f3331b5045 | ||
|
|
6a06a91fa2 | ||
|
|
0cba5ba67e | ||
|
|
b815352a45 | ||
|
|
2f299dbb20 | ||
|
|
f76fbe75e0 | ||
|
|
cc1e356c35 | ||
|
|
8094f3c0e1 | ||
|
|
900beaa955 | ||
|
|
6521c02526 | ||
|
|
2412333152 | ||
|
|
4a2d2f7336 | ||
|
|
d03baa4613 | ||
|
|
41437e4d8f | ||
|
|
7d220dd8c2 | ||
|
|
a99ec062e6 | ||
|
|
8fe6395287 | ||
|
|
b4d51599ee | ||
|
|
9350a20189 | ||
|
|
889d5da71e | ||
|
|
5546ab08c2 | ||
|
|
ef183f256c | ||
|
|
3a96e501cc | ||
|
|
6b1784f2ee | ||
|
|
1a872b1643 | ||
|
|
deef9ad181 | ||
|
|
0dc7e8b8dc | ||
|
|
a5f075548a | ||
|
|
1706ddd511 | ||
|
|
a220986d16 | ||
|
|
2a8ac81eed | ||
|
|
d3cb3c03d2 | ||
|
|
c60bc5cdbe | ||
|
|
6cfbf835c7 | ||
|
|
047b77e038 | ||
|
|
766b370264 | ||
|
|
da33277234 | ||
|
|
3e832e5e94 | ||
|
|
82e795b642 | ||
|
|
95516b0343 | ||
|
|
83fb6826ee | ||
|
|
b1e92ab866 | ||
|
|
96241cb67c | ||
|
|
b680dab5c9 | ||
|
|
984840dc82 | ||
|
|
84e447af09 | ||
|
|
c7ddabcc8b | ||
|
|
bee80fcf8a | ||
|
|
0aff35b622 | ||
|
|
46a6a84ecb | ||
|
|
374812f8fe | ||
|
|
94e00b8a3e | ||
|
|
f2b78d18a4 | ||
|
|
17eccfcd8b | ||
|
|
01e4382d20 | ||
|
|
1c664af326 | ||
|
|
392d34422a | ||
|
|
35e7a3163c | ||
|
|
96fafa6952 | ||
|
|
a55693211f | ||
|
|
1acd24fdbe | ||
|
|
59b7d5db4b | ||
|
|
16aa47509b | ||
|
|
d1bb3ef6bf | ||
|
|
5cd9dd4a67 | ||
|
|
fc405d9d73 | ||
|
|
5786ff5035 | ||
|
|
8a206a6d92 | ||
|
|
67b3ab820f | ||
|
|
b06501dd02 | ||
|
|
e3049fffd4 | ||
|
|
5ecdb7e07c | ||
|
|
4ed3347f52 | ||
|
|
364775dcfe | ||
|
|
bfebcdc7ed | ||
|
|
480e4f3a69 | ||
|
|
78a0417ee9 | ||
|
|
b83d148b37 | ||
|
|
ad794248fe | ||
|
|
b804791ff6 | ||
|
|
c45bf870b7 | ||
|
|
a0bd8b6049 | ||
|
|
ae4f278df1 | ||
|
|
bc0ff706b0 | ||
|
|
28abb8d8cc | ||
|
|
24d948a3f6 | ||
|
|
c1c9c3554e | ||
|
|
fcea564afa | ||
|
|
f44abd0b28 | ||
|
|
854903805b | ||
|
|
868f117b67 | ||
|
|
b6cac4baae | ||
|
|
4fcb3df1d9 | ||
|
|
b60e22bcb4 | ||
|
|
61312c2eec | ||
|
|
b0063b1d4a | ||
|
|
fffcbdc44d | ||
|
|
8d1fa362f7 | ||
|
|
31a2765b30 | ||
|
|
e79a5b7efe | ||
|
|
ca531e85f3 | ||
|
|
0941c0944a | ||
|
|
9b80843c77 | ||
|
|
8684a3efc3 | ||
|
|
fccfce2ae8 | ||
|
|
935d2ce29a | ||
|
|
437ddc01b4 | ||
|
|
f19e58b352 | ||
|
|
eb6f330e67 | ||
|
|
f19899543d | ||
|
|
3a2611f8e1 | ||
|
|
53ad312700 | ||
|
|
89d375daad | ||
|
|
d46f9776fe | ||
|
|
f9a47c8a9f | ||
|
|
e395ee1878 | ||
|
|
4971c54b05 | ||
|
|
4c5b82ae37 | ||
|
|
fd7082c30f | ||
|
|
8d0b72293f | ||
|
|
f3d98f90c0 | ||
|
|
4eccb5ffc6 | ||
|
|
86049624c7 | ||
|
|
3972782033 | ||
|
|
33b86057d1 | ||
|
|
3c3b922eae | ||
|
|
e8d0147075 | ||
|
|
c2bcc2e2d9 | ||
|
|
55f9886412 | ||
|
|
a77ece01a6 | ||
|
|
1c1101aeac | ||
|
|
de04ead55d | ||
|
|
bc18fac97e | ||
|
|
3a0b03348e | ||
|
|
b18baf74d2 | ||
|
|
28c7355697 | ||
|
|
0dd0baa511 | ||
|
|
c3a296c19f | ||
|
|
f6cc923e01 | ||
|
|
aab29fbb6b | ||
|
|
d9d3ff27ae | ||
|
|
614d05acb5 | ||
|
|
e4a82edd3f | ||
|
|
eceaa72781 | ||
|
|
ddf45c5ee9 | ||
|
|
ff100c0b9f | ||
|
|
037cc4d098 | ||
|
|
ff6e6ef88c | ||
|
|
e8ec11652f | ||
|
|
cd7f276c40 | ||
|
|
fc8bb82a02 | ||
|
|
5d7f1f77a3 | ||
|
|
5dea3f4495 | ||
|
|
c6e709cd36 | ||
|
|
daf5a80081 | ||
|
|
759ab78f80 | ||
|
|
b6d9f736e3 | ||
|
|
243ab51def | ||
|
|
c5603a0e10 | ||
|
|
57f6ecb1da | ||
|
|
64082ada1e | ||
|
|
068535a80c | ||
|
|
08f4fe5f35 | ||
|
|
018b5684fc | ||
|
|
73a80a5fbc | ||
|
|
4c8bf9ae19 | ||
|
|
f77d300549 | ||
|
|
62655be2d0 | ||
|
|
eb938bdba3 | ||
|
|
129d3b35fb | ||
|
|
84df23e1f6 | ||
|
|
a439d8abe8 | ||
|
|
24b7659c23 | ||
|
|
44dbbeb608 | ||
|
|
7161b6416e | ||
|
|
1331bffaca | ||
|
|
9b422e5c97 | ||
|
|
0c86855392 | ||
|
|
962ae5231d | ||
|
|
590d13061a | ||
|
|
c3a2cdeee9 | ||
|
|
ef77fb91c0 | ||
|
|
02bd8d7ea1 | ||
|
|
da4ec145d7 | ||
|
|
ef145e47b4 | ||
|
|
adf58a06da | ||
|
|
63f0b5279d | ||
|
|
c96ccbb6cb | ||
|
|
950fff31ed | ||
|
|
f05d8281f3 | ||
|
|
d5c9fa823e | ||
|
|
99741db645 | ||
|
|
90d5a6d8ab | ||
|
|
5b8529bc83 | ||
|
|
f76c68f0ed | ||
|
|
8368fb5c41 | ||
|
|
d1c39a737f | ||
|
|
425bfa4318 | ||
|
|
293abbd1d0 | ||
|
|
b64b774bd5 | ||
|
|
466ab1e3c0 | ||
|
|
daa9e7810e | ||
|
|
95a0f3dbe5 | ||
|
|
bb465dbfaa | ||
|
|
e5ab12496f | ||
|
|
4b906e39be | ||
|
|
0dc8f27240 | ||
|
|
8cbcc237c0 | ||
|
|
c4135c71d4 | ||
|
|
172da4d655 | ||
|
|
6f8ccd4e10 | ||
|
|
8d36a75342 | ||
|
|
9457e5b890 | ||
|
|
bce7a278ae | ||
|
|
97e3af8fc9 | ||
|
|
914e29210a | ||
|
|
a6041ae525 | ||
|
|
ab1e097b7c | ||
|
|
a781f84aea | ||
|
|
90a2a808d6 | ||
|
|
73e2d8e305 | ||
|
|
0e1e13f54a | ||
|
|
869c06f454 | ||
|
|
b61ab423ca | ||
|
|
090a595c99 | ||
|
|
bc417308c7 | ||
|
|
e77e16b9aa | ||
|
|
0ae297634e | ||
|
|
0f2f559d7a | ||
|
|
57b5b12952 | ||
|
|
9baaaae1a3 | ||
|
|
1f4118a146 | ||
|
|
f4a3823d88 | ||
|
|
b2a69efc9d | ||
|
|
00ebc8b64d | ||
|
|
effd273245 | ||
|
|
7858c72b98 | ||
|
|
09e2c0beab | ||
|
|
e8864ffb01 | ||
|
|
d893de2b7e | ||
|
|
7f7cfef81b | ||
|
|
d01972bbe5 | ||
|
|
b9fdb5880a | ||
|
|
1139ed676a | ||
|
|
64be353156 | ||
|
|
750015684d | ||
|
|
992d16664d | ||
|
|
900c19b76d | ||
|
|
effd2bce24 | ||
|
|
de8816d837 | ||
|
|
593554daed | ||
|
|
fecd877e8b | ||
|
|
ba6d8ae8c7 | ||
|
|
175862d3c9 | ||
|
|
5eea08088d | ||
|
|
c752c2a125 | ||
|
|
4ac4f9b0a9 | ||
|
|
09abcb44bb | ||
|
|
7f566b9152 | ||
|
|
30835fe9ba | ||
|
|
910e98b573 | ||
|
|
4623e40077 | ||
|
|
20868b9ede | ||
|
|
efc84efabf | ||
|
|
7c7fa96334 | ||
|
|
e08acb851c | ||
|
|
bbad84c6cc | ||
|
|
f2acb98afa | ||
|
|
38affea3f3 | ||
|
|
cea31c5b11 | ||
|
|
254e2f120b | ||
|
|
2bfee0c29a | ||
|
|
c08bd8f88b | ||
|
|
9818c2f033 | ||
|
|
f63fd25ce0 | ||
|
|
57ecdd6765 | ||
|
|
9d890b35c6 | ||
|
|
8fc5c0b5be | ||
|
|
c4e7448d31 | ||
|
|
ffd2687734 | ||
|
|
6f3eedf5e6 | ||
|
|
241eb7b031 | ||
|
|
ca399794c3 | ||
|
|
736d4cc59a | ||
|
|
ad85f8be2f | ||
|
|
be17ef4047 | ||
|
|
c6c006f143 | ||
|
|
10856516ac | ||
|
|
beac4c8b8a | ||
|
|
b39b39bc8a | ||
|
|
e373be7dde | ||
|
|
ca7c0aa47c | ||
|
|
48a8cf6b70 | ||
|
|
7ba1646703 | ||
|
|
daa550cc82 | ||
|
|
e9f9d3c259 | ||
|
|
63377853b8 | ||
|
|
fc86eefeac | ||
|
|
a98ad76c6a | ||
|
|
32c768792a | ||
|
|
793cf27318 | ||
|
|
ba568b7975 | ||
|
|
4b5bd76225 | ||
|
|
a54403ef01 | ||
|
|
13262c5125 | ||
|
|
9ea38fbb3a | ||
|
|
94619e3284 | ||
|
|
8b05ef6db4 | ||
|
|
8e03083b83 | ||
|
|
dc2debcd83 | ||
|
|
b7ab8b9e8f | ||
|
|
c50ab1af67 | ||
|
|
7773d334ba | ||
|
|
2a0697022e | ||
|
|
8d1cc22c58 | ||
|
|
e1eb963962 | ||
|
|
87c2dc5bb5 | ||
|
|
06efcd3c46 | ||
|
|
faa791e94c | ||
|
|
e328dec9f9 | ||
|
|
8e60a7b22b | ||
|
|
958b6035e1 | ||
|
|
94e0739a74 | ||
|
|
4465aef991 | ||
|
|
822bc6f085 | ||
|
|
19bd99d159 | ||
|
|
5c17fefb08 | ||
|
|
f984b45de2 | ||
|
|
d56252c6b3 | ||
|
|
44b950cb8e | ||
|
|
985714d504 | ||
|
|
97ead7120e | ||
|
|
670021a482 | ||
|
|
9858cc5baf | ||
|
|
fa5b59cf21 | ||
|
|
0ef58a9aef | ||
|
|
0c4e498df3 | ||
|
|
b5b60f22d5 | ||
|
|
2f0c74aef0 | ||
|
|
f56006fb6b | ||
|
|
7685de45f2 | ||
|
|
199e68ff29 | ||
|
|
81bffccf01 | ||
|
|
79a13e3618 | ||
|
|
d9b7df5b85 | ||
|
|
0c933bcc5d | ||
|
|
4c4b0f722a | ||
|
|
6c3cafa72f | ||
|
|
16ae23dbeb | ||
|
|
4f3064bdb1 | ||
|
|
2b0627c1f6 | ||
|
|
cfca1514c0 | ||
|
|
345a4306e8 | ||
|
|
beb0836d69 | ||
|
|
b4ed01243b | ||
|
|
3772a21a51 | ||
|
|
05e278d08b | ||
|
|
c21821a864 | ||
|
|
ba13b9924b | ||
|
|
04b6cb763f | ||
|
|
2f8306cba8 | ||
|
|
baa7e7d561 | ||
|
|
ffaacc04ef | ||
|
|
dd32341502 | ||
|
|
f58e3114a2 | ||
|
|
eba494ad8c | ||
|
|
7e89b58746 | ||
|
|
b7744105a0 | ||
|
|
d5e1dc54c6 | ||
|
|
69263f0e5b | ||
|
|
a3b2912e89 | ||
|
|
7f412ec3f5 | ||
|
|
94e881e5f0 | ||
|
|
5bb47e290f | ||
|
|
d53a032402 | ||
|
|
70d95877d8 | ||
|
|
48821f8391 | ||
|
|
bb00edda4e | ||
|
|
aaa2858337 | ||
|
|
017530ba4b | ||
|
|
4895023f3b | ||
|
|
0ed637f0ce | ||
|
|
45bf428092 | ||
|
|
e6f2c457a8 | ||
|
|
04f723327e | ||
|
|
4a9bd95ed3 | ||
|
|
78bd950daa | ||
|
|
d3070a0be1 | ||
|
|
0a70bcc0a9 | ||
|
|
c8dad528a8 | ||
|
|
256e989ba1 | ||
|
|
39810f9ba5 | ||
|
|
9793016603 | ||
|
|
9e06f2d17f | ||
|
|
7464c3622e | ||
|
|
d4d19569ee | ||
|
|
1a541ce220 | ||
|
|
43c7504f89 | ||
|
|
c1937b6ea8 | ||
|
|
7584c30722 | ||
|
|
62f4b75156 | ||
|
|
1fa6a763bc | ||
|
|
726308bfd5 | ||
|
|
22ddb695f2 | ||
|
|
09b2feac54 | ||
|
|
57e1df86c8 | ||
|
|
3d5b1b6c6d | ||
|
|
5f4ee39f2b | ||
|
|
45d7e2dc42 | ||
|
|
18f7ca860d | ||
|
|
ea881ff95e | ||
|
|
c1916d6fe4 | ||
|
|
da9932d147 | ||
|
|
da42127a7c | ||
|
|
090410edb5 | ||
|
|
43d4bc8dc4 | ||
|
|
ebcc48ec80 | ||
|
|
5ca62f3ef8 | ||
|
|
3ae16ff8ca | ||
|
|
2e2f59b290 | ||
|
|
c16db436d3 | ||
|
|
8eb68d3901 | ||
|
|
fc7809192d | ||
|
|
e79111fed5 | ||
|
|
423b48279b | ||
|
|
6f29c0a7cf | ||
|
|
27bc16604b | ||
|
|
86ae4a3513 | ||
|
|
0b60cbc531 | ||
|
|
e502968707 | ||
|
|
12dcac4994 | ||
|
|
1837da4508 | ||
|
|
e1a6b441d7 | ||
|
|
23c1d664fe | ||
|
|
c891342fdb | ||
|
|
8ce577db37 | ||
|
|
cdb1140f10 | ||
|
|
5fa0c87ab0 | ||
|
|
75aa01791a | ||
|
|
97a6152ea9 | ||
|
|
03f091a77f | ||
|
|
8696a423b0 | ||
|
|
107f8db9bc | ||
|
|
b11c900a4c | ||
|
|
53ccd196d7 | ||
|
|
99d7155729 | ||
|
|
f30439a544 | ||
|
|
997eddfed1 | ||
|
|
52340aca78 | ||
|
|
e1fb446888 | ||
|
|
83443ad2b5 | ||
|
|
3fcc067481 | ||
|
|
3e564eaf19 | ||
|
|
0a90df2b14 | ||
|
|
4ab75c1c03 | ||
|
|
bf10fd0cf0 | ||
|
|
83ce04dc8d | ||
|
|
f7bbec6be4 | ||
|
|
cec84b857b | ||
|
|
1d4a7a7b02 | ||
|
|
701411c1b9 | ||
|
|
013463aafc | ||
|
|
39c15b2868 | ||
|
|
60ca634eff | ||
|
|
be282dd038 | ||
|
|
8cc1397ace | ||
|
|
e7b9903341 | ||
|
|
e3e01e07b1 | ||
|
|
d18aa1db98 | ||
|
|
c155e4a7c9 | ||
|
|
fdf0be09db | ||
|
|
e1addc5aef | ||
|
|
b4b4927370 | ||
|
|
0ffceb9691 | ||
|
|
1e810d2426 | ||
|
|
c0110e7f29 | ||
|
|
86ab880c90 | ||
|
|
f6ab0f8f46 | ||
|
|
f01b205486 | ||
|
|
8962ced038 | ||
|
|
04d7884af8 | ||
|
|
6732b6601e | ||
|
|
eb8f1dd553 | ||
|
|
c8341d9dc4 | ||
|
|
b239b3a4db | ||
|
|
eac01868ca | ||
|
|
c025e25839 |
@@ -3154,6 +3154,69 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "r-xyz",
|
||||
"name": "r-xyz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/100710244?v=4",
|
||||
"profile": "https://github.com/r-xyz",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "DrekiDegga",
|
||||
"name": "Steven Mainor",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/47491036?v=4",
|
||||
"profile": "https://github.com/DrekiDegga",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "arne-kroeger",
|
||||
"name": "arne-kroeger",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/65785975?v=4",
|
||||
"profile": "https://github.com/arne-kroeger",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Glukose1",
|
||||
"name": "Glukose1",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/167117705?v=4",
|
||||
"profile": "https://github.com/Glukose1",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Scarzy",
|
||||
"name": "Scarzy",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1197791?v=4",
|
||||
"profile": "https://github.com/Scarzy",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "setpill",
|
||||
"name": "setpill",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37372069?v=4",
|
||||
"profile": "https://github.com/setpill",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "swift2512",
|
||||
"name": "swift2512",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
|
||||
"profile": "https://github.com/swift2512",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DB SETUP
|
||||
# --------------------------------------------
|
||||
# https://mariadb.com/kb/en/mariadb-server-docker-official-image-environment-variables/
|
||||
|
||||
MYSQL_DATABASE=snipeit
|
||||
MYSQL_USER=snipeit
|
||||
MYSQL_PASSWORD=changeme1234
|
||||
|
||||
@@ -14,7 +14,7 @@ APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
|
||||
APP_URL=http://localhost:8000
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier
|
||||
APP_TIMEZONE='UTC'
|
||||
APP_LOCALE=en
|
||||
APP_LOCALE=en-US
|
||||
MAX_RESULTS=500
|
||||
|
||||
# --------------------------------------------
|
||||
|
||||
@@ -6,7 +6,7 @@ APP_DEBUG=false
|
||||
APP_KEY=base64:hTUIUh9CP6dQx+6EjSlfWTgbaMaaRvlpEwk45vp+xmk=
|
||||
APP_URL=http://127.0.0.1:8000
|
||||
APP_TIMEZONE='US/Eastern'
|
||||
APP_LOCALE=en
|
||||
APP_LOCALE=en-US
|
||||
APP_LOCKED=false
|
||||
MAX_RESULTS=200
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ DB_PREFIX=null
|
||||
DB_DUMP_PATH='/usr/bin'
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
DB_SANITIZE_BY_DEFAULT=false
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SSL DATABASE SETTINGS
|
||||
@@ -87,6 +89,7 @@ SESSION_LIFETIME=12000
|
||||
EXPIRE_ON_CLOSE=false
|
||||
ENCRYPT=false
|
||||
COOKIE_NAME=snipeit_session
|
||||
PASSPORT_COOKIE_NAME='snipeit_passport_token'
|
||||
COOKIE_DOMAIN=null
|
||||
SECURE_COOKIES=false
|
||||
API_TOKEN_EXPIRATION_YEARS=15
|
||||
|
||||
43
.github/stale.yml
vendored
43
.github/stale.yml
vendored
@@ -1,43 +0,0 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- :woman_technologist: ready for dev
|
||||
- :moneybag: bounty
|
||||
- :hand: bug
|
||||
- "🔐 security"
|
||||
- "👩💻 ready for dev"
|
||||
- "💰 bounty"
|
||||
- "✋ bug"
|
||||
|
||||
exemptMilestones: true
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
only: issues
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
unmarkComment: >
|
||||
Okay, it looks like this issue or feature request might still be important. We'll re-open
|
||||
it for now. Thank you for letting us know!
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
Is this still relevant? We haven't heard from anyone in a bit. If so,
|
||||
please comment with any updates or additional detail.
|
||||
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Don't
|
||||
take it personally, we just need to keep a handle on things. Thank you
|
||||
for your contributions!
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because it has not had
|
||||
recent activity. If you believe this is still an issue, please confirm that
|
||||
this issue is still happening in the most recent version of Snipe-IT and reply
|
||||
to this thread to re-open it.
|
||||
39
.github/workflows/stale.yml
vendored
Normal file
39
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: 'Close stale issues'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# contents: write # only for delete-branch option
|
||||
issues: write
|
||||
# pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
debug-only: true
|
||||
operations-per-run: 100 # just while we're debugging
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 60
|
||||
days-before-close: 7
|
||||
exempt-all-milestones: true
|
||||
stale-issue-message: >
|
||||
Is this still relevant? We haven't heard from anyone in a bit. If so,
|
||||
please comment with any updates or additional detail.
|
||||
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Don't
|
||||
take it personally, we just need to keep a handle on things. Thank you
|
||||
for your contributions!
|
||||
close-issue-message: >
|
||||
This issue has been automatically closed because it has not had
|
||||
recent activity. If you believe this is still an issue, please confirm that
|
||||
this issue is still happening in the most recent version of Snipe-IT and reply
|
||||
to this thread to re-open it.
|
||||
# There doesn't seem to be a 'reopen issue message'?
|
||||
# Since there is no 'stale-pr-message' - PR's should not be stale'd
|
||||
stale-issue-label: stale
|
||||
exempt-issue-labels: >
|
||||
pinned,security,:woman_technologist: ready for dev,:moneybag: bounty,:hand: bug,🔐 security,👩💻 ready for dev,💰 bounty,✋ bug
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -67,3 +67,6 @@ _ide_helper_models.php
|
||||
/.phplint-cache
|
||||
storage/ldap_client_tls.cert
|
||||
storage/ldap_client_tls.key
|
||||
/storage/framework/testing
|
||||
|
||||
/.phpunit.cache
|
||||
@@ -51,7 +51,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "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/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/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/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") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
FROM alpine:3.18.6
|
||||
FROM alpine:3.19
|
||||
# Apache + PHP
|
||||
RUN apk add --no-cache \
|
||||
apache2 \
|
||||
php81 \
|
||||
php81-common \
|
||||
php81-apache2 \
|
||||
php81-curl \
|
||||
php81-ldap \
|
||||
php81-mysqli \
|
||||
php81-gd \
|
||||
php81-xml \
|
||||
php81-mbstring \
|
||||
php81-zip \
|
||||
php81-ctype \
|
||||
php81-tokenizer \
|
||||
php81-pdo_mysql \
|
||||
php81-openssl \
|
||||
php81-bcmath \
|
||||
php81-phar \
|
||||
php81-json \
|
||||
php81-iconv \
|
||||
php81-fileinfo \
|
||||
php81-simplexml \
|
||||
php81-session \
|
||||
php81-dom \
|
||||
php81-xmlwriter \
|
||||
php81-xmlreader \
|
||||
php81-sodium \
|
||||
php81-redis \
|
||||
php81-pecl-memcached \
|
||||
php81-exif \
|
||||
php82 \
|
||||
php82-common \
|
||||
php82-apache2 \
|
||||
php82-curl \
|
||||
php82-ldap \
|
||||
php82-mysqli \
|
||||
php82-gd \
|
||||
php82-xml \
|
||||
php82-mbstring \
|
||||
php82-zip \
|
||||
php82-ctype \
|
||||
php82-tokenizer \
|
||||
php82-pdo_mysql \
|
||||
php82-openssl \
|
||||
php82-bcmath \
|
||||
php82-phar \
|
||||
php82-json \
|
||||
php82-iconv \
|
||||
php82-fileinfo \
|
||||
php82-simplexml \
|
||||
php82-session \
|
||||
php82-dom \
|
||||
php82-xmlwriter \
|
||||
php82-xmlreader \
|
||||
php82-sodium \
|
||||
php82-redis \
|
||||
php82-pecl-memcached \
|
||||
php82-exif \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
@@ -42,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
# Where apache's PID lives
|
||||
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
|
||||
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini
|
||||
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
|
||||
|
||||
# Enable mod_rewrite
|
||||
@@ -79,12 +79,12 @@ USER root
|
||||
|
||||
VOLUME ["/var/lib/snipeit"]
|
||||
|
||||
# Entrypoints
|
||||
COPY docker/entrypoint_alpine.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
# Startup script
|
||||
COPY docker/startup_alpine.sh /startup.sh
|
||||
RUN chmod +x /startup.sh
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
CMD ["/startup.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
@@ -97,7 +97,7 @@ RUN set -eux; \
|
||||
VOLUME [ "/var/lib/snipeit" ]
|
||||
|
||||
COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
|
||||
COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
|
||||
COPY --chmod=655 docker/startup_alpine_fpm.sh /startup.sh
|
||||
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
|
||||
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
|
||||
ENTRYPOINT [ "/startup.sh" ]
|
||||
CMD [ "/startup.sh", "php-fpm" ]
|
||||
|
||||
@@ -72,12 +72,13 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
|
||||
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT.
|
||||
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT.
|
||||
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
|
||||
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
|
||||
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
|
||||
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
|
||||
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
|
||||
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ APP_DEBUG=true
|
||||
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
|
||||
APP_URL=http://localhost:8000
|
||||
APP_TIMEZONE='UTC'
|
||||
APP_LOCALE=en
|
||||
APP_LOCALE=en-US
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
|
||||
66
app/Console/Commands/FixupAssignedToWithoutAssignedType.php
Normal file
66
app/Console/Commands/FixupAssignedToWithoutAssignedType.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class FixupAssignedToWithoutAssignedType extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:assigned-to-fixup
|
||||
{--debug : Display debugging output}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Fixes up assets that have an assigned_to but no assigned_type';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$assets = Asset::whereNull("assigned_type")->whereNotNull("assigned_to")->withTrashed();
|
||||
$this->withProgressBar($assets->get(), function (Asset $asset) {
|
||||
//now check each action log, from the most recent backwards, to find the last checkin or checkout
|
||||
foreach($asset->log()->orderBy("id","desc")->get() as $action_log) {
|
||||
if($this->option("debug")) {
|
||||
$this->info("Asset id: " . $asset->id . " action log, action type is: " . $action_log->action_type);
|
||||
}
|
||||
switch($action_log->action_type) {
|
||||
case 'checkin from':
|
||||
if($this->option("debug")) {
|
||||
$this->info("Doing a checkin for ".$asset->id);
|
||||
}
|
||||
$asset->assigned_to = null;
|
||||
// if you have a required custom field, we still want to save, and we *don't* want an action_log
|
||||
$asset->saveQuietly();
|
||||
return;
|
||||
|
||||
case 'checkout':
|
||||
if($this->option("debug")) {
|
||||
$this->info("Doing a checkout for " . $asset->id . " picking target type: " . $action_log->target_type);
|
||||
}
|
||||
if($asset->assigned_to != $action_log->target_id) {
|
||||
$this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to." vs. \$action_log->target_id=".$action_log->target_id);
|
||||
//FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up...
|
||||
}
|
||||
$asset->assigned_type = $action_log->target_type;
|
||||
$asset->saveQuietly(); // see above
|
||||
return;
|
||||
}
|
||||
}
|
||||
$asset->assigned_to = null; //asset was never checked in or out in its lifetime - it stays 'checked in'
|
||||
$asset->saveQuietly(); //see above
|
||||
});
|
||||
$this->newLine();
|
||||
$this->info("Assets assigned_type are fixed");
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
@@ -15,6 +16,8 @@ use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Purge extends Command
|
||||
{
|
||||
@@ -141,6 +144,20 @@ class Purge extends Command
|
||||
$this->info($users->count().' users purged.');
|
||||
$user_assoc = 0;
|
||||
foreach ($users as $user) {
|
||||
|
||||
$rel_path = 'private_uploads/users';
|
||||
$filenames = Actionlog::where('action_type', 'uploaded')
|
||||
->where('item_id', $user->id)
|
||||
->pluck('filename');
|
||||
foreach($filenames as $filename) {
|
||||
try {
|
||||
if (Storage::exists($rel_path . '/' . $filename)) {
|
||||
Storage::delete($rel_path . '/' . $filename);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::info('An error occurred while deleting files: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
$this->info('- User "'.$user->username.'" deleted.');
|
||||
$user_assoc += $user->userlog()->count();
|
||||
$user->userlog()->forceDelete();
|
||||
|
||||
60
app/Console/Commands/RemoveExplicitEols.php
Normal file
60
app/Console/Commands/RemoveExplicitEols.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RemoveExplicitEols extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:remove-explicit-eols {--model_name= : The name of the asset model to update (use "all" to update all models)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Removes explicit EOLs on assets with selected model so they may inherit the asset model EOL';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
if ($this->option('model_name') == 'all') {
|
||||
$assets = Asset::all();
|
||||
$this->updateAssets($assets);
|
||||
} else {
|
||||
$assetModel = AssetModel::where('name', '=', $this->option('model_name'))->first();
|
||||
|
||||
if ($assetModel) {
|
||||
$assets = Asset::where('model_id', '=', $assetModel->id)->get();
|
||||
$this->updateAssets($assets);
|
||||
} else {
|
||||
$this->error('Asset model not found');
|
||||
}
|
||||
}
|
||||
$endTime = microtime(true);
|
||||
$executionTime = ($endTime - $startTime);
|
||||
$this->info('Command executed in ' . round($executionTime, 2) . ' seconds.');
|
||||
}
|
||||
|
||||
private function updateAssets($assets)
|
||||
{
|
||||
foreach ($assets as $asset) {
|
||||
$asset->eol_explicit = 0;
|
||||
$asset->asset_eol_date = null;
|
||||
$asset->save();
|
||||
}
|
||||
|
||||
$this->info($assets->count() . ' Assets updated successfully');
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ class ResetDemoSettings extends Command
|
||||
$settings->saml_forcelogin = '0';
|
||||
$settings->saml_slo = null;
|
||||
$settings->saml_custom_settings = null;
|
||||
$settings->default_avatar = 'default.png';
|
||||
|
||||
|
||||
$settings->save();
|
||||
|
||||
@@ -30,8 +30,11 @@ class SQLStreamer {
|
||||
public function parse_sql(string $line): string {
|
||||
// take into account the 'start of line or not' setting as an instance variable?
|
||||
// 'continuation' lines for a permitted statement are PERMITTED.
|
||||
// remove *only* line-feeds & carriage-returns; helpful for regexes against lines from
|
||||
// Windows dumps
|
||||
$line = trim($line, "\r\n");
|
||||
if($this->statement_is_permitted && $line[0] === ' ') {
|
||||
return $line;
|
||||
return $line . "\n"; //re-add the newline
|
||||
}
|
||||
|
||||
$table_regex = '`?([a-zA-Z0-9_]+)`?';
|
||||
@@ -42,8 +45,12 @@ class SQLStreamer {
|
||||
"/^(INSERT INTO )$table_regex(.*)$/" => false,
|
||||
"/^UNLOCK TABLES/" => false,
|
||||
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
|
||||
"/^\\)[a-zA-Z0-9_= ]*;$/" => false
|
||||
// ^^^^^^ that bit should *exit* the 'perimitted' black
|
||||
"/^\\)[a-zA-Z0-9_= ]*;$/" => false,
|
||||
// ^^^^^^ that bit should *exit* the 'permitted' block
|
||||
"/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line
|
||||
/* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with
|
||||
a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to
|
||||
assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */
|
||||
];
|
||||
|
||||
foreach($allowed_statements as $statement => $statechange) {
|
||||
@@ -67,7 +74,7 @@ class SQLStreamer {
|
||||
}
|
||||
//how do we *replace* the tablename?
|
||||
// print "RETURNING LINE: $line";
|
||||
return $line;
|
||||
return $line . "\n"; //re-add newline
|
||||
}
|
||||
}
|
||||
// all that is not allowed is denied.
|
||||
@@ -85,7 +92,7 @@ class SQLStreamer {
|
||||
$parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
|
||||
|
||||
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
|
||||
//can't use 'users' because the 'accessories_users' table?
|
||||
//can't use 'users' because the 'accessories_checkout' table?
|
||||
// can't use 'assets' because 'ver1_components_assets'
|
||||
foreach($check_tables as $check_table => $_ignore) {
|
||||
foreach ($parser->tablenames as $tablename => $_count) {
|
||||
@@ -164,7 +171,8 @@ class RestoreFromBackup extends Command
|
||||
{filename : The zip file to be migrated}
|
||||
{--no-progress : Don\'t show a progress bar}
|
||||
{--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL}
|
||||
{--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}';
|
||||
{--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}
|
||||
{--sql-stdout-only : ONLY "Sanitize" the SQL and print it to stdout - useful for debugging - probably requires --sanitize-with-prefix= }';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -365,6 +373,15 @@ class RestoreFromBackup extends Command
|
||||
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL.");
|
||||
}
|
||||
|
||||
// If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness
|
||||
if ($this->option('sql-stdout-only')) {
|
||||
$sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix'));
|
||||
$bytes_read = $sql_importer->line_aware_piping();
|
||||
return $this->warn("$bytes_read total bytes read");
|
||||
//TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
|
||||
// which would be good for redirecting to a file, and not having to trim the last line off of it
|
||||
}
|
||||
|
||||
//how to invoke the restore?
|
||||
$pipes = [];
|
||||
|
||||
@@ -466,6 +483,9 @@ class RestoreFromBackup extends Command
|
||||
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
|
||||
$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($file_details['dest'].'/'.basename($pretty_file_name), 'w');
|
||||
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
|
||||
fwrite($migrated_file, $buffer);
|
||||
|
||||
@@ -47,9 +47,10 @@ class SendAcceptanceReminder extends Command
|
||||
{
|
||||
$pending = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')
|
||||
->whereHas('checkoutable', function($query) {
|
||||
$query->where('archived', 0);
|
||||
$query->where('accepted_at', null)
|
||||
->where('declined_at', null);
|
||||
})
|
||||
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
|
||||
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.admin'])
|
||||
->get();
|
||||
|
||||
$count = 0;
|
||||
|
||||
@@ -62,8 +62,9 @@ class Helper
|
||||
'mn' => 'mn-MN', // Mongolian
|
||||
'ms' => 'ms-MY', // Malay
|
||||
'nl' => 'nl-NL', // Dutch
|
||||
'no' => 'no-NO', // Norwegian
|
||||
'no' => 'nb-NO', // Norwegian Bokmål
|
||||
'pl' => 'pl-PL', // Polish
|
||||
'pt' => 'pt-PT', // Portuguese
|
||||
'ro' => 'ro-RO', // Romanian
|
||||
'ru' => 'ru-RU', // Russian
|
||||
'sk' => 'sk-SK', // Slovak
|
||||
@@ -720,7 +721,7 @@ class Helper
|
||||
{
|
||||
$alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
|
||||
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
||||
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
||||
$accessories = Accessory::withCount('checkouts as checkouts_count')->whereNotNull('min_amt')->get();
|
||||
$components = Component::whereNotNull('min_amt')->get();
|
||||
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
|
||||
$licenses = License::where('min_amt', '>', 0)->get();
|
||||
@@ -748,7 +749,7 @@ class Helper
|
||||
}
|
||||
|
||||
foreach ($accessories as $accessory) {
|
||||
$avail = $accessory->qty - $accessory->users_count;
|
||||
$avail = $accessory->qty - $accessory->checkouts_count;
|
||||
if ($avail < ($accessory->min_amt) + $alert_threshold) {
|
||||
if ($accessory->qty > 0) {
|
||||
$percent = number_format((($avail / $accessory->qty) * 100), 0);
|
||||
@@ -913,13 +914,22 @@ class Helper
|
||||
$rules = $class::rules();
|
||||
foreach ($rules as $rule_name => $rule) {
|
||||
if ($rule_name == $field) {
|
||||
if (strpos($rule, 'required') === false) {
|
||||
return false;
|
||||
if (is_array($rule)) {
|
||||
if (in_array('required', $rule)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
if (strpos($rule, 'required') === false) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1440,7 +1450,6 @@ class Helper
|
||||
|
||||
foreach (self::$language_map as $legacy => $new) {
|
||||
if ($language_code == $legacy) {
|
||||
Log::debug('Current language is '.$legacy.', using '.$new.' instead');
|
||||
return $new;
|
||||
}
|
||||
}
|
||||
@@ -1451,6 +1460,7 @@ class Helper
|
||||
|
||||
public static function mapBackToLegacyLocale($new_locale = null)
|
||||
{
|
||||
|
||||
if (strlen($new_locale) <= 4) {
|
||||
return $new_locale; //"new locale" apparently wasn't quite so new
|
||||
}
|
||||
@@ -1458,42 +1468,73 @@ class Helper
|
||||
// This does a *reverse* search against our new language map array - given the value, find the *key* for it
|
||||
$legacy_locale = array_search($new_locale, self::$language_map);
|
||||
|
||||
if($legacy_locale !== false) {
|
||||
if ($legacy_locale !== false) {
|
||||
return $legacy_locale;
|
||||
}
|
||||
return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void'
|
||||
}
|
||||
|
||||
public static function determineLanguageDirection() {
|
||||
return in_array(app()->getLocale(),
|
||||
[
|
||||
'ar-SA',
|
||||
'fa-IR',
|
||||
'he-IL'
|
||||
]) ? 'rtl' : 'ltr';
|
||||
}
|
||||
|
||||
static public function getRedirectOption($request, $id, $table, $asset_id = null)
|
||||
|
||||
static public function getRedirectOption($request, $id, $table, $item_id = null)
|
||||
{
|
||||
|
||||
$redirect_option = Session::get('redirect_option');
|
||||
$checkout_to_type = Session::get('checkout_to_type');
|
||||
|
||||
//return to index
|
||||
if ($redirect_option == '0') {
|
||||
// return to index
|
||||
if ($redirect_option == 'index') {
|
||||
switch ($table) {
|
||||
case "Assets":
|
||||
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
return route('hardware.index');
|
||||
case "Users":
|
||||
return route('users.index');
|
||||
case "Licenses":
|
||||
return route('licenses.index');
|
||||
case "Accessories":
|
||||
return route('accessories.index');
|
||||
case "Components":
|
||||
return route('components.index');
|
||||
case "Consumables":
|
||||
return route('consumables.index');
|
||||
}
|
||||
}
|
||||
//return to thing being assigned
|
||||
if ($redirect_option == '1') {
|
||||
|
||||
// return to thing being assigned
|
||||
if ($redirect_option == 'item') {
|
||||
switch ($table) {
|
||||
case "Assets":
|
||||
return redirect()->route('hardware.show', $id ? $id : $asset_id)->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
return route('hardware.show', $id ?? $item_id);
|
||||
case "Users":
|
||||
return route('users.show', $id ?? $item_id);
|
||||
case "Licenses":
|
||||
return route('licenses.show', $id ?? $item_id);
|
||||
case "Accessories":
|
||||
return route('accessories.show', $id ?? $item_id);
|
||||
case "Components":
|
||||
return route('components.show', $id ?? $item_id);
|
||||
case "Consumables":
|
||||
return route('consumables.show', $id ?? $item_id);
|
||||
}
|
||||
}
|
||||
//return to thing being assigned to
|
||||
if ($redirect_option == '2') {
|
||||
|
||||
// return to assignment target
|
||||
if ($redirect_option == 'target') {
|
||||
switch ($checkout_to_type) {
|
||||
case 'user':
|
||||
return redirect()->route('users.show', $request->assigned_user)->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
return route('users.show', ['user' => $request->assigned_user]);
|
||||
case 'location':
|
||||
return redirect()->route('locations.show', $request->assigned_location)->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
return route('locations.show', ['location' => $request->assigned_location]);
|
||||
case 'asset':
|
||||
return redirect()->route('hardware.show', $request->assigned_asset)->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
return route('hardware.show', ['hardware' => $request->assigned_asset]);
|
||||
}
|
||||
}
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
|
||||
|
||||
179
app/Helpers/IconHelper.php
Normal file
179
app/Helpers/IconHelper.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class IconHelper
|
||||
{
|
||||
|
||||
public static function icon($type) {
|
||||
switch ($type) {
|
||||
case 'checkout':
|
||||
return 'fa-solid fa-rotate-left';
|
||||
case 'checkin':
|
||||
return 'fa-solid fa-rotate-right';
|
||||
case 'edit':
|
||||
return 'fas fa-pencil-alt';
|
||||
case 'clone':
|
||||
return 'far fa-clone';
|
||||
case 'delete':
|
||||
return 'fas fa-trash';
|
||||
case 'create':
|
||||
return 'fa-solid fa-plus';
|
||||
case 'audit':
|
||||
return 'fa-solid fa-clipboard-check';
|
||||
case '2fa reset':
|
||||
return 'fa-solid fa-mobile-screen';
|
||||
case 'new-user':
|
||||
return 'fa-solid fa-user-plus';
|
||||
case 'merged-user':
|
||||
return 'fa-solid fa-people-arrows';
|
||||
case 'delete-user':
|
||||
return 'fa-solid fa-user-minus';
|
||||
case 'update-user':
|
||||
return 'fa-solid fa-user-pen';
|
||||
case 'user':
|
||||
return 'fa-solid fa-user';
|
||||
case 'users':
|
||||
return 'fas fa-users';
|
||||
case 'restore':
|
||||
return 'fa-solid fa-trash-arrow-up';
|
||||
case 'external-link':
|
||||
return 'fa fa-external-link';
|
||||
case 'email':
|
||||
return 'fa-regular fa-envelope';
|
||||
case 'phone':
|
||||
return 'fa-solid fa-phone';
|
||||
case 'long-arrow-right':
|
||||
return 'fas fa-long-arrow-alt-right';
|
||||
case 'download':
|
||||
return 'fas fa-download';
|
||||
case 'checkmark':
|
||||
return 'fas fa-check icon-white';
|
||||
case 'x':
|
||||
return 'fas fa-times';
|
||||
case 'logout':
|
||||
return 'fa fa-sign-out';
|
||||
case 'admin-settings':
|
||||
return 'fas fa-cogs';
|
||||
case 'settings':
|
||||
return 'fas fa-cog';
|
||||
case 'angle-left':
|
||||
return 'fas fa-angle-left';
|
||||
case 'warning':
|
||||
return 'fas fa-exclamation-triangle';
|
||||
case 'kits':
|
||||
return 'fas fa-object-group';
|
||||
case 'assets':
|
||||
return 'fas fa-barcode';
|
||||
case 'accessories':
|
||||
return 'far fa-keyboard';
|
||||
case 'components':
|
||||
return 'far fa-hdd';
|
||||
case 'consumables':
|
||||
return 'fas fa-tint';
|
||||
case 'licenses':
|
||||
return 'far fa-save';
|
||||
case 'requestable':
|
||||
return 'fas fa-laptop';
|
||||
case 'reports':
|
||||
return 'fas fa-chart-bar';
|
||||
case 'heart':
|
||||
return 'fas fa-heart';
|
||||
case 'circle':
|
||||
return 'fa-regular fa-circle';
|
||||
case 'circle-solid':
|
||||
return 'fa-solid fa-circle';
|
||||
case 'due':
|
||||
return 'fas fa-history';
|
||||
case 'import':
|
||||
return 'fas fa-cloud-upload-alt';
|
||||
case 'search':
|
||||
return 'fas fa-search';
|
||||
case 'alerts':
|
||||
return 'far fa-flag';
|
||||
case 'password':
|
||||
return 'fa-solid fa-key';
|
||||
case 'api-key':
|
||||
return 'fa-solid fa-user-secret';
|
||||
case 'nav-toggle':
|
||||
return 'fas fa-bars';
|
||||
case 'dashboard':
|
||||
return 'fas fa-tachometer-alt';
|
||||
case 'info-circle':
|
||||
return 'fas fa-info-circle';
|
||||
case 'caret-right':
|
||||
return 'fa fa-caret-right';
|
||||
case 'caret-up':
|
||||
return 'fa fa-caret-up';
|
||||
case 'caret-down':
|
||||
return 'fa fa-caret-down';
|
||||
case 'arrow-circle-right':
|
||||
return 'fa fa-arrow-circle-right';
|
||||
case 'minus':
|
||||
return 'fas fa-minus';
|
||||
case 'spinner':
|
||||
return 'fas fa-spinner fa-spin';
|
||||
case 'copy-clipboard':
|
||||
return 'fa-regular fa-clipboard';
|
||||
case 'paperclip':
|
||||
return 'fas fa-paperclip';
|
||||
case 'files':
|
||||
return 'fa-regular fa-file';
|
||||
case 'more-info':
|
||||
return 'far fa-life-ring';
|
||||
case 'calendar':
|
||||
return 'fas fa-calendar';
|
||||
case 'plus':
|
||||
return 'fas fa-plus';
|
||||
case 'history':
|
||||
return 'fas fa-history';
|
||||
case 'more-files':
|
||||
return 'fa-solid fa-laptop-file';
|
||||
case 'maintenances':
|
||||
return 'fas fa-wrench';
|
||||
case 'seats':
|
||||
return 'far fa-list-alt';
|
||||
case 'globe-us':
|
||||
return 'fas fa-globe-americas';
|
||||
case 'locked':
|
||||
return 'fas fa-lock';
|
||||
case 'unlocked':
|
||||
return 'fas fa-lock';
|
||||
case 'locations':
|
||||
return 'fas fa-map-marker-alt';
|
||||
case 'superadmin':
|
||||
return 'fas fa-crown';
|
||||
case 'print':
|
||||
return 'fa-solid fa-print';
|
||||
case 'checkin-and-delete':
|
||||
return 'fa-solid fa-user-xmark';
|
||||
case 'branding':
|
||||
return 'fas fa-copyright';
|
||||
case 'general-settings':
|
||||
return 'fa-solid fa-list-check';
|
||||
case 'groups':
|
||||
return 'fa-solid fa-user-group';
|
||||
case 'bell':
|
||||
return 'fa-solid fa-bell';
|
||||
case 'hashtag':
|
||||
return 'fa-solid fa-hashtag';
|
||||
case 'asset-tags':
|
||||
return 'fas fa-list-ol';
|
||||
case 'labels':
|
||||
return 'fas fa-tags';
|
||||
case 'ldap':
|
||||
return 'fas fa-sitemap';
|
||||
case 'google':
|
||||
return 'fa-brands fa-google';
|
||||
case 'saml':
|
||||
return 'fas fa-sign-in-alt';
|
||||
case 'backups':
|
||||
return 'fas fa-file-archive';
|
||||
case 'logins':
|
||||
return 'fas fa-crosshairs';
|
||||
case 'oauth':
|
||||
return 'fas fa-user-secret';
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,10 +79,11 @@ class AccessoriesController extends Controller
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
// Was the accessory created?
|
||||
if ($accessory->save()) {
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
||||
@@ -143,12 +144,12 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse
|
||||
{
|
||||
if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
|
||||
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId)) {
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$accessory->users_count"
|
||||
"qty" => "required|numeric|min:$accessory->checkouts_count"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
@@ -176,9 +177,10 @@ class AccessoriesController extends Controller
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory updated?
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($accessory->save()) {
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
@@ -231,7 +233,7 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function show($accessoryID = null) : View | RedirectResponse
|
||||
{
|
||||
$accessory = Accessory::withCount('users as users_count')->find($accessoryID);
|
||||
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryID);
|
||||
$this->authorize('view', $accessory);
|
||||
if (isset($accessory->id)) {
|
||||
return view('accessories/view', compact('accessory'));
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\AccessoryCheckout;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -23,7 +25,7 @@ class AccessoryCheckinController extends Controller
|
||||
*/
|
||||
public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse
|
||||
{
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
@@ -38,16 +40,16 @@ class AccessoryCheckinController extends Controller
|
||||
*
|
||||
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param null $accessoryUserId
|
||||
* @param null $accessoryCheckoutId
|
||||
* @param string $backto
|
||||
*/
|
||||
public function store(Request $request, $accessoryUserId = null, $backto = null) : RedirectResponse
|
||||
public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse
|
||||
{
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
||||
|
||||
$this->authorize('checkin', $accessory);
|
||||
|
||||
@@ -58,12 +60,12 @@ class AccessoryCheckinController extends Controller
|
||||
}
|
||||
|
||||
// Was the accessory updated?
|
||||
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
|
||||
$return_to = e($accessory_user->assigned_to);
|
||||
if ($accessory_checkout->delete()) {
|
||||
event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at));
|
||||
|
||||
event(new CheckoutableCheckedIn($accessory, User::find($return_to), auth()->user(), $request->input('note'), $checkin_at));
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
|
||||
|
||||
@@ -3,18 +3,24 @@
|
||||
namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AccessoryCheckoutRequest;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\AccessoryCheckout;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use \Illuminate\Http\RedirectResponse;
|
||||
|
||||
class AccessoryCheckoutController extends Controller
|
||||
{
|
||||
|
||||
use CheckInOutRequest;
|
||||
|
||||
/**
|
||||
* Return the form to checkout an Accessory to a user.
|
||||
*
|
||||
@@ -24,7 +30,7 @@ class AccessoryCheckoutController extends Controller
|
||||
public function create($id) : View | RedirectResponse
|
||||
{
|
||||
|
||||
if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
|
||||
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) {
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
@@ -57,44 +63,38 @@ class AccessoryCheckoutController extends Controller
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param Request $request
|
||||
* @param int $accessoryId
|
||||
* @param Accessory $accessory
|
||||
*/
|
||||
public function store(Request $request, $accessoryId) : RedirectResponse
|
||||
public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
|
||||
}
|
||||
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
if (!$user = User::find($request->input('assigned_to'))) {
|
||||
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
|
||||
$target = $this->determineCheckoutTarget();
|
||||
|
||||
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
||||
|
||||
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
|
||||
AccessoryCheckout::create([
|
||||
'accessory_id' => $accessory->id,
|
||||
'created_at' => Carbon::now(),
|
||||
'user_id' => Auth::id(),
|
||||
'assigned_to' => $target->id,
|
||||
'assigned_type' => $target::class,
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
}
|
||||
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($accessory->numRemaining() <= 0){
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
|
||||
}
|
||||
// Set this as user since we only allow checkout to user for this item type
|
||||
$request->request->add(['checkout_to_type' => request('checkout_to_type')]);
|
||||
$request->request->add(['assigned_user' => $target->id]);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->assigned_to = e($request->input('assigned_to'));
|
||||
|
||||
$accessory->users()->attach($accessory->id, [
|
||||
'accessory_id' => $accessory->id,
|
||||
'created_at' => Carbon::now(),
|
||||
'user_id' => Auth::id(),
|
||||
'assigned_to' => $request->get('assigned_to'),
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
|
||||
|
||||
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
|
||||
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))
|
||||
->with('success', trans('admin/accessories/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,7 @@ class AcceptanceController extends Controller
|
||||
'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'),
|
||||
@@ -308,6 +309,7 @@ class AcceptanceController extends Controller
|
||||
'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,
|
||||
|
||||
@@ -4,7 +4,10 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AccessoryCheckoutRequest;
|
||||
use App\Http\Requests\StoreAccessoryRequest;
|
||||
use App\Http\Transformers\AccessoriesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Accessory;
|
||||
@@ -15,10 +18,12 @@ use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
|
||||
use App\Models\AccessoryCheckout;
|
||||
|
||||
class AccessoriesController extends Controller
|
||||
{
|
||||
use CheckInOutRequest;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
@@ -46,13 +51,13 @@ class AccessoriesController extends Controller
|
||||
'min_amt',
|
||||
'company_id',
|
||||
'notes',
|
||||
'users_count',
|
||||
'checkouts_count',
|
||||
'qty',
|
||||
];
|
||||
|
||||
|
||||
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
|
||||
->withCount('users as users_count');
|
||||
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier')
|
||||
->withCount('checkouts as checkouts_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$accessories = $accessories->TextSearch($request->input('search'));
|
||||
@@ -121,12 +126,12 @@ class AccessoriesController extends Controller
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\ImageUploadRequest $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @param \App\Http\Requests\ImageUploadRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
public function store(StoreAccessoryRequest $request)
|
||||
{
|
||||
$this->authorize('create', Accessory::class);
|
||||
$accessory = new Accessory;
|
||||
@@ -144,15 +149,15 @@ class AccessoriesController extends Controller
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return array
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', Accessory::class);
|
||||
$accessory = Accessory::withCount('users as users_count')->findOrFail($id);
|
||||
$accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id);
|
||||
|
||||
return (new AccessoriesTransformer)->transformAccessory($accessory);
|
||||
}
|
||||
@@ -161,10 +166,10 @@ class AccessoriesController extends Controller
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return array
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function accessory_detail($id)
|
||||
{
|
||||
@@ -195,28 +200,23 @@ class AccessoriesController extends Controller
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$accessory_users = $accessory->users;
|
||||
$total = $accessory_users->count();
|
||||
$accessory_checkouts = $accessory->checkouts;
|
||||
$total = $accessory_checkouts->count();
|
||||
|
||||
if ($total < $offset) {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$accessory_users = $accessory->users()->skip($offset)->take($limit)->get();
|
||||
$accessory_checkouts = $accessory->checkouts()->skip($offset)->take($limit)->get();
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$accessory_users = $accessory->users()
|
||||
->where(function ($query) use ($request) {
|
||||
$search_str = '%' . $request->input('search') . '%';
|
||||
$query->where('first_name', 'like', $search_str)
|
||||
->orWhere('last_name', 'like', $search_str)
|
||||
->orWhere('note', 'like', $search_str);
|
||||
})
|
||||
|
||||
$accessory_checkouts = $accessory->checkouts()->TextSearch($request->input('search'))
|
||||
->get();
|
||||
$total = $accessory_users->count();
|
||||
$total = $accessory_checkouts->count();
|
||||
}
|
||||
|
||||
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
|
||||
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_checkouts, $total);
|
||||
}
|
||||
|
||||
|
||||
@@ -273,43 +273,31 @@ class AccessoriesController extends Controller
|
||||
* If Slack is enabled and/or asset acceptance is enabled, it will also
|
||||
* trigger a Slack message and send an email.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
public function checkout(Request $request, $accessoryId)
|
||||
public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
$target = $this->determineCheckoutTarget();
|
||||
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
||||
|
||||
|
||||
if ($accessory->numRemaining() > 0) {
|
||||
|
||||
if (! $user = User::find($request->input('assigned_to'))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
|
||||
}
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->assigned_to = $request->input('assigned_to');
|
||||
|
||||
$accessory->users()->attach($accessory->id, [
|
||||
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
|
||||
AccessoryCheckout::create([
|
||||
'accessory_id' => $accessory->id,
|
||||
'created_at' => Carbon::now(),
|
||||
'user_id' => Auth::id(),
|
||||
'assigned_to' => $request->get('assigned_to'),
|
||||
'note' => $request->get('note'),
|
||||
'assigned_to' => $target->id,
|
||||
'assigned_type' => $target::class,
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
|
||||
// Set this value to be able to pass the qty through to the event
|
||||
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
|
||||
|
||||
}
|
||||
|
||||
@@ -326,19 +314,19 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function checkin(Request $request, $accessoryUserId = null)
|
||||
{
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
||||
$this->authorize('checkin', $accessory);
|
||||
|
||||
$logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note'));
|
||||
$logaction = $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note'));
|
||||
|
||||
// Was the accessory updated?
|
||||
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
|
||||
if (! is_null($accessory_user->assigned_to)) {
|
||||
$user = User::find($accessory_user->assigned_to);
|
||||
if ($accessory_checkout->delete()) {
|
||||
if (! is_null($accessory_checkout->assigned_to)) {
|
||||
$user = User::find($accessory_checkout->assigned_to);
|
||||
}
|
||||
|
||||
$data['log_id'] = $logaction->id;
|
||||
|
||||
200
app/Http/Controllers/Api/AssetModelFilesController.php
Normal file
200
app/Http/Controllers/Api/AssetModelFilesController.php
Normal file
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
|
||||
/**
|
||||
* This class controls file related actions related
|
||||
* to assets for the Snipe-IT Asset Management application.
|
||||
*
|
||||
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
|
||||
*
|
||||
* @version v1.0
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
class AssetModelFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Accepts a POST to upload a file to the server.
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param int $assetModelId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// Make sure we are allowed to update this asset
|
||||
$this->authorize('update', $assetModel);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
// If the file storage directory doesn't exist; create it
|
||||
if (! Storage::exists('private_uploads/assetmodels')) {
|
||||
Storage::makeDirectory('private_uploads/assetmodels', 775);
|
||||
}
|
||||
|
||||
// Loop over the attached files and add them to the asset
|
||||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
|
||||
|
||||
$assetModel->logUpload($file_name, e($request->get('notes')));
|
||||
}
|
||||
|
||||
// All done - report success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
|
||||
}
|
||||
|
||||
// We only reach here if no files were included in the POST, so tell the user this
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the files for an asset.
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function list($assetModelId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('view', $assetModel);
|
||||
|
||||
// Check that there are some uploads on this asset that can be listed
|
||||
if ($assetModel->uploads->count() > 0) {
|
||||
$files = array();
|
||||
foreach ($assetModel->uploads as $upload) {
|
||||
array_push($files, $upload);
|
||||
}
|
||||
// Give the list of files back to the user
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/models/message.upload.success')));
|
||||
}
|
||||
|
||||
// There are no files.
|
||||
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/models/message.upload.success')));
|
||||
}
|
||||
|
||||
// Send back an error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error')), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('view', $assetModel);
|
||||
|
||||
// Check that the file being requested exists for the asset
|
||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
// Form the full filename with path
|
||||
$file = 'private_uploads/assetmodels/'.$log->filename;
|
||||
Log::debug('Checking for '.$file);
|
||||
|
||||
if ($log->action_type == 'audit') {
|
||||
$file = 'private_uploads/audits/'.$log->filename;
|
||||
}
|
||||
|
||||
// Check the file actually exists on the filesystem
|
||||
if (! Storage::exists($file)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
}
|
||||
|
||||
// Send back an error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the associated file
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @param int $fileId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
$rel_path = 'private_uploads/assetmodels';
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('update', $assetModel);
|
||||
|
||||
// Check for the file
|
||||
$log = Actionlog::find($fileId);
|
||||
if ($log) {
|
||||
// Check the file actually exists, and delete it
|
||||
if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
Storage::delete($rel_path.'/'.$log->filename);
|
||||
}
|
||||
// Delete the record of the file
|
||||
$log->delete();
|
||||
|
||||
// All deleting done - notify the user of success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
|
||||
}
|
||||
|
||||
// The file doesn't seem to really exist, so report an error
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,10 @@ class AssetModelsController extends Controller
|
||||
$assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation_id')) {
|
||||
$assetmodels = $assetmodels->where('models.depreciation_id', '=', $request->input('depreciation_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$assetmodels->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Http\Requests\StoreAssetRequest;
|
||||
use App\Http\Requests\UpdateAssetRequest;
|
||||
use App\Http\Traits\MigratesLegacyAssetLocations;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\LicenseSeat;
|
||||
@@ -601,7 +602,7 @@ class AssetsController extends Controller
|
||||
if ($field->field_encrypted == '1') {
|
||||
Log::debug('This model field is encrypted in this fieldset.');
|
||||
|
||||
if (Gate::allows('admin')) {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
|
||||
// If input value is null, use custom field's default value
|
||||
if (($field_val == null) && ($request->has('model_id') != '')) {
|
||||
@@ -651,36 +652,35 @@ class AssetsController extends Controller
|
||||
* Accepts a POST request to update an asset
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param \App\Http\Requests\ImageUploadRequest $request
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $id) : JsonResponse
|
||||
public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
$asset->fill($request->validated());
|
||||
|
||||
if ($asset = Asset::find($id)) {
|
||||
$asset->fill($request->all());
|
||||
if ($request->has('model_id')) {
|
||||
$asset->model()->associate(AssetModel::find($request->validated()['model_id']));
|
||||
}
|
||||
if ($request->has('company_id')) {
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']);
|
||||
}
|
||||
if ($request->has('rtd_location_id') && !$request->has('location_id')) {
|
||||
$asset->location_id = $request->validated()['rtd_location_id'];
|
||||
}
|
||||
if ($request->input('last_audit_date')) {
|
||||
$asset->last_audit_date = Carbon::parse($request->input('last_audit_date'))->startOfDay()->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
($request->filled('model_id')) ?
|
||||
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
|
||||
($request->filled('rtd_location_id')) ?
|
||||
$asset->location_id = $request->get('rtd_location_id') : '';
|
||||
($request->filled('company_id')) ?
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
|
||||
/**
|
||||
* this is here just legacy reasons. Api\AssetController
|
||||
* used image_source once to allow encoded image uploads.
|
||||
*/
|
||||
if ($request->has('image_source')) {
|
||||
$request->offsetSet('image', $request->offsetGet('image_source'));
|
||||
}
|
||||
|
||||
($request->filled('rtd_location_id')) ?
|
||||
$asset->location_id = $request->get('rtd_location_id') : null;
|
||||
|
||||
/**
|
||||
* this is here just legacy reasons. Api\AssetController
|
||||
* used image_source once to allow encoded image uploads.
|
||||
*/
|
||||
if ($request->has('image_source')) {
|
||||
$request->offsetSet('image', $request->offsetGet('image_source'));
|
||||
}
|
||||
|
||||
$asset = $request->handleImages($asset);
|
||||
$model = AssetModel::find($asset->model_id);
|
||||
$asset = $request->handleImages($asset);
|
||||
$model = $asset->model;
|
||||
|
||||
// Update custom fields
|
||||
$problems_updating_encrypted_custom_fields = false;
|
||||
@@ -695,7 +695,7 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
$field_val = Crypt::encrypt($field_val);
|
||||
} else {
|
||||
$problems_updating_encrypted_custom_fields = true;
|
||||
@@ -706,15 +706,13 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($asset->save()) {
|
||||
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
|
||||
$location = $target->location_id;
|
||||
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
|
||||
$location = $target->location_id;
|
||||
|
||||
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $id)
|
||||
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
|
||||
->update(['location_id' => $target->location_id]);
|
||||
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
|
||||
$location = $target->id;
|
||||
@@ -728,17 +726,13 @@ class AssetsController extends Controller
|
||||
$asset->image = $asset->getImageUrl();
|
||||
}
|
||||
|
||||
if ($problems_updating_encrypted_custom_fields) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
||||
}
|
||||
if ($problems_updating_encrypted_custom_fields) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
}
|
||||
|
||||
|
||||
@@ -934,7 +928,7 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->has('status_id')) {
|
||||
if ($request->filled('status_id')) {
|
||||
$asset->status_id = $request->input('status_id');
|
||||
}
|
||||
|
||||
@@ -984,7 +978,7 @@ class AssetsController extends Controller
|
||||
public function checkinByTag(Request $request, $tag = null) : JsonResponse
|
||||
{
|
||||
$this->authorize('checkin', Asset::class);
|
||||
if(null == $tag && null !== ($request->input('asset_tag'))) {
|
||||
if (null == $tag && null !== ($request->input('asset_tag'))) {
|
||||
$tag = $request->input('asset_tag');
|
||||
}
|
||||
$asset = Asset::where('asset_tag', $tag)->first();
|
||||
|
||||
@@ -20,9 +20,22 @@ class DepreciationsController extends Controller
|
||||
public function index(Request $request) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('view', Depreciation::class);
|
||||
$allowed_columns = ['id','name','months','depreciation_min','created_at'];
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'name',
|
||||
'months',
|
||||
'depreciation_min',
|
||||
'depreciation_type',
|
||||
'created_at',
|
||||
'assets_count',
|
||||
'models_count',
|
||||
'licenses_count',
|
||||
];
|
||||
|
||||
$depreciations = Depreciation::select('id','name','months','depreciation_min','user_id','created_at','updated_at');
|
||||
$depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','user_id','created_at','updated_at')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('models as models_count')
|
||||
->withCount('licenses as licenses_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$depreciations = $depreciations->TextSearch($request->input('search'));
|
||||
|
||||
@@ -24,10 +24,10 @@ class LicensesController extends Controller
|
||||
{
|
||||
$this->authorize('view', License::class);
|
||||
|
||||
$licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count');
|
||||
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$licenses->where('company_id', '=', $request->input('company_id'));
|
||||
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
@@ -70,6 +70,9 @@ class LicensesController extends Controller
|
||||
$licenses->where('depreciation_id', '=', $request->input('depreciation_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('user_id')) {
|
||||
$licenses->where('user_id', '=', $request->input('user_id'));
|
||||
}
|
||||
|
||||
if (($request->filled('maintained')) && ($request->input('maintained')=='true')) {
|
||||
$licenses->where('maintained','=',1);
|
||||
@@ -113,6 +116,9 @@ class LicensesController extends Controller
|
||||
case 'company':
|
||||
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
|
||||
break;
|
||||
case 'created_by':
|
||||
$licenses = $licenses->OrderCreatedBy($order);
|
||||
break;
|
||||
default:
|
||||
$allowed_columns =
|
||||
[
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\LocationsTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
@@ -222,6 +224,15 @@ class LocationsController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors()));
|
||||
}
|
||||
|
||||
public function assets(Request $request, Location $location) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$this->authorize('view', $location);
|
||||
$assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
|
||||
$assets = $assets->get();
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
@@ -237,6 +248,7 @@ class LocationsController extends Controller
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count')
|
||||
->withCount('accessories as accessories_count')
|
||||
->findOrFail($id);
|
||||
|
||||
if (! $location->isDeletable()) {
|
||||
|
||||
@@ -246,7 +246,7 @@ class PredefinedKitsController extends Controller
|
||||
|
||||
$relation = $kit->models();
|
||||
if ($relation->find($model_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => 'Model already attached to kit']));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => trans('admin/kits/general.model_already_attached')]));
|
||||
}
|
||||
$relation->attach($model_id, ['quantity' => $quantity]);
|
||||
|
||||
|
||||
@@ -83,11 +83,19 @@ class ReportsController extends Controller
|
||||
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
|
||||
|
||||
switch ($request->input('sort')) {
|
||||
case 'admin':
|
||||
$actionlogs->OrderAdmin($order);
|
||||
break;
|
||||
default:
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$actionlogs = $actionlogs->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
|
||||
$actionlogs = $actionlogs->skip($offset)->take($limit)->get();
|
||||
|
||||
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Http\Transformers\StatuslabelsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Transformers\PieChartTransformer;
|
||||
@@ -187,8 +188,14 @@ class StatuslabelsController extends Controller
|
||||
public function getAssetCountByStatuslabel() : array
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
$statuslabels = Statuslabel::withCount('assets')->get();
|
||||
$total = Array();
|
||||
|
||||
if (Setting::getSettings()->show_archived_in_list == 0 ) {
|
||||
$statuslabels = Statuslabel::withCount('assets')->where('archived','0')->get();
|
||||
} else {
|
||||
$statuslabels = Statuslabel::withCount('assets')->get();
|
||||
}
|
||||
|
||||
$total = [];
|
||||
|
||||
foreach ($statuslabels as $statuslabel) {
|
||||
|
||||
|
||||
@@ -427,13 +427,10 @@ class UsersController extends Controller
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
*/
|
||||
public function update(SaveUserRequest $request, $id) : JsonResponse
|
||||
public function update(SaveUserRequest $request, User $user): JsonResponse
|
||||
{
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
if ($user = User::find($id)) {
|
||||
|
||||
|
||||
$this->authorize('update', $user);
|
||||
|
||||
/**
|
||||
@@ -443,12 +440,10 @@ class UsersController extends Controller
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
|
||||
if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
|
||||
}
|
||||
|
||||
|
||||
$user->fill($request->all());
|
||||
|
||||
if ($user->id == $request->input('manager_id')) {
|
||||
@@ -473,16 +468,13 @@ class UsersController extends Controller
|
||||
$user->permissions = $permissions_array;
|
||||
}
|
||||
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
|
||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||
|
||||
if ($user->save()) {
|
||||
|
||||
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
||||
if (($request->has('groups')) && (auth()->user()->isSuperUser())) {
|
||||
|
||||
@@ -496,18 +488,10 @@ class UsersController extends Controller
|
||||
|
||||
// Sync the groups since the user is a superuser and the groups pass validation
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -151,17 +151,17 @@ class AssetModelsController extends Controller
|
||||
$model->notes = $request->input('notes');
|
||||
$model->requestable = $request->input('requestable', '0');
|
||||
|
||||
$this->removeCustomFieldsDefaultValues($model);
|
||||
|
||||
$model->fieldset_id = $request->input('fieldset_id');
|
||||
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
|
||||
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($model->save()) {
|
||||
$this->removeCustomFieldsDefaultValues($model);
|
||||
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))) {
|
||||
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($model->wasChanged('eol')) {
|
||||
if ($model->eol > 0) {
|
||||
$newEol = $model->eol;
|
||||
@@ -202,6 +202,7 @@ class AssetModelsController extends Controller
|
||||
if ($model->image) {
|
||||
try {
|
||||
Storage::disk('public')->delete('models/'.$model->image);
|
||||
$model->update(['image' => null]);
|
||||
} catch (\Exception $e) {
|
||||
Log::info($e);
|
||||
}
|
||||
@@ -233,7 +234,7 @@ class AssetModelsController extends Controller
|
||||
|
||||
if ($model->restore()) {
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = User::class;
|
||||
$logaction->item_type = AssetModel::class;
|
||||
$logaction->item_id = $model->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = auth()->id();
|
||||
|
||||
@@ -11,7 +11,6 @@ use App\Models\Asset;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\LicenseSeat;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use \Illuminate\Http\RedirectResponse;
|
||||
@@ -83,7 +82,6 @@ class AssetCheckinController extends Controller
|
||||
}
|
||||
|
||||
$asset->expected_checkin = null;
|
||||
//$asset->last_checkout = null;
|
||||
$asset->last_checkin = now();
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->accepted = null;
|
||||
@@ -128,12 +126,12 @@ class AssetCheckinController extends Controller
|
||||
$acceptance->delete();
|
||||
});
|
||||
|
||||
Session::put('redirect_option', $request->get('redirect_option'));
|
||||
// Was the asset updated?
|
||||
session()->put('redirect_option', $request->get('redirect_option'));
|
||||
|
||||
if ($asset->save()) {
|
||||
|
||||
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
|
||||
return Helper::getRedirectOption($asset, $assetId, 'Assets');
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());
|
||||
|
||||
@@ -109,10 +109,11 @@ class AssetCheckoutController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
Session::put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
|
||||
return Helper::getRedirectOption($request, $assetId, 'Assets');
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
|
||||
|
||||
@@ -165,7 +165,7 @@ class AssetsController extends Controller
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
@@ -204,9 +204,13 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
|
||||
if ($success) {
|
||||
return redirect()->route('hardware.index')
|
||||
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
|
||||
|
||||
|
||||
}
|
||||
@@ -289,6 +293,7 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $assetId = null) : RedirectResponse
|
||||
{
|
||||
|
||||
// Check if the asset exists
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
// Redirect to the asset management page with error
|
||||
@@ -331,7 +336,7 @@ class AssetsController extends Controller
|
||||
|
||||
$status = Statuslabel::find($asset->status_id);
|
||||
|
||||
if($status->archived){
|
||||
if ($status && $status->archived) {
|
||||
$asset->assigned_to = null;
|
||||
}
|
||||
|
||||
@@ -350,14 +355,26 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
// Update the asset data
|
||||
$asset_tag = $request->input('asset_tags');
|
||||
|
||||
$serial = $request->input('serials');
|
||||
$asset->serial = $request->input('serials');
|
||||
|
||||
if (is_array($request->input('serials'))) {
|
||||
$asset->serial = $serial[1];
|
||||
}
|
||||
|
||||
$asset->name = $request->input('name');
|
||||
$asset->serial = $serial[1];
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$asset->model_id = $request->input('model_id');
|
||||
$asset->order_number = $request->input('order_number');
|
||||
$asset->asset_tag = $asset_tag[1];
|
||||
|
||||
$asset_tags = $request->input('asset_tags');
|
||||
$asset->asset_tag = $request->input('asset_tags');
|
||||
|
||||
if (is_array($request->input('asset_tags'))) {
|
||||
$asset->asset_tag = $asset_tags[1];
|
||||
}
|
||||
|
||||
$asset->notes = $request->input('notes');
|
||||
|
||||
$asset = $request->handleImages($asset);
|
||||
@@ -369,8 +386,9 @@ class AssetsController extends Controller
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
@@ -387,9 +405,10 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
if ($asset->save()) {
|
||||
return redirect()->route('hardware.show', $assetId)
|
||||
return redirect()->to(Helper::getRedirectOption($request, $assetId, 'Assets'))
|
||||
->with('success', trans('admin/hardware/message.update.success'));
|
||||
}
|
||||
|
||||
@@ -459,9 +478,16 @@ class AssetsController extends Controller
|
||||
$tag = $tag ? $tag : $request->get('assetTag');
|
||||
$topsearch = ($request->get('topsearch') == 'true');
|
||||
|
||||
if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
// Search for an exact and unique asset tag match
|
||||
$assets = Asset::where('asset_tag', '=', $tag);
|
||||
|
||||
// If not a unique result, redirect to the index view
|
||||
if ($assets->count() != 1) {
|
||||
return redirect()->route('hardware.index')
|
||||
->with('search', $tag)
|
||||
->with('warning', trans('admin/hardware/message.does_not_exist_var', [ 'asset_tag' => $tag ]));
|
||||
}
|
||||
$asset = $assets->first();
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch);
|
||||
@@ -576,26 +602,20 @@ class AssetsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function getClone($assetId = null)
|
||||
public function getClone(Asset $asset)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (is_null($asset_to_clone = Asset::find($assetId))) {
|
||||
// Redirect to the asset management page
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('create', $asset_to_clone);
|
||||
|
||||
$asset = clone $asset_to_clone;
|
||||
$asset->id = null;
|
||||
$asset->asset_tag = '';
|
||||
$asset->serial = '';
|
||||
$asset->assigned_to = '';
|
||||
$this->authorize('create', $asset);
|
||||
$cloned = clone $asset;
|
||||
$cloned->id = null;
|
||||
$cloned->asset_tag = '';
|
||||
$cloned->serial = '';
|
||||
$cloned->assigned_to = '';
|
||||
$cloned->deleted_at = '';
|
||||
|
||||
return view('hardware/edit')
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('statuslabel_types', Helper::statusTypeList())
|
||||
->with('item', $asset);
|
||||
->with('item', $cloned);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -824,7 +844,7 @@ class AssetsController extends Controller
|
||||
{
|
||||
$this->authorize('checkin', Asset::class);
|
||||
|
||||
return view('hardware/quickscan-checkin');
|
||||
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
|
||||
}
|
||||
|
||||
public function audit($id)
|
||||
|
||||
@@ -92,7 +92,9 @@ class BulkAssetsController extends Controller
|
||||
// This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array)
|
||||
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id';
|
||||
|
||||
$assets = Asset::with('assignedTo', 'location', 'model')->whereIn('assets.id', $asset_ids);
|
||||
$assets = Asset::with('assignedTo', 'location', 'model')
|
||||
->whereIn('assets.id', $asset_ids)
|
||||
->withTrashed();
|
||||
|
||||
$assets = $assets->get();
|
||||
|
||||
@@ -225,7 +227,8 @@ class BulkAssetsController extends Controller
|
||||
* its checkout status.
|
||||
*/
|
||||
|
||||
if (($request->filled('purchase_date'))
|
||||
if (($request->filled('name'))
|
||||
|| ($request->filled('purchase_date'))
|
||||
|| ($request->filled('expected_checkin'))
|
||||
|| ($request->filled('purchase_cost'))
|
||||
|| ($request->filled('supplier_id'))
|
||||
@@ -237,6 +240,7 @@ class BulkAssetsController extends Controller
|
||||
|| ($request->filled('status_id'))
|
||||
|| ($request->filled('model_id'))
|
||||
|| ($request->filled('next_audit_date'))
|
||||
|| ($request->filled('null_name'))
|
||||
|| ($request->filled('null_purchase_date'))
|
||||
|| ($request->filled('null_expected_checkin_date'))
|
||||
|| ($request->filled('null_next_audit_date'))
|
||||
@@ -249,13 +253,14 @@ class BulkAssetsController extends Controller
|
||||
$this->update_array = [];
|
||||
|
||||
/**
|
||||
* Leave out model_id and status here because we do math on that later. We have to do some extra
|
||||
* validation and checks on those two.
|
||||
* Leave out model_id and status here because we do math on that later. We have to do some
|
||||
* extra validation and checks on those two.
|
||||
*
|
||||
* It's tempting to make these match the request check above, but some of these values require
|
||||
* extra work to make sure the data makes sense.
|
||||
*/
|
||||
$this->conditionallyAddItem('purchase_date')
|
||||
$this->conditionallyAddItem('name')
|
||||
->conditionallyAddItem('purchase_date')
|
||||
->conditionallyAddItem('expected_checkin')
|
||||
->conditionallyAddItem('order_number')
|
||||
->conditionallyAddItem('requestable')
|
||||
@@ -269,6 +274,11 @@ class BulkAssetsController extends Controller
|
||||
/**
|
||||
* Blank out fields that were requested to be blanked out via checkbox
|
||||
*/
|
||||
if ($request->input('null_name')=='1') {
|
||||
|
||||
$this->update_array['name'] = null;
|
||||
}
|
||||
|
||||
if ($request->input('null_purchase_date')=='1') {
|
||||
$this->update_array['purchase_date'] = null;
|
||||
}
|
||||
@@ -483,12 +493,7 @@ class BulkAssetsController extends Controller
|
||||
if ($request->filled('ids')) {
|
||||
$assets = Asset::find($request->get('ids'));
|
||||
foreach ($assets as $asset) {
|
||||
$update_array['deleted_at'] = date('Y-m-d H:i:s');
|
||||
$update_array['assigned_to'] = null;
|
||||
|
||||
DB::table('assets')
|
||||
->where('id', $asset->id)
|
||||
->update($update_array);
|
||||
$asset->delete();
|
||||
} // endforeach
|
||||
|
||||
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
|
||||
|
||||
@@ -508,8 +508,8 @@ class LoginController extends Controller
|
||||
protected function validator(array $data)
|
||||
{
|
||||
return Validator::make($data, [
|
||||
'username' => 'required',
|
||||
'password' => 'required',
|
||||
'username' => 'required|not_array',
|
||||
'password' => 'required|not_array',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class ResetPasswordController extends Controller
|
||||
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
|
||||
];
|
||||
|
||||
$request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
|
||||
$request->validate($this->rules());
|
||||
|
||||
Log::debug('Checking if '.$request->input('username').' exists');
|
||||
// Check to see if the user even exists - we'll treat the response the same to prevent user sniffing
|
||||
|
||||
@@ -99,12 +99,18 @@ class SamlController extends Controller
|
||||
{
|
||||
$saml = $this->saml;
|
||||
$auth = $saml->getAuth();
|
||||
$auth->processResponse();
|
||||
$saml_exception = false;
|
||||
try {
|
||||
$auth->processResponse();
|
||||
} catch (\Exception $e) {
|
||||
Log::warning("Exception caught in SAML login: " . $e->getMessage());
|
||||
$saml_exception = true;
|
||||
}
|
||||
$errors = $auth->getErrors();
|
||||
|
||||
if (! empty($errors)) {
|
||||
Log::error('There was an error with SAML ACS: '.implode(', ', $errors));
|
||||
Log::error('Reason: '.$auth->getLastErrorReason());
|
||||
if (!empty($errors) || $saml_exception) {
|
||||
Log::warning('There was an error with SAML ACS: ' . implode(', ', $errors));
|
||||
Log::warning('Reason: ' . $auth->getLastErrorReason());
|
||||
|
||||
return redirect()->route('login')->with('error', trans('auth/message.signin.error'));
|
||||
}
|
||||
@@ -132,12 +138,18 @@ class SamlController extends Controller
|
||||
{
|
||||
$auth = $this->saml->getAuth();
|
||||
$retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false);
|
||||
$sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
|
||||
$saml_exception = false;
|
||||
try {
|
||||
$sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
|
||||
} catch (\Exception $e) {
|
||||
Log::warning("Exception caught in SAML single-logout: " . $e->getMessage());
|
||||
$saml_exception = true;
|
||||
}
|
||||
$errors = $auth->getErrors();
|
||||
|
||||
if (! empty($errors)) {
|
||||
Log::error('There was an error with SAML SLS: '.implode(', ', $errors));
|
||||
Log::error('Reason: '.$auth->getLastErrorReason());
|
||||
if (!empty($errors) || $saml_exception) {
|
||||
Log::warning('There was an error with SAML SLS: ' . implode(', ', $errors));
|
||||
Log::warning('Reason: ' . $auth->getLastErrorReason());
|
||||
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ trait CheckInOutRequest
|
||||
return Location::findOrFail(request('assigned_location'));
|
||||
case 'asset':
|
||||
return Asset::findOrFail(request('assigned_asset'));
|
||||
case 'user':
|
||||
default:
|
||||
return User::findOrFail(request('assigned_user'));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Events\ComponentCheckedIn;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Component;
|
||||
@@ -96,12 +97,10 @@ class ComponentCheckinController extends Controller
|
||||
$asset = Asset::find($component_assets->asset_id);
|
||||
|
||||
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
|
||||
if ($backto == 'asset'){
|
||||
return redirect()->route('hardware.show', $asset->id)->with('success',
|
||||
trans('admin/components/message.checkin.success'));
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('success',
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success',
|
||||
trans('admin/components/message.checkin.success'));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Events\ComponentCheckedOut;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Component;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
@@ -93,14 +95,18 @@ class ComponentCheckoutController extends Controller
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// Check if the user exists
|
||||
// Check if the asset exists
|
||||
$asset = Asset::find($request->input('asset_id'));
|
||||
|
||||
if ((Setting::getSettings()->full_multiple_companies_support) && $component->company_id !== $asset->company_id) {
|
||||
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
|
||||
}
|
||||
|
||||
// Update the component data
|
||||
$component->asset_id = $request->input('asset_id');
|
||||
$component->assets()->attach($component->id, [
|
||||
'component_id' => $component->id,
|
||||
'user_id' => auth()->user(),
|
||||
'user_id' => auth()->user()->id,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'assigned_qty' => $request->input('assigned_qty'),
|
||||
'asset_id' => $request->input('asset_id'),
|
||||
@@ -109,6 +115,11 @@ class ComponentCheckoutController extends Controller
|
||||
|
||||
event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note')));
|
||||
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
|
||||
$request->request->add(['checkout_to_type' => 'asset']);
|
||||
$request->request->add(['assigned_asset' => $asset->id]);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,10 @@ class ComponentsController extends Controller
|
||||
|
||||
$component = $request->handleImages($component);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
@@ -160,8 +162,10 @@ class ComponentsController extends Controller
|
||||
|
||||
$component = $request->handleImages($component);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Consumables;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
@@ -33,7 +34,7 @@ class ConsumableCheckoutController extends Controller
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($consumable->numRemaining() <= 0){
|
||||
return redirect()->route('consumables.index')
|
||||
->with('error', trans('admin/consumables/message.checkout.unavailable'));
|
||||
->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()]));
|
||||
}
|
||||
|
||||
// Return the checkout view
|
||||
@@ -76,7 +77,7 @@ class ConsumableCheckoutController extends Controller
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) {
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => $quantity, 'remaining' => $consumable->numRemaining() ]));
|
||||
}
|
||||
|
||||
$admin_user = auth()->user();
|
||||
@@ -101,7 +102,13 @@ class ConsumableCheckoutController extends Controller
|
||||
}
|
||||
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
|
||||
|
||||
$request->request->add(['checkout_to_type' => 'user']);
|
||||
$request->request->add(['assigned_user' => $user->id]);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
|
||||
// Redirect to the new consumable page
|
||||
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,10 @@ class ConsumablesController extends Controller
|
||||
|
||||
$consumable = $request->handleImages($consumable);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($consumable->save()) {
|
||||
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
|
||||
@@ -160,8 +162,10 @@ class ConsumablesController extends Controller
|
||||
|
||||
$consumable = $request->handleImages($consumable);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($consumable->save()) {
|
||||
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
|
||||
@@ -200,7 +204,7 @@ class ConsumablesController extends Controller
|
||||
*/
|
||||
public function show($consumableId = null)
|
||||
{
|
||||
$consumable = Consumable::find($consumableId);
|
||||
$consumable = Consumable::withCount('users as users_consumables')->find($consumableId);
|
||||
$this->authorize($consumable);
|
||||
if (isset($consumable->id)) {
|
||||
return view('consumables/view', compact('consumable'));
|
||||
@@ -209,4 +213,16 @@ class ConsumablesController extends Controller
|
||||
return redirect()->route('consumables.index')
|
||||
->with('error', trans('admin/consumables/message.does_not_exist'));
|
||||
}
|
||||
|
||||
public function clone(Consumable $consumable) : View
|
||||
{
|
||||
$this->authorize('create', $consumable);
|
||||
$consumable_to_close = $consumable;
|
||||
$consumable = clone $consumable_to_close;
|
||||
$consumable->id = null;
|
||||
$consumable->image = null;
|
||||
$consumable->user_id = null;
|
||||
|
||||
return view('consumables/edit')->with('item', $consumable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ class CustomFieldsetsController extends Controller
|
||||
return redirect()->route('fieldsets.show', [$id])->with('success', trans('admin/custom_fields/message.field.create.assoc_success'));
|
||||
}
|
||||
|
||||
return redirect()->route('fieldsets.show', [$id])->with('error', 'No field selected.');
|
||||
return redirect()->route('fieldsets.show', [$id])->with('error', trans('admin/custom_fields/message.field.none_selected'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,6 +62,20 @@ class DepreciationsController extends Controller
|
||||
$depreciation->name = $request->input('name');
|
||||
$depreciation->months = $request->input('months');
|
||||
$depreciation->user_id = Auth::id();
|
||||
|
||||
$request->validate([
|
||||
'depreciation_min' => [
|
||||
'required',
|
||||
'numeric',
|
||||
function ($attribute, $value, $fail) use ($request) {
|
||||
if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
|
||||
$fail(trans('validation.percent'));
|
||||
}
|
||||
},
|
||||
],
|
||||
'depreciation_type' => 'required|in:amount,percent',
|
||||
]);
|
||||
$depreciation->depreciation_type = $request->input('depreciation_type');
|
||||
$depreciation->depreciation_min = $request->input('depreciation_min');
|
||||
|
||||
// Was the asset created?
|
||||
@@ -116,6 +130,20 @@ class DepreciationsController extends Controller
|
||||
// Depreciation data
|
||||
$depreciation->name = $request->input('name');
|
||||
$depreciation->months = $request->input('months');
|
||||
|
||||
$request->validate([
|
||||
'depreciation_min' => [
|
||||
'required',
|
||||
'numeric',
|
||||
function ($attribute, $value, $fail) use ($request) {
|
||||
if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
|
||||
$fail(trans('validation.percent'));
|
||||
}
|
||||
},
|
||||
],
|
||||
'depreciation_type' => 'required|in:amount,percent',
|
||||
]);
|
||||
$depreciation->depreciation_type = $request->input('depreciation_type');
|
||||
$depreciation->depreciation_min = $request->input('depreciation_min');
|
||||
|
||||
// Was the asset created?
|
||||
@@ -165,13 +193,20 @@ class DepreciationsController extends Controller
|
||||
*/
|
||||
public function show($id) : View | RedirectResponse
|
||||
{
|
||||
if (is_null($depreciation = Depreciation::find($id))) {
|
||||
// Redirect to the blogs management page
|
||||
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
|
||||
}
|
||||
$depreciation = Depreciation::withCount('assets as assets_count')
|
||||
->withCount('models as models_count')
|
||||
->withCount('licenses as licenses_count')
|
||||
->find($id);
|
||||
|
||||
$this->authorize('view', $depreciation);
|
||||
|
||||
return view('depreciations/view', compact('depreciation'));
|
||||
if ($depreciation) {
|
||||
return view('depreciations/view', compact('depreciation'));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,10 @@ class CheckoutKitController extends Controller
|
||||
|
||||
$checkout_result = $this->kitService->checkout($request, $kit, $user);
|
||||
if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) {
|
||||
return redirect()->back()->with('error', trans('general.checkout_error'))->with('error_messages', $checkout_result['errors']);
|
||||
return redirect()->back()->with('error', trans('admin/kits/general.checkout_error'))->with('error_messages', $checkout_result['errors']);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans('general.checkout_success'))
|
||||
return redirect()->back()->with('success', trans('admin/kits/general.checkout_success'))
|
||||
->with('assets', Arr::get($checkout_result, 'assets', null))
|
||||
->with('accessories', Arr::get($checkout_result, 'accessories', null))
|
||||
->with('consumables', Arr::get($checkout_result, 'consumables', null));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Licenses;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
@@ -100,15 +101,15 @@ class LicenseCheckinController extends Controller
|
||||
$licenseSeat->asset_id = null;
|
||||
$licenseSeat->notes = $request->input('notes');
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
|
||||
// Was the asset updated?
|
||||
if ($licenseSeat->save()) {
|
||||
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
|
||||
|
||||
if ($backTo == 'user') {
|
||||
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
|
||||
}
|
||||
|
||||
return redirect()->route('licenses.show', $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success'));
|
||||
}
|
||||
|
||||
// Redirect to the license page with error
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Licenses;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\LicenseCheckoutRequest;
|
||||
use App\Models\Accessory;
|
||||
@@ -81,10 +82,27 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
|
||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
||||
if ($this->$checkoutMethod($licenseSeat)) {
|
||||
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.checkout.success'));
|
||||
|
||||
if ($request->filled('asset_id')) {
|
||||
|
||||
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
||||
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
||||
|
||||
} elseif ($request->filled('assigned_to')) {
|
||||
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
||||
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($checkoutTarget) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.'));
|
||||
}
|
||||
|
||||
@@ -94,14 +112,14 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
if (! $licenseSeat) {
|
||||
if ($seatId) {
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.'));
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.unavailable')));
|
||||
}
|
||||
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.'));
|
||||
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)) {
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.'));
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
|
||||
}
|
||||
|
||||
return $licenseSeat;
|
||||
@@ -120,8 +138,7 @@ class LicenseCheckoutController extends Controller
|
||||
}
|
||||
if ($licenseSeat->save()) {
|
||||
event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
|
||||
|
||||
return true;
|
||||
return $target;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -137,8 +154,7 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
if ($licenseSeat->save()) {
|
||||
event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
|
||||
|
||||
return true;
|
||||
return $target;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -102,8 +102,10 @@ class LicensesController extends Controller
|
||||
$license->user_id = Auth::id();
|
||||
$license->min_amt = $request->input('min_amt');
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($license->save()) {
|
||||
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($license->getErrors());
|
||||
@@ -180,8 +182,10 @@ class LicensesController extends Controller
|
||||
$license->category_id = $request->input('category_id');
|
||||
$license->min_amt = $request->input('min_amt');
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($license->save()) {
|
||||
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success'));
|
||||
}
|
||||
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
|
||||
return redirect()->back()->withInput()->withErrors($license->getErrors());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Location;
|
||||
use App\Models\User;
|
||||
@@ -193,7 +194,13 @@ class LocationsController extends Controller
|
||||
*/
|
||||
public function show($id = null) : View | RedirectResponse
|
||||
{
|
||||
$location = Location::find($id);
|
||||
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count')
|
||||
->withTrashed()
|
||||
->find($id);
|
||||
|
||||
if (isset($location->id)) {
|
||||
return view('locations/view', compact('location'));
|
||||
@@ -249,6 +256,41 @@ class LocationsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restore a given Asset Model (mark as un-deleted)
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $id
|
||||
*/
|
||||
public function postRestore($id) : RedirectResponse
|
||||
{
|
||||
$this->authorize('create', Location::class);
|
||||
|
||||
if ($location = Location::withTrashed()->find($id)) {
|
||||
|
||||
if ($location->deleted_at == '') {
|
||||
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.location')]));
|
||||
}
|
||||
|
||||
if ($location->restore()) {
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = Location::class;
|
||||
$logaction->item_id = $location->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = auth()->id();
|
||||
$logaction->logaction('restore');
|
||||
|
||||
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('admin/models/message.does_not_exist'));
|
||||
|
||||
}
|
||||
public function print_all_assigned($id) : View | RedirectResponse
|
||||
{
|
||||
if ($location = Location::where('id', $id)->first()) {
|
||||
|
||||
@@ -49,6 +49,8 @@ class ProfileController extends Controller
|
||||
$user->gravatar = $request->input('gravatar');
|
||||
$user->skin = $request->input('skin');
|
||||
$user->phone = $request->input('phone');
|
||||
$user->enable_sounds = $request->input('enable_sounds', false);
|
||||
$user->enable_confetti = $request->input('enable_confetti', false);
|
||||
|
||||
if (! config('app.lock_passwords')) {
|
||||
$user->locale = $request->input('locale', 'en-US');
|
||||
|
||||
@@ -14,7 +14,6 @@ use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use App\Notifications\FirstAdminNotification;
|
||||
use App\Notifications\MailTest;
|
||||
use Illuminate\Http\Client\HttpClientException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -129,11 +128,11 @@ class SettingsController extends Controller
|
||||
protected function dotEnvFileIsExposed() : bool
|
||||
{
|
||||
try {
|
||||
return Http::timeout(10)
|
||||
return Http::withoutVerifying()->timeout(10)
|
||||
->accept('*/*')
|
||||
->get(URL::to('.env'))
|
||||
->successful();
|
||||
} catch (HttpClientException $e) {
|
||||
} catch (\Exception $e) {
|
||||
Log::debug($e->getMessage());
|
||||
return true;
|
||||
}
|
||||
@@ -325,6 +324,7 @@ class SettingsController extends Controller
|
||||
|
||||
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
|
||||
$setting->unique_serial = $request->input('unique_serial', '0');
|
||||
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
|
||||
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
|
||||
$setting->show_archived_in_list = $request->input('show_archived_in_list', '0');
|
||||
$setting->dashboard_message = $request->input('dashboard_message');
|
||||
@@ -414,10 +414,7 @@ class SettingsController extends Controller
|
||||
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
|
||||
|
||||
if ($request->input('clear_logo') == '1') {
|
||||
|
||||
if (($setting->logo) && (Storage::exists($setting->logo))) {
|
||||
Storage::disk('public')->delete($setting->logo);
|
||||
}
|
||||
$setting = $request->deleteExistingImage($setting, '', 'logo');
|
||||
$setting->logo = null;
|
||||
$setting->brand = 1;
|
||||
}
|
||||
@@ -425,43 +422,38 @@ class SettingsController extends Controller
|
||||
// Email logo upload
|
||||
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
|
||||
if ($request->input('clear_email_logo') == '1') {
|
||||
|
||||
if (($setting->email_logo) && (Storage::exists($setting->email_logo))) {
|
||||
Storage::disk('public')->delete($setting->email_logo);
|
||||
}
|
||||
$setting = $request->deleteExistingImage($setting, '', 'email_logo');
|
||||
$setting->email_logo = null;
|
||||
// If they are uploading an image, validate it and upload it
|
||||
}
|
||||
|
||||
// Label logo upload
|
||||
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
|
||||
if ($request->input('clear_label_logo') == '1') {
|
||||
|
||||
if (($setting->label_logo) && (Storage::exists($setting->label_logo))) {
|
||||
Storage::disk('public')->delete($setting->label_logo);
|
||||
}
|
||||
if ($request->input('clear_label_logo') == '1') {
|
||||
$setting = $request->deleteExistingImage($setting, '', 'label_logo');
|
||||
$setting->label_logo = null;
|
||||
}
|
||||
|
||||
// Favicon upload
|
||||
$setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon');
|
||||
if ('1' == $request->input('clear_favicon')) {
|
||||
|
||||
if (($setting->favicon) && (Storage::exists($setting->favicon))) {
|
||||
Storage::disk('public')->delete($setting->favicon);
|
||||
}
|
||||
$setting = $request->deleteExistingImage($setting, '', 'favicon');
|
||||
$setting->favicon = null;
|
||||
}
|
||||
|
||||
// Default avatar upload
|
||||
$setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar');
|
||||
if ($request->input('clear_default_avatar') == '1') {
|
||||
|
||||
if (($setting->default_avatar) && (Storage::exists('avatars/'.$setting->default_avatar))) {
|
||||
Storage::disk('public')->delete('avatars/'.$setting->default_avatar);
|
||||
if ($request->input('clear_default_avatar') == '1') {
|
||||
// Don't delete the file, just update the field if this is the default
|
||||
if ($setting->default_avatar!='default.png') {
|
||||
$setting = $request->deleteExistingImage($setting, 'avatars', 'default_avatar');
|
||||
}
|
||||
$setting->default_avatar = null;
|
||||
}
|
||||
|
||||
if ($request->input('restore_default_avatar') == '1') {
|
||||
$setting->default_avatar = 'default.png';
|
||||
}
|
||||
}
|
||||
|
||||
if ($setting->save()) {
|
||||
@@ -645,6 +637,7 @@ class SettingsController extends Controller
|
||||
$setting->alert_threshold = $request->input('alert_threshold');
|
||||
$setting->audit_interval = $request->input('audit_interval');
|
||||
$setting->audit_warning_days = $request->input('audit_warning_days');
|
||||
$setting->due_checkin_days = $request->input('due_checkin_days');
|
||||
$setting->show_alerts_in_menu = $request->input('show_alerts_in_menu', '0');
|
||||
|
||||
if ($setting->save()) {
|
||||
@@ -1211,7 +1204,7 @@ class SettingsController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0]
|
||||
*/
|
||||
public function postRestore($filename = null) : RedirectResponse
|
||||
public function postRestore(Request $request, $filename = null): RedirectResponse
|
||||
{
|
||||
|
||||
if (! config('app.lock_passwords')) {
|
||||
@@ -1231,13 +1224,29 @@ class SettingsController extends Controller
|
||||
|
||||
Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename);
|
||||
|
||||
// run the restore command
|
||||
Artisan::call('snipeit:restore',
|
||||
[
|
||||
$restore_params = [
|
||||
'--force' => true,
|
||||
'--no-progress' => true,
|
||||
'filename' => storage_path($path).'/'.$filename
|
||||
]);
|
||||
'filename' => storage_path($path) . '/' . $filename
|
||||
];
|
||||
|
||||
if ($request->input('clean')) {
|
||||
Log::debug("Attempting 'clean' - first, guessing prefix...");
|
||||
Artisan::call('snipeit:restore', [
|
||||
'--sanitize-guess-prefix' => true,
|
||||
'filename' => storage_path($path) . '/' . $filename
|
||||
]);
|
||||
$guess_prefix_output = Artisan::output();
|
||||
Log::debug("Sanitize output is: $guess_prefix_output");
|
||||
list($prefix, $_output) = explode("\n", $guess_prefix_output);
|
||||
Log::debug("prefix is: '$prefix'");
|
||||
$restore_params['--sanitize-with-prefix'] = $prefix;
|
||||
}
|
||||
|
||||
// run the restore command
|
||||
Artisan::call('snipeit:restore',
|
||||
$restore_params
|
||||
);
|
||||
|
||||
// If it's greater than 300, it probably worked
|
||||
$output = Artisan::output();
|
||||
@@ -1264,7 +1273,7 @@ class SettingsController extends Controller
|
||||
DB::table('users')->update(['remember_token' => null]);
|
||||
Auth::logout();
|
||||
|
||||
return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.');
|
||||
return redirect()->route('login')->with('success', trans('admin/settings/message.restore.success'));
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found'));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
@@ -29,7 +30,7 @@ class BulkUsersController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.7]
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @return \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit(Request $request)
|
||||
@@ -115,6 +116,8 @@ class BulkUsersController extends Controller
|
||||
->conditionallyAddItem('remote')
|
||||
->conditionallyAddItem('ldap_import')
|
||||
->conditionallyAddItem('activated')
|
||||
->conditionallyAddItem('start_date')
|
||||
->conditionallyAddItem('end_date')
|
||||
->conditionallyAddItem('autoassign_licenses');
|
||||
|
||||
|
||||
@@ -145,7 +148,14 @@ class BulkUsersController extends Controller
|
||||
$this->update_array['company_id'] = null;
|
||||
}
|
||||
|
||||
|
||||
if ($request->input('null_start_date')=='1') {
|
||||
$this->update_array['start_date'] = null;
|
||||
}
|
||||
|
||||
if ($request->input('null_end_date')=='1') {
|
||||
$this->update_array['end_date'] = null;
|
||||
}
|
||||
|
||||
if (! $manager_conflict) {
|
||||
$this->conditionallyAddItem('manager_id');
|
||||
}
|
||||
@@ -218,21 +228,19 @@ class BulkUsersController extends Controller
|
||||
}
|
||||
|
||||
$users = User::whereIn('id', $user_raw_array)->get();
|
||||
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get();
|
||||
$accessories = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', User::class)->get();
|
||||
$accessoryUserRows = DB::table('accessories_checkout')->where('assigned_type', User::class)->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$consumables = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$consumableUserRows = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
|
||||
if ((($assets->count() > 0) && ((!$request->filled('status_id')) || ($request->input('status_id') == '')))) {
|
||||
return redirect()->route('users.index')->with('error', 'No status selected');
|
||||
}
|
||||
|
||||
|
||||
$this->logItemCheckinAndDelete($assets, Asset::class);
|
||||
$this->logItemCheckinAndDelete($accessories, Accessory::class);
|
||||
$this->logAccessoriesCheckin($accessoryUserRows);
|
||||
$this->logItemCheckinAndDelete($licenses, License::class);
|
||||
$this->logItemCheckinAndDelete($consumables, Consumable::class);
|
||||
|
||||
$this->logConsumablesCheckin($consumableUserRows);
|
||||
|
||||
Asset::whereIn('id', $assets->pluck('id'))->update([
|
||||
'status_id' => e(request('status_id')),
|
||||
@@ -241,19 +249,14 @@ class BulkUsersController extends Controller
|
||||
'expected_checkin' => null,
|
||||
]);
|
||||
|
||||
|
||||
LicenseSeat::whereIn('id', $licenses->pluck('id'))->update(['assigned_to' => null]);
|
||||
ConsumableAssignment::whereIn('id', $consumables->pluck('id'))->delete();
|
||||
|
||||
ConsumableAssignment::whereIn('id', $consumableUserRows->pluck('id'))->delete();
|
||||
|
||||
foreach ($users as $user) {
|
||||
|
||||
$user->consumables()->sync([]);
|
||||
$user->accessories()->sync([]);
|
||||
if ($request->input('delete_user')=='1') {
|
||||
$user->delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$msg = trans('general.bulk_checkin_success');
|
||||
@@ -279,7 +282,7 @@ class BulkUsersController extends Controller
|
||||
if ($itemType == License::class){
|
||||
$item_id = $item->license_id;
|
||||
}
|
||||
|
||||
|
||||
$logAction->item_id = $item_id;
|
||||
// We can't rely on get_class here because the licenses/accessories fetched above are not eloquent models, but simply arrays.
|
||||
$logAction->item_type = $itemType;
|
||||
@@ -291,6 +294,34 @@ class BulkUsersController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function logAccessoriesCheckin(Collection $accessoryUserRows): void
|
||||
{
|
||||
foreach ($accessoryUserRows as $accessoryUserRow) {
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $accessoryUserRow->accessory_id;
|
||||
$logAction->item_type = Accessory::class;
|
||||
$logAction->target_id = $accessoryUserRow->assigned_to;
|
||||
$logAction->target_type = User::class;
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->note = 'Bulk checkin items';
|
||||
$logAction->logaction('checkin from');
|
||||
}
|
||||
}
|
||||
|
||||
private function logConsumablesCheckin(Collection $consumableUserRows): void
|
||||
{
|
||||
foreach ($consumableUserRows as $consumableUserRow) {
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $consumableUserRow->consumable_id;
|
||||
$logAction->item_type = Consumable::class;
|
||||
$logAction->target_id = $consumableUserRow->assigned_to;
|
||||
$logAction->target_type = User::class;
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->note = 'Bulk checkin items';
|
||||
$logAction->logaction('checkin from');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save bulk-edited users
|
||||
*
|
||||
|
||||
@@ -133,6 +133,8 @@ class UsersController extends Controller
|
||||
// we have to invoke the
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($user->save()) {
|
||||
if ($request->filled('groups')) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
@@ -152,7 +154,7 @@ class UsersController extends Controller
|
||||
$user->notify(new WelcomeNotification($data));
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.create'));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))->with('success', trans('admin/users/message.success.create'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
@@ -184,7 +186,7 @@ class UsersController extends Controller
|
||||
{
|
||||
|
||||
$this->authorize('update', User::class);
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id);
|
||||
$user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($id);
|
||||
|
||||
if ($user) {
|
||||
|
||||
@@ -212,83 +214,79 @@ class UsersController extends Controller
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(SaveUserRequest $request, $id = null)
|
||||
public function update(SaveUserRequest $request, User $user)
|
||||
{
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
// This is a janky hack to prevent people from changing admin demo user data on the public demo.
|
||||
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
|
||||
// Thanks, jerks. You are why we can't have nice things. - snipe
|
||||
|
||||
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
|
||||
if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) {
|
||||
return redirect()->route('users.index')->with('error', trans('general.permission_denied_superuser_demo'));
|
||||
}
|
||||
|
||||
|
||||
// We need to reverse the UI specific logic for our
|
||||
// permissions here before we update the user.
|
||||
$permissions = $request->input('permissions', []);
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id);
|
||||
$user->load(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed();
|
||||
|
||||
// User is valid - continue...
|
||||
if ($user) {
|
||||
$this->authorize('update', $user);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
// Figure out of this user was an admin before this edit
|
||||
$orig_permissions_array = $user->decodePermissions();
|
||||
$orig_superuser = '0';
|
||||
if (is_array($orig_permissions_array)) {
|
||||
if (array_key_exists('superuser', $orig_permissions_array)) {
|
||||
$orig_superuser = $orig_permissions_array['superuser'];
|
||||
}
|
||||
// Figure out of this user was an admin before this edit
|
||||
$orig_permissions_array = $user->decodePermissions();
|
||||
$orig_superuser = '0';
|
||||
if (is_array($orig_permissions_array)) {
|
||||
if (array_key_exists('superuser', $orig_permissions_array)) {
|
||||
$orig_superuser = $orig_permissions_array['superuser'];
|
||||
}
|
||||
}
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (auth()->user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
// Only save groups if the user is a superuser
|
||||
if (auth()->user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
|
||||
// Update the user fields
|
||||
$user->username = trim($request->input('username'));
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
$user->manager_id = $request->input('manager_id', null);
|
||||
$user->notes = $request->input('notes');
|
||||
$user->department_id = $request->input('department_id', null);
|
||||
$user->address = $request->input('address', null);
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
// Update the user fields
|
||||
$user->username = trim($request->input('username'));
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
$user->manager_id = $request->input('manager_id', null);
|
||||
$user->notes = $request->input('notes');
|
||||
$user->department_id = $request->input('department_id', null);
|
||||
$user->address = $request->input('address', null);
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
@@ -309,19 +307,14 @@ class UsersController extends Controller
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($user->save()) {
|
||||
// Redirect to the user page
|
||||
return redirect()->route('users.index')
|
||||
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))
|
||||
->with('success', trans('admin/users/message.success.update'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -598,29 +591,29 @@ class UsersController extends Controller
|
||||
/**
|
||||
* Print inventory
|
||||
*
|
||||
* @author Aladin Alaily
|
||||
* @since [v1.8]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @author Aladin Alaily
|
||||
*/
|
||||
public function printInventory($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$user = User::where('id', $id)->withTrashed()->first();
|
||||
|
||||
if ($user = User::where('id', $id)->withTrashed()->first()) {
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user);
|
||||
$this->authorize('view', $user);
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get();
|
||||
$accessories = $user->accessories()->get();
|
||||
$consumables = $user->consumables()->get();
|
||||
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get();
|
||||
$accessories = $user->accessories()->get();
|
||||
$consumables = $user->consumables()->get();
|
||||
return view('users/print')->with('assets', $assets)
|
||||
->with('licenses', $user->licenses()->get())
|
||||
->with('accessories', $accessories)
|
||||
->with('consumables', $consumables)
|
||||
->with('show_user', $user)
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
|
||||
return view('users/print')->with('assets', $assets)
|
||||
->with('licenses', $user->licenses()->get())
|
||||
->with('accessories', $accessories)
|
||||
->with('consumables', $consumables)
|
||||
->with('show_user', $user)
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ class Kernel extends HttpKernel
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\App\Http\Middleware\NoSessionStore::class,
|
||||
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
@@ -21,6 +22,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\CheckForSetup::class,
|
||||
\App\Http\Middleware\CheckForDebug::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\App\Http\Middleware\SecurityHeaders::class,
|
||||
\App\Http\Middleware\PreventBackHistory::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
@@ -43,10 +45,13 @@ class Kernel extends HttpKernel
|
||||
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
|
||||
\App\Http\Middleware\AssetCountForSidebar::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'auth:api',
|
||||
\App\Http\Middleware\CheckLocale::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -20,5 +20,5 @@ class EncryptCookies extends BaseEncrypter
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $serialize = true;
|
||||
protected static $serialize = false;
|
||||
}
|
||||
|
||||
76
app/Http/Requests/AccessoryCheckoutRequest.php
Normal file
76
app/Http/Requests/AccessoryCheckoutRequest.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AccessoryCheckoutRequest extends ImageUploadRequest
|
||||
{
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::allows('checkout', new Accessory);
|
||||
}
|
||||
|
||||
public function prepareForValidation(): void
|
||||
{
|
||||
|
||||
if ($this->accessory) {
|
||||
|
||||
$this->diff = ($this->accessory->numRemaining() - $this->checkout_qty);
|
||||
$this->merge([
|
||||
'checkout_qty' => $this->checkout_qty ?? 1,
|
||||
'number_remaining_after_checkout' => (int) ($this->accessory->numRemaining() - $this->checkout_qty),
|
||||
'number_currently_remaining' => (int) $this->accessory->numRemaining(),
|
||||
'checkout_difference' => (int) $this->diff,
|
||||
]);
|
||||
|
||||
\Log::debug('---------------------------------------------');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
||||
return array_merge(
|
||||
[
|
||||
'assigned_user' => 'required_without_all:assigned_asset,assigned_location',
|
||||
'assigned_asset' => 'required_without_all:assigned_user,assigned_location',
|
||||
'assigned_location' => 'required_without_all:assigned_user,assigned_asset',
|
||||
|
||||
'number_remaining_after_checkout' => [
|
||||
'min:0',
|
||||
'required',
|
||||
'integer',
|
||||
],
|
||||
|
||||
'checkout_qty' => [
|
||||
'integer',
|
||||
'lte:number_currently_remaining',
|
||||
'min:1',
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
$messages = [
|
||||
'checkout_qty.lte' => trans_choice('admin/accessories/message.checkout.checkout_qty.lte', $this->number_currently_remaining, [
|
||||
'number_currently_remaining' => $this->number_currently_remaining,
|
||||
'checkout_qty' => $this->checkout_qty,
|
||||
]),
|
||||
];
|
||||
return $messages;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ImageUploadRequest extends Request
|
||||
{
|
||||
@@ -123,7 +124,7 @@ class ImageUploadRequest extends Request
|
||||
|
||||
} catch(NotReadableException $e) {
|
||||
Log::debug($e);
|
||||
$validator = \Validator::make([], []);
|
||||
$validator = Validator::make([], []);
|
||||
$validator->errors()->add($form_fieldname, trans('general.unaccepted_image_type', ['mimetype' => $image->getClientMimeType()]));
|
||||
|
||||
throw new \Illuminate\Validation\ValidationException($validator);
|
||||
@@ -135,28 +136,28 @@ class ImageUploadRequest extends Request
|
||||
}
|
||||
|
||||
// Remove Current image if exists
|
||||
if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
|
||||
}
|
||||
}
|
||||
|
||||
$item = $this->deleteExistingImage($item, $path, $db_fieldname);
|
||||
$item->{$db_fieldname} = $file_name;
|
||||
}
|
||||
|
||||
|
||||
// If the user isn't uploading anything new but wants to delete their old image, do so
|
||||
} elseif ($this->input('image_delete') == '1') {
|
||||
Log::debug('Deleting image');
|
||||
$item = $this->deleteExistingImage($item, $path, $db_fieldname);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function deleteExistingImage($item, $path = null, $db_fieldname = 'image') {
|
||||
|
||||
if ($item->{$db_fieldname}!='') {
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$db_fieldname});
|
||||
$item->{$db_fieldname} = null;
|
||||
$item->{$db_fieldname} = null;
|
||||
} catch (\Exception $e) {
|
||||
Log::debug($e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $item;
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\Setting;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use App\Rules\UserCannotSwitchCompaniesIfItemsAssigned;
|
||||
|
||||
class SaveUserRequest extends FormRequest
|
||||
{
|
||||
@@ -34,6 +35,7 @@ class SaveUserRequest extends FormRequest
|
||||
$rules = [
|
||||
'department_id' => 'nullable|exists:departments,id',
|
||||
'manager_id' => 'nullable|exists:users,id',
|
||||
'company_id' => ['nullable','exists:companies,id']
|
||||
];
|
||||
|
||||
switch ($this->method()) {
|
||||
@@ -52,11 +54,13 @@ class SaveUserRequest extends FormRequest
|
||||
$rules['first_name'] = 'required|string|min:1';
|
||||
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
|
||||
$rules['password'] = Setting::passwordComplexityRulesSaving('update').'|confirmed';
|
||||
$rules['company_id'] = [new UserCannotSwitchCompaniesIfItemsAssigned()];
|
||||
break;
|
||||
|
||||
// Save only what's passed
|
||||
case 'PATCH':
|
||||
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
|
||||
$rules['company_id'] = [new UserCannotSwitchCompaniesIfItemsAssigned()];
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
56
app/Http/Requests/StoreAccessoryRequest.php
Normal file
56
app/Http/Requests/StoreAccessoryRequest.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Category;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class StoreAccessoryRequest extends ImageUploadRequest
|
||||
{
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::allows('create', new Accessory);
|
||||
}
|
||||
|
||||
public function prepareForValidation(): void
|
||||
{
|
||||
|
||||
if ($this->category_id) {
|
||||
if ($category = Category::find($this->category_id)) {
|
||||
$this->merge([
|
||||
'category_type' => $category->category_type ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
['category_type' => 'in:accessory'],
|
||||
parent::rules(),
|
||||
);
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
$messages = ['category_type.in' => trans('admin/accessories/message.invalid_category_type')];
|
||||
return $messages;
|
||||
}
|
||||
|
||||
public function response(array $errors)
|
||||
{
|
||||
return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Http\Requests\Traits\MayContainCustomFields;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Setting;
|
||||
@@ -11,6 +12,7 @@ use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class StoreAssetRequest extends ImageUploadRequest
|
||||
{
|
||||
use MayContainCustomFields;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
|
||||
39
app/Http/Requests/Traits/MayContainCustomFields.php
Normal file
39
app/Http/Requests/Traits/MayContainCustomFields.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Traits;
|
||||
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\CustomField;
|
||||
|
||||
trait MayContainCustomFields
|
||||
{
|
||||
// this gets called automatically on a form request
|
||||
public function withValidator($validator)
|
||||
{
|
||||
// find the model
|
||||
if ($this->method() == 'POST') {
|
||||
$asset_model = AssetModel::find($this->model_id);
|
||||
}
|
||||
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
|
||||
$asset_model = $this->asset->model;
|
||||
}
|
||||
// collect the custom fields in the request
|
||||
$validator->after(function ($validator) use ($asset_model) {
|
||||
$request_fields = $this->collect()->keys()->filter(function ($attributes) {
|
||||
return str_starts_with($attributes, '_snipeit_');
|
||||
});
|
||||
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
|
||||
if (count($request_fields) > 0) {
|
||||
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
|
||||
->each(function ($request_field_name) use ($request_fields, $validator) {
|
||||
if (CustomField::where('db_column', $request_field_name)->exists()) {
|
||||
$validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found_on_model'));
|
||||
} else {
|
||||
$validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found'));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
53
app/Http/Requests/UpdateAssetRequest.php
Normal file
53
app/Http/Requests/UpdateAssetRequest.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Http\Requests\Traits\MayContainCustomFields;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateAssetRequest extends ImageUploadRequest
|
||||
{
|
||||
use MayContainCustomFields;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return Gate::allows('update', $this->asset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$rules = array_merge(
|
||||
parent::rules(),
|
||||
(new Asset)->getRules(),
|
||||
// this is to overwrite rulesets that include required, and rewrite unique_undeleted
|
||||
[
|
||||
'model_id' => ['integer', 'exists:models,id,deleted_at,NULL', 'not_array'],
|
||||
'status_id' => ['integer', 'exists:status_labels,id'],
|
||||
'asset_tag' => [
|
||||
'min:1', 'max:255', 'not_array',
|
||||
Rule::unique('assets', 'asset_tag')->ignore($this->asset)->withoutTrashed()
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// if the purchase cost is passed in as a string **and** the digit_separator is ',' (as is common in the EU)
|
||||
// then we tweak the purchase_cost rule to make it a string
|
||||
if (Setting::getSettings()->digit_separator === '1.234,56' && is_string($this->input('purchase_cost'))) {
|
||||
$rules['purchase_cost'] = ['nullable', 'string'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,17 @@ trait TwoColumnUniqueUndeletedTrait
|
||||
* @param string $field
|
||||
* @return string
|
||||
*/
|
||||
protected function prepareTwoColumnUniqueUndeletedRule($parameters, $field)
|
||||
protected function prepareTwoColumnUniqueUndeletedRule($parameters)
|
||||
{
|
||||
$column = $parameters[0];
|
||||
$value = $this->{$parameters[0]};
|
||||
|
||||
// This is an existing model we're updating so ignore the current ID ($this->getKey())
|
||||
if ($this->exists) {
|
||||
return 'two_column_unique_undeleted:'.$this->table.','.$this->getKey().','.$column.','.$value;
|
||||
}
|
||||
|
||||
// This is a new record, so we can ignore the current ID
|
||||
return 'two_column_unique_undeleted:'.$this->table.',0,'.$column.','.$value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class AccessoriesTransformer
|
||||
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
|
||||
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
|
||||
'remaining_qty' => (int) $accessory->numRemaining(),
|
||||
'users_count' => $accessory->users_count,
|
||||
'checkouts_count' => $accessory->checkouts_count,
|
||||
|
||||
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
|
||||
@@ -66,27 +66,42 @@ class AccessoriesTransformer
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function transformCheckedoutAccessory($accessory, $accessory_users, $total)
|
||||
public function transformCheckedoutAccessory($accessory, $accessory_checkouts, $total)
|
||||
{
|
||||
$array = [];
|
||||
|
||||
foreach ($accessory_users as $user) {
|
||||
foreach ($accessory_checkouts as $checkout) {
|
||||
$array[] = [
|
||||
|
||||
'assigned_pivot_id' => $user->pivot->id,
|
||||
'id' => (int) $user->id,
|
||||
'username' => e($user->username),
|
||||
'name' => e($user->getFullNameAttribute()),
|
||||
'first_name'=> e($user->first_name),
|
||||
'last_name'=> e($user->last_name),
|
||||
'employee_number' => e($user->employee_num),
|
||||
'checkout_notes' => e($user->pivot->note),
|
||||
'last_checkout' => Helper::getFormattedDateObject($user->pivot->created_at, 'datetime'),
|
||||
'type' => 'user',
|
||||
'id' => $checkout->id,
|
||||
'assigned_to' => $this->transformAssignedTo($checkout),
|
||||
'checkout_notes' => e($checkout->note),
|
||||
'last_checkout' => Helper::getFormattedDateObject($checkout->created_at, 'datetime'),
|
||||
'available_actions' => ['checkin' => true],
|
||||
];
|
||||
}
|
||||
|
||||
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
||||
}
|
||||
|
||||
public function transformAssignedTo($accessoryCheckout)
|
||||
{
|
||||
if ($accessoryCheckout->checkedOutToUser()) {
|
||||
return [
|
||||
'id' => (int) $accessoryCheckout->assigned->id,
|
||||
'username' => e($accessoryCheckout->assigned->username),
|
||||
'name' => e($accessoryCheckout->assigned->getFullNameAttribute()),
|
||||
'first_name'=> e($accessoryCheckout->assigned->first_name),
|
||||
'last_name'=> ($accessoryCheckout->assigned->last_name) ? e($accessoryCheckout->assigned->last_name) : null,
|
||||
'email'=> ($accessoryCheckout->assigned->email) ? e($accessoryCheckout->assigned->email) : null,
|
||||
'employee_number' => ($accessoryCheckout->assigned->employee_num) ? e($accessoryCheckout->assigned->employee_num) : null,
|
||||
'type' => 'user',
|
||||
];
|
||||
}
|
||||
|
||||
return $accessoryCheckout->assigned ? [
|
||||
'id' => $accessoryCheckout->assigned->id,
|
||||
'name' => e($accessoryCheckout->assigned->display_name),
|
||||
'type' => $accessoryCheckout->assignedType(),
|
||||
] : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,11 +205,11 @@ class ActionlogsTransformer
|
||||
|
||||
|
||||
|
||||
public function transformCheckedoutActionlog (Collection $accessories_users, $total)
|
||||
public function transformCheckedoutActionlog (Collection $accessories_checkout, $total)
|
||||
{
|
||||
|
||||
$array = array();
|
||||
foreach ($accessories_users as $user) {
|
||||
foreach ($accessories_checkout as $user) {
|
||||
$array[] = (new UsersTransformer)->transformUser($user);
|
||||
}
|
||||
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
||||
@@ -256,7 +256,7 @@ class ActionlogsTransformer
|
||||
|
||||
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $oldRtdName : '';
|
||||
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $newRtdName : '';
|
||||
$clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
|
||||
$clean_meta[trans('admin/hardware/form.default_location')] = $clean_meta['rtd_location_id'];
|
||||
unset($clean_meta['rtd_location_id']);
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ class ActionlogsTransformer
|
||||
|
||||
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ". $oldLocationName : '';
|
||||
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ". $newLocationName : '';
|
||||
$clean_meta['Current Location'] = $clean_meta['location_id'];
|
||||
$clean_meta[trans('admin/locations/message.current_location')] = $clean_meta['location_id'];
|
||||
unset($clean_meta['location_id']);
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ class ActionlogsTransformer
|
||||
$clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".$oldModelName;
|
||||
$clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".$newModelName; /** model is required at asset creation */
|
||||
|
||||
$clean_meta['Model'] = $clean_meta['model_id'];
|
||||
$clean_meta[trans('admin/hardware/form.model')] = $clean_meta['model_id'];
|
||||
unset($clean_meta['model_id']);
|
||||
}
|
||||
if(array_key_exists('company_id', $clean_meta)) {
|
||||
@@ -300,7 +300,7 @@ class ActionlogsTransformer
|
||||
|
||||
$clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."] ". $oldCompanyName : trans('general.unassigned');
|
||||
$clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ". $newCompanyName : trans('general.unassigned');
|
||||
$clean_meta['Company'] = $clean_meta['company_id'];
|
||||
$clean_meta[trans('general.company')] = $clean_meta['company_id'];
|
||||
unset($clean_meta['company_id']);
|
||||
}
|
||||
if(array_key_exists('supplier_id', $clean_meta)) {
|
||||
@@ -313,7 +313,7 @@ class ActionlogsTransformer
|
||||
|
||||
$clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ". $oldSupplierName : trans('general.unassigned');
|
||||
$clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ". $newSupplierName : trans('general.unassigned');
|
||||
$clean_meta['Supplier'] = $clean_meta['supplier_id'];
|
||||
$clean_meta[trans('general.supplier')] = $clean_meta['supplier_id'];
|
||||
unset($clean_meta['supplier_id']);
|
||||
}
|
||||
if(array_key_exists('status_id', $clean_meta)) {
|
||||
@@ -326,11 +326,11 @@ class ActionlogsTransformer
|
||||
|
||||
$clean_meta['status_id']['old'] = $clean_meta['status_id']['old'] ? "[id: ".$clean_meta['status_id']['old']."] ". $oldStatusName : trans('general.unassigned');
|
||||
$clean_meta['status_id']['new'] = $clean_meta['status_id']['new'] ? "[id: ".$clean_meta['status_id']['new']."] ". $newStatusName : trans('general.unassigned');
|
||||
$clean_meta['Status'] = $clean_meta['status_id'];
|
||||
$clean_meta[trans('general.status_label')] = $clean_meta['status_id'];
|
||||
unset($clean_meta['status_id']);
|
||||
}
|
||||
if(array_key_exists('asset_eol_date', $clean_meta)) {
|
||||
$clean_meta['EOL date'] = $clean_meta['asset_eol_date'];
|
||||
$clean_meta[trans('admin/hardware/form.eol_date')] = $clean_meta['asset_eol_date'];
|
||||
unset($clean_meta['asset_eol_date']);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class AssetsTransformer
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'),
|
||||
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
|
||||
'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '',
|
||||
'age' => $asset->purchase_date ? $asset->purchase_date->locale(app()->getLocale())->diffForHumans() : '',
|
||||
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
|
||||
'last_checkin' => Helper::getFormattedDateObject($asset->last_checkin, 'datetime'),
|
||||
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
|
||||
|
||||
@@ -55,6 +55,7 @@ class ConsumablesTransformer
|
||||
'checkin' => Gate::allows('checkin', Consumable::class),
|
||||
'update' => Gate::allows('update', Consumable::class),
|
||||
'delete' => Gate::allows('delete', Consumable::class),
|
||||
'clone' => (Gate::allows('create', Consumable::class) && ($consumable->deleted_at == '')),
|
||||
];
|
||||
$array += $permissions_array;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Depreciable;
|
||||
use App\Models\Depreciation;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DepreciationsTransformer
|
||||
{
|
||||
@@ -26,7 +27,10 @@ class DepreciationsTransformer
|
||||
'id' => (int) $depreciation->id,
|
||||
'name' => e($depreciation->name),
|
||||
'months' => $depreciation->months.' '.trans('general.months'),
|
||||
'depreciation_min' => $depreciation->depreciation_min,
|
||||
'depreciation_min' => $depreciation->depreciation_type === 'percent' ? $depreciation->depreciation_min.'%' : $depreciation->depreciation_min,
|
||||
'assets_count' => $depreciation->assets_count,
|
||||
'models_count' => $depreciation->models_count,
|
||||
'licenses_count' => $depreciation->licenses_count,
|
||||
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime')
|
||||
];
|
||||
|
||||
@@ -51,7 +51,7 @@ class LicenseSeatsTransformer
|
||||
];
|
||||
|
||||
if ($seat_count != 0) {
|
||||
$array['name'] = 'Seat '.$seat_count;
|
||||
$array['name'] = trans('admin/licenses/general.seat_count', ['count' => $seat_count]);
|
||||
}
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
|
||||
@@ -45,6 +45,10 @@ class LicensesTransformer
|
||||
'maintained' => ($license->maintained == 1) ? true : false,
|
||||
'supplier' => ($license->supplier) ? ['id' => (int) $license->supplier->id, 'name'=> e($license->supplier->name)] : null,
|
||||
'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null,
|
||||
'created_by' => ($license->adminuser) ? [
|
||||
'id' => (int) $license->adminuser->id,
|
||||
'name'=> e($license->adminuser->present()->fullName()),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
|
||||
|
||||
@@ -3,14 +3,10 @@
|
||||
namespace App\Importer;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\User;
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class AssetImporter extends ItemImporter
|
||||
{
|
||||
@@ -75,8 +71,10 @@ class AssetImporter extends ItemImporter
|
||||
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
|
||||
if ($asset) {
|
||||
if (! $this->updating) {
|
||||
$this->log('A matching Asset '.$asset_tag.' already exists');
|
||||
return;
|
||||
$exists_error = trans('general.import_asset_tag_exists', ['asset_tag' => $asset_tag]);
|
||||
$this->log($exists_error);
|
||||
$this->addErrorToBag($asset, 'asset_tag', $exists_error);
|
||||
return $exists_error;
|
||||
}
|
||||
|
||||
$this->log('Updating Asset');
|
||||
|
||||
@@ -281,6 +281,13 @@ abstract class Importer
|
||||
}
|
||||
}
|
||||
|
||||
protected function addErrorToBag($item, $field, $error_message)
|
||||
{
|
||||
if ($this->errorCallback) {
|
||||
call_user_func($this->errorCallback, $item, $field, [$field => [$error_message]]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the user matching given data, or creates a new one if there is no match.
|
||||
* This is NOT used by the User Import, only for Asset/Accessory/etc where
|
||||
|
||||
@@ -196,64 +196,77 @@ class ItemImporter extends Importer
|
||||
{
|
||||
$condition = array();
|
||||
$asset_model_name = $this->findCsvMatch($row, 'asset_model');
|
||||
$asset_model_category = $this->findCsvMatch($row, 'category');
|
||||
$asset_modelNumber = $this->findCsvMatch($row, 'model_number');
|
||||
|
||||
// TODO: At the moment, this means we can't update the model number if the model name stays the same.
|
||||
if (! $this->shouldUpdateField($asset_model_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((empty($asset_model_name)) && (! empty($asset_modelNumber))) {
|
||||
$asset_model_name = $asset_modelNumber;
|
||||
} elseif ((empty($asset_model_name)) && (empty($asset_modelNumber))) {
|
||||
$asset_model_name = 'Unknown';
|
||||
}
|
||||
|
||||
if ((!empty($asset_model_name)) && (empty($asset_modelNumber))) {
|
||||
$condition[] = ['name', '=', $asset_model_name];
|
||||
} elseif ((!empty($asset_model_name)) && (!empty($asset_modelNumber))) {
|
||||
$condition[] = ['name', '=', $asset_model_name];
|
||||
$condition[] = ['model_number', '=', $asset_modelNumber];
|
||||
$asset_model = AssetModel::select('id');
|
||||
|
||||
if (!empty($asset_model_name)) {
|
||||
$asset_model = $asset_model->where('name', '=', $asset_model_name);
|
||||
|
||||
if (!empty($asset_modelNumber)) {
|
||||
$asset_model = $asset_model->where('model_number', '=', $asset_modelNumber);
|
||||
}
|
||||
}
|
||||
|
||||
$editingModel = $this->updating;
|
||||
$asset_model = AssetModel::where($condition)->first();
|
||||
$asset_model = $asset_model->first();
|
||||
|
||||
if ($asset_model) {
|
||||
|
||||
if (! $this->updating) {
|
||||
$this->log('A matching model already exists, returning it.');
|
||||
|
||||
return $asset_model->id;
|
||||
}
|
||||
|
||||
$this->log('Matching Model found, updating it.');
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
||||
|
||||
if(!empty($asset_modelNumber)){
|
||||
if (!empty($asset_modelNumber)){
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
}
|
||||
|
||||
$asset_model->update($item);
|
||||
$asset_model->save();
|
||||
$this->log('Asset Model Updated');
|
||||
|
||||
|
||||
return $asset_model->id;
|
||||
}
|
||||
$this->log('No Matching Model, Creating a new one');
|
||||
|
||||
}
|
||||
|
||||
$this->log('No Matching Model, Creating a new one');
|
||||
$asset_model = new AssetModel();
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
||||
$item['category_id'] = $this->createOrFetchCategory($asset_model_category);
|
||||
|
||||
$asset_model->fill($item);
|
||||
//$asset_model = AssetModel::firstOrNew($item);
|
||||
$item = null;
|
||||
|
||||
|
||||
|
||||
if ($asset_model->save()) {
|
||||
$this->log('Asset Model '.$asset_model_name.' with model number '.$asset_modelNumber.' was created');
|
||||
|
||||
return $asset_model->id;
|
||||
}
|
||||
$this->log('Asset Model Errors: '.$asset_model->getErrors());
|
||||
$this->logError($asset_model, 'Asset Model "'.$asset_model_name.'"');
|
||||
|
||||
return null;
|
||||
|
||||
@@ -67,6 +67,7 @@ class UserImporter extends ItemImporter
|
||||
$this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0;
|
||||
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0;
|
||||
|
||||
$this->handleEmptyStringsForDates();
|
||||
|
||||
$user_department = trim($this->findCsvMatch($row, 'department'));
|
||||
if ($this->shouldUpdateField($user_department)) {
|
||||
@@ -179,4 +180,22 @@ class UserImporter extends ItemImporter
|
||||
{
|
||||
$this->send_welcome = $send;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the findCsvMatch() method will set '' for columns that are present but empty,
|
||||
* we need to set those empty strings to null to avoid passing bad data to the database
|
||||
* (ie ending up with 0000-00-00 instead of the intended null).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handleEmptyStringsForDates(): void
|
||||
{
|
||||
if ($this->item['start_date'] === '') {
|
||||
$this->item['start_date'] = null;
|
||||
}
|
||||
|
||||
if ($this->item['end_date'] === '') {
|
||||
$this->item['end_date'] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,11 @@ class CheckoutableListener
|
||||
*/
|
||||
private function getCheckoutAcceptance($event)
|
||||
{
|
||||
if (! $event->checkoutable->requireAcceptance()) {
|
||||
$checkedOutToType = get_class($event->checkedOutTo);
|
||||
if ($checkedOutToType != "App\Models\User") {
|
||||
return null;
|
||||
}
|
||||
if (!$event->checkoutable->requireAcceptance()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,30 +3,25 @@
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use Livewire\Component;
|
||||
|
||||
use App\Models\Import;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
|
||||
class Importer extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public $files;
|
||||
|
||||
public $progress; //upload progress - '-1' means don't show
|
||||
public $progress = -1; //upload progress - '-1' means don't show
|
||||
public $progress_message;
|
||||
public $progress_bar_class;
|
||||
public $progress_bar_class = 'progress-bar-warning';
|
||||
|
||||
public $message; //status/error message?
|
||||
public $message_type; //success/error?
|
||||
|
||||
//originally from ImporterFile
|
||||
public $import_errors; //
|
||||
public ?Import $activeFile = null;
|
||||
public $activeFileId;
|
||||
public $headerRow = [];
|
||||
public $typeOfImport;
|
||||
public $importTypes;
|
||||
public $columnOptions;
|
||||
public $statusType;
|
||||
@@ -35,7 +30,6 @@ class Importer extends Component
|
||||
public $send_welcome;
|
||||
public $run_backup;
|
||||
public $field_map; // we need a separate variable for the field-mapping, because the keys in the normal array are too complicated for Livewire to understand
|
||||
public $file_id; // TODO: I can't figure out *why* we need this, but it really seems like we do. I can't seem to pull the id from the activeFile for some reason?
|
||||
|
||||
// Make these variables public - we set the properties in the constructor so we can localize them (versus the old static arrays)
|
||||
public $accessories_fields;
|
||||
@@ -51,10 +45,8 @@ class Importer extends Component
|
||||
'files.*.file_path' => 'required|string',
|
||||
'files.*.created_at' => 'required|string',
|
||||
'files.*.filesize' => 'required|integer',
|
||||
'activeFile' => 'Import',
|
||||
'activeFile.import_type' => 'string',
|
||||
'activeFile.field_map' => 'array',
|
||||
'activeFile.header_row' => 'array',
|
||||
'headerRow' => 'array',
|
||||
'typeOfImport' => 'string',
|
||||
'field_map' => 'array'
|
||||
];
|
||||
|
||||
@@ -68,15 +60,13 @@ class Importer extends Component
|
||||
{
|
||||
$tmp = array();
|
||||
if ($this->activeFile) {
|
||||
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
|
||||
$tmp = array_combine($this->headerRow, $this->field_map);
|
||||
$tmp = array_filter($tmp);
|
||||
}
|
||||
return json_encode($tmp);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function getColumns($type)
|
||||
{
|
||||
switch ($type) {
|
||||
@@ -115,76 +105,66 @@ class Importer extends Component
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function updating($name, $new_import_type)
|
||||
public function updatingTypeOfImport($type)
|
||||
{
|
||||
if ($name == "activeFile.import_type") {
|
||||
|
||||
// go through each header, find a matching field to try and map it to.
|
||||
foreach ($this->activeFile->header_row as $i => $header) {
|
||||
// do we have something mapped already?
|
||||
if (array_key_exists($i, $this->field_map)) {
|
||||
// yes, we do. Is it valid for this type of import?
|
||||
// (e.g. the import type might have been changed...?)
|
||||
if (array_key_exists($this->field_map[$i], $this->columnOptions[$new_import_type])) {
|
||||
//yes, this key *is* valid. Continue on to the next field.
|
||||
continue;
|
||||
} else {
|
||||
//no, this key is *INVALID* for this import type. Better set it to null
|
||||
// and we'll hope that the $aliases_fields or something else picks it up.
|
||||
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
|
||||
} // TODO - strictly speaking, this isn't necessary here I don't think.
|
||||
// go through each header, find a matching field to try and map it to.
|
||||
foreach ($this->headerRow as $i => $header) {
|
||||
// do we have something mapped already?
|
||||
if (array_key_exists($i, $this->field_map)) {
|
||||
// yes, we do. Is it valid for this type of import?
|
||||
// (e.g. the import type might have been changed...?)
|
||||
if (array_key_exists($this->field_map[$i], $this->columnOptions[$type])) {
|
||||
//yes, this key *is* valid. Continue on to the next field.
|
||||
continue;
|
||||
} else {
|
||||
//no, this key is *INVALID* for this import type. Better set it to null
|
||||
// and we'll hope that the $aliases_fields or something else picks it up.
|
||||
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
|
||||
} // TODO - strictly speaking, this isn't necessary here I don't think.
|
||||
}
|
||||
// first, check for exact matches
|
||||
foreach ($this->columnOptions[$type] as $v => $text) {
|
||||
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
|
||||
$this->field_map[$i] = $v;
|
||||
continue 2; //don't bother with the alias check, go to the next header
|
||||
}
|
||||
// first, check for exact matches
|
||||
foreach ($this->columnOptions[$new_import_type] as $value => $text) {
|
||||
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
|
||||
$this->field_map[$i] = $value;
|
||||
continue 2; //don't bother with the alias check, go to the next header
|
||||
}
|
||||
}
|
||||
// if you got here, we didn't find a match. Try the $aliases_fields
|
||||
foreach ($this->aliases_fields as $key => $alias_values) {
|
||||
foreach ($alias_values as $alias_value) {
|
||||
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
|
||||
// Make *absolutely* sure that this key actually _exists_ in this import type -
|
||||
// you can trigger this by importing accessories with a 'Warranty' column (which don't exist
|
||||
// in "Accessories"!)
|
||||
if (array_key_exists($key, $this->columnOptions[$new_import_type])) {
|
||||
$this->field_map[$i] = $key;
|
||||
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
|
||||
}
|
||||
}
|
||||
// if you got here, we didn't find a match. Try the $aliases_fields
|
||||
foreach ($this->aliases_fields as $key => $alias_values) {
|
||||
foreach ($alias_values as $alias_value) {
|
||||
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
|
||||
// Make *absolutely* sure that this key actually _exists_ in this import type -
|
||||
// you can trigger this by importing accessories with a 'Warranty' column (which don't exist
|
||||
// in "Accessories"!)
|
||||
if (array_key_exists($key, $this->columnOptions[$type])) {
|
||||
$this->field_map[$i] = $key;
|
||||
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
|
||||
}
|
||||
}
|
||||
}
|
||||
// and if you got here, we got nothing. Let's recommend 'null'
|
||||
$this->field_map[$i] = null; // Booooo :(
|
||||
}
|
||||
// and if you got here, we got nothing. Let's recommend 'null'
|
||||
$this->field_map[$i] = null; // Booooo :(
|
||||
}
|
||||
}
|
||||
|
||||
public function boot() { // FIXME - delete or undelete.
|
||||
///////$this->activeFile = null; // I do *not* understand why I have to do this, but, well, whatever.
|
||||
}
|
||||
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->authorize('import');
|
||||
$this->progress = -1; // '-1' means 'don't show the progressbar'
|
||||
$this->progress_bar_class = 'progress-bar-warning';
|
||||
$this->importTypes = [
|
||||
'asset' => trans('general.assets'),
|
||||
'accessory' => trans('general.accessories'),
|
||||
'asset' => trans('general.assets'),
|
||||
'accessory' => trans('general.accessories'),
|
||||
'consumable' => trans('general.consumables'),
|
||||
'component' => trans('general.components'),
|
||||
'license' => trans('general.licenses'),
|
||||
'user' => trans('general.users'),
|
||||
'location' => trans('general.locations'),
|
||||
'component' => trans('general.components'),
|
||||
'license' => trans('general.licenses'),
|
||||
'user' => trans('general.users'),
|
||||
'location' => trans('general.locations'),
|
||||
];
|
||||
|
||||
/**
|
||||
* These are the item-type specific columns
|
||||
*/
|
||||
$this->accessories_fields = [
|
||||
$this->accessories_fields = [
|
||||
'company' => trans('general.company'),
|
||||
'location' => trans('general.location'),
|
||||
'quantity' => trans('general.qty'),
|
||||
@@ -307,7 +287,7 @@ class Importer extends Component
|
||||
'manufacturer' => trans('general.manufacturer'),
|
||||
];
|
||||
|
||||
$this->users_fields = [
|
||||
$this->users_fields = [
|
||||
'id' => trans('general.id'),
|
||||
'company' => trans('general.company'),
|
||||
'location' => trans('general.location'),
|
||||
@@ -332,12 +312,12 @@ class Importer extends Component
|
||||
'website' => trans('general.website'),
|
||||
'avatar' => trans('general.image'),
|
||||
'gravatar' => trans('general.importer.gravatar'),
|
||||
'start_date' => trans('general.start_date'),
|
||||
'end_date' => trans('general.end_date'),
|
||||
'employee_num' => trans('general.employee_number'),
|
||||
'start_date' => trans('general.start_date'),
|
||||
'end_date' => trans('general.end_date'),
|
||||
'employee_num' => trans('general.employee_number'),
|
||||
];
|
||||
|
||||
$this->locations_fields = [
|
||||
$this->locations_fields = [
|
||||
'name' => trans('general.item_name_var', ['item' => trans('general.location')]),
|
||||
'address' => trans('general.address'),
|
||||
'address2' => trans('general.importer.address2'),
|
||||
@@ -374,6 +354,12 @@ class Importer extends Component
|
||||
'model name',
|
||||
'model',
|
||||
],
|
||||
'eol_date' =>
|
||||
[
|
||||
'eol',
|
||||
'eol date',
|
||||
'asset eol date',
|
||||
],
|
||||
'gravatar' =>
|
||||
[
|
||||
'gravatar',
|
||||
@@ -504,19 +490,16 @@ class Importer extends Component
|
||||
];
|
||||
|
||||
$this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean
|
||||
foreach($this->importTypes AS $type => $name) {
|
||||
foreach ($this->importTypes as $type => $name) {
|
||||
$this->columnOptions[$type] = $this->getColumns($type);
|
||||
}
|
||||
if ($this->activeFile) {
|
||||
$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : [];
|
||||
}
|
||||
}
|
||||
|
||||
public function selectFile($id)
|
||||
{
|
||||
$this->clearMessage();
|
||||
|
||||
$this->activeFile = Import::find($id);
|
||||
$this->activeFileId = $id;
|
||||
|
||||
if (!$this->activeFile) {
|
||||
$this->message = trans('admin/hardware/message.import.file_missing');
|
||||
@@ -525,15 +508,17 @@ class Importer extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
$this->headerRow = $this->activeFile->header_row;
|
||||
$this->typeOfImport = $this->activeFile->import_type;
|
||||
|
||||
$this->field_map = null;
|
||||
foreach($this->activeFile->header_row as $element) {
|
||||
if(isset($this->activeFile->field_map[$element])) {
|
||||
foreach ($this->headerRow as $element) {
|
||||
if (isset($this->activeFile->field_map[$element])) {
|
||||
$this->field_map[] = $this->activeFile->field_map[$element];
|
||||
} else {
|
||||
$this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings
|
||||
}
|
||||
}
|
||||
$this->file_id = $id;
|
||||
$this->import_errors = null;
|
||||
$this->statusText = null;
|
||||
|
||||
@@ -541,21 +526,33 @@ class Importer extends Component
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
// TODO: why don't we just do File::find($id)? This seems dumb.
|
||||
foreach($this->files as $file) {
|
||||
if ($id == $file->id) {
|
||||
if (Storage::delete('private_uploads/imports/'.$file->file_path)) {
|
||||
$file->delete();
|
||||
$this->authorize('import');
|
||||
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_success');
|
||||
$this->message_type = 'success';
|
||||
return;
|
||||
} else {
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_error');
|
||||
$this->message_type = 'danger';
|
||||
}
|
||||
}
|
||||
$import = Import::find($id);
|
||||
|
||||
// Check that the import wasn't deleted after while page was already loaded...
|
||||
// @todo: next up...handle the file being missing for other interactions...
|
||||
// for example having an import open in two tabs, deleting it, and then changing
|
||||
// the import type in the other tab. The error message below wouldn't display in that case.
|
||||
if (!$import) {
|
||||
$this->message = trans('admin/hardware/message.import.file_already_deleted');
|
||||
$this->message_type = 'danger';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Storage::delete('private_uploads/imports/' . $import->file_path)) {
|
||||
$import->delete();
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_success');
|
||||
$this->message_type = 'success';
|
||||
|
||||
unset($this->files);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_error');
|
||||
$this->message_type = 'danger';
|
||||
}
|
||||
|
||||
public function clearMessage()
|
||||
@@ -564,11 +561,22 @@ class Importer extends Component
|
||||
$this->message_type = null;
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function files()
|
||||
{
|
||||
return Import::orderBy('id', 'desc')->get();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function activeFile()
|
||||
{
|
||||
return Import::find($this->activeFileId);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
|
||||
return view('livewire.importer')
|
||||
->extends('layouts.default')
|
||||
->section('content');
|
||||
->extends('layouts.default')
|
||||
->section('content');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class Accessory extends SnipeModel
|
||||
'company_id' => 'integer|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
];
|
||||
|
||||
|
||||
@@ -253,9 +253,10 @@ class Accessory extends SnipeModel
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function users()
|
||||
public function checkouts()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->withPivot('id', 'created_at', 'note')->withTrashed();
|
||||
return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id')
|
||||
->with('assignedTo');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +268,9 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function hasUsers()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->count();
|
||||
return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id')
|
||||
->where('assigned_type', User::class)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,11 +332,24 @@ class Accessory extends SnipeModel
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check how many items within an accessory are checked out
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numCheckedOut()
|
||||
{
|
||||
return $this->checkouts_count ?? $this->checkouts()->count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check how many items of an accessory remain.
|
||||
*
|
||||
* In order to use this model method, you MUST call withCount('users as users_count')
|
||||
* on the eloquent query in the controller, otherwise $this->>users_count will be null and
|
||||
* In order to use this model method, you MUST call withCount('checkouts as checkouts_count')
|
||||
* on the eloquent query in the controller, otherwise $this->checkouts_count will be null and
|
||||
* bad things happen.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
@@ -342,11 +358,11 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function numRemaining()
|
||||
{
|
||||
$checkedout = $this->users_count;
|
||||
$checkedout = $this->numCheckedOut();
|
||||
$total = $this->qty;
|
||||
$remaining = $total - $checkedout;
|
||||
|
||||
return (int) $remaining;
|
||||
return $remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,12 +373,12 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function declinedCheckout(User $declinedBy, $signature)
|
||||
{
|
||||
if (is_null($accessory_user = \DB::table('accessories_users')->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) {
|
||||
if (is_null($accessory_checkout = AccessoryCheckout::userAssigned()->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$accessory_user->limit(1)->delete();
|
||||
$accessory_checkout->limit(1)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
148
app/Models/AccessoryCheckout.php
Executable file
148
app/Models/AccessoryCheckout.php
Executable file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
* Model for Accessories.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class AccessoryCheckout extends Model
|
||||
{
|
||||
use Searchable;
|
||||
|
||||
protected $fillable = ['user_id', 'accessory_id', 'assigned_to', 'assigned_type', 'note'];
|
||||
protected $table = 'accessories_checkout';
|
||||
|
||||
/**
|
||||
* Establishes the accessory checkout -> accessory relationship
|
||||
*
|
||||
* @author [A. Kroeger]
|
||||
* @since [v7.0.9]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessory()
|
||||
{
|
||||
return $this->hasOne(\App\Models\Accessory::class, 'accessory_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the accessory checkout -> user relationship
|
||||
*
|
||||
* @author [A. Kroeger]
|
||||
* @since [v7.0.9]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->hasOne(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target this asset is checked out to
|
||||
*
|
||||
* @author [A. Kroeger]
|
||||
* @since [v7.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assignedTo()
|
||||
{
|
||||
return $this->morphTo('assigned', 'assigned_type', 'assigned_to')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lowercased name of the type of target the asset is assigned to
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return string
|
||||
*/
|
||||
public function assignedType()
|
||||
{
|
||||
return $this->assigned_type ? strtolower(class_basename($this->assigned_type)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the accessory is checked out to a user
|
||||
*
|
||||
* Even though we allow allow for checkout to things beyond users
|
||||
* this method is an easy way of seeing if we are checked out to a user.
|
||||
*
|
||||
* @author [A. Kroeger]
|
||||
* @since [v7.0]
|
||||
*/
|
||||
public function checkedOutToUser(): bool
|
||||
{
|
||||
return $this->assignedType() === Asset::USER;
|
||||
}
|
||||
|
||||
public function scopeUserAssigned(Builder $query): void
|
||||
{
|
||||
$query->where('assigned_type', '=', User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run additional, advanced searches.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $terms The search terms
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function advancedTextSearch(Builder $query, array $terms)
|
||||
{
|
||||
|
||||
$userQuery = User::where(function ($query) use ($terms) {
|
||||
foreach ($terms as $term) {
|
||||
$search_str = '%' . $term . '%';
|
||||
$query->where('first_name', 'like', $search_str)
|
||||
->orWhere('last_name', 'like', $search_str)
|
||||
->orWhere('note', 'like', $search_str);
|
||||
}
|
||||
})->select('id');
|
||||
|
||||
$locationQuery = Location::where(function ($query) use ($terms) {
|
||||
foreach ($terms as $term) {
|
||||
$search_str = '%' . $term . '%';
|
||||
$query->where('name', 'like', $search_str);
|
||||
}
|
||||
})->select('id');
|
||||
|
||||
$assetQuery = Asset::where(function ($query) use ($terms) {
|
||||
foreach ($terms as $term) {
|
||||
$search_str = '%' . $term . '%';
|
||||
$query->where('name', 'like', $search_str);
|
||||
}
|
||||
})->select('id');
|
||||
|
||||
$query->where(function ($query) use ($userQuery) {
|
||||
$query->where('assigned_type', User::class)
|
||||
->whereIn('assigned_to', $userQuery);
|
||||
})->orWhere(function($query) use ($locationQuery) {
|
||||
$query->where('assigned_type', Location::class)
|
||||
->whereIn('assigned_to', $locationQuery);
|
||||
})->orWhere(function($query) use ($assetQuery) {
|
||||
$query->where('assigned_type', Asset::class)
|
||||
->whereIn('assigned_to', $assetQuery);
|
||||
})->orWhere(function($query) use ($terms) {
|
||||
foreach ($terms as $term) {
|
||||
$search_str = '%' . $term . '%';
|
||||
$query->where('note', 'like', $search_str);
|
||||
}
|
||||
});
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use App\Presenters\Presentable;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* Model for the Actionlog (the table that keeps a historical log of
|
||||
@@ -17,10 +16,12 @@ use Illuminate\Support\Facades\Auth;
|
||||
*/
|
||||
class Actionlog extends SnipeModel
|
||||
{
|
||||
use CompanyableTrait;
|
||||
use HasFactory;
|
||||
|
||||
// This is to manually set the source (via setActionSource()) for determineActionSource()
|
||||
protected ?string $source = null;
|
||||
protected $with = ['admin'];
|
||||
|
||||
protected $presenter = \App\Presenters\ActionlogPresenter::class;
|
||||
use SoftDeletes;
|
||||
@@ -372,4 +373,9 @@ class Actionlog extends SnipeModel
|
||||
{
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
public function scopeOrderAdmin($query, $order)
|
||||
{
|
||||
return $query->leftJoin('users as admin_sort', 'action_logs.user_id', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\AssetCheckedOut;
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Helpers\Helper;
|
||||
@@ -31,6 +30,7 @@ class Asset extends Depreciable
|
||||
{
|
||||
|
||||
protected $presenter = AssetPresenter::class;
|
||||
protected $with = ['model', 'admin'];
|
||||
|
||||
use CompanyableTrait;
|
||||
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
|
||||
@@ -97,35 +97,38 @@ class Asset extends Depreciable
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array',
|
||||
'status_id' => 'required|integer|exists:status_labels,id',
|
||||
'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag|not_array',
|
||||
'name' => 'nullable|max:255',
|
||||
'company_id' => 'nullable|integer|exists:companies,id',
|
||||
'warranty_months' => 'nullable|numeric|digits_between:0,240',
|
||||
'last_checkout' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
'last_checkin' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
'expected_checkin' => 'nullable|date',
|
||||
'last_audit_date' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
// 'next_audit_date' => 'nullable|date|after:last_audit_date',
|
||||
'next_audit_date' => 'nullable|date',
|
||||
'location_id' => 'nullable|exists:locations,id',
|
||||
'rtd_location_id' => 'nullable|exists:locations,id',
|
||||
'purchase_date' => 'nullable|date|date_format:Y-m-d',
|
||||
'serial' => 'nullable|unique_undeleted:assets,serial',
|
||||
'purchase_cost' => 'nullable|numeric|gte:0',
|
||||
'supplier_id' => 'nullable|exists:suppliers,id',
|
||||
'asset_eol_date' => 'nullable|date',
|
||||
'eol_explicit' => 'nullable|boolean',
|
||||
'byod' => 'nullable|boolean',
|
||||
'order_number' => 'nullable|string|max:191',
|
||||
'notes' => 'nullable|string|max:65535',
|
||||
'assigned_to' => 'nullable|integer',
|
||||
'requestable' => 'nullable|boolean',
|
||||
'model_id' => ['required', 'integer', 'exists:models,id,deleted_at,NULL', 'not_array'],
|
||||
'status_id' => ['required', 'integer', 'exists:status_labels,id'],
|
||||
'asset_tag' => ['required', 'min:1', 'max:255', 'unique_undeleted:assets,asset_tag', 'not_array'],
|
||||
'name' => ['nullable', 'max:255'],
|
||||
'company_id' => ['nullable', 'integer', 'exists:companies,id'],
|
||||
'warranty_months' => ['nullable', 'numeric', 'digits_between:0,240'],
|
||||
'last_checkout' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'last_checkin' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'expected_checkin' => ['nullable', 'date'],
|
||||
'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'next_audit_date' => ['nullable', 'date'],
|
||||
//'after:last_audit_date'],
|
||||
'location_id' => ['nullable', 'exists:locations,id'],
|
||||
'rtd_location_id' => ['nullable', 'exists:locations,id'],
|
||||
'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
|
||||
'serial' => ['nullable', 'unique_undeleted:assets,serial'],
|
||||
'purchase_cost' => ['nullable', 'numeric', 'gte:0'],
|
||||
'supplier_id' => ['nullable', 'exists:suppliers,id'],
|
||||
'asset_eol_date' => ['nullable', 'date'],
|
||||
'eol_explicit' => ['nullable', 'boolean'],
|
||||
'byod' => ['nullable', 'boolean'],
|
||||
'order_number' => ['nullable', 'string', 'max:191'],
|
||||
'notes' => ['nullable', 'string', 'max:65535'],
|
||||
'assigned_to' => ['nullable', 'integer'],
|
||||
'requestable' => ['nullable', 'boolean'],
|
||||
'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'],
|
||||
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL'],
|
||||
'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL']
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
@@ -713,7 +716,7 @@ class Asset extends Depreciable
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function adminuser()
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
@@ -1312,7 +1315,7 @@ class Asset extends Depreciable
|
||||
|
||||
public function scopeDueForCheckin($query, $settings)
|
||||
{
|
||||
$interval = $settings->audit_warning_days ?? 0;
|
||||
$interval = $settings->due_checkin_days ?? 0;
|
||||
$today = Carbon::now();
|
||||
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');
|
||||
|
||||
@@ -1558,7 +1561,7 @@ class Asset extends Depreciable
|
||||
$leftJoin->on('assets_dept_users.id', '=', 'assets.assigned_to')
|
||||
->where('assets.assigned_type', '=', User::class);
|
||||
})->where(function ($query) use ($search) {
|
||||
$query->where('assets_dept_users.department_id', '=', $search);
|
||||
$query->whereIn('assets_dept_users.department_id', $search);
|
||||
|
||||
})->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug
|
||||
}
|
||||
@@ -1808,7 +1811,9 @@ class Asset extends Depreciable
|
||||
public function scopeInCategory($query, $category_id)
|
||||
{
|
||||
return $query->join('models as category_models', 'assets.model_id', '=', 'category_models.id')
|
||||
->join('categories', 'category_models.category_id', '=', 'categories.id')->where('category_models.category_id', '=', $category_id);
|
||||
->join('categories', 'category_models.category_id', '=', 'categories.id')
|
||||
->whereIn('category_models.category_id', (!is_array($category_id) ? explode(',',$category_id): $category_id));
|
||||
//->whereIn('category_models.category_id', $category_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1822,7 +1827,7 @@ class Asset extends Depreciable
|
||||
public function scopeByManufacturer($query, $manufacturer_id)
|
||||
{
|
||||
return $query->join('models', 'assets.model_id', '=', 'models.id')
|
||||
->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->where('models.manufacturer_id', '=', $manufacturer_id);
|
||||
->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->whereIn('models.manufacturer_id', (!is_array($manufacturer_id) ? explode(',',$manufacturer_id): $manufacturer_id));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
use \App\Presenters\AssetModelPresenter;
|
||||
use App\Http\Traits\TwoColumnUniqueUndeletedTrait;
|
||||
|
||||
/**
|
||||
* Model for Asset Models. Asset Models contain higher level
|
||||
@@ -21,21 +22,8 @@ class AssetModel extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
protected $presenter = AssetModelPresenter::class;
|
||||
use Loggable, Requestable, Presentable;
|
||||
|
||||
protected $table = 'models';
|
||||
protected $hidden = ['user_id', 'deleted_at'];
|
||||
|
||||
// Declare the rules for the model validation
|
||||
protected $rules = [
|
||||
'name' => 'required|min:1|max:255',
|
||||
'model_number' => 'max:255|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||
'eol' => 'integer:min:0|max:240|nullable',
|
||||
];
|
||||
use TwoColumnUniqueUndeletedTrait;
|
||||
|
||||
/**
|
||||
* Whether the model should inject its identifier to the unique
|
||||
@@ -44,8 +32,26 @@ class AssetModel extends SnipeModel
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
protected $injectUniqueIdentifier = true;
|
||||
use ValidatingTrait;
|
||||
protected $table = 'models';
|
||||
protected $hidden = ['user_id', 'deleted_at'];
|
||||
protected $presenter = AssetModelPresenter::class;
|
||||
|
||||
// Declare the rules for the model validation
|
||||
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'string|required|min:1|max:255|two_column_unique_undeleted:model_number',
|
||||
'model_number' => 'string|max:255|nullable|two_column_unique_undeleted:name',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||
'eol' => 'integer:min:0|max:240|nullable',
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -73,7 +79,12 @@ class AssetModel extends SnipeModel
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'model_number', 'notes', 'eol'];
|
||||
protected $searchableAttributes = [
|
||||
'name',
|
||||
'model_number',
|
||||
'notes',
|
||||
'eol'
|
||||
];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
@@ -86,6 +97,9 @@ class AssetModel extends SnipeModel
|
||||
'manufacturer' => ['name'],
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the model -> assets relationship
|
||||
*
|
||||
|
||||
@@ -205,7 +205,11 @@ class Component extends SnipeModel
|
||||
public function numCheckedOut()
|
||||
{
|
||||
$checkedout = 0;
|
||||
foreach ($this->assets as $checkout) {
|
||||
|
||||
// In case there are elements checked out to assets that belong to a different company
|
||||
// than this asset and full multiple company support is on we'll remove the global scope,
|
||||
// so they are included in the count.
|
||||
foreach ($this->assets()->withoutGlobalScope(new CompanyableScope)->get() as $checkout) {
|
||||
$checkedout += $checkout->pivot->assigned_qty;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use EasySlugger\Utf8Slugger;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Schema;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
class CustomField extends Model
|
||||
{
|
||||
|
||||
@@ -76,9 +76,9 @@ class Depreciable extends SnipeModel
|
||||
|
||||
if ($months_passed >= $this->get_depreciation()->months){
|
||||
//if there is a floor use it
|
||||
if(!$this->get_depreciation()->depreciation_min == null) {
|
||||
if($this->get_depreciation()->depreciation_min) {
|
||||
|
||||
$current_value = $this->get_depreciation()->depreciation_min;
|
||||
$current_value = $this->calculateDepreciation();
|
||||
|
||||
}else{
|
||||
$current_value = 0;
|
||||
@@ -86,7 +86,7 @@ class Depreciable extends SnipeModel
|
||||
}
|
||||
else {
|
||||
// The equation here is (Purchase_Cost-Floor_min)*(Months_passed/Months_til_depreciated)
|
||||
$current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->get_depreciation()->depreciation_min)) * ($months_passed / $this->get_depreciation()->months)), 2);
|
||||
$current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->calculateDepreciation())) * ($months_passed / $this->get_depreciation()->months)), 2);
|
||||
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class Depreciable extends SnipeModel
|
||||
|
||||
public function getMonthlyDepreciation(){
|
||||
|
||||
return ($this->purchase_cost-$this->get_depreciation()->depreciation_min)/$this->get_depreciation()->months;
|
||||
return ($this->purchase_cost-$this->calculateDepreciation())/$this->get_depreciation()->months;
|
||||
|
||||
}
|
||||
|
||||
@@ -158,17 +158,20 @@ class Depreciable extends SnipeModel
|
||||
|
||||
public function time_until_depreciated()
|
||||
{
|
||||
// @link http://www.php.net/manual/en/class.datetime.php
|
||||
$d1 = new \DateTime();
|
||||
$d2 = $this->depreciated_date();
|
||||
if ($this->depreciated_date()) {
|
||||
// @link http://www.php.net/manual/en/class.datetime.php
|
||||
$d1 = new \DateTime();
|
||||
$d2 = $this->depreciated_date();
|
||||
|
||||
// @link http://www.php.net/manual/en/class.dateinterval.php
|
||||
$interval = $d1->diff($d2);
|
||||
if (! $interval->invert) {
|
||||
return $interval;
|
||||
} else {
|
||||
return new \DateInterval('PT0S'); //null interval (zero seconds from now)
|
||||
// @link http://www.php.net/manual/en/class.dateinterval.php
|
||||
$interval = $d1->diff($d2);
|
||||
if (! $interval->invert) {
|
||||
return $interval;
|
||||
} else {
|
||||
return new \DateInterval('PT0S'); //null interval (zero seconds from now)
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function depreciated_date()
|
||||
@@ -188,4 +191,16 @@ class Depreciable extends SnipeModel
|
||||
{
|
||||
return new \DateTime($time);
|
||||
}
|
||||
|
||||
private function calculateDepreciation()
|
||||
{
|
||||
if($this->get_depreciation()->depreciation_type === 'percent') {
|
||||
$depreciation_percent= $this->get_depreciation()->depreciation_min / 100;
|
||||
$depreciation_min= $this->purchase_cost * $depreciation_percent;
|
||||
return $depreciation_min;
|
||||
}
|
||||
|
||||
$depreciation_min = $this->get_depreciation()->depreciation_min;
|
||||
return $depreciation_min;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,4 +75,17 @@ class Depreciation extends SnipeModel
|
||||
{
|
||||
return $this->hasMany(\App\Models\License::class, 'depreciation_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the depreciation -> assets relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v5.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'depreciation_id', 'model_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class License extends Depreciable
|
||||
'category_id' => 'required|exists:categories,id',
|
||||
'company_id' => 'integer|nullable',
|
||||
'purchase_cost'=> 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable|max:10|required_with:depreciation_id',
|
||||
'expiration_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'termination_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'min_amt' => 'numeric|nullable|gte:0',
|
||||
@@ -736,4 +736,17 @@ class License extends Depreciable
|
||||
return $query->leftJoin('companies as companies', 'licenses.company_id', '=', 'companies.id')->select('licenses.*')
|
||||
->orderBy('companies.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on the user that created it
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderCreatedBy($query, $order)
|
||||
{
|
||||
return $query->leftJoin('users as users_sort', 'licenses.user_id', '=', 'users_sort.id')->select('licenses.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class Location extends SnipeModel
|
||||
'country' => 'min:2|max:191|nullable',
|
||||
'zip' => 'max:10|nullable',
|
||||
'manager_id' => 'exists:users,id|nullable',
|
||||
'parent_id' => 'non_circular:locations,id',
|
||||
'parent_id' => 'nullable|exists:locations,id|non_circular:locations,id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -108,10 +108,11 @@ class Location extends SnipeModel
|
||||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets_count === 0)
|
||||
&& ($this->assigned_assets_count === 0)
|
||||
&& ($this->children_count === 0)
|
||||
&& ($this->users_count === 0);
|
||||
&& ($this->assets_count == 0)
|
||||
&& ($this->assigned_assets_count == 0)
|
||||
&& ($this->children_count == 0)
|
||||
&& ($this->accessories_count == 0)
|
||||
&& ($this->users_count == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,9 +22,9 @@ class Manufacturer extends SnipeModel
|
||||
// Declare the rules for the form validation
|
||||
protected $rules = [
|
||||
'name' => 'required|min:2|max:255|unique:manufacturers,name,NULL,id,deleted_at,NULL',
|
||||
'url' => 'url|nullable',
|
||||
'url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://',
|
||||
'support_email' => 'email|nullable',
|
||||
'support_url' => 'nullable|url',
|
||||
'support_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://',
|
||||
'warranty_lookup_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://'
|
||||
];
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -73,7 +74,10 @@ class Setting extends Model
|
||||
'login_remote_user_header_name' => 'string|nullable',
|
||||
'thumbnail_max_h' => 'numeric|max:500|min:25',
|
||||
'pwd_secure_min' => 'numeric|required|min:8',
|
||||
'alert_threshold' => 'numeric|nullable',
|
||||
'alert_interval' => 'numeric|nullable',
|
||||
'audit_warning_days' => 'numeric|nullable',
|
||||
'due_checkin_days' => 'numeric|nullable',
|
||||
'audit_interval' => 'numeric|nullable',
|
||||
'custom_forgot_pass_url' => 'url|nullable',
|
||||
'privacy_policy_link' => 'nullable|url',
|
||||
|
||||
@@ -21,6 +21,11 @@ class SnipeModel extends Model
|
||||
*/
|
||||
public function setPurchaseCostAttribute($value)
|
||||
{
|
||||
if (is_float($value)) {
|
||||
//value is *already* a floating-point number. Just assign it directly
|
||||
$this->attributes['purchase_cost'] = $value;
|
||||
return;
|
||||
}
|
||||
$value = Helper::ParseCurrency($value);
|
||||
|
||||
if ($value == 0) {
|
||||
|
||||
@@ -331,7 +331,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_users', 'assigned_to', 'accessory_id')
|
||||
return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_checkout', 'assigned_to', 'accessory_id')
|
||||
->withPivot('id', 'created_at', 'note')->withTrashed()->orderBy('accessory_id');
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
|
||||
$this->item_tag = $params['item_tag'];
|
||||
$this->item_model = $params['item_model'];
|
||||
$this->item_serial = $params['item_serial'];
|
||||
$this->item_status = $params['item_status'];
|
||||
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false);
|
||||
$this->assigned_to = $params['assigned_to'];
|
||||
$this->note = $params['note'];
|
||||
@@ -65,6 +66,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
|
||||
'item_tag' => $this->item_tag,
|
||||
'item_model' => $this->item_model,
|
||||
'item_serial' => $this->item_serial,
|
||||
'item_status' => $this->item_status,
|
||||
'note' => $this->note,
|
||||
'accepted_date' => $this->accepted_date,
|
||||
'assigned_to' => $this->assigned_to,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user