Compare commits
469 Commits
#17642-che
...
form-row-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
750c376725 | ||
|
|
08630d948f | ||
|
|
dc6ee342c5 | ||
|
|
eea1922841 | ||
|
|
57824848e9 | ||
|
|
235dcbc7d9 | ||
|
|
f0561475cc | ||
|
|
3b4c51bab6 | ||
|
|
150c205615 | ||
|
|
cde22977b0 | ||
|
|
1c09657631 | ||
|
|
02a4268180 | ||
|
|
f8362f4a45 | ||
|
|
682c1a8fa7 | ||
|
|
3863e82dcc | ||
|
|
b766f6e2b5 | ||
|
|
a3bad98096 | ||
|
|
33e7425dee | ||
|
|
2554b50b38 | ||
|
|
433a3e11fd | ||
|
|
c3efdd0c8d | ||
|
|
d2e3a13043 | ||
|
|
c4923fa971 | ||
|
|
253026de5d | ||
|
|
a59914e9f9 | ||
|
|
671e79f01b | ||
|
|
5c716c3f24 | ||
|
|
e138c9307e | ||
|
|
7c0c3b2bb8 | ||
|
|
514711ddbb | ||
|
|
9023eda66f | ||
|
|
8ce3001ef9 | ||
|
|
25ce63f00b | ||
|
|
2462bc05b3 | ||
|
|
c3748da0b1 | ||
|
|
90c242a441 | ||
|
|
52239a88b5 | ||
|
|
7a3596c86d | ||
|
|
ac8a9e38f0 | ||
|
|
5c08f3a27e | ||
|
|
5216dd75bf | ||
|
|
b8b45d2d81 | ||
|
|
4b2b2cb68e | ||
|
|
be4ace293e | ||
|
|
764b363bbc | ||
|
|
9da9166442 | ||
|
|
8ea339f0ef | ||
|
|
89b36ba63f | ||
|
|
1d3dfa1fa4 | ||
|
|
ca567eec8a | ||
|
|
41da31c379 | ||
|
|
e81f63f46b | ||
|
|
ade03e4827 | ||
|
|
33a4c88c3a | ||
|
|
1f79776b8f | ||
|
|
11e5f851f0 | ||
|
|
4ca1db8a1b | ||
|
|
14b829aa30 | ||
|
|
384652b3df | ||
|
|
9db65c6ae9 | ||
|
|
1346e33e99 | ||
|
|
ab9cc447aa | ||
|
|
fe9e0444b4 | ||
|
|
a18957dbe9 | ||
|
|
13d5b724ee | ||
|
|
c7d8203da9 | ||
|
|
96b5c1d8e1 | ||
|
|
882ee80424 | ||
|
|
e977771fe4 | ||
|
|
4339e4552e | ||
|
|
b54d222943 | ||
|
|
e4e613550a | ||
|
|
d1207444db | ||
|
|
0bad75b263 | ||
|
|
74b98083e2 | ||
|
|
9034b5ec11 | ||
|
|
927f557672 | ||
|
|
86fb089901 | ||
|
|
630ea05e17 | ||
|
|
7df5196083 | ||
|
|
01f7b5d709 | ||
|
|
ec47ee3573 | ||
|
|
7062962cc8 | ||
|
|
fde447846a | ||
|
|
319cb1bd1e | ||
|
|
58cda5ae6d | ||
|
|
251a3db880 | ||
|
|
30b6dcd767 | ||
|
|
05f6622912 | ||
|
|
36183ac19d | ||
|
|
f6a823e0a8 | ||
|
|
312353551d | ||
|
|
fd5c9cee38 | ||
|
|
84bf71802c | ||
|
|
35739c2eef | ||
|
|
1914a71623 | ||
|
|
dcc53886d9 | ||
|
|
21ef87ef09 | ||
|
|
b67f808da9 | ||
|
|
ad69447b53 | ||
|
|
b4614df88c | ||
|
|
7171247cdc | ||
|
|
6d0084f108 | ||
|
|
29359f42ae | ||
|
|
cf875bf872 | ||
|
|
13c0d335d3 | ||
|
|
ceb33409b5 | ||
|
|
83597d4a8b | ||
|
|
81eefc5448 | ||
|
|
082bc3ece4 | ||
|
|
b2406b61fb | ||
|
|
15698d7694 | ||
|
|
990cd82f97 | ||
|
|
c87829b3e8 | ||
|
|
6799c41d65 | ||
|
|
80c059be58 | ||
|
|
aa3f896538 | ||
|
|
d8c17a8a5e | ||
|
|
850939367c | ||
|
|
bf4fef9bf7 | ||
|
|
d5175961a4 | ||
|
|
e7e1d6a232 | ||
|
|
712345f3a0 | ||
|
|
54c9bc3dcb | ||
|
|
e796c0da4a | ||
|
|
cf8ff0f43e | ||
|
|
e67ce23a7c | ||
|
|
a66bb95a81 | ||
|
|
c66fa33b2e | ||
|
|
56eebb9db4 | ||
|
|
d9773f107e | ||
|
|
e09112f46a | ||
|
|
50a17a82b6 | ||
|
|
7eb032d646 | ||
|
|
e065f22f8e | ||
|
|
c1b4ba1f85 | ||
|
|
eeaec471f0 | ||
|
|
0d3c8678d8 | ||
|
|
bbddf5f95b | ||
|
|
3b8c8b3af9 | ||
|
|
84753aa13f | ||
|
|
90b84451d8 | ||
|
|
54c8ae41cc | ||
|
|
7d32b1a724 | ||
|
|
69ffd63ca6 | ||
|
|
4857c19eb6 | ||
|
|
d535e23da0 | ||
|
|
30e02544ab | ||
|
|
ee53925bd2 | ||
|
|
40495b8a17 | ||
|
|
6bc9a82a7a | ||
|
|
6504ee37bd | ||
|
|
082bff2fa8 | ||
|
|
ab7bd86336 | ||
|
|
eada0b0bb5 | ||
|
|
f221f9f22a | ||
|
|
6731e44a0d | ||
|
|
acc37045e4 | ||
|
|
a7c5899c16 | ||
|
|
80b02635a9 | ||
|
|
f90de5ec67 | ||
|
|
9a3e046530 | ||
|
|
7f56e461fe | ||
|
|
1da37e0d38 | ||
|
|
0004d4936c | ||
|
|
7a6fdc4e0a | ||
|
|
2eb727bd0c | ||
|
|
57af507170 | ||
|
|
e37f87465c | ||
|
|
324070f345 | ||
|
|
e1aa843b6d | ||
|
|
e652a7fd61 | ||
|
|
2397bfbad0 | ||
|
|
7e2bc8e452 | ||
|
|
00e8fd0483 | ||
|
|
6d8bf2c665 | ||
|
|
72466f1aab | ||
|
|
6901deccbf | ||
|
|
5a9c906eb9 | ||
|
|
b95b60b49e | ||
|
|
14408ef18f | ||
|
|
6b87c90e02 | ||
|
|
2b4d5222eb | ||
|
|
9604ecebad | ||
|
|
0d67970a45 | ||
|
|
913b9f0c40 | ||
|
|
610a5745f0 | ||
|
|
dff12324c6 | ||
|
|
f340390fc8 | ||
|
|
be81e74921 | ||
|
|
2bee8729e4 | ||
|
|
5d03038734 | ||
|
|
75b11de0f4 | ||
|
|
c5bede8594 | ||
|
|
cd9ea6ae3b | ||
|
|
113b762ec7 | ||
|
|
78704d8b85 | ||
|
|
1109db76fe | ||
|
|
b1b390febf | ||
|
|
be451fa0c0 | ||
|
|
1fa553c785 | ||
|
|
905f61371d | ||
|
|
7da5210a01 | ||
|
|
18172d3896 | ||
|
|
c28e78b9e2 | ||
|
|
e7827a3847 | ||
|
|
039564e74c | ||
|
|
e164595a0f | ||
|
|
d29e09a3ff | ||
|
|
db9f85e9da | ||
|
|
27022954b1 | ||
|
|
30362c924f | ||
|
|
bf63b15b46 | ||
|
|
19aea4bd6c | ||
|
|
090890e9c6 | ||
|
|
605022a9e3 | ||
|
|
b06c58fe7b | ||
|
|
f5c8b3eb04 | ||
|
|
739980aa09 | ||
|
|
afde5943e3 | ||
|
|
32300cb42c | ||
|
|
de3b1697c8 | ||
|
|
a18fb10b5a | ||
|
|
52140dbe06 | ||
|
|
db5bb1928e | ||
|
|
65b66beb07 | ||
|
|
c83504b4e7 | ||
|
|
cd2e7ee31d | ||
|
|
c3a0a0415a | ||
|
|
709f4672b7 | ||
|
|
e6c030b050 | ||
|
|
7bd3a791a1 | ||
|
|
b9cfc03b4f | ||
|
|
131327a64d | ||
|
|
77d002a158 | ||
|
|
94699893ac | ||
|
|
9f81989bdd | ||
|
|
15abe36c53 | ||
|
|
3094e007ee | ||
|
|
eb259aee22 | ||
|
|
c05c8defb9 | ||
|
|
bf5668a42e | ||
|
|
ec310bc8fb | ||
|
|
db477421b2 | ||
|
|
30a9496cf5 | ||
|
|
6cefa0d0b3 | ||
|
|
9284984265 | ||
|
|
53b96168a9 | ||
|
|
eadce51f10 | ||
|
|
b3c583b6dc | ||
|
|
28abeab31d | ||
|
|
12a649ec4b | ||
|
|
35b79e4d14 | ||
|
|
751dad7f2e | ||
|
|
b08d86220a | ||
|
|
3a27ecc475 | ||
|
|
da6fab5d43 | ||
|
|
ca95b29cd6 | ||
|
|
c5c68e9dd5 | ||
|
|
44fbde26fa | ||
|
|
6e2bcd6aa9 | ||
|
|
9c0202e5ce | ||
|
|
39ef353073 | ||
|
|
7b5d90dd81 | ||
|
|
d1129081df | ||
|
|
315a812df5 | ||
|
|
cfc979acf0 | ||
|
|
d7407d70a3 | ||
|
|
8ccd2e97a8 | ||
|
|
988204619f | ||
|
|
cad6cc3007 | ||
|
|
b303875f1d | ||
|
|
d5cc61f378 | ||
|
|
0d7ec43262 | ||
|
|
d3747f4daa | ||
|
|
af695e7dc8 | ||
|
|
1edbfd87df | ||
|
|
454be01a6c | ||
|
|
745fc515f1 | ||
|
|
715b9c1182 | ||
|
|
95be847d87 | ||
|
|
c1a6546eba | ||
|
|
648c25a0a7 | ||
|
|
f2ec7f2975 | ||
|
|
f518af6d61 | ||
|
|
b11c6a5c06 | ||
|
|
5822e4e692 | ||
|
|
e4f06b0ca8 | ||
|
|
2f093c0e82 | ||
|
|
5d9dc0e74d | ||
|
|
adc3a34929 | ||
|
|
cb2ffe6b3f | ||
|
|
b3e3d01672 | ||
|
|
4a6520fc78 | ||
|
|
75ab6c9b13 | ||
|
|
2f77fcb526 | ||
|
|
60604c3481 | ||
|
|
671c113cd2 | ||
|
|
8a74d21ede | ||
|
|
75995b2109 | ||
|
|
4298aad008 | ||
|
|
823c67400d | ||
|
|
3160d1064d | ||
|
|
d1eefc3fea | ||
|
|
16795382fc | ||
|
|
eb17974adc | ||
|
|
22852c27f8 | ||
|
|
f4a94d975d | ||
|
|
7a36bbbd1e | ||
|
|
2b401b965b | ||
|
|
314bc5b44f | ||
|
|
76374f0d5a | ||
|
|
264efb015e | ||
|
|
e74460aefc | ||
|
|
55a5a12b30 | ||
|
|
58944a38eb | ||
|
|
469e3bd475 | ||
|
|
17650c5735 | ||
|
|
15e64155b5 | ||
|
|
39955ac760 | ||
|
|
855a176ca9 | ||
|
|
47b2b30455 | ||
|
|
b702e3e2de | ||
|
|
a6b74d56c6 | ||
|
|
a4222bcaef | ||
|
|
ecf24511cd | ||
|
|
abb097a391 | ||
|
|
dd742a2e4a | ||
|
|
128bdf500a | ||
|
|
73ac00bc51 | ||
|
|
3524e23e38 | ||
|
|
be0f3910bb | ||
|
|
07dbc6842c | ||
|
|
5a16b59462 | ||
|
|
13cd7071b8 | ||
|
|
40108b196c | ||
|
|
c8e79aa5ca | ||
|
|
e60f2b2332 | ||
|
|
b6d397bcca | ||
|
|
918426a2fa | ||
|
|
c076b37c9b | ||
|
|
7c58bfa282 | ||
|
|
0caaba156d | ||
|
|
c22575812d | ||
|
|
0ede4da816 | ||
|
|
98e23ff92e | ||
|
|
6503f9c667 | ||
|
|
4770e469b4 | ||
|
|
29a18c7c8b | ||
|
|
6db0003e3f | ||
|
|
c538c460fa | ||
|
|
822339fe42 | ||
|
|
b84d9282ca | ||
|
|
952b6f33bb | ||
|
|
c57c4b8ff2 | ||
|
|
39e6223ff2 | ||
|
|
d8dd274c08 | ||
|
|
15f97b6cb9 | ||
|
|
fc091c1174 | ||
|
|
c07ef4d87f | ||
|
|
c7bdad649a | ||
|
|
18c2508d2f | ||
|
|
4dcfd8b353 | ||
|
|
726116574d | ||
|
|
27f02014ca | ||
|
|
f80f1acaa7 | ||
|
|
39d5ffeceb | ||
|
|
48ba7eed3e | ||
|
|
11eee833bb | ||
|
|
35b358d336 | ||
|
|
ae109be631 | ||
|
|
3f7ed73395 | ||
|
|
fec9d716ee | ||
|
|
da5b1afd19 | ||
|
|
618106c103 | ||
|
|
312be98132 | ||
|
|
e0bb77a6d6 | ||
|
|
855922c21a | ||
|
|
bc645d2621 | ||
|
|
9c06ff3899 | ||
|
|
2a37aa3b49 | ||
|
|
bf591320af | ||
|
|
56e687bed2 | ||
|
|
9caa240fdb | ||
|
|
3ffb73a516 | ||
|
|
cc1132be87 | ||
|
|
1c31f126ef | ||
|
|
d8eaf2676f | ||
|
|
07b25fe376 | ||
|
|
c2ecd20b7d | ||
|
|
1b42abcc98 | ||
|
|
9efb49d510 | ||
|
|
2d6270c697 | ||
|
|
0823c23a6e | ||
|
|
b3f0ce4b2a | ||
|
|
8b83584b67 | ||
|
|
9eb686fe08 | ||
|
|
765051ce88 | ||
|
|
ed402e0122 | ||
|
|
1488271a83 | ||
|
|
48bbf8d005 | ||
|
|
e97b969d66 | ||
|
|
cdd12df81a | ||
|
|
050a3afc74 | ||
|
|
270401c693 | ||
|
|
551822ce7d | ||
|
|
4b8c371097 | ||
|
|
90fbf6da46 | ||
|
|
0c3103e3d2 | ||
|
|
6a8e1566fe | ||
|
|
ced30082a6 | ||
|
|
f6c64abc1a | ||
|
|
7f9939a896 | ||
|
|
1c99f2dfdd | ||
|
|
1974fccac3 | ||
|
|
911552035e | ||
|
|
ff25d275ee | ||
|
|
1fcf5e03e7 | ||
|
|
3101212c49 | ||
|
|
3f0ac103a1 | ||
|
|
59bd6ca360 | ||
|
|
22fe9a786e | ||
|
|
d2ee8de9ac | ||
|
|
03d3fb6a5f | ||
|
|
ba85af11aa | ||
|
|
c94a8c42f4 | ||
|
|
16fdb16a56 | ||
|
|
822f9a6f28 | ||
|
|
b264bbf69f | ||
|
|
553ab8851a | ||
|
|
2b8ea9a233 | ||
|
|
b0067fee51 | ||
|
|
732c3dae89 | ||
|
|
d45bd67cae | ||
|
|
9200de5032 | ||
|
|
3fbbff5a47 | ||
|
|
c22efc2c3d | ||
|
|
8c0281bf70 | ||
|
|
720a4bc4a2 | ||
|
|
7fd93645b3 | ||
|
|
fcbfbca6d0 | ||
|
|
f2bca9491c | ||
|
|
b48f309ab6 | ||
|
|
0b1be3e63b | ||
|
|
8c129c10af | ||
|
|
63ce2a14fe | ||
|
|
f435ebb110 | ||
|
|
843f001bf6 | ||
|
|
0d28165c04 | ||
|
|
ee31bfbcd4 | ||
|
|
1c67d6802d | ||
|
|
5da8c86ec7 | ||
|
|
10a2d59ec1 | ||
|
|
34e8360b10 | ||
|
|
ca259ee4c3 | ||
|
|
2143952a1e | ||
|
|
6bab6e7151 | ||
|
|
d217c2e295 | ||
|
|
52bf0faaa5 | ||
|
|
3f3f2bfc61 | ||
|
|
f050864fb4 | ||
|
|
db11fc35f4 | ||
|
|
f47a2b10c0 | ||
|
|
344b4e7d60 | ||
|
|
7a23372489 | ||
|
|
9da15a8e58 | ||
|
|
50e0e4a07b | ||
|
|
5e1562ae4c | ||
|
|
8a0ed49623 |
@@ -3206,7 +3206,8 @@
|
|||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
|
||||||
"profile": "https://github.com/swift2512",
|
"profile": "https://github.com/swift2512",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug",
|
||||||
|
"code"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4198,6 +4199,42 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "FlorestanII",
|
||||||
|
"name": "Johannes Pollitt",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/15015119?v=4",
|
||||||
|
"profile": "https://github.com/FlorestanII",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "strobelm",
|
||||||
|
"name": "Michael Strobel",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/14185442?v=4",
|
||||||
|
"profile": "https://strobelm.de",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "nickwest",
|
||||||
|
"name": "Nicky West",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/634790?v=4",
|
||||||
|
"profile": "http://nickwest.me",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "akaspeh1",
|
||||||
|
"name": "akaspeh1",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1347327?v=4",
|
||||||
|
"profile": "https://github.com/akaspeh1",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,11 +193,17 @@ LDAP_TIME_LIM=600
|
|||||||
IMPORT_TIME_LIMIT=600
|
IMPORT_TIME_LIMIT=600
|
||||||
IMPORT_MEMORY_LIMIT=500M
|
IMPORT_MEMORY_LIMIT=500M
|
||||||
REPORT_TIME_LIMIT=12000
|
REPORT_TIME_LIMIT=12000
|
||||||
REQUIRE_SAML=false
|
|
||||||
API_THROTTLE_PER_MINUTE=120
|
API_THROTTLE_PER_MINUTE=120
|
||||||
CSV_ESCAPE_FORMULAS=true
|
CSV_ESCAPE_FORMULAS=true
|
||||||
LIVEWIRE_URL_PREFIX=null
|
LIVEWIRE_URL_PREFIX=null
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# OPTIONAL: SAML SETTINGS
|
||||||
|
# --------------------------------------------
|
||||||
|
REQUIRE_SAML=false
|
||||||
|
SAML_KEY_SIZE=2048
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# OPTIONAL: HASHING
|
# OPTIONAL: HASHING
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|||||||
137
.github/ISSUE_TEMPLATE/Bug-Report.yml
vendored
Normal file
137
.github/ISSUE_TEMPLATE/Bug-Report.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: File a bug report.
|
||||||
|
title: "[Bug]: "
|
||||||
|
projects: ["grokability/snipe-it"]
|
||||||
|
type: bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report! Most issues are documented in the [Snipe-IT repository's issues](https://github.com/grokability/snipe-it/issues) or in the official [Common Issues section of the Documentation](https://snipe-it.readme.io/docs/common-issues#/) and are due to the following:
|
||||||
|
|
||||||
|
- `.env` misconfiguration
|
||||||
|
- [Server Permissions](https://snipe-it.readme.io/docs/debugging-permissions#/)
|
||||||
|
- [Database Migrations](https://snipe-it.readme.io/docs/database-issues#run-migrations)
|
||||||
|
|
||||||
|
Please make sure you've checked these resources before submitting a new issue. If you find an existing issue, please add your context to it instead of opening a new issue. If your issue is more of a question, consider [opening a new discussion](https://github.com/grokability/snipe-it/discussions) or [pop by our Discord](https://discord.gg/yZFtShAcKk) instead of creating an issue.
|
||||||
|
|
||||||
|
**Please write your bug report in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
|
||||||
|
|
||||||
|
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Snipe-IT Version
|
||||||
|
description: What version of Snipe-IT are you seeing this issue on? You can find the version number in the footer of any page in Snipe-IT.
|
||||||
|
placeholder: ex. v8.3.1 - build 19577 (master)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: db-version
|
||||||
|
attributes:
|
||||||
|
label: MySQL/MariaDB version
|
||||||
|
description: What database are you using, and what version?
|
||||||
|
placeholder: ex. MySQL 5.7
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: install-method
|
||||||
|
attributes:
|
||||||
|
label: How did you install Snipe-IT?
|
||||||
|
options:
|
||||||
|
- Git install
|
||||||
|
- Manual install (downloading zip/tar.gz)
|
||||||
|
- Docker
|
||||||
|
- install.sh
|
||||||
|
- Hosted by Grokability
|
||||||
|
- Other
|
||||||
|
- Not sure
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
placeholder: Tell us what you see! (Be nice!)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: browsers
|
||||||
|
attributes:
|
||||||
|
label: What browsers are you seeing the problem on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Firefox
|
||||||
|
- Chrome
|
||||||
|
- Safari
|
||||||
|
- Microsoft Edge
|
||||||
|
- Other
|
||||||
|
- type: dropdown
|
||||||
|
id: on-demo
|
||||||
|
attributes:
|
||||||
|
label: Can you reproduce this on the public demo?
|
||||||
|
description: You can check this at https://demo.snipeitapp.com.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
- N/A
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: fmcs
|
||||||
|
attributes:
|
||||||
|
label: Do you have full multiple company support enabled?
|
||||||
|
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: fmcs-location
|
||||||
|
attributes:
|
||||||
|
label: If you have full multiple company support enabled, do you have location scoping to company enabled?
|
||||||
|
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
- I do not have full multiple company support enabled
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: server-logs
|
||||||
|
attributes:
|
||||||
|
label: Application log output
|
||||||
|
description: Please copy and paste any relevant log output from `storage/logs/laravel.log`. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: browser-logs
|
||||||
|
attributes:
|
||||||
|
label: Browser console output
|
||||||
|
description: Please copy and paste any relevant log output from your browser console. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: checkboxes
|
||||||
|
id: common-issues
|
||||||
|
attributes:
|
||||||
|
label: Common Issues
|
||||||
|
description: Please make sure you have done the following before submitting your issue.
|
||||||
|
options:
|
||||||
|
- label: I have searched this repo for existing issues related to my issue (including closed issues)
|
||||||
|
required: true
|
||||||
|
- label: My APP_URL is set correctly in my .env file (including http or https and no trailing slash)
|
||||||
|
required: true
|
||||||
|
- label: I have searched the official Snipe-IT documentation and have checked the Common Issues documentation (where applicable)
|
||||||
|
required: true
|
||||||
|
- label: I have run database migrations (where applicable).
|
||||||
|
required: true
|
||||||
|
- label: I have attached screenshots and/or videos of the issue (where applicable)
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
38
.github/ISSUE_TEMPLATE/Feature-Request.yml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/Feature-Request.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Request a new feature.
|
||||||
|
title: "[Feature]: "
|
||||||
|
projects: ["grokability/snipe-it"]
|
||||||
|
type: feature
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this feature request! Please make sure to search the existing issues in this repository to see if your feature has already been requested, and feel free to add your context to any existing requests.
|
||||||
|
|
||||||
|
**Please write your issue in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
|
||||||
|
|
||||||
|
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Snipe-IT Version
|
||||||
|
description: What version of Snipe-IT are you currently running? You can find the version number in the footer of any page in Snipe-IT.
|
||||||
|
placeholder: ex. v8.3.1 - build 19577 (master)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: feature-description
|
||||||
|
attributes:
|
||||||
|
label: How can we help?
|
||||||
|
description: Let us know in detail what feature you'd like to see added. While we can't promise to implement every feature request, we do read every one and take them into consideration when planning future releases.
|
||||||
|
placeholder: Tell us what you'd like to see in Snipe-IT! (Be nice!)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
2
.github/workflows/SA-codeql.yml
vendored
2
.github/workflows/SA-codeql.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
language: [ 'javascript' ]
|
language: [ 'javascript' ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
2
.github/workflows/codacy-analysis.yml
vendored
2
.github/workflows/codacy-analysis.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout the repository to the GitHub Actions runner
|
# Checkout the repository to the GitHub Actions runner
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||||
- name: Run Codacy Analysis CLI
|
- name: Run Codacy Analysis CLI
|
||||||
|
|||||||
2
.github/workflows/crowdin-upload.yml
vendored
2
.github/workflows/crowdin-upload.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Crowdin push
|
- name: Crowdin push
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
|
|||||||
2
.github/workflows/docker-alpine.yml
vendored
2
.github/workflows/docker-alpine.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
|
|||||||
2
.github/workflows/docker-ubuntu.yml
vendored
2
.github/workflows/docker-ubuntu.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
|
|||||||
2
.github/workflows/dockerhub-description.yml
vendored
2
.github/workflows/dockerhub-description.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
dockerHubDescription:
|
dockerHubDescription:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4
|
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4
|
||||||
|
|||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
# pull-requests: write
|
# pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
debug-only: true
|
debug-only: true
|
||||||
ascending: true
|
ascending: true
|
||||||
|
|||||||
14
.github/workflows/tests-mysql.yml
vendored
14
.github/workflows/tests-mysql.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
@@ -76,4 +76,16 @@ jobs:
|
|||||||
DB_DATABASE: snipeit
|
DB_DATABASE: snipeit
|
||||||
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
14
.github/workflows/tests-postgres.yml
vendored
14
.github/workflows/tests-postgres.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
@@ -75,4 +75,16 @@ jobs:
|
|||||||
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
||||||
DB_USERNAME: snipeit
|
DB_USERNAME: snipeit
|
||||||
DB_PASSWORD: password
|
DB_PASSWORD: password
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
14
.github/workflows/tests-sqlite.yml
vendored
14
.github/workflows/tests-sqlite.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
@@ -61,4 +61,16 @@ jobs:
|
|||||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||||
env:
|
env:
|
||||||
DB_CONNECTION: sqlite
|
DB_CONNECTION: sqlite
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||||||
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=swift2512 "Code") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
|
||||||
@@ -68,7 +68,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||||||
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [<img src="https://avatars.githubusercontent.com/u/15015119?v=4" width="110px;"/><br /><sub>Johannes Pollitt</sub>](https://github.com/FlorestanII)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [<img src="https://avatars.githubusercontent.com/u/14185442?v=4" width="110px;"/><br /><sub>Michael Strobel</sub>](https://strobelm.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [<img src="https://avatars.githubusercontent.com/u/634790?v=4" width="110px;"/><br /><sub>Nicky West</sub>](http://nickwest.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [<img src="https://avatars.githubusercontent.com/u/1347327?v=4" width="110px;"/><br /><sub>akaspeh1</sub>](https://github.com/akaspeh1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") |
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ class LdapSync extends Command
|
|||||||
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
|
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
|
||||||
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
||||||
|
|
||||||
|
|
||||||
|
// Map the LDAP attributes to the Snipe-IT user fields.
|
||||||
$ldap_map = [
|
$ldap_map = [
|
||||||
"username" => Setting::getSettings()->ldap_username_field,
|
"username" => Setting::getSettings()->ldap_username_field,
|
||||||
"last_name" => Setting::getSettings()->ldap_lname_field,
|
"last_name" => Setting::getSettings()->ldap_lname_field,
|
||||||
@@ -63,11 +65,17 @@ class LdapSync extends Command
|
|||||||
"emp_num" => Setting::getSettings()->ldap_emp_num,
|
"emp_num" => Setting::getSettings()->ldap_emp_num,
|
||||||
"email" => Setting::getSettings()->ldap_email,
|
"email" => Setting::getSettings()->ldap_email,
|
||||||
"phone" => Setting::getSettings()->ldap_phone_field,
|
"phone" => Setting::getSettings()->ldap_phone_field,
|
||||||
|
"mobile" => Setting::getSettings()->ldap_mobile,
|
||||||
"jobtitle" => Setting::getSettings()->ldap_jobtitle,
|
"jobtitle" => Setting::getSettings()->ldap_jobtitle,
|
||||||
|
"address" => Setting::getSettings()->ldap_address,
|
||||||
|
"city" => Setting::getSettings()->ldap_city,
|
||||||
|
"state" => Setting::getSettings()->ldap_state,
|
||||||
|
"zip" => Setting::getSettings()->ldap_zip,
|
||||||
"country" => Setting::getSettings()->ldap_country,
|
"country" => Setting::getSettings()->ldap_country,
|
||||||
"location" => Setting::getSettings()->ldap_location,
|
"location" => Setting::getSettings()->ldap_location,
|
||||||
"dept" => Setting::getSettings()->ldap_dept,
|
"dept" => Setting::getSettings()->ldap_dept,
|
||||||
"manager" => Setting::getSettings()->ldap_manager,
|
"manager" => Setting::getSettings()->ldap_manager,
|
||||||
|
"display_name" => Setting::getSettings()->ldap_display_name,
|
||||||
];
|
];
|
||||||
|
|
||||||
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
||||||
@@ -234,9 +242,11 @@ class LdapSync extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Assign the mapped LDAP attributes for each user to the Snipe-IT user fields
|
||||||
for ($i = 0; $i < $results['count']; $i++) {
|
for ($i = 0; $i < $results['count']; $i++) {
|
||||||
$item = [];
|
$item = [];
|
||||||
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
|
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
|
||||||
|
$item['display_name'] = $results[$i][$ldap_map["display_name"]][0] ?? '';
|
||||||
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
|
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
|
||||||
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
|
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
|
||||||
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
|
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
|
||||||
@@ -244,8 +254,13 @@ class LdapSync extends Command
|
|||||||
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
|
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
|
||||||
$item['location_id'] = $results[$i]['location_id'] ?? '';
|
$item['location_id'] = $results[$i]['location_id'] ?? '';
|
||||||
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
|
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
|
||||||
|
$item['mobile'] = $results[$i][$ldap_map["mobile"]][0] ?? '';
|
||||||
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
|
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
|
||||||
|
$item['address'] = $results[$i][$ldap_map["address"]][0] ?? '';
|
||||||
|
$item['city'] = $results[$i][$ldap_map["city"]][0] ?? '';
|
||||||
|
$item['state'] = $results[$i][$ldap_map["state"]][0] ?? '';
|
||||||
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
|
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
|
||||||
|
$item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? '';
|
||||||
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
|
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
|
||||||
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
|
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
|
||||||
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
|
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
|
||||||
@@ -278,6 +293,9 @@ class LdapSync extends Command
|
|||||||
if($ldap_map["username"] != null){
|
if($ldap_map["username"] != null){
|
||||||
$user->username = $item['username'];
|
$user->username = $item['username'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["display_name"] != null){
|
||||||
|
$user->display_name = $item['display_name'];
|
||||||
|
}
|
||||||
if($ldap_map["last_name"] != null){
|
if($ldap_map["last_name"] != null){
|
||||||
$user->last_name = $item['lastname'];
|
$user->last_name = $item['lastname'];
|
||||||
}
|
}
|
||||||
@@ -293,6 +311,9 @@ class LdapSync extends Command
|
|||||||
if($ldap_map["phone"] != null){
|
if($ldap_map["phone"] != null){
|
||||||
$user->phone = $item['telephone'];
|
$user->phone = $item['telephone'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["mobile"] != null){
|
||||||
|
$user->mobile = $item['mobile'];
|
||||||
|
}
|
||||||
if($ldap_map["jobtitle"] != null){
|
if($ldap_map["jobtitle"] != null){
|
||||||
$user->jobtitle = $item['jobtitle'];
|
$user->jobtitle = $item['jobtitle'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
|
|||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use App\Models\Ldap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a given ip is in a network
|
* Check if a given ip is in a network
|
||||||
@@ -160,7 +161,15 @@ class LdapTroubleshooter extends Command
|
|||||||
$output[] = "-x";
|
$output[] = "-x";
|
||||||
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
|
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
|
||||||
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
|
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
|
||||||
$output[] = "-w ".escapeshellarg(Crypt::Decrypt($settings->ldap_pword));
|
|
||||||
|
try {
|
||||||
|
$w = Crypt::Decrypt($settings->ldap_pword);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output[] = "-w ". escapeshellarg($w);
|
||||||
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
|
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
|
||||||
if($settings->ldap_tls) {
|
if($settings->ldap_tls) {
|
||||||
$this->line("# adding STARTTLS option");
|
$this->line("# adding STARTTLS option");
|
||||||
@@ -171,6 +180,23 @@ class LdapTroubleshooter extends Command
|
|||||||
$this->line(implode(" \\\n",$output));
|
$this->line(implode(" \\\n",$output));
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//PHP Version check for warning
|
||||||
|
$php_version = phpversion();
|
||||||
|
list($major, $minor, $patch) = explode('.', $php_version);
|
||||||
|
if (
|
||||||
|
$major < 8 ||
|
||||||
|
($major == 8 && $minor < 3) ||
|
||||||
|
($major == 8 && $minor == 3 && $patch < 21) ||
|
||||||
|
($major == 8 && $minor == 4 && $patch < 7)
|
||||||
|
) {
|
||||||
|
$this->warn("PHP Version: $php_version WARNING - Versions before 8.3.21 or 8.4.7 will return INCONSISTENT results!");
|
||||||
|
if (!$this->confirm("Are you sure you wish to continue?")) {
|
||||||
|
$this->warn("ABORTING");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!$this->option('force')) {
|
if(!$this->option('force')) {
|
||||||
$confirmation = $this->confirm('WARNING: This command will make several attempts to connect to your LDAP server. Are you sure this is ok?');
|
$confirmation = $this->confirm('WARNING: This command will make several attempts to connect to your LDAP server. Are you sure this is ok?');
|
||||||
if(!$confirmation) {
|
if(!$confirmation) {
|
||||||
@@ -179,7 +205,7 @@ class LdapTroubleshooter extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//$this->line(print_r($settings,true));
|
//$this->line(print_r($settings,true));
|
||||||
$this->info("STAGE 1: Checking settings");
|
$this->line("STAGE 1: Checking settings");
|
||||||
if(!$settings->ldap_enabled) {
|
if(!$settings->ldap_enabled) {
|
||||||
$this->error("WARNING: Snipe-IT's LDAP setting is not turned on. (That may be OK if you're still trying to figure out settings)");
|
$this->error("WARNING: Snipe-IT's LDAP setting is not turned on. (That may be OK if you're still trying to figure out settings)");
|
||||||
}
|
}
|
||||||
@@ -210,32 +236,40 @@ class LdapTroubleshooter extends Command
|
|||||||
$this->info("Determined LDAP hostname to be: ".$parsed['host']);
|
$this->info("Determined LDAP hostname to be: ".$parsed['host']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("Performing DNS lookup of: ".$parsed['host']);
|
|
||||||
$ips = dns_get_record($parsed['host']);
|
|
||||||
$raw_ips = [];
|
$raw_ips = [];
|
||||||
|
|
||||||
//$this->info("Host IP is: ".print_r($ips,true));
|
if (inet_pton($parsed['host']) !== false) {
|
||||||
|
$this->line($parsed['host'] . " already looks like an address; skipping DNS lookup");
|
||||||
|
$raw_ips[] = $parsed['host'];
|
||||||
|
} else {
|
||||||
|
$this->line("Performing DNS lookup of: " . $parsed['host']);
|
||||||
|
$ips = dns_get_record($parsed['host']);
|
||||||
|
|
||||||
if(!$ips || count($ips) == 0) {
|
//$this->info("Host IP is: ".print_r($ips,true));
|
||||||
$this->error("ERROR: DNS lookup of host: ".$parsed['host']." has failed. ABORTING.");
|
|
||||||
exit(-1);
|
if (!$ips || count($ips) == 0) {
|
||||||
}
|
$this->error("ERROR: DNS lookup of host: " . $parsed['host'] . " has failed. ABORTING.");
|
||||||
$this->debugout("IP's? ".print_r($ips,true));
|
exit(-1);
|
||||||
foreach($ips as $ip) {
|
|
||||||
if(!isset($ip['ip'])) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
$raw_ips[]=$ip['ip'];
|
$this->debugout("IP's? " . print_r($ips, true));
|
||||||
if($ip['ip'] == "127.0.0.1") {
|
foreach ($ips as $ip) {
|
||||||
|
if (!isset($ip['ip'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$raw_ips[] = $ip['ip'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($raw_ips as $ip) {
|
||||||
|
if ($ip == "127.0.0.1") {
|
||||||
$this->error("WARNING: Using the localhost IP as the LDAP server. This is usually wrong");
|
$this->error("WARNING: Using the localhost IP as the LDAP server. This is usually wrong");
|
||||||
}
|
}
|
||||||
if(ip_in_range($ip['ip'],'10.0.0.0/8') || ip_in_range($ip['ip'],'192.168.0.0/16') || ip_in_range($ip['ip'], '172.16.0.0/12')) {
|
if (ip_in_range($ip, '10.0.0.0/8') || ip_in_range($ip, '192.168.0.0/16') || ip_in_range($ip, '172.16.0.0/12')) {
|
||||||
$this->error("WARNING: Using an RFC1918 Private address for LDAP server. This may be correct, but it can be a problem if your Snipe-IT instance is not hosted on your private network");
|
$this->error("WARNING: Using an RFC1918 Private address for LDAP server. This may be correct, but it can be a problem if your Snipe-IT instance is not hosted on your private network");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("STAGE 2: Checking basic network connectivity");
|
$this->line("STAGE 2: Checking basic network connectivity");
|
||||||
$ports = [389,636];
|
$ports = [636, 389];
|
||||||
if(@$parsed['port'] && !in_array($parsed['port'],$ports)) {
|
if(@$parsed['port'] && !in_array($parsed['port'],$ports)) {
|
||||||
$ports[] = $parsed['port'];
|
$ports[] = $parsed['port'];
|
||||||
}
|
}
|
||||||
@@ -246,7 +280,7 @@ class LdapTroubleshooter extends Command
|
|||||||
$errstr = '';
|
$errstr = '';
|
||||||
$timeout = 30.0;
|
$timeout = 30.0;
|
||||||
$result = '';
|
$result = '';
|
||||||
$this->info("Attempting to connect to port: ".$port." - may take up to $timeout seconds");
|
$this->line("Attempting to connect to port: " . $port . " - may take up to $timeout seconds");
|
||||||
try {
|
try {
|
||||||
$result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0);
|
$result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0);
|
||||||
} catch(Exception $e) {
|
} catch(Exception $e) {
|
||||||
@@ -265,9 +299,9 @@ class LdapTroubleshooter extends Command
|
|||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("STAGE 3: Determine encryption algorithm, if any");
|
$this->line("STAGE 3: Determine encryption algorithm, if any");
|
||||||
|
|
||||||
$ldap_urls = [];
|
$ldap_urls = []; // [url, cert-check?, start_tls?]
|
||||||
$pretty_ldap_urls = [];
|
$pretty_ldap_urls = [];
|
||||||
foreach($open_ports as $port) {
|
foreach($open_ports as $port) {
|
||||||
$this->line("Trying TLS first for port $port");
|
$this->line("Trying TLS first for port $port");
|
||||||
@@ -275,35 +309,46 @@ class LdapTroubleshooter extends Command
|
|||||||
if($this->test_anonymous_bind($ldap_url)) {
|
if($this->test_anonymous_bind($ldap_url)) {
|
||||||
$this->info("Anonymous bind succesful to $ldap_url!");
|
$this->info("Anonymous bind succesful to $ldap_url!");
|
||||||
$ldap_urls[] = [ $ldap_url, true, false ];
|
$ldap_urls[] = [ $ldap_url, true, false ];
|
||||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ];
|
$pretty_ldap_urls[] = [$ldap_url, "enabled", "n/a (no)"];
|
||||||
continue; // TODO - lots of copypasta in these if(test_anonymous_bind()) routines...
|
continue; // TODO - lots of copypasta in these if(test_anonymous_bind()) routines...
|
||||||
} else {
|
} else {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks.");
|
$this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->test_anonymous_bind($ldap_url, false)) {
|
if($this->test_anonymous_bind($ldap_url, false)) {
|
||||||
$this->info("Anonymous bind succesful to $ldap_url with certifcate-checks disabled");
|
$this->info("Anonymous bind successful to $ldap_url with certificate-checks disabled");
|
||||||
$ldap_urls[] = [ $ldap_url, false, false ];
|
$ldap_urls[] = [$ldap_url, false, false];
|
||||||
$pretty_ldap_urls[] = [ $ldap_url, "no", "no" ];
|
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "n/a (no)"];
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url with certificate checks disabled. Trying unencrypted with STARTTLS");
|
$this->error("WARNING: Failed to bind to $ldap_url with certificate checks disabled. Trying unencrypted with STARTTLS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now switching to ldap:// URL's from ldaps://
|
||||||
$ldap_url = "ldap://".$parsed['host'].":$port";
|
$ldap_url = "ldap://".$parsed['host'].":$port";
|
||||||
|
|
||||||
if($this->test_anonymous_bind($ldap_url, true, true)) {
|
if($this->test_anonymous_bind($ldap_url, true, true)) {
|
||||||
$this->info("Plain connection to $ldap_url with STARTTLS succesful!");
|
$this->info("Plain connection to $ldap_url with STARTTLS succesful!");
|
||||||
$ldap_urls[] = [ $ldap_url, true, true ];
|
$ldap_urls[] = [ $ldap_url, true, true ];
|
||||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "YES" ];
|
$pretty_ldap_urls[] = [$ldap_url, "enabled", "STARTTLS ENABLED"];
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without STARTTLS");
|
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without certificate checks.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->test_anonymous_bind($ldap_url, false, true)) {
|
||||||
|
$this->info("Plain connection to $ldap_url with STARTTLS and cert checks *disabled* successful!");
|
||||||
|
$ldap_urls[] = [$ldap_url, false, true];
|
||||||
|
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "STARTTLS ENABLED"];
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled, and cert checks disabled. Trying without STARTTLS");
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->test_anonymous_bind($ldap_url)) {
|
if($this->test_anonymous_bind($ldap_url)) {
|
||||||
$this->info("Plain connection to $ldap_url succesful!");
|
$this->info("Plain connection to $ldap_url succesful!");
|
||||||
$ldap_urls[] = [ $ldap_url, true, false ];
|
$ldap_urls[] = [ $ldap_url, true, false ];
|
||||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ];
|
$pretty_ldap_urls[] = [$ldap_url, "n/a", "starttls disabled"];
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url. Giving up on port $port");
|
$this->error("WARNING: Failed to bind to $ldap_url. Giving up on port $port");
|
||||||
@@ -313,23 +358,29 @@ class LdapTroubleshooter extends Command
|
|||||||
$this->debugout(print_r($ldap_urls,true));
|
$this->debugout(print_r($ldap_urls,true));
|
||||||
|
|
||||||
if(count($ldap_urls) > 0 ) {
|
if(count($ldap_urls) > 0 ) {
|
||||||
$this->info("Found working LDAP URL's: ");
|
$this->debugout("Found working LDAP URL's: ");
|
||||||
foreach($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead?
|
foreach($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead?
|
||||||
$this->info("LDAP URL: ".$ldap_url[0]);
|
$this->debugout("LDAP URL: " . $ldap_url[0]);
|
||||||
$this->info($ldap_url[0]. ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled"). ($ldap_url[2] ? " STARTTLS Enabled ": " STARTTLS Disabled"));
|
$this->debugout($ldap_url[0] . ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled") . ($ldap_url[2] ? " STARTTLS Enabled " : " STARTTLS Disabled"));
|
||||||
}
|
}
|
||||||
$this->table(["URL", "Cert Checks Enabled?", "STARTTLS Enabled?"],$pretty_ldap_urls);
|
$this->table(["URL", "Cert Checks?", "STARTTLS?"], $pretty_ldap_urls);
|
||||||
} else {
|
} else {
|
||||||
$this->error("ERROR - no valid LDAP URL's available - ABORTING");
|
$this->error("ERROR - no valid LDAP URL's available - ABORTING");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("STAGE 4: Test Administrative Bind for LDAP Sync");
|
$this->line("STAGE 4: Test Administrative Bind for LDAP Sync");
|
||||||
foreach($ldap_urls AS $ldap_url) {
|
foreach($ldap_urls AS $ldap_url) {
|
||||||
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, Crypt::decrypt($settings->ldap_pword));
|
try {
|
||||||
|
$w = Crypt::Decrypt($settings->ldap_pword);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, $w);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("STAGE 5: Test BaseDN");
|
$this->line("STAGE 5: Test BaseDN");
|
||||||
//grab all LDAP_ constants and fill up a reversed array mapping from weird LDAP dotted-strings to (Constant Name)
|
//grab all LDAP_ constants and fill up a reversed array mapping from weird LDAP dotted-strings to (Constant Name)
|
||||||
$all_defined_constants = get_defined_constants();
|
$all_defined_constants = get_defined_constants();
|
||||||
$ldap_constants = [];
|
$ldap_constants = [];
|
||||||
@@ -341,16 +392,23 @@ class LdapTroubleshooter extends Command
|
|||||||
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
|
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
|
||||||
|
|
||||||
foreach($ldap_urls AS $ldap_url) {
|
foreach($ldap_urls AS $ldap_url) {
|
||||||
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,Crypt::decrypt($settings->ldap_pword),$settings)) {
|
try {
|
||||||
|
$w = Crypt::Decrypt($settings->ldap_pword);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,$w,$settings)) {
|
||||||
$this->info("Success getting informational bind!");
|
$this->info("Success getting informational bind!");
|
||||||
} else {
|
} else {
|
||||||
$this->error("Unable to get information from bind.");
|
$this->error("Unable to get information from bind.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("STAGE 6: Test LDAP Login to Snipe-IT");
|
$this->line("STAGE 6: Test LDAP Login to Snipe-IT");
|
||||||
foreach($ldap_urls AS $ldap_url) {
|
foreach($ldap_urls AS $ldap_url) {
|
||||||
$this->info("Starting auth to ".$ldap_url[0]);
|
$this->line("Starting auth to " . $ldap_url[0]);
|
||||||
while(true) {
|
while(true) {
|
||||||
$with_tls = $ldap_url[1] ? "with": "without";
|
$with_tls = $ldap_url[1] ? "with": "without";
|
||||||
$with_startssl = $ldap_url[2] ? "using": "not using";
|
$with_startssl = $ldap_url[2] ? "using": "not using";
|
||||||
@@ -359,7 +417,12 @@ class LdapTroubleshooter extends Command
|
|||||||
}
|
}
|
||||||
$username = $this->ask("Username");
|
$username = $this->ask("Username");
|
||||||
$password = $this->secret("Password");
|
$password = $this->secret("Password");
|
||||||
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results?
|
$results = $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results?
|
||||||
|
if ($results) {
|
||||||
|
$this->info("Success authenticating with " . $username);
|
||||||
|
} else {
|
||||||
|
$this->error("Unable to authenticate with " . $username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,14 +431,17 @@ class LdapTroubleshooter extends Command
|
|||||||
|
|
||||||
public function connect_to_ldap($ldap_url, $check_cert, $start_tls)
|
public function connect_to_ldap($ldap_url, $check_cert, $start_tls)
|
||||||
{
|
{
|
||||||
|
if ($check_cert) {
|
||||||
|
$this->line("we *ARE* checking certs");
|
||||||
|
Ldap::ignoreCertificates(false);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->line("we are IGNORING certs");
|
||||||
|
Ldap::ignoreCertificates(true);
|
||||||
|
}
|
||||||
$lconn = ldap_connect($ldap_url);
|
$lconn = ldap_connect($ldap_url);
|
||||||
ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3); // should we 'test' different protocol versions here? Does anyone even use anything other than LDAPv3?
|
ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3); // should we 'test' different protocol versions here? Does anyone even use anything other than LDAPv3?
|
||||||
// no - it's formally deprecated: https://tools.ietf.org/html/rfc3494
|
// no - it's formally deprecated: https://tools.ietf.org/html/rfc3494
|
||||||
if(!$check_cert) {
|
|
||||||
putenv('LDAPTLS_REQCERT=never'); // This is horrible; is this *really* the only way to do it?
|
|
||||||
} else {
|
|
||||||
putenv('LDAPTLS_REQCERT'); // have to very explicitly and manually *UN* set the env var here to ensure it works
|
|
||||||
}
|
|
||||||
if($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) {
|
if($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) {
|
||||||
// client-side TLS certificate support for LDAP (Google Secure LDAP)
|
// client-side TLS certificate support for LDAP (Google Secure LDAP)
|
||||||
putenv('LDAPTLS_CERT=storage/ldap_client_tls.cert');
|
putenv('LDAPTLS_CERT=storage/ldap_client_tls.cert');
|
||||||
@@ -404,9 +470,10 @@ class LdapTroubleshooter extends Command
|
|||||||
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert , $start_tls) {
|
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert , $start_tls) {
|
||||||
try {
|
try {
|
||||||
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
||||||
$this->info("gonna try to bind now, this can take a while if we mess it up");
|
$this->line("Attempting to bind now, this can take a while if we mess it up");
|
||||||
$bind_results = ldap_bind($lconn);
|
$bind_results = ldap_bind($lconn);
|
||||||
$this->info("Bind results are: ".$bind_results." which translate into boolean: ".(bool)$bind_results);
|
$this->line("Bind results are: " . $bind_results . " which translate into boolean: " . (bool)$bind_results);
|
||||||
|
ldap_close($lconn);
|
||||||
return (bool)$bind_results;
|
return (bool)$bind_results;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->error("WARNING: Exception caught during bind - ".$e->getMessage());
|
$this->error("WARNING: Exception caught during bind - ".$e->getMessage());
|
||||||
@@ -421,6 +488,7 @@ class LdapTroubleshooter extends Command
|
|||||||
try {
|
try {
|
||||||
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
||||||
$bind_results = ldap_bind($lconn, $username, $password);
|
$bind_results = ldap_bind($lconn, $username, $password);
|
||||||
|
ldap_close($lconn);
|
||||||
if(!$bind_results) {
|
if(!$bind_results) {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url as $username");
|
$this->error("WARNING: Failed to bind to $ldap_url as $username");
|
||||||
return false;
|
return false;
|
||||||
@@ -446,22 +514,62 @@ class LdapTroubleshooter extends Command
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
|
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
|
||||||
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
|
$cleaned_results = [];
|
||||||
$results = ldap_get_entries($conn, $result);
|
try {
|
||||||
$cleaned_results = $this->ldap_results_cleaner($results);
|
// This _may_ only work for Active Directory?
|
||||||
$this->line(print_r($cleaned_results,true));
|
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
|
||||||
//okay, great - now how do we display those results? I have no idea.
|
$results = ldap_get_entries($conn, $result);
|
||||||
|
$cleaned_results = $this->ldap_results_cleaner($results);
|
||||||
|
//$this->line(print_r($cleaned_results,true));
|
||||||
|
$default_naming_contexts = $cleaned_results[0]['namingcontexts'];
|
||||||
|
$this->info("Default Naming Contexts:");
|
||||||
|
$this->info(implode(", ", $default_naming_contexts));
|
||||||
|
//okay, great - now how do we display those results? I have no idea.
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error("Unable to get base naming contexts - here's what we *did* get:");
|
||||||
|
$this->line(print_r($cleaned_results, true));
|
||||||
|
}
|
||||||
// I don't see why this throws an Exception for Google LDAP, but I guess we ought to try and catch it?
|
// I don't see why this throws an Exception for Google LDAP, but I guess we ought to try and catch it?
|
||||||
$this->comment("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
|
$this->debugout("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
|
||||||
$this->debugout("Base DN is: ".$settings->ldap_basedn." and filter is: ".parenthesized_filter($settings->ldap_filter));
|
$this->debugout("Base DN is: ".$settings->ldap_basedn." and filter is: ".parenthesized_filter($settings->ldap_filter));
|
||||||
$search_results = ldap_search($conn, $settings->ldap_basedn, parenthesized_filter($settings->ldap_filter));
|
$search_results = ldap_search($conn, $settings->ldap_basedn, parenthesized_filter($settings->ldap_filter));
|
||||||
|
$entries = ldap_get_entries($conn, $search_results);
|
||||||
$this->info("Printing first 10 results: ");
|
$this->info("Printing first 10 results: ");
|
||||||
for($i=0;$i<10;$i++) {
|
$pretty_data = array_slice($this->ldap_results_cleaner($entries), 0, 10);
|
||||||
$this->info($search_results[$i]);
|
//print_r($data);
|
||||||
|
$headers = [];
|
||||||
|
foreach ($pretty_data as $row) {
|
||||||
|
//populate headers
|
||||||
|
foreach ($row as $key => $value) {
|
||||||
|
//skip objectsid and objectguid because it junks up output
|
||||||
|
if ($key == "objectsid" || $key == "objectguid") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!in_array($key, $headers)) {
|
||||||
|
$headers[] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$table = [];
|
||||||
|
//repeat again to populate table
|
||||||
|
foreach ($pretty_data as $row) {
|
||||||
|
$newrow = [];
|
||||||
|
foreach ($headers as $header) {
|
||||||
|
if (is_array(@$row[$header])) {
|
||||||
|
$newrow[] = "[" . implode(", ", $row[$header]) . "]";
|
||||||
|
} else {
|
||||||
|
$newrow[] = @$row[$header];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$table[] = $newrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->table($headers, $table);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
|
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
|
ldap_close($conn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -477,7 +585,7 @@ class LdapTroubleshooter extends Command
|
|||||||
{
|
{
|
||||||
if(!(function_exists('pcntl_sigtimedwait') && function_exists('posix_getpid') && function_exists('pcntl_fork') && function_exists('posix_kill') && function_exists('pcntl_wifsignaled'))) {
|
if(!(function_exists('pcntl_sigtimedwait') && function_exists('posix_getpid') && function_exists('pcntl_fork') && function_exists('posix_kill') && function_exists('pcntl_wifsignaled'))) {
|
||||||
// POSIX functions needed for forking aren't present, just run the function inline (ignoring timeout)
|
// POSIX functions needed for forking aren't present, just run the function inline (ignoring timeout)
|
||||||
$this->info('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected');
|
$this->line('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected');
|
||||||
return $function();
|
return $function();
|
||||||
} else {
|
} else {
|
||||||
$parent_pid = posix_getpid();
|
$parent_pid = posix_getpid();
|
||||||
@@ -514,4 +622,6 @@ class LdapTroubleshooter extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ class PaveIt extends Command
|
|||||||
'migrations',
|
'migrations',
|
||||||
'settings',
|
'settings',
|
||||||
'users',
|
'users',
|
||||||
|
'telescope_entries',
|
||||||
|
'telescope_entries_tags',
|
||||||
|
'telescope_monitoring',
|
||||||
];
|
];
|
||||||
|
|
||||||
// We only need to find out what these are so we can nuke these columns on the assets table.
|
// We only need to find out what these are so we can nuke these columns on the assets table.
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Purge extends Command
|
|||||||
$maintenances = 0;
|
$maintenances = 0;
|
||||||
|
|
||||||
foreach ($assets as $asset) {
|
foreach ($assets as $asset) {
|
||||||
$this->info('- Asset "'.$asset->present()->name().'" deleted.');
|
$this->info('- Asset "'.$asset->display_name.'" deleted.');
|
||||||
$asset_assoc += $asset->assetlog()->count();
|
$asset_assoc += $asset->assetlog()->count();
|
||||||
$asset->assetlog()->forceDelete();
|
$asset->assetlog()->forceDelete();
|
||||||
$maintenances += $asset->maintenances()->count();
|
$maintenances += $asset->maintenances()->count();
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class SendAcceptanceReminder extends Command
|
|||||||
if(!$email){
|
if(!$email){
|
||||||
$no_email_list[] = [
|
$no_email_list[] = [
|
||||||
'id' => $acceptance->assignedTo?->id,
|
'id' => $acceptance->assignedTo?->id,
|
||||||
'name' => $acceptance->assignedTo?->present()->fullName(),
|
'name' => $acceptance->assignedTo?->display_name,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$count++;
|
$count++;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
use App\Mail\ExpiringAssetsMail;
|
use App\Mail\ExpiringAssetsMail;
|
||||||
use App\Mail\ExpiringLicenseMail;
|
use App\Mail\ExpiringLicenseMail;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
@@ -52,19 +53,35 @@ class SendExpirationAlerts extends Command
|
|||||||
->filter(fn($item) => !empty($item))
|
->filter(fn($item) => !empty($item))
|
||||||
->all();
|
->all();
|
||||||
// Expiring Assets
|
// Expiring Assets
|
||||||
$assets = Asset::getExpiringWarrantee($alert_interval);
|
$assets = Asset::getExpiringWarrantyOrEol($alert_interval);
|
||||||
|
|
||||||
if ($assets->count() > 0) {
|
if ($assets->count() > 0) {
|
||||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
|
|
||||||
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
|
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
['ID', 'Tag', 'Model', 'Model Number', 'EOL', 'EOL Months', 'Warranty Expires', 'Warranty Months'],
|
||||||
|
$assets->map(fn($item) => ['ID' => $item->id, 'Tag' => $item->asset_tag, 'Model' => $item->model->name, 'Model Number' => $item->model->model_number, 'EOL' => $item->asset_eol_date, 'EOL Months' => $item->model->eol, 'Warranty Expires' => $item->warranty_expires, 'Warranty Months' => $item->warranty_months])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expiring licenses
|
// Expiring licenses
|
||||||
$licenses = License::getExpiringLicenses($alert_interval);
|
$licenses = License::getExpiringLicenses($alert_interval);
|
||||||
if ($licenses->count() > 0) {
|
if ($licenses->count() > 0) {
|
||||||
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
|
|
||||||
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
|
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
['ID', 'Name', 'Expires', 'Termination Date'],
|
||||||
|
$licenses->map(fn($item) => ['ID' => $item->id, 'Name' => $item->name, 'Expires' => $item->expiration_date, 'Termination Date' => $item->termination_date])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send a message even if the count is 0
|
||||||
|
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
|
||||||
|
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if ($settings->alert_email == '') {
|
if ($settings->alert_email == '') {
|
||||||
$this->error('Could not send email. No alert email configured in settings');
|
$this->error('Could not send email. No alert email configured in settings');
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class Helper
|
|||||||
$Parsedown->setSafeMode(true);
|
$Parsedown->setSafeMode(true);
|
||||||
|
|
||||||
if ($str) {
|
if ($str) {
|
||||||
return $Parsedown->text($str);
|
return $Parsedown->text(strip_tags($str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class Helper
|
|||||||
$Parsedown->setSafeMode(true);
|
$Parsedown->setSafeMode(true);
|
||||||
|
|
||||||
if ($str) {
|
if ($str) {
|
||||||
return $Parsedown->line($str);
|
return $Parsedown->line(strip_tags($str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +435,34 @@ class Helper
|
|||||||
return $colors[$index];
|
return $colors[$index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string has any RTL characters
|
||||||
|
* @param $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function hasRtl($string) {
|
||||||
|
$rtlChar = '/[\x{0590}-\x{083F}]|[\x{08A0}-\x{08FF}]|[\x{FB1D}-\x{FDFF}]|[\x{FE70}-\x{FEFF}]/u';
|
||||||
|
return preg_match($rtlChar, $string) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is chinese, japanese or korean language
|
||||||
|
public static function isCjk($string) {
|
||||||
|
return Helper::isChinese($string) || Helper::isJapanese($string) || Helper::isKorean($string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isChinese($string) {
|
||||||
|
return preg_match("/\p{Han}+/u", $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isJapanese($string) {
|
||||||
|
return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isKorean($string) {
|
||||||
|
return preg_match('/[\x{3130}-\x{318F}\x{AC00}-\x{D7AF}]/u', $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
||||||
*
|
*
|
||||||
@@ -1197,19 +1225,30 @@ class Helper
|
|||||||
'webp' => 'far fa-image',
|
'webp' => 'far fa-image',
|
||||||
'avif' => 'far fa-image',
|
'avif' => 'far fa-image',
|
||||||
'svg' => 'fas fa-vector-square',
|
'svg' => 'fas fa-vector-square',
|
||||||
|
|
||||||
// word
|
// word
|
||||||
'doc' => 'far fa-file-word',
|
'doc' => 'far fa-file-word',
|
||||||
'docx' => 'far fa-file-word',
|
'docx' => 'far fa-file-word',
|
||||||
|
|
||||||
// Excel
|
// Excel
|
||||||
'xls' => 'far fa-file-excel',
|
'xls' => 'far fa-file-excel',
|
||||||
'xlsx' => 'far fa-file-excel',
|
'xlsx' => 'far fa-file-excel',
|
||||||
|
'ods' => 'far fa-file-excel',
|
||||||
|
|
||||||
|
// Presentation
|
||||||
|
'ppt' => 'far fa-file-powerpoint',
|
||||||
|
'odp' => 'far fa-file-powerpoint',
|
||||||
|
|
||||||
// archive
|
// archive
|
||||||
'zip' => 'fas fa-file-archive',
|
'zip' => 'fas fa-file-archive',
|
||||||
'rar' => 'fas fa-file-archive',
|
'rar' => 'fas fa-file-archive',
|
||||||
|
|
||||||
//Text
|
//Text
|
||||||
|
'odt' => 'far fa-file-alt',
|
||||||
'txt' => 'far fa-file-alt',
|
'txt' => 'far fa-file-alt',
|
||||||
'rtf' => 'far fa-file-alt',
|
'rtf' => 'far fa-file-alt',
|
||||||
'xml' => 'fas fa-code',
|
'xml' => 'fas fa-code',
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
'pdf' => 'far fa-file-pdf',
|
'pdf' => 'far fa-file-pdf',
|
||||||
'lic' => 'far fa-save',
|
'lic' => 'far fa-save',
|
||||||
@@ -1695,5 +1734,5 @@ class Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $mismatched;
|
return $mismatched;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class IconHelper
|
|||||||
case 'clone':
|
case 'clone':
|
||||||
return 'far fa-clone';
|
return 'far fa-clone';
|
||||||
case 'delete':
|
case 'delete':
|
||||||
|
case 'upload deleted':
|
||||||
return 'fas fa-trash';
|
return 'fas fa-trash';
|
||||||
case 'create':
|
case 'create':
|
||||||
return 'fa-solid fa-plus';
|
return 'fa-solid fa-plus';
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class StorageHelper
|
|||||||
|
|
||||||
public static function getMediaType($file_with_path) {
|
public static function getMediaType($file_with_path) {
|
||||||
|
|
||||||
// The file exists and is allowed to be displayed inline
|
// Get the file extension and determine the media type
|
||||||
if (Storage::exists($file_with_path)) {
|
if (Storage::exists($file_with_path)) {
|
||||||
$fileinfo = pathinfo($file_with_path);
|
$fileinfo = pathinfo($file_with_path);
|
||||||
$extension = strtolower($fileinfo['extension']);
|
$extension = strtolower($fileinfo['extension']);
|
||||||
@@ -51,6 +51,15 @@ class StorageHelper
|
|||||||
case 'webm':
|
case 'webm':
|
||||||
case 'mov':
|
case 'mov':
|
||||||
return 'video';
|
return 'video';
|
||||||
|
case 'doc':
|
||||||
|
case 'docx':
|
||||||
|
return 'document';
|
||||||
|
case 'txt':
|
||||||
|
return 'text';
|
||||||
|
case 'xls':
|
||||||
|
case 'xlsx':
|
||||||
|
case 'ods':
|
||||||
|
return 'spreadsheet';
|
||||||
default:
|
default:
|
||||||
return $extension; // Default for unknown types
|
return $extension; // Default for unknown types
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class AccessoryCheckoutController extends Controller
|
|||||||
$this->authorize('checkout', $accessory);
|
$this->authorize('checkout', $accessory);
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
||||||
|
|
||||||
|
|||||||
@@ -8,33 +8,23 @@ use App\Events\ItemAccepted;
|
|||||||
use App\Events\ItemDeclined;
|
use App\Events\ItemDeclined;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Mail\CheckoutAcceptanceResponseMail;
|
use App\Mail\CheckoutAcceptanceResponseMail;
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Asset;
|
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Contracts\Acceptable;
|
use App\Models\Contracts\Acceptable;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\AssetModel;
|
|
||||||
use App\Models\Accessory;
|
|
||||||
use App\Models\License;
|
|
||||||
use App\Models\Component;
|
|
||||||
use App\Models\Consumable;
|
|
||||||
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
||||||
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
|
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
|
||||||
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Http\Controllers\SettingsController;
|
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use \Illuminate\Contracts\View\View;
|
use \Illuminate\Contracts\View\View;
|
||||||
use \Illuminate\Http\RedirectResponse;
|
use \Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
|
||||||
class AcceptanceController extends Controller
|
class AcceptanceController extends Controller
|
||||||
{
|
{
|
||||||
@@ -85,6 +75,11 @@ class AcceptanceController extends Controller
|
|||||||
public function store(Request $request, $id) : RedirectResponse
|
public function store(Request $request, $id) : RedirectResponse
|
||||||
{
|
{
|
||||||
$acceptance = CheckoutAcceptance::find($id);
|
$acceptance = CheckoutAcceptance::find($id);
|
||||||
|
$assigned_user = User::find($acceptance->assigned_to_id);
|
||||||
|
$settings = Setting::getSettings();
|
||||||
|
$path_logo = '';
|
||||||
|
$sig_filename='';
|
||||||
|
|
||||||
|
|
||||||
if (is_null($acceptance)) {
|
if (is_null($acceptance)) {
|
||||||
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||||
@@ -107,140 +102,75 @@ class AcceptanceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the signature and save it
|
* Check for the signature directory
|
||||||
*/
|
*/
|
||||||
if (! Storage::exists('private_uploads/signatures')) {
|
if (! Storage::exists('private_uploads/signatures')) {
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
Storage::makeDirectory('private_uploads/signatures', 775);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for the eula-pdfs directory
|
||||||
|
*/
|
||||||
|
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
||||||
|
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
||||||
|
}
|
||||||
|
|
||||||
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
||||||
$display_model = '';
|
|
||||||
$pdf_view_route = '';
|
|
||||||
$pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
|
||||||
$sig_filename='';
|
// If signatures are required, make sure we have one
|
||||||
|
if (Setting::getSettings()->require_accept_signature == '1') {
|
||||||
|
|
||||||
|
// The item was accepted, check for a signature
|
||||||
|
if ($request->filled('signature_output')) {
|
||||||
|
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
||||||
|
$data_uri = $request->input('signature_output');
|
||||||
|
$encoded_image = explode(',', $data_uri);
|
||||||
|
$decoded_image = base64_decode($encoded_image[1]);
|
||||||
|
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
||||||
|
|
||||||
|
// No image data is present, kick them back.
|
||||||
|
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
||||||
|
} else {
|
||||||
|
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get the data array ready for the notifications and PDF generation
|
||||||
|
$data = [
|
||||||
|
'item_tag' => $item->asset_tag,
|
||||||
|
'item_name' => $item->name, // this handles licenses seats, which don't have a 'name' field
|
||||||
|
'item_model' => $item->model?->name,
|
||||||
|
'item_serial' => $item->serial,
|
||||||
|
'item_status' => $item->assetstatus?->name,
|
||||||
|
'eula' => $item->getEula(),
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'check_out_date' => Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false),
|
||||||
|
'accepted_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
|
||||||
|
'declined_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
|
||||||
|
'assigned_to' => $assigned_user->display_name,
|
||||||
|
'site_name' => $settings->site_name,
|
||||||
|
'company_name' => $item->company?->name?? $settings->site_name,
|
||||||
|
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
||||||
|
'logo' => ($settings->acceptance_pdf_logo) ? public_path() . '/uploads/' . $settings->acceptance_pdf_logo : null,
|
||||||
|
'date_settings' => $settings->date_display_format,
|
||||||
|
'admin' => auth()->user()->present()?->fullName,
|
||||||
|
'qty' => $acceptance->qty ?? 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
if ($request->input('asset_acceptance') == 'accepted') {
|
if ($request->input('asset_acceptance') == 'accepted') {
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for the eula-pdfs directory
|
|
||||||
*/
|
|
||||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
||||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Setting::getSettings()->require_accept_signature == '1') {
|
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
||||||
|
|
||||||
// Check if the signature directory exists, if not create it
|
|
||||||
if (!Storage::exists('private_uploads/signatures')) {
|
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item was accepted, check for a signature
|
// Generate the PDF content
|
||||||
if ($request->filled('signature_output')) {
|
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
|
||||||
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
|
||||||
$data_uri = $request->input('signature_output');
|
|
||||||
$encoded_image = explode(',', $data_uri);
|
|
||||||
$decoded_image = base64_decode($encoded_image[1]);
|
|
||||||
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
|
||||||
|
|
||||||
// No image data is present, kick them back.
|
|
||||||
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
|
||||||
} else {
|
|
||||||
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$assigned_user = User::find($acceptance->assigned_to_id);
|
|
||||||
// this is horrible
|
|
||||||
switch($acceptance->checkoutable_type){
|
|
||||||
case 'App\Models\Asset':
|
|
||||||
$pdf_view_route ='account.accept.accept-asset-eula';
|
|
||||||
$asset_model = AssetModel::find($item->model_id);
|
|
||||||
if (!$asset_model) {
|
|
||||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
|
||||||
}
|
|
||||||
$display_model = $asset_model->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Accessory':
|
|
||||||
$pdf_view_route ='account.accept.accept-accessory-eula';
|
|
||||||
$accessory = Accessory::find($item->id);
|
|
||||||
$display_model = $accessory->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\LicenseSeat':
|
|
||||||
$pdf_view_route ='account.accept.accept-license-eula';
|
|
||||||
$license = License::find($item->license_id);
|
|
||||||
$display_model = $license->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Component':
|
|
||||||
$pdf_view_route ='account.accept.accept-component-eula';
|
|
||||||
$component = Component::find($item->id);
|
|
||||||
$display_model = $component->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Consumable':
|
|
||||||
$pdf_view_route ='account.accept.accept-consumable-eula';
|
|
||||||
$consumable = Consumable::find($item->id);
|
|
||||||
$display_model = $consumable->name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
|
|
||||||
// $pdf_view_route ='account.accept.accept-asset-eula';
|
|
||||||
// $asset_model = AssetModel::find($item->model_id);
|
|
||||||
// $display_model = $asset_model->name;
|
|
||||||
// $assigned_to = User::find($item->assigned_to)->present()->fullName;
|
|
||||||
//
|
|
||||||
// } elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
|
|
||||||
// $pdf_view_route ='account.accept.accept-accessory-eula';
|
|
||||||
// $accessory = Accessory::find($item->id);
|
|
||||||
// $display_model = $accessory->name;
|
|
||||||
// $assigned_to = User::find($item->assignedTo);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gather the data for the PDF. We fire this whether there is a signature required or not,
|
|
||||||
* since we want the moment-in-time proof of what the EULA was when they accepted it.
|
|
||||||
*/
|
|
||||||
$branding_settings = SettingsController::getPDFBranding();
|
|
||||||
|
|
||||||
$path_logo = "";
|
|
||||||
|
|
||||||
// Check for the PDF logo path and use that, otherwise use the regular logo path
|
|
||||||
if (!is_null($branding_settings->acceptance_pdf_logo)) {
|
|
||||||
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
|
|
||||||
} elseif (!is_null($branding_settings->logo)) {
|
|
||||||
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'item_tag' => $item->asset_tag,
|
|
||||||
'item_model' => $display_model,
|
|
||||||
'item_serial' => $item->serial,
|
|
||||||
'item_status' => $item->assetstatus?->name,
|
|
||||||
'eula' => $item->getEula(),
|
|
||||||
'note' => $request->input('note'),
|
|
||||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
|
|
||||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
|
|
||||||
'assigned_to' => $assigned_user->present()->fullName,
|
|
||||||
'company_name' => $branding_settings->site_name,
|
|
||||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
|
||||||
'logo' => $path_logo,
|
|
||||||
'date_settings' => $branding_settings->date_display_format,
|
|
||||||
'admin' => auth()->user()->present()?->fullName,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($pdf_view_route!='') {
|
|
||||||
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
|
||||||
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
||||||
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Log the acceptance
|
||||||
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
||||||
|
|
||||||
// Send the PDF to the signing user
|
// Send the PDF to the signing user
|
||||||
@@ -248,15 +178,14 @@ class AcceptanceController extends Controller
|
|||||||
|
|
||||||
// Add the attachment for the signing user into the $data array
|
// Add the attachment for the signing user into the $data array
|
||||||
$data['file'] = $pdf_filename;
|
$data['file'] = $pdf_filename;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$assigned_user->notify(new AcceptanceAssetAcceptedToUserNotification($data));
|
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($assigned_user->locale));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
|
$acceptance->notify((new AcceptanceAssetAcceptedNotification($data))->locale(Setting::getSettings()->locale));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
@@ -264,95 +193,21 @@ class AcceptanceController extends Controller
|
|||||||
|
|
||||||
$return_msg = trans('admin/users/message.accepted');
|
$return_msg = trans('admin/users/message.accepted');
|
||||||
|
|
||||||
|
// Item was declined
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
/**
|
for ($i = 0; $i < ($acceptance->qty ?? 1); $i++) {
|
||||||
* Check for the eula-pdfs directory
|
$acceptance->decline($sig_filename, $request->input('note'));
|
||||||
*/
|
|
||||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
||||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->require_accept_signature == '1') {
|
|
||||||
|
|
||||||
// Check if the signature directory exists, if not create it
|
|
||||||
if (!Storage::exists('private_uploads/signatures')) {
|
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item was accepted, check for a signature
|
|
||||||
if ($request->filled('signature_output')) {
|
|
||||||
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
|
||||||
$data_uri = $request->input('signature_output');
|
|
||||||
$encoded_image = explode(',', $data_uri);
|
|
||||||
$decoded_image = base64_decode($encoded_image[1]);
|
|
||||||
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
|
||||||
|
|
||||||
// No image data is present, kick them back.
|
|
||||||
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
|
||||||
} else {
|
|
||||||
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the data to send the declined notification
|
|
||||||
$branding_settings = SettingsController::getPDFBranding();
|
|
||||||
|
|
||||||
// This is the most horriblest
|
|
||||||
switch($acceptance->checkoutable_type){
|
|
||||||
case 'App\Models\Asset':
|
|
||||||
$asset_model = AssetModel::find($item->model_id);
|
|
||||||
$display_model = $asset_model->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Accessory':
|
|
||||||
$accessory = Accessory::find($item->id);
|
|
||||||
$display_model = $accessory->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\LicenseSeat':
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Component':
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Consumable':
|
|
||||||
$consumable = Consumable::find($item->id);
|
|
||||||
$display_model = $consumable->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'item_tag' => $item->asset_tag,
|
|
||||||
'item_model' => $display_model,
|
|
||||||
'item_serial' => $item->serial,
|
|
||||||
'item_status' => $item->assetstatus?->name,
|
|
||||||
'note' => $request->input('note'),
|
|
||||||
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
|
|
||||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
|
||||||
'assigned_to' => $assigned_to,
|
|
||||||
'company_name' => $branding_settings->site_name,
|
|
||||||
'date_settings' => $branding_settings->date_display_format,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($pdf_view_route!='') {
|
|
||||||
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
|
||||||
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
||||||
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
||||||
}
|
|
||||||
|
|
||||||
$acceptance->decline($sig_filename, $request->input('note'));
|
|
||||||
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
||||||
Log::debug('New event acceptance.');
|
Log::debug('New event acceptance.');
|
||||||
event(new CheckoutDeclined($acceptance));
|
event(new CheckoutDeclined($acceptance));
|
||||||
$return_msg = trans('admin/users/message.declined');
|
$return_msg = trans('admin/users/message.declined');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send an email notification if one is requested
|
||||||
if ($acceptance->alert_on_response_id) {
|
if ($acceptance->alert_on_response_id) {
|
||||||
try {
|
try {
|
||||||
$recipient = User::find($acceptance->alert_on_response_id);
|
$recipient = User::find($acceptance->alert_on_response_id);
|
||||||
@@ -371,9 +226,10 @@ class AcceptanceController extends Controller
|
|||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->to('account/accept')->with('success', $return_msg);
|
return redirect()->to('account/accept')->with('success', $return_msg);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
|
use App\Models\Actionlog;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use \Illuminate\Http\Response;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use \Illuminate\Http\Response;
|
||||||
|
|
||||||
class ActionlogController extends Controller
|
class ActionlogController extends Controller
|
||||||
{
|
{
|
||||||
public function displaySig($filename) : RedirectResponse | Response | bool
|
public function displaySig($filename) : RedirectResponse | Response | bool
|
||||||
@@ -39,17 +41,29 @@ class ActionlogController extends Controller
|
|||||||
|
|
||||||
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('view', \App\Models\Asset::class);
|
|
||||||
|
|
||||||
if (config('filesystems.default') == 's3_private') {
|
if ($actionlog = Actionlog::where('filename', $filename)->with('user')->with('target')->firstOrFail()) {
|
||||||
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
|
|
||||||
|
$this->authorize('view', $actionlog->target);
|
||||||
|
$this->authorize('view', $actionlog->user);
|
||||||
|
|
||||||
|
|
||||||
|
if (config('filesystems.default') == 's3_private') {
|
||||||
|
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/' . $filename, now()->addMinutes(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Storage::exists('private_uploads/eula-pdfs/' . $filename)) {
|
||||||
|
|
||||||
|
if (request()->input('inline') == 'true') {
|
||||||
|
return response()->file(config('app.private_uploads') . '/eula-pdfs/' . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->download(config('app.private_uploads') . '/eula-pdfs/' . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
|
return redirect()->back()->with('error', trans('general.record_not_found'));
|
||||||
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,32 +288,42 @@ class AccessoriesController extends Controller
|
|||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
$accessory_checkout->created_by = auth()->id();
|
$accessory_checkout->created_by = auth()->id();
|
||||||
$accessory_checkout->save();
|
$accessory_checkout->save();
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'accessory_id' => $accessory->id,
|
||||||
|
'assigned_to' => $target->id,
|
||||||
|
'assigned_type' => $target::class,
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'created_by' => auth()->id(),
|
||||||
|
'pivot' => $accessory_checkout->id,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this value to be able to pass the qty through to the event
|
// Set this value to be able to pass the qty through to the event
|
||||||
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkout.success')));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check in the item so that it can be checked out again to someone else
|
* Check in the item so that it can be checked out again to someone else
|
||||||
*
|
*
|
||||||
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param int $accessoryUserId
|
* @param int $accessoryUserId
|
||||||
* @param string $backto
|
* @param string $backto
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return JsonResponse
|
||||||
|
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
||||||
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @internal param int $accessoryId
|
* @internal param int $accessoryId
|
||||||
*/
|
*/
|
||||||
public function checkin(Request $request, $accessoryUserId = null)
|
public function checkin(Request $request, $accessoryUserId = null)
|
||||||
{
|
{
|
||||||
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
|
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist', ['id' => $accessoryUserId])));
|
||||||
}
|
}
|
||||||
|
|
||||||
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
||||||
@@ -327,7 +337,14 @@ class AccessoriesController extends Controller
|
|||||||
$user = User::find($accessory_checkout->assigned_to);
|
$user = User::find($accessory_checkout->assigned_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
|
$payload = [
|
||||||
|
'accessory_id' => $accessory->id,
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'created_by' => auth()->id(),
|
||||||
|
'pivot' => $accessory_checkout->id,
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkin.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class AssetModelsController extends Controller
|
|||||||
'fieldset',
|
'fieldset',
|
||||||
'deleted_at',
|
'deleted_at',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
|
'require_serial',
|
||||||
];
|
];
|
||||||
|
|
||||||
$assetmodels = AssetModel::select([
|
$assetmodels = AssetModel::select([
|
||||||
@@ -69,6 +70,7 @@ class AssetModelsController extends Controller
|
|||||||
'models.fieldset_id',
|
'models.fieldset_id',
|
||||||
'models.deleted_at',
|
'models.deleted_at',
|
||||||
'models.updated_at',
|
'models.updated_at',
|
||||||
|
'models.require_serial'
|
||||||
])
|
])
|
||||||
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
||||||
->withCount('assets as assets_count');
|
->withCount('assets as assets_count');
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ class AssetsController extends Controller
|
|||||||
'model.category',
|
'model.category',
|
||||||
'model.manufacturer',
|
'model.manufacturer',
|
||||||
'model.fieldset',
|
'model.fieldset',
|
||||||
|
'model.depreciation',
|
||||||
'supplier'
|
'supplier'
|
||||||
); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
||||||
|
|
||||||
@@ -609,7 +610,7 @@ class AssetsController extends Controller
|
|||||||
$asset->use_text = $asset->present()->fullName;
|
$asset->use_text = $asset->present()->fullName;
|
||||||
|
|
||||||
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
||||||
$asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute();
|
$asset->use_text .= ' → ' . $asset->assigned->display_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1289,9 +1290,19 @@ class AssetsController extends Controller
|
|||||||
|
|
||||||
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
|
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
|
||||||
{
|
{
|
||||||
|
$this->authorize('view', Asset::class);
|
||||||
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
return [];
|
$query = Asset::where([
|
||||||
// to do
|
'assigned_to' => $asset->id,
|
||||||
|
'assigned_type' => Asset::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$total = $query->count();
|
||||||
|
|
||||||
|
$assets = $query->applyOffsetAndLimit($total)->get();
|
||||||
|
|
||||||
|
return (new AssetsTransformer)->transformAssets($assets, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
|
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ class CategoriesController extends Controller
|
|||||||
'consumables_count',
|
'consumables_count',
|
||||||
'components_count',
|
'components_count',
|
||||||
'licenses_count',
|
'licenses_count',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
'image',
|
'image',
|
||||||
'notes',
|
'notes',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -230,13 +230,13 @@ class ConsumablesController extends Controller
|
|||||||
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
|
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
|
||||||
'user' => ($consumable_assignment->user) ? [
|
'user' => ($consumable_assignment->user) ? [
|
||||||
'id' => (int) $consumable_assignment->user->id,
|
'id' => (int) $consumable_assignment->user->id,
|
||||||
'name'=> e($consumable_assignment->user->present()->fullName()),
|
'name'=> e($consumable_assignment->user->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
|
||||||
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
|
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
|
||||||
'created_by' => ($consumable_assignment->adminuser) ? [
|
'created_by' => ($consumable_assignment->adminuser) ? [
|
||||||
'id' => (int) $consumable_assignment->adminuser->id,
|
'id' => (int) $consumable_assignment->adminuser->id,
|
||||||
'name'=> e($consumable_assignment->adminuser->present()->fullName()),
|
'name'=> e($consumable_assignment->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class ImportController extends Controller
|
|||||||
if (function_exists('iconv')) {
|
if (function_exists('iconv')) {
|
||||||
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
||||||
$encoding = $detector->getEncoding($file_contents);
|
$encoding = $detector->getEncoding($file_contents);
|
||||||
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
|
\Log::debug("Discovered encoding: $encoding in uploaded CSV");
|
||||||
$reader = null;
|
$reader = null;
|
||||||
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
||||||
$transliterated = false;
|
$transliterated = false;
|
||||||
@@ -103,7 +103,7 @@ class ImportController extends Controller
|
|||||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$import->header_row = $reader->fetchOne(0);
|
$import->header_row = $reader->nth(0);
|
||||||
} catch (JsonEncodingException $e) {
|
} catch (JsonEncodingException $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse(
|
Helper::formatStandardApiResponse(
|
||||||
@@ -136,7 +136,7 @@ class ImportController extends Controller
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Grab the first row to display via ajax as the user picks fields
|
// Grab the first row to display via ajax as the user picks fields
|
||||||
$import->first_row = $reader->fetchOne(1);
|
$import->first_row = $reader->nth(1);
|
||||||
} catch (JsonEncodingException $e) {
|
} catch (JsonEncodingException $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse(
|
Helper::formatStandardApiResponse(
|
||||||
@@ -195,7 +195,7 @@ class ImportController extends Controller
|
|||||||
// Run a backup immediately before processing
|
// Run a backup immediately before processing
|
||||||
if ($request->get('run-backup')) {
|
if ($request->get('run-backup')) {
|
||||||
Log::debug('Backup manually requested via importer');
|
Log::debug('Backup manually requested via importer');
|
||||||
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]);
|
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H-i-s')]);
|
||||||
} else {
|
} else {
|
||||||
Log::debug('NO BACKUP requested via importer');
|
Log::debug('NO BACKUP requested via importer');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,9 @@ class LicenseSeatsController extends Controller
|
|||||||
// nothing to update
|
// nothing to update
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||||
}
|
}
|
||||||
|
if( $touched && $licenseSeat->unreassignable_seat) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.checkout.unavailable')));
|
||||||
|
}
|
||||||
// the logging functions expect only one "target". if both asset and user are present in the request,
|
// the logging functions expect only one "target". if both asset and user are present in the request,
|
||||||
// we simply let assets take precedence over users...
|
// we simply let assets take precedence over users...
|
||||||
if ($licenseSeat->isDirty('assigned_to')) {
|
if ($licenseSeat->isDirty('assigned_to')) {
|
||||||
@@ -145,7 +147,11 @@ class LicenseSeatsController extends Controller
|
|||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
|
|
||||||
if ($is_checkin) {
|
if ($is_checkin) {
|
||||||
$licenseSeat->logCheckin($target, $request->input('notes'));
|
if(!$licenseSeat->license->reassignable){
|
||||||
|
$licenseSeat->unreassignable_seat = true;
|
||||||
|
$licenseSeat->save();
|
||||||
|
}
|
||||||
|
$licenseSeat->logCheckin($target, $licenseSeat->notes);
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ class LicensesController extends Controller
|
|||||||
|
|
||||||
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
|
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
|
||||||
|
|
||||||
|
if ($request->input('status')=='inactive') {
|
||||||
|
$licenses->ExpiredLicenses();
|
||||||
|
} else {
|
||||||
|
$licenses->ActiveLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('company_id')) {
|
if ($request->filled('company_id')) {
|
||||||
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
|
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
|
||||||
}
|
}
|
||||||
@@ -94,6 +100,8 @@ class LicensesController extends Controller
|
|||||||
$licenses->onlyTrashed();
|
$licenses->onlyTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
|
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|||||||
@@ -37,10 +37,14 @@ class LocationsController extends Controller
|
|||||||
'address',
|
'address',
|
||||||
'address2',
|
'address2',
|
||||||
'assets_count',
|
'assets_count',
|
||||||
'assets_count',
|
'assigned_assets_count',
|
||||||
|
'rtd_assets_count',
|
||||||
|
'accessories_count',
|
||||||
'assigned_accessories_count',
|
'assigned_accessories_count',
|
||||||
'assigned_assets_count',
|
'components_count',
|
||||||
'assigned_assets_count',
|
'consumables_count',
|
||||||
|
'users_count',
|
||||||
|
'children_count',
|
||||||
'city',
|
'city',
|
||||||
'country',
|
'country',
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -54,7 +58,6 @@ class LocationsController extends Controller
|
|||||||
'rtd_assets_count',
|
'rtd_assets_count',
|
||||||
'state',
|
'state',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
'users_count',
|
|
||||||
'zip',
|
'zip',
|
||||||
'notes',
|
'notes',
|
||||||
];
|
];
|
||||||
@@ -79,8 +82,9 @@ class LocationsController extends Controller
|
|||||||
'locations.currency',
|
'locations.currency',
|
||||||
'locations.company_id',
|
'locations.company_id',
|
||||||
'locations.notes',
|
'locations.notes',
|
||||||
|
'locations.created_by',
|
||||||
|
'locations.deleted_at',
|
||||||
])
|
])
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
->withCount('assignedAccessories as assigned_accessories_count')
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
@@ -88,6 +92,8 @@ class LocationsController extends Controller
|
|||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->with('adminuser');
|
->with('adminuser');
|
||||||
|
|
||||||
// Only scope locations if the setting is enabled
|
// Only scope locations if the setting is enabled
|
||||||
@@ -131,6 +137,14 @@ class LocationsController extends Controller
|
|||||||
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('parent_id')) {
|
||||||
|
$locations->where('locations.parent_id', '=', $request->input('parent_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('status') == 'deleted') {
|
||||||
|
$locations->onlyTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
@@ -224,8 +238,13 @@ class LocationsController extends Controller
|
|||||||
])
|
])
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
return (new LocationsTransformer)->transformLocation($location);
|
return (new LocationsTransformer)->transformLocation($location);
|
||||||
@@ -320,11 +339,15 @@ class LocationsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('delete', Location::class);
|
$this->authorize('delete', Location::class);
|
||||||
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||||
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
->withCount('accessories as accessories_count')
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
if (! $location->isDeletable()) {
|
if (! $location->isDeletable()) {
|
||||||
|
|||||||
95
app/Http/Controllers/Api/NotesController.php
Normal file
95
app/Http/Controllers/Api/NotesController.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Actionlog;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class controls all API actions related to notes for
|
||||||
|
* the Snipe-IT Asset Management application.
|
||||||
|
*/
|
||||||
|
class NotesController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Retrieve a list of manual notes (action logs) for a given asset.
|
||||||
|
*
|
||||||
|
* Checks authorization to view assets, attempts to find the asset by ID,
|
||||||
|
* and fetches related action log entries of type 'note added', including
|
||||||
|
* user information for each note. Returns a JSON response with the notes or errors.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The incoming HTTP request.
|
||||||
|
* @param Asset $asset The ID of the asset whose notes to retrieve.
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function index(Asset $asset): JsonResponse
|
||||||
|
{
|
||||||
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
|
// Get the manual notes for the asset
|
||||||
|
$notes = ActionLog::with('user:id,username')
|
||||||
|
->where('item_type', Asset::class)
|
||||||
|
->where('item_id', $asset->id)
|
||||||
|
->where('action_type', 'note added')
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->get(['id', 'created_at', 'note', 'created_by', 'item_id', 'item_type', 'action_type', 'target_id', 'target_type']);
|
||||||
|
|
||||||
|
$notesArray = $notes->map(function ($note) {
|
||||||
|
return [
|
||||||
|
'id' => $note->id,
|
||||||
|
'created_at' => $note->created_at,
|
||||||
|
'note' => $note->note,
|
||||||
|
'created_by' => $note->created_by,
|
||||||
|
'username' => $note->user?->username, // adding the username
|
||||||
|
'item_id' => $note->item_id,
|
||||||
|
'item_type' => $note->item_type,
|
||||||
|
'action_type' => $note->action_type,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return a success response
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', ['notes' => $notesArray, 'asset_id' => $asset->id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a manual note on a specified asset and log the action.
|
||||||
|
*
|
||||||
|
* Checks authorization for updating assets, validates the presence of the 'note',
|
||||||
|
* attempts to find the asset by ID, and creates a new ActionLog entry if successful.
|
||||||
|
* Returns JSON responses indicating success or failure with appropriate HTTP status codes.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The incoming HTTP request containing the 'note'.
|
||||||
|
* @param Asset $asset The ID of the asset to attach the note to.
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function store(Request $request, Asset $asset): JsonResponse
|
||||||
|
{
|
||||||
|
$this->authorize('update', $asset);
|
||||||
|
|
||||||
|
if ($request->input('note', '') == '') {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('validation.required', ['attribute' => 'note'])), 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the note
|
||||||
|
$logaction = new ActionLog();
|
||||||
|
$logaction->item_type = get_class($asset);
|
||||||
|
$logaction->created_by = Auth::id();
|
||||||
|
$logaction->item_id = $asset->id;
|
||||||
|
$logaction->note = $request->input('note', '');
|
||||||
|
|
||||||
|
if ($logaction->logaction('note added')) {
|
||||||
|
// Return a success response
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', ['note' => $logaction->note, 'item_id' => $asset->id], trans('general.note_added')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an error response if something went wrong
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, 'Something went wrong'), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ class ProfileController extends Controller
|
|||||||
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
||||||
$assets = [
|
$assets = [
|
||||||
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
||||||
'name' => e($checkoutRequest->itemRequested()->present()->name()),
|
'name' => e($checkoutRequest->itemRequested()->display_name),
|
||||||
'type' => e($checkoutRequest->itemType()),
|
'type' => e($checkoutRequest->itemType()),
|
||||||
'qty' => (int) $checkoutRequest->quantity,
|
'qty' => (int) $checkoutRequest->quantity,
|
||||||
'location' => ($checkoutRequest->location()) ? e($checkoutRequest->location()->name) : null,
|
'location' => ($checkoutRequest->location()) ? e($checkoutRequest->location()->name) : null,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Transformers\DatatablesTransformer;
|
use App\Http\Transformers\DatatablesTransformer;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@@ -51,10 +50,22 @@ class SettingsController extends Controller
|
|||||||
})->slice(0, 10)->map(function ($item) use ($settings) {
|
})->slice(0, 10)->map(function ($item) use ($settings) {
|
||||||
return (object) [
|
return (object) [
|
||||||
'username' => $item[$settings['ldap_username_field']][0] ?? null,
|
'username' => $item[$settings['ldap_username_field']][0] ?? null,
|
||||||
|
'display_name' => $item[$settings['ldap_display_name']][0] ?? null,
|
||||||
'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null,
|
'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null,
|
||||||
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
|
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
|
||||||
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
|
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
|
||||||
'email' => $item[$settings['ldap_email']][0] ?? null,
|
'email' => $item[$settings['ldap_email']][0] ?? null,
|
||||||
|
'phone' => $item[$settings['ldap_phone_field']][0] ?? null,
|
||||||
|
'mobile' => $item[$settings['ldap_mobile']][0] ?? null,
|
||||||
|
'jobtitle' => $item[$settings['ldap_jobtitle']][0] ?? null,
|
||||||
|
'department' => $item[$settings['ldap_department']][0] ?? null,
|
||||||
|
'manager' => $item[$settings['ldap_manager']][0] ?? null,
|
||||||
|
'address' => $item[$settings['ldap_address']][0] ?? null,
|
||||||
|
'city' => $item[$settings['ldap_city']][0] ?? null,
|
||||||
|
'state' => $item[$settings['ldap_state']][0] ?? null,
|
||||||
|
'zip' => $item[$settings['ldap_zip']][0] ?? null,
|
||||||
|
'country' => $item[$settings['ldap_country']][0] ?? null,
|
||||||
|
'location' => $item[$settings['ldap_location']][0] ?? null,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
if ($users->count() > 0) {
|
if ($users->count() > 0) {
|
||||||
@@ -78,7 +89,7 @@ class SettingsController extends Controller
|
|||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::debug('Connection failed but we cannot debug it any further on our end.');
|
Log::debug('Connection failed but we cannot debug it any further on our end.');
|
||||||
return response()->json(['message' => $e->getMessage()], 500);
|
return response()->json(['message' => $e->getMessage()], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -51,11 +51,7 @@ class UploadedFilesController extends Controller
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
$uploads = Actionlog::select('action_logs.*')
|
$uploads = self::$map_object_type[$object_type]::withTrashed()->find($id)->uploads()
|
||||||
->whereNotNull('filename')
|
|
||||||
->where('item_type', self::$map_object_type[$object_type])
|
|
||||||
->where('item_id', $object->id)
|
|
||||||
->where('action_type', '=', 'uploaded')
|
|
||||||
->with('adminuser');
|
->with('adminuser');
|
||||||
|
|
||||||
$offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset'));
|
||||||
@@ -96,7 +92,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -144,7 +140,7 @@ class UploadedFilesController extends Controller
|
|||||||
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||||
{
|
{
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -188,7 +184,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('update', self::$map_object_type[$object_type]);
|
$this->authorize('update', self::$map_object_type[$object_type]);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -206,7 +202,7 @@ class UploadedFilesController extends Controller
|
|||||||
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
||||||
}
|
}
|
||||||
// Delete the record of the file
|
// Delete the record of the file
|
||||||
if ($log->delete()) {
|
if ($log->logUploadDelete($object, $log->filename)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use App\Models\Consumable;
|
|||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\CurrentInventory;
|
use App\Notifications\CurrentInventory;
|
||||||
|
use App\Notifications\WelcomeNotification;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -64,6 +65,7 @@ class UsersController extends Controller
|
|||||||
'users.jobtitle',
|
'users.jobtitle',
|
||||||
'users.last_login',
|
'users.last_login',
|
||||||
'users.last_name',
|
'users.last_name',
|
||||||
|
'users.display_name',
|
||||||
'users.locale',
|
'users.locale',
|
||||||
'users.location_id',
|
'users.location_id',
|
||||||
'users.manager_id',
|
'users.manager_id',
|
||||||
@@ -154,6 +156,10 @@ class UsersController extends Controller
|
|||||||
$users = $users->where('users.last_name', '=', $request->input('last_name'));
|
$users = $users->where('users.last_name', '=', $request->input('last_name'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('display_name')) {
|
||||||
|
$users = $users->where('users.display_name', '=', $request->input('display_name'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('employee_num')) {
|
if ($request->filled('employee_num')) {
|
||||||
$users = $users->where('users.employee_num', '=', $request->input('employee_num'));
|
$users = $users->where('users.employee_num', '=', $request->input('employee_num'));
|
||||||
}
|
}
|
||||||
@@ -284,6 +290,7 @@ class UsersController extends Controller
|
|||||||
[
|
[
|
||||||
'last_name',
|
'last_name',
|
||||||
'first_name',
|
'first_name',
|
||||||
|
'display_name',
|
||||||
'email',
|
'email',
|
||||||
'jobtitle',
|
'jobtitle',
|
||||||
'username',
|
'username',
|
||||||
@@ -355,6 +362,7 @@ class UsersController extends Controller
|
|||||||
'users.employee_num',
|
'users.employee_num',
|
||||||
'users.first_name',
|
'users.first_name',
|
||||||
'users.last_name',
|
'users.last_name',
|
||||||
|
'users.display_name',
|
||||||
'users.gravatar',
|
'users.gravatar',
|
||||||
'users.avatar',
|
'users.avatar',
|
||||||
'users.email',
|
'users.email',
|
||||||
@@ -365,20 +373,17 @@ class UsersController extends Controller
|
|||||||
$users = $users->where(function ($query) use ($request) {
|
$users = $users->where(function ($query) use ($request) {
|
||||||
$query->SimpleNameSearch($request->get('search'))
|
$query->SimpleNameSearch($request->get('search'))
|
||||||
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
||||||
|
->orWhere('display_name', 'LIKE', '%'.$request->get('search').'%')
|
||||||
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
|
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
|
||||||
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
|
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
|
$users = $users->orderBy('display_name', 'asc')->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
|
||||||
$users = $users->paginate(50);
|
$users = $users->paginate(50);
|
||||||
|
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$name_str = '';
|
$name_str = $user->display_name;
|
||||||
if ($user->last_name != '') {
|
|
||||||
$name_str .= $user->last_name.', ';
|
|
||||||
}
|
|
||||||
$name_str .= $user->first_name;
|
|
||||||
|
|
||||||
if ($user->username != '') {
|
if ($user->username != '') {
|
||||||
$name_str .= ' ('.$user->username.')';
|
$name_str .= ' ('.$user->username.')';
|
||||||
@@ -430,9 +435,20 @@ class UsersController extends Controller
|
|||||||
$user->password = $user->noPassword();
|
$user->password = $user->noPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
|
|
||||||
|
if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user->notify(new WelcomeNotification($user));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Could not send welcome notification for user: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('groups')) {
|
if ($request->filled('groups')) {
|
||||||
$user->groups()->sync($request->input('groups'));
|
$user->groups()->sync($request->input('groups'));
|
||||||
} else {
|
} else {
|
||||||
@@ -511,6 +527,10 @@ class UsersController extends Controller
|
|||||||
$user->username = $request->input('username');
|
$user->username = $request->input('username');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('display_name')) {
|
||||||
|
$user->display_name = $request->input('display_name');
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('email')) {
|
if ($request->filled('email')) {
|
||||||
$user->email = $request->input('email');
|
$user->email = $request->input('email');
|
||||||
}
|
}
|
||||||
@@ -540,7 +560,7 @@ class UsersController extends Controller
|
|||||||
Asset::where('assigned_type', User::class)
|
Asset::where('assigned_type', User::class)
|
||||||
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
||||||
}
|
}
|
||||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class AssetModelsController extends Controller
|
|||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
$model->created_by = auth()->id();
|
$model->created_by = auth()->id();
|
||||||
$model->requestable = $request->has('requestable');
|
$model->requestable = $request->has('requestable');
|
||||||
|
$model->require_serial = $request->input('require_serial', 0);
|
||||||
|
|
||||||
if ($request->input('fieldset_id') != '') {
|
if ($request->input('fieldset_id') != '') {
|
||||||
$model->fieldset_id = $request->input('fieldset_id');
|
$model->fieldset_id = $request->input('fieldset_id');
|
||||||
@@ -155,7 +156,7 @@ class AssetModelsController extends Controller
|
|||||||
$model->category_id = $request->input('category_id');
|
$model->category_id = $request->input('category_id');
|
||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
$model->requestable = $request->input('requestable', '0');
|
$model->requestable = $request->input('requestable', '0');
|
||||||
|
$model->require_serial = $request->input('require_serial', 0);
|
||||||
$model->fieldset_id = $request->input('fieldset_id');
|
$model->fieldset_id = $request->input('fieldset_id');
|
||||||
|
|
||||||
if ($model->save()) {
|
if ($model->save()) {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class AssetCheckoutController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
|
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if the asset exists
|
// Check if the asset exists
|
||||||
if (! $asset = Asset::find($assetId)) {
|
if (! $asset = Asset::find($assetId)) {
|
||||||
@@ -81,6 +83,7 @@ class AssetCheckoutController extends Controller
|
|||||||
$admin = auth()->user();
|
$admin = auth()->user();
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
$asset = $this->updateAssetLocation($asset, $target);
|
$asset = $this->updateAssetLocation($asset, $target);
|
||||||
|
|
||||||
|
|||||||
@@ -110,17 +110,35 @@ class AssetsController extends Controller
|
|||||||
// This is only necessary on create, not update, since bulk editing is handled
|
// This is only necessary on create, not update, since bulk editing is handled
|
||||||
// differently
|
// differently
|
||||||
$asset_tags = $request->input('asset_tags');
|
$asset_tags = $request->input('asset_tags');
|
||||||
|
$model = AssetModel::find($request->input('model_id'));
|
||||||
|
$serial_errors = [];
|
||||||
|
$serials = $request->input('serials');
|
||||||
|
|
||||||
$settings = Setting::getSettings();
|
$settings = Setting::getSettings();
|
||||||
|
|
||||||
|
//Validate required serial based on model setting
|
||||||
|
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
|
||||||
|
if ($model && $model->require_serial === 1 && empty($serials[$a])) {
|
||||||
|
$serial_errors["serials.$a"] = trans('admin/hardware/form.serial_required', ['number' => $a]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($serial_errors)) {
|
||||||
|
return redirect()->back()
|
||||||
|
->withInput()
|
||||||
|
->withErrors($serial_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$asset = null;
|
||||||
|
$companyId = Company::getIdForCurrentUser($request->input('company_id'));
|
||||||
$successes = [];
|
$successes = [];
|
||||||
$failures = [];
|
$failures = [];
|
||||||
$serials = $request->input('serials');
|
|
||||||
$asset = null;
|
|
||||||
|
|
||||||
for ($a = 1; $a <= count($asset_tags); $a++) {
|
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
|
||||||
$asset = new Asset();
|
$asset = new Asset();
|
||||||
$asset->model()->associate(AssetModel::find($request->input('model_id')));
|
|
||||||
|
$asset->model()->associate($model);
|
||||||
$asset->name = $request->input('name');
|
$asset->name = $request->input('name');
|
||||||
|
|
||||||
// Check for a corresponding serial
|
// Check for a corresponding serial
|
||||||
@@ -132,7 +150,7 @@ class AssetsController extends Controller
|
|||||||
$asset->asset_tag = $asset_tags[$a];
|
$asset->asset_tag = $asset_tags[$a];
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
$asset->company_id = $companyId;
|
||||||
$asset->model_id = $request->input('model_id');
|
$asset->model_id = $request->input('model_id');
|
||||||
$asset->order_number = $request->input('order_number');
|
$asset->order_number = $request->input('order_number');
|
||||||
$asset->notes = $request->input('notes');
|
$asset->notes = $request->input('notes');
|
||||||
@@ -172,7 +190,6 @@ class AssetsController extends Controller
|
|||||||
|
|
||||||
// Update custom fields in the database.
|
// Update custom fields in the database.
|
||||||
// Validation for these fields is handled through the AssetRequest form request
|
// Validation for these fields is handled through the AssetRequest form request
|
||||||
$model = AssetModel::find($request->get('model_id'));
|
|
||||||
|
|
||||||
if (($model) && ($model->fieldset)) {
|
if (($model) && ($model->fieldset)) {
|
||||||
foreach ($model->fieldset->fields as $field) {
|
foreach ($model->fieldset->fields as $field) {
|
||||||
@@ -453,6 +470,13 @@ class AssetsController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
//Validate required serial based on model setting
|
||||||
|
if ($model && $model->require_serial === 1 && empty($serial[1])) {
|
||||||
|
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||||
|
->with('warning', trans('admin/hardware/form.serial_required_post_model_update', [
|
||||||
|
'asset_model' => $model->name
|
||||||
|
]));
|
||||||
|
}
|
||||||
if ($asset->save()) {
|
if ($asset->save()) {
|
||||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||||
->with('success', trans('admin/hardware/message.update.success'));
|
->with('success', trans('admin/hardware/message.update.success'));
|
||||||
@@ -797,7 +821,7 @@ class AssetsController extends Controller
|
|||||||
'item_id' => $asset->id,
|
'item_id' => $asset->id,
|
||||||
'item_type' => Asset::class,
|
'item_type' => Asset::class,
|
||||||
'created_by' => auth()->id(),
|
'created_by' => auth()->id(),
|
||||||
'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer',
|
'note' => 'Checkout imported by '.auth()->user()->display_name.' from history importer',
|
||||||
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
|
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
|
||||||
'target_type' => User::class,
|
'target_type' => User::class,
|
||||||
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
|
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
|
||||||
@@ -825,7 +849,7 @@ class AssetsController extends Controller
|
|||||||
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
|
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
|
||||||
'item_type' => Asset::class,
|
'item_type' => Asset::class,
|
||||||
'created_by' => auth()->id(),
|
'created_by' => auth()->id(),
|
||||||
'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer',
|
'note' => 'Checkin imported by '.auth()->user()->display_name.' from history importer',
|
||||||
'target_id' => null,
|
'target_id' => null,
|
||||||
'created_at' => $checkin_date,
|
'created_at' => $checkin_date,
|
||||||
'action_type' => 'checkin',
|
'action_type' => 'checkin',
|
||||||
|
|||||||
@@ -637,6 +637,7 @@ class BulkAssetsController extends Controller
|
|||||||
$admin = auth()->user();
|
$admin = auth()->user();
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
if (! is_array($request->get('selected_assets'))) {
|
if (! is_array($request->get('selected_assets'))) {
|
||||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||||
@@ -646,6 +647,15 @@ class BulkAssetsController extends Controller
|
|||||||
|
|
||||||
$assets = Asset::findOrFail($asset_ids);
|
$assets = Asset::findOrFail($asset_ids);
|
||||||
|
|
||||||
|
// Prevent checking out assets that are already checked out
|
||||||
|
if ($assets->pluck('assigned_to')->unique()->filter()->isNotEmpty()) {
|
||||||
|
// re-add the asset ids so the assets select is re-populated
|
||||||
|
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
||||||
|
|
||||||
|
return redirect(route('hardware.bulkcheckout.show'))
|
||||||
|
->with('error', trans('general.error_assets_already_checked_out'));
|
||||||
|
}
|
||||||
|
|
||||||
if (request('checkout_to_type') == 'asset') {
|
if (request('checkout_to_type') == 'asset') {
|
||||||
foreach ($asset_ids as $asset_id) {
|
foreach ($asset_ids as $asset_id) {
|
||||||
if ($target->id == $asset_id) {
|
if ($target->id == $asset_id) {
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ class BulkAssetModelsController extends Controller
|
|||||||
$update_array['min_amt'] = $request->input('min_amt');
|
$update_array['min_amt'] = $request->input('min_amt');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('require_serial')) {
|
||||||
|
$update_array['require_serial'] = $request->input('require_serial');
|
||||||
|
}
|
||||||
|
|
||||||
if (count($update_array) > 0) {
|
if (count($update_array) > 0) {
|
||||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||||
|
|||||||
@@ -102,13 +102,15 @@ class ComponentCheckoutController extends Controller
|
|||||||
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
|
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$component->checkout_qty = $request->input('assigned_qty');
|
||||||
|
|
||||||
// Update the component data
|
// Update the component data
|
||||||
$component->asset_id = $request->input('asset_id');
|
$component->asset_id = $request->input('asset_id');
|
||||||
$component->assets()->attach($component->id, [
|
$component->assets()->attach($component->id, [
|
||||||
'component_id' => $component->id,
|
'component_id' => $component->id,
|
||||||
'created_by' => auth()->user()->id,
|
'created_by' => auth()->user()->id,
|
||||||
'created_at' => date('Y-m-d H:i:s'),
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
'assigned_qty' => $request->input('assigned_qty'),
|
'assigned_qty' => $component->checkout_qty,
|
||||||
'asset_id' => $request->input('asset_id'),
|
'asset_id' => $request->input('asset_id'),
|
||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -64,12 +64,7 @@ class LicenseCheckinController extends Controller
|
|||||||
|
|
||||||
$this->authorize('checkout', $license);
|
$this->authorize('checkout', $license);
|
||||||
|
|
||||||
if (! $license->reassignable) {
|
|
||||||
// Not allowed to checkin
|
|
||||||
Session::flash('error', trans('admin/licenses/message.checkin.not_reassignable') . '.');
|
|
||||||
|
|
||||||
return redirect()->back()->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare the rules for the form validation
|
// Declare the rules for the form validation
|
||||||
$rules = [
|
$rules = [
|
||||||
@@ -98,6 +93,9 @@ class LicenseCheckinController extends Controller
|
|||||||
$licenseSeat->assigned_to = null;
|
$licenseSeat->assigned_to = null;
|
||||||
$licenseSeat->asset_id = null;
|
$licenseSeat->asset_id = null;
|
||||||
$licenseSeat->notes = $request->input('notes');
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
if (! $licenseSeat->license->reassignable) {
|
||||||
|
$licenseSeat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
|
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||||
if ($request->get('redirect_option') === 'target'){
|
if ($request->get('redirect_option') === 'target'){
|
||||||
@@ -106,7 +104,7 @@ class LicenseCheckinController extends Controller
|
|||||||
|
|
||||||
// Was the asset updated?
|
// Was the asset updated?
|
||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
|
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $licenseSeat->notes));
|
||||||
|
|
||||||
|
|
||||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||||
@@ -132,21 +130,17 @@ class LicenseCheckinController extends Controller
|
|||||||
$license = License::findOrFail($licenseId);
|
$license = License::findOrFail($licenseId);
|
||||||
$this->authorize('checkin', $license);
|
$this->authorize('checkin', $license);
|
||||||
|
|
||||||
if (! $license->reassignable) {
|
|
||||||
// Not allowed to checkin
|
|
||||||
Session::flash('error', 'License not reassignable.');
|
|
||||||
|
|
||||||
return redirect()->back()->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
||||||
->whereNotNull('assigned_to')
|
->whereNotNull('assigned_to')
|
||||||
->with('user')
|
->with('user', 'license')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$license = $licenseSeatsByUser->first()?->license;
|
||||||
foreach ($licenseSeatsByUser as $user_seat) {
|
foreach ($licenseSeatsByUser as $user_seat) {
|
||||||
$user_seat->assigned_to = null;
|
$user_seat->assigned_to = null;
|
||||||
|
if ($license && ! $license->reassignable) {
|
||||||
|
$user_seat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
if ($user_seat->save()) {
|
if ($user_seat->save()) {
|
||||||
Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
|
Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
|
||||||
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
||||||
@@ -159,9 +153,12 @@ class LicenseCheckinController extends Controller
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
|
$license = $licenseSeatsByAsset->first()?->license;
|
||||||
foreach ($licenseSeatsByAsset as $asset_seat) {
|
foreach ($licenseSeatsByAsset as $asset_seat) {
|
||||||
$asset_seat->asset_id = null;
|
$asset_seat->asset_id = null;
|
||||||
|
if ($license && ! $license->reassignable) {
|
||||||
|
$asset_seat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
if ($asset_seat->save()) {
|
if ($asset_seat->save()) {
|
||||||
Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
|
Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
|
||||||
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @param $id
|
* @param $id
|
||||||
* @return \Illuminate\Contracts\View\View
|
* @return \Illuminate\Contracts\View\View |\Illuminate\Http\RedirectResponse
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function create(License $license)
|
public function create(License $license)
|
||||||
@@ -39,6 +39,16 @@ class LicenseCheckoutController extends Controller
|
|||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the license is expired or terminated
|
||||||
|
if ($license->isInactive()) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't currently allow checking out licenses to locations, so we'll reset that to user if needed
|
||||||
|
if (session()->get('checkout_to_type') == 'location') {
|
||||||
|
session()->put(['checkout_to_type' => 'user']);
|
||||||
|
}
|
||||||
|
|
||||||
// Return the checkout view
|
// Return the checkout view
|
||||||
return view('licenses/checkout', compact('license'));
|
return view('licenses/checkout', compact('license'));
|
||||||
}
|
}
|
||||||
@@ -65,22 +75,31 @@ class LicenseCheckoutController extends Controller
|
|||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->authorize('checkout', $license);
|
$this->authorize('checkout', $license);
|
||||||
|
|
||||||
|
// Make sure there is at least one available to checkout
|
||||||
|
if ($license->availCount()->count() < 1) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the license is expired or terminated
|
||||||
|
if ($license->isInactive()) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
|
||||||
|
}
|
||||||
|
|
||||||
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
||||||
$licenseSeat->created_by = auth()->id();
|
$licenseSeat->created_by = auth()->id();
|
||||||
$licenseSeat->notes = $request->input('notes');
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
|
||||||
|
|
||||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
|
||||||
|
|
||||||
if ($request->filled('asset_id')) {
|
if ($request->filled('asset_id')) {
|
||||||
|
session()->put(['checkout_to_type' => 'asset']);
|
||||||
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
||||||
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
||||||
|
|
||||||
} elseif ($request->filled('assigned_to')) {
|
} elseif ($request->filled('assigned_to')) {
|
||||||
|
session()->put(['checkout_to_type' => 'user']);
|
||||||
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
||||||
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
||||||
@@ -89,6 +108,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
if ($checkoutTarget) {
|
if ($checkoutTarget) {
|
||||||
|
|
||||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||||
->with('success', trans('admin/licenses/message.checkout.success'));
|
->with('success', trans('admin/licenses/message.checkout.success'));
|
||||||
}
|
}
|
||||||
@@ -110,6 +130,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (! $licenseSeat->license->is($license)) {
|
if (! $licenseSeat->license->is($license)) {
|
||||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,16 +245,28 @@ class LicensesController extends Controller
|
|||||||
$license = License::with('assignedusers')->find($license->id);
|
$license = License::with('assignedusers')->find($license->id);
|
||||||
|
|
||||||
$users_count = User::where('autoassign_licenses', '1')->count();
|
$users_count = User::where('autoassign_licenses', '1')->count();
|
||||||
$total_seats_count = $license->totalSeatsByLicenseID();
|
|
||||||
|
$total_seats_count = (int) $license->totalSeatsByLicenseID();
|
||||||
$available_seats_count = $license->availCount()->count();
|
$available_seats_count = $license->availCount()->count();
|
||||||
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
|
$unreassignable_seats_count = License::unReassignableCount($license);
|
||||||
|
|
||||||
|
if(!$license->reassignable){
|
||||||
|
$checkedout_seats_count = ($total_seats_count - $available_seats_count - $unreassignable_seats_count );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
|
||||||
|
}
|
||||||
|
if($license->isInactive()){
|
||||||
|
session()->flash('warning', (trans('admin/licenses/message.checkout.license_is_inactive')));
|
||||||
|
}
|
||||||
|
|
||||||
$this->authorize('view', $license);
|
$this->authorize('view', $license);
|
||||||
return view('licenses.view', compact('license'))
|
return view('licenses.view', compact('license'))
|
||||||
->with('users_count', $users_count)
|
->with('users_count', $users_count)
|
||||||
->with('total_seats_count', $total_seats_count)
|
->with('total_seats_count', $total_seats_count)
|
||||||
->with('available_seats_count', $available_seats_count)
|
->with('available_seats_count', $available_seats_count)
|
||||||
->with('checkedout_seats_count', $checkedout_seats_count);
|
->with('checkedout_seats_count', $checkedout_seats_count)
|
||||||
|
->with('unreassignable_seats_count', $unreassignable_seats_count);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +376,7 @@ class LicensesController extends Controller
|
|||||||
$license->order_number,
|
$license->order_number,
|
||||||
$license->free_seat_count,
|
$license->free_seat_count,
|
||||||
$license->seats,
|
$license->seats,
|
||||||
($license->adminuser ? $license->adminuser->present()->fullName() : trans('admin/reports/general.deleted_user')),
|
($license->adminuser ? $license->adminuser->display_name : trans('admin/reports/general.deleted_user')),
|
||||||
$license->depreciation ? $license->depreciation->name: '',
|
$license->depreciation ? $license->depreciation->name: '',
|
||||||
$license->updated_at,
|
$license->updated_at,
|
||||||
$license->deleted_at,
|
$license->deleted_at,
|
||||||
|
|||||||
@@ -189,30 +189,36 @@ class LocationsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('delete', Location::class);
|
$this->authorize('delete', Location::class);
|
||||||
|
|
||||||
if (is_null($location = Location::find($locationId))) {
|
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||||
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
|
->withCount('children as children_count')
|
||||||
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
|
->find($locationId);
|
||||||
|
|
||||||
|
if (!$location) {
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($location->users()->count() > 0) {
|
if ($location->isDeletable()) {
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
|
|
||||||
} elseif ($location->children()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_child_loc'));
|
|
||||||
} elseif ($location->assets()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
|
|
||||||
} elseif ($location->assignedassets()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($location->image) {
|
if ($location->image) {
|
||||||
try {
|
try {
|
||||||
Storage::disk('public')->delete('locations/'.$location->image);
|
Storage::disk('public')->delete('locations/'.$location->image);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$location->delete();
|
||||||
|
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
|
||||||
|
} else {
|
||||||
|
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
|
||||||
}
|
}
|
||||||
$location->delete();
|
|
||||||
|
|
||||||
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,23 +253,41 @@ class LocationsController extends Controller
|
|||||||
$this->authorize('view', Location::class);
|
$this->authorize('view', Location::class);
|
||||||
|
|
||||||
if ($location = Location::where('id', $id)->first()) {
|
if ($location = Location::where('id', $id)->first()) {
|
||||||
$parent = Location::where('id', $location->parent_id)->first();
|
|
||||||
$manager = User::where('id', $location->manager_id)->first();
|
|
||||||
$company = Company::where('id', $location->company_id)->first();
|
|
||||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
|
||||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
|
|
||||||
return view('locations/print')
|
return view('locations/print')
|
||||||
->with('assets', $assets)
|
->with('assigned', false)
|
||||||
->with('users',$users)
|
->with('assets', $location->assets)
|
||||||
|
->with('assignedAssets', $location->assignedAssets)
|
||||||
|
->with('accessories', $location->accessories)
|
||||||
|
->with('assignedAccessories', $location->assignedAccessories)
|
||||||
|
->with('users',$location->users)
|
||||||
->with('location', $location)
|
->with('location', $location)
|
||||||
->with('parent', $parent)
|
->with('consumables', $location->consumables)
|
||||||
->with('manager', $manager)
|
->with('components', $location->components)
|
||||||
->with('company', $company);
|
->with('children', $location->children);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function print_all_assigned($id) : View | RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('view', Location::class);
|
||||||
|
if ($location = Location::where('id', $id)->first()) {
|
||||||
|
return view('locations/print')
|
||||||
|
->with('assigned', true)
|
||||||
|
->with('assets', $location->assets)
|
||||||
|
->with('assignedAssets', $location->assignedAssets)
|
||||||
|
->with('accessories', $location->accessories)
|
||||||
|
->with('assignedAccessories', $location->assignedAccessories)
|
||||||
|
->with('users',$location->users)
|
||||||
|
->with('location', $location)
|
||||||
|
->with('consumables', $location->consumables)
|
||||||
|
->with('components', $location->components)
|
||||||
|
->with('children', $location->children);
|
||||||
|
}
|
||||||
|
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a view that presents a form to clone a location.
|
* Returns a view that presents a form to clone a location.
|
||||||
@@ -321,33 +345,12 @@ class LocationsController extends Controller
|
|||||||
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
|
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check validation
|
|
||||||
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
|
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
||||||
|
|
||||||
}
|
}
|
||||||
public function print_all_assigned($id) : View | RedirectResponse
|
|
||||||
{
|
|
||||||
$this->authorize('view', Location::class);
|
|
||||||
if ($location = Location::where('id', $id)->first()) {
|
|
||||||
$parent = Location::where('id', $location->parent_id)->first();
|
|
||||||
$manager = User::where('id', $location->manager_id)->first();
|
|
||||||
$company = Company::where('id', $location->company_id)->first();
|
|
||||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
|
||||||
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
|
|
||||||
return view('locations/print')
|
|
||||||
->with('assets', $assets)
|
|
||||||
->with('users',$users)
|
|
||||||
->with('location', $location)
|
|
||||||
->with('parent', $parent)
|
|
||||||
->with('manager', $manager)
|
|
||||||
->with('company', $company);
|
|
||||||
}
|
|
||||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a view that allows the user to bulk delete locations
|
* Returns a view that allows the user to bulk delete locations
|
||||||
@@ -366,8 +369,12 @@ class LocationsController extends Controller
|
|||||||
$locations = Location::whereIn('id', $locations_raw_array)
|
$locations = Location::whereIn('id', $locations_raw_array)
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->withCount('users as users_count')->get();
|
->withCount('users as users_count')->get();
|
||||||
|
|
||||||
$valid_count = 0;
|
$valid_count = 0;
|
||||||
@@ -400,9 +407,13 @@ class LocationsController extends Controller
|
|||||||
$locations = Location::whereIn('id', $locations_raw_array)
|
$locations = Location::whereIn('id', $locations_raw_array)
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')->get();
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')->get();
|
||||||
|
|
||||||
$success_count = 0;
|
$success_count = 0;
|
||||||
$error_count = 0;
|
$error_count = 0;
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class ReportsController extends Controller
|
|||||||
$row[] = e($asset->serial);
|
$row[] = e($asset->serial);
|
||||||
|
|
||||||
if ($target = $asset->assignedTo) {
|
if ($target = $asset->assignedTo) {
|
||||||
$row[] = e($target->present()->name());
|
$row[] = e($target->display_name);
|
||||||
} else {
|
} else {
|
||||||
$row[] = ''; // Empty string if unassigned
|
$row[] = ''; // Empty string if unassigned
|
||||||
}
|
}
|
||||||
@@ -274,22 +274,18 @@ class ReportsController extends Controller
|
|||||||
$target_name = '';
|
$target_name = '';
|
||||||
|
|
||||||
if ($actionlog->target) {
|
if ($actionlog->target) {
|
||||||
if ($actionlog->targetType() == 'user') {
|
$target_name = $actionlog->target->display_name;
|
||||||
$target_name = $actionlog->target->getFullNameAttribute();
|
|
||||||
} else {
|
|
||||||
$target_name = $actionlog->target->getDisplayNameAttribute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($actionlog->item){
|
if ($actionlog->item){
|
||||||
$item_name = e($actionlog->item->getDisplayNameAttribute());
|
$item_name = e($actionlog->item->display_name);
|
||||||
} else {
|
} else {
|
||||||
$item_name = '';
|
$item_name = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$row = [
|
$row = [
|
||||||
$actionlog->created_at,
|
$actionlog->created_at,
|
||||||
($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '',
|
($actionlog->adminuser) ? $actionlog->adminuser->display_name : '',
|
||||||
$actionlog->present()->actionType(),
|
$actionlog->present()->actionType(),
|
||||||
e($actionlog->itemType()),
|
e($actionlog->itemType()),
|
||||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||||
@@ -298,10 +294,10 @@ class ReportsController extends Controller
|
|||||||
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
|
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
|
||||||
$target_name,
|
$target_name,
|
||||||
($actionlog->note) ? e($actionlog->note) : '',
|
($actionlog->note) ? e($actionlog->note) : '',
|
||||||
$actionlog->log_meta,
|
|
||||||
$actionlog->remote_ip,
|
$actionlog->remote_ip,
|
||||||
$actionlog->user_agent,
|
$actionlog->user_agent,
|
||||||
$actionlog->action_source,
|
$actionlog->action_source,
|
||||||
|
$actionlog->log_meta,
|
||||||
];
|
];
|
||||||
fputcsv($handle, $row);
|
fputcsv($handle, $row);
|
||||||
}
|
}
|
||||||
@@ -830,7 +826,7 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('location')) {
|
if ($request->filled('location')) {
|
||||||
$row[] = ($asset->location) ? $asset->location->present()->name() : '';
|
$row[] = ($asset->location) ? $asset->location->display_name : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('location_address')) {
|
if ($request->filled('location_address')) {
|
||||||
@@ -843,7 +839,7 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('rtd_location')) {
|
if ($request->filled('rtd_location')) {
|
||||||
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->present()->name() : '';
|
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->display_name : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('rtd_location_address')) {
|
if ($request->filled('rtd_location_address')) {
|
||||||
@@ -856,8 +852,8 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('assigned_to')) {
|
if ($request->filled('assigned_to')) {
|
||||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? $asset->assigned->getFullNameAttribute() : ($asset->assigned ? $asset->assigned->display_name : '');
|
$row[] = ($asset->assigned) ? $asset->assigned->display_name : '';
|
||||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : $asset->assignedType();
|
$row[] = ($asset->assigned) ? $asset->assignedType() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('username')) {
|
if ($request->filled('username')) {
|
||||||
@@ -1260,7 +1256,7 @@ class ReportsController extends Controller
|
|||||||
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
|
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
|
||||||
$row[] = str_replace(',', '', e($item['assetItem']->name));
|
$row[] = str_replace(',', '', e($item['assetItem']->name));
|
||||||
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
|
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
|
||||||
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
|
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->display_name : trans('admin/reports/general.deleted_user')));
|
||||||
$rows[] = implode(',', $row);
|
$rows[] = implode(',', $row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -873,6 +873,7 @@ class SettingsController extends Controller
|
|||||||
$setting->ldap_default_group = $request->input('ldap_default_group');
|
$setting->ldap_default_group = $request->input('ldap_default_group');
|
||||||
$setting->ldap_filter = $request->input('ldap_filter');
|
$setting->ldap_filter = $request->input('ldap_filter');
|
||||||
$setting->ldap_username_field = $request->input('ldap_username_field');
|
$setting->ldap_username_field = $request->input('ldap_username_field');
|
||||||
|
$setting->ldap_display_name = $request->input('ldap_display_name');
|
||||||
$setting->ldap_lname_field = $request->input('ldap_lname_field');
|
$setting->ldap_lname_field = $request->input('ldap_lname_field');
|
||||||
$setting->ldap_fname_field = $request->input('ldap_fname_field');
|
$setting->ldap_fname_field = $request->input('ldap_fname_field');
|
||||||
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
|
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
|
||||||
@@ -889,7 +890,12 @@ class SettingsController extends Controller
|
|||||||
$setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0');
|
$setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0');
|
||||||
$setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url');
|
$setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url');
|
||||||
$setting->ldap_phone_field = $request->input('ldap_phone');
|
$setting->ldap_phone_field = $request->input('ldap_phone');
|
||||||
|
$setting->ldap_mobile = $request->input('ldap_mobile');
|
||||||
$setting->ldap_jobtitle = $request->input('ldap_jobtitle');
|
$setting->ldap_jobtitle = $request->input('ldap_jobtitle');
|
||||||
|
$setting->ldap_address = $request->input('ldap_address');
|
||||||
|
$setting->ldap_city = $request->input('ldap_city');
|
||||||
|
$setting->ldap_state = $request->input('ldap_state');
|
||||||
|
$setting->ldap_zip = $request->input('ldap_zip');
|
||||||
$setting->ldap_country = $request->input('ldap_country');
|
$setting->ldap_country = $request->input('ldap_country');
|
||||||
$setting->ldap_location = $request->input('ldap_location');
|
$setting->ldap_location = $request->input('ldap_location');
|
||||||
$setting->ldap_dept = $request->input('ldap_dept');
|
$setting->ldap_dept = $request->input('ldap_dept');
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('update', $object);
|
$this->authorize('update', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -85,7 +85,7 @@ class UploadedFilesController extends Controller
|
|||||||
public function show($object_type, $id, $file_id) : RedirectResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
public function show($object_type, $id, $file_id) : RedirectResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||||
{
|
{
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -130,7 +130,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('update', self::$map_object_type[$object_type]);
|
$this->authorize('update', self::$map_object_type[$object_type]);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -139,7 +139,7 @@ class UploadedFilesController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
// Check for the file
|
// Check for the file
|
||||||
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
|
$log = Actionlog::where('id',$file_id)->where('item_type', self::$map_object_type[$object_type])
|
||||||
->where('item_id', $object->id)->first();
|
->where('item_id', $object->id)->first();
|
||||||
|
|
||||||
if ($log) {
|
if ($log) {
|
||||||
@@ -148,7 +148,7 @@ class UploadedFilesController extends Controller
|
|||||||
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
||||||
}
|
}
|
||||||
// Delete the record of the file
|
// Delete the record of the file
|
||||||
if ($log->delete()) {
|
if ($log->logUploadDelete($object, $log->filename)) {
|
||||||
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1));
|
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ use App\Models\Company;
|
|||||||
use App\Models\Group;
|
use App\Models\Group;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Notifications\WelcomeNotification;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
use App\Notifications\CurrentInventory;
|
use App\Notifications\CurrentInventory;
|
||||||
@@ -88,6 +90,7 @@ class UsersController extends Controller
|
|||||||
//Username, email, and password need to be handled specially because the need to respect config values on an edit.
|
//Username, email, and password need to be handled specially because the need to respect config values on an edit.
|
||||||
$user->email = trim($request->input('email'));
|
$user->email = trim($request->input('email'));
|
||||||
$user->username = trim($request->input('username'));
|
$user->username = trim($request->input('username'));
|
||||||
|
$user->display_name = $request->input('display_name');
|
||||||
if ($request->filled('password')) {
|
if ($request->filled('password')) {
|
||||||
$user->password = bcrypt($request->input('password'));
|
$user->password = bcrypt($request->input('password'));
|
||||||
}
|
}
|
||||||
@@ -127,7 +130,7 @@ class UsersController extends Controller
|
|||||||
// we have to invoke the form request here to handle image uploads
|
// we have to invoke the form request here to handle image uploads
|
||||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
|
|
||||||
if($request->get('redirect_option') === 'back'){
|
if ($request->get('redirect_option') === 'back'){
|
||||||
session()->put(['redirect_option' => 'index']);
|
session()->put(['redirect_option' => 'index']);
|
||||||
} else {
|
} else {
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||||
@@ -135,6 +138,18 @@ class UsersController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
|
|
||||||
|
if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user->notify(new WelcomeNotification($user));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Could not send welcome notification for user: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('groups')) {
|
if ($request->filled('groups')) {
|
||||||
$user->groups()->sync($request->input('groups'));
|
$user->groups()->sync($request->input('groups'));
|
||||||
} else {
|
} else {
|
||||||
@@ -240,6 +255,7 @@ class UsersController extends Controller
|
|||||||
|
|
||||||
$user->first_name = $request->input('first_name');
|
$user->first_name = $request->input('first_name');
|
||||||
$user->last_name = $request->input('last_name');
|
$user->last_name = $request->input('last_name');
|
||||||
|
$user->display_name = $request->input('display_name');
|
||||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||||
$user->locale = $request->input('locale');
|
$user->locale = $request->input('locale');
|
||||||
$user->employee_num = $request->input('employee_num');
|
$user->employee_num = $request->input('employee_num');
|
||||||
@@ -562,10 +578,10 @@ class UsersController extends Controller
|
|||||||
$user->employee_num,
|
$user->employee_num,
|
||||||
$user->first_name,
|
$user->first_name,
|
||||||
$user->last_name,
|
$user->last_name,
|
||||||
$user->present()->fullName(),
|
$user->display_name,
|
||||||
$user->username,
|
$user->username,
|
||||||
$user->email,
|
$user->email,
|
||||||
($user->manager) ? $user->manager->present()->fullName() : '',
|
($user->manager) ? $user->manager->display_name : '',
|
||||||
($user->userloc) ? $user->userloc->name : '',
|
($user->userloc) ? $user->userloc->name : '',
|
||||||
($user->department) ? $user->department->name : '',
|
($user->department) ? $user->department->name : '',
|
||||||
$user->assets->count(),
|
$user->assets->count(),
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ class ViewAssetsController extends Controller
|
|||||||
$logaction->target_type = User::class;
|
$logaction->target_type = User::class;
|
||||||
|
|
||||||
$data['item_quantity'] = $request->has('request-quantity') ? e($request->input('request-quantity')) : 1;
|
$data['item_quantity'] = $request->has('request-quantity') ? e($request->input('request-quantity')) : 1;
|
||||||
$data['requested_by'] = $user->present()->fullName();
|
$data['requested_by'] = $user->display_name;
|
||||||
$data['item'] = $item;
|
$data['item'] = $item;
|
||||||
$data['item_type'] = $itemType;
|
$data['item_type'] = $itemType;
|
||||||
$data['target'] = auth()->user();
|
$data['target'] = auth()->user();
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ class AssetCountForSidebar
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$total_assets = Asset::count();
|
$total_assets = Asset::AssetsForShow()->count();
|
||||||
if ($settings->show_archived_in_list != '1') {
|
|
||||||
$total_assets -= Asset::Archived()->count();
|
|
||||||
}
|
|
||||||
view()->share('total_assets', $total_assets);
|
view()->share('total_assets', $total_assets);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::debug($e);
|
Log::debug($e);
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class SettingsSamlRequest extends FormRequest
|
|||||||
];
|
];
|
||||||
|
|
||||||
$pkey = openssl_pkey_new([
|
$pkey = openssl_pkey_new([
|
||||||
'private_key_bits' => 2048,
|
'private_key_bits' => config('app.saml_key_size'),
|
||||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class AccessoriesTransformer
|
|||||||
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
|
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
|
||||||
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
|
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
|
||||||
|
'total_cost' => Helper::formatCurrencyOutput($accessory->totalCostSum()),
|
||||||
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
|
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
|
||||||
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter
|
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter
|
||||||
'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
|
'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
|
||||||
@@ -44,7 +45,7 @@ class AccessoriesTransformer
|
|||||||
'checkouts_count' => $accessory->checkouts_count,
|
'checkouts_count' => $accessory->checkouts_count,
|
||||||
'created_by' => ($accessory->adminuser) ? [
|
'created_by' => ($accessory->adminuser) ? [
|
||||||
'id' => (int) $accessory->adminuser->id,
|
'id' => (int) $accessory->adminuser->id,
|
||||||
'name'=> e($accessory->adminuser->present()->fullName()),
|
'name'=> e($accessory->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -50,17 +50,20 @@ class ActionlogsTransformer
|
|||||||
|
|
||||||
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
$icon = $actionlog->present()->icon();
|
$icon = $actionlog->present()->icon();
|
||||||
|
|
||||||
|
if (($actionlog->filename!='') && ($actionlog->action_type!='upload deleted')) {
|
||||||
|
$icon = Helper::filetype_icon($actionlog->filename);
|
||||||
|
}
|
||||||
|
|
||||||
static $custom_fields = false;
|
static $custom_fields = false;
|
||||||
|
|
||||||
if ($custom_fields === false) {
|
if ($custom_fields === false) {
|
||||||
$custom_fields = CustomField::all();
|
$custom_fields = CustomField::all();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($actionlog->filename!='') {
|
|
||||||
$icon = Helper::filetype_icon($actionlog->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is necessary since we can't escape special characters within a JSON object
|
// This is necessary since we can't escape special characters within a JSON object
|
||||||
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
|
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
|
||||||
@@ -144,15 +147,15 @@ class ActionlogsTransformer
|
|||||||
[
|
[
|
||||||
'url' => $actionlog->uploads_file_url(),
|
'url' => $actionlog->uploads_file_url(),
|
||||||
'filename' => $actionlog->filename,
|
'filename' => $actionlog->filename,
|
||||||
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_url()),
|
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_path()),
|
||||||
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
|
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
|
||||||
] : null,
|
] : null,
|
||||||
|
|
||||||
'item' => ($actionlog->item) ? [
|
'item' => ($actionlog->item) ? [
|
||||||
'id' => (int) $actionlog->item->id,
|
'id' => (int) $actionlog->item->id,
|
||||||
'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()),
|
'name' => e($actionlog->item->display_name) ?? null,
|
||||||
'type' => e($actionlog->itemType()),
|
'type' => e($actionlog->itemType()),
|
||||||
'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null
|
'serial' => e($actionlog->item->serial) ? e($actionlog->item->serial) : null
|
||||||
] : null,
|
] : null,
|
||||||
'location' => ($actionlog->location) ? [
|
'location' => ($actionlog->location) ? [
|
||||||
'id' => (int) $actionlog->location->id,
|
'id' => (int) $actionlog->location->id,
|
||||||
@@ -165,19 +168,19 @@ class ActionlogsTransformer
|
|||||||
'action_type' => $actionlog->present()->actionType(),
|
'action_type' => $actionlog->present()->actionType(),
|
||||||
'admin' => ($actionlog->adminuser) ? [
|
'admin' => ($actionlog->adminuser) ? [
|
||||||
'id' => (int) $actionlog->adminuser->id,
|
'id' => (int) $actionlog->adminuser->id,
|
||||||
'name' => e($actionlog->adminuser->getFullNameAttribute()),
|
'name' => e($actionlog->adminuser->display_name) ?? null,
|
||||||
'first_name'=> e($actionlog->adminuser->first_name),
|
'first_name'=> e($actionlog->adminuser->first_name),
|
||||||
'last_name'=> e($actionlog->adminuser->last_name)
|
'last_name'=> e($actionlog->adminuser->last_name)
|
||||||
] : null,
|
] : null,
|
||||||
'created_by' => ($actionlog->adminuser) ? [
|
'created_by' => ($actionlog->adminuser) ? [
|
||||||
'id' => (int) $actionlog->adminuser->id,
|
'id' => (int) $actionlog->adminuser->id,
|
||||||
'name' => e($actionlog->adminuser->getFullNameAttribute()),
|
'name' => e($actionlog->adminuser->display_name),
|
||||||
'first_name'=> e($actionlog->adminuser->first_name),
|
'first_name'=> e($actionlog->adminuser->first_name),
|
||||||
'last_name'=> e($actionlog->adminuser->last_name)
|
'last_name'=> e($actionlog->adminuser->last_name)
|
||||||
] : null,
|
] : null,
|
||||||
'target' => ($actionlog->target) ? [
|
'target' => ($actionlog->target) ? [
|
||||||
'id' => (int) $actionlog->target->id,
|
'id' => (int) $actionlog->target->id,
|
||||||
'name' => ($actionlog->targetType()=='user') ? e($actionlog->target->getFullNameAttribute()) : e($actionlog->target->getDisplayNameAttribute()),
|
'name' => e($actionlog->target->display_name) ?? null,
|
||||||
'type' => e($actionlog->targetType()),
|
'type' => e($actionlog->targetType()),
|
||||||
] : null,
|
] : null,
|
||||||
|
|
||||||
|
|||||||
@@ -65,10 +65,11 @@ class AssetModelsTransformer
|
|||||||
'default_fieldset_values' => $default_field_values,
|
'default_fieldset_values' => $default_field_values,
|
||||||
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
||||||
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
||||||
|
'require_serial' => $assetmodel->require_serial,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
|
||||||
'created_by' => ($assetmodel->adminuser) ? [
|
'created_by' => ($assetmodel->adminuser) ? [
|
||||||
'id' => (int) $assetmodel->adminuser->id,
|
'id' => (int) $assetmodel->adminuser->id,
|
||||||
'name'=> e($assetmodel->adminuser->present()->fullName()),
|
'name'=> e($assetmodel->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -58,6 +58,13 @@ class AssetsTransformer
|
|||||||
'id' => (int) $asset->model->manufacturer->id,
|
'id' => (int) $asset->model->manufacturer->id,
|
||||||
'name'=> e($asset->model->manufacturer->name),
|
'name'=> e($asset->model->manufacturer->name),
|
||||||
] : null,
|
] : null,
|
||||||
|
'depreciation' => (($asset->model) && ($asset->model->depreciation)) ? [
|
||||||
|
'id' => (int) $asset->model->depreciation->id,
|
||||||
|
'name'=> e($asset->model->depreciation->name),
|
||||||
|
'months'=> (int) $asset->model->depreciation->months,
|
||||||
|
'type'=> e($asset->model->depreciation->depreciation_type),
|
||||||
|
'minimum'=> ($asset->model->depreciation->depreciation_min) ? (int) $asset->model->depreciation->depreciation_min : null,
|
||||||
|
] : null,
|
||||||
'supplier' => ($asset->supplier) ? [
|
'supplier' => ($asset->supplier) ? [
|
||||||
'id' => (int) $asset->supplier->id,
|
'id' => (int) $asset->supplier->id,
|
||||||
'name'=> e($asset->supplier->name),
|
'name'=> e($asset->supplier->name),
|
||||||
@@ -84,7 +91,7 @@ class AssetsTransformer
|
|||||||
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
|
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
|
||||||
'created_by' => ($asset->adminuser) ? [
|
'created_by' => ($asset->adminuser) ? [
|
||||||
'id' => (int) $asset->adminuser->id,
|
'id' => (int) $asset->adminuser->id,
|
||||||
'name'=> e($asset->adminuser->present()->fullName()),
|
'name'=> e($asset->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($asset->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($asset->updated_at, 'datetime'),
|
||||||
@@ -280,7 +287,7 @@ class AssetsTransformer
|
|||||||
'id' => (int) $asset->id,
|
'id' => (int) $asset->id,
|
||||||
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
|
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
|
||||||
'type' => 'asset',
|
'type' => 'asset',
|
||||||
'name' => e($asset->present()->fullName()),
|
'name' => e($asset->display_name),
|
||||||
'model' => ($asset->model) ? e($asset->model->name) : null,
|
'model' => ($asset->model) ? e($asset->model->name) : null,
|
||||||
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
||||||
'asset_tag' => e($asset->asset_tag),
|
'asset_tag' => e($asset->asset_tag),
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class CategoriesTransformer
|
|||||||
'licenses_count' => (int) $category->licenses_count,
|
'licenses_count' => (int) $category->licenses_count,
|
||||||
'created_by' => ($category->adminuser) ? [
|
'created_by' => ($category->adminuser) ? [
|
||||||
'id' => (int) $category->adminuser->id,
|
'id' => (int) $category->adminuser->id,
|
||||||
'name'=> e($category->adminuser->present()->fullName()),
|
'name'=> e($category->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($category->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($category->notes),
|
||||||
'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class CompaniesTransformer
|
|||||||
'users_count' => (int) $company->users_count,
|
'users_count' => (int) $company->users_count,
|
||||||
'created_by' => ($company->adminuser) ? [
|
'created_by' => ($company->adminuser) ? [
|
||||||
'id' => (int) $company->adminuser->id,
|
'id' => (int) $company->adminuser->id,
|
||||||
'name'=> e($company->adminuser->present()->fullName()),
|
'name'=> e($company->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($company->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($company->notes),
|
||||||
'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'),
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class ComponentsTransformer
|
|||||||
'order_number' => e($component->order_number),
|
'order_number' => e($component->order_number),
|
||||||
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
|
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
|
||||||
|
'total_cost' => Helper::formatCurrencyOutput($component->totalCostSum()),
|
||||||
'remaining' => (int) $component->numRemaining(),
|
'remaining' => (int) $component->numRemaining(),
|
||||||
'company' => ($component->company) ? [
|
'company' => ($component->company) ? [
|
||||||
'id' => (int) $component->company->id,
|
'id' => (int) $component->company->id,
|
||||||
@@ -51,7 +52,7 @@ class ComponentsTransformer
|
|||||||
'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null,
|
'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null,
|
||||||
'created_by' => ($component->adminuser) ? [
|
'created_by' => ($component->adminuser) ? [
|
||||||
'id' => (int) $component->adminuser->id,
|
'id' => (int) $component->adminuser->id,
|
||||||
'name'=> e($component->adminuser->present()->fullName()),
|
'name'=> e($component->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'),
|
||||||
@@ -76,7 +77,7 @@ class ComponentsTransformer
|
|||||||
$array[] = [
|
$array[] = [
|
||||||
'assigned_pivot_id' => $asset->pivot->id,
|
'assigned_pivot_id' => $asset->pivot->id,
|
||||||
'id' => (int) $asset->id,
|
'id' => (int) $asset->id,
|
||||||
'name' => e($asset->model->present()->name).' '.e($asset->present()->name),
|
'name' => e($asset->model->display_name).' '.e($asset->display_name),
|
||||||
'qty' => $asset->pivot->assigned_qty,
|
'qty' => $asset->pivot->assigned_qty,
|
||||||
'note' => $asset->pivot->note,
|
'note' => $asset->pivot->note,
|
||||||
'type' => 'asset',
|
'type' => 'asset',
|
||||||
|
|||||||
@@ -37,12 +37,13 @@ class ConsumablesTransformer
|
|||||||
'remaining' => $consumable->numRemaining(),
|
'remaining' => $consumable->numRemaining(),
|
||||||
'order_number' => e($consumable->order_number),
|
'order_number' => e($consumable->order_number),
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($consumable->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($consumable->purchase_cost),
|
||||||
|
'total_cost' => Helper::formatCurrencyOutput($consumable->totalCostSum()),
|
||||||
'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'),
|
'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'),
|
||||||
'qty' => (int) $consumable->qty,
|
'qty' => (int) $consumable->qty,
|
||||||
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
|
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
|
||||||
'created_by' => ($consumable->adminuser) ? [
|
'created_by' => ($consumable->adminuser) ? [
|
||||||
'id' => (int) $consumable->adminuser->id,
|
'id' => (int) $consumable->adminuser->id,
|
||||||
'name'=> e($consumable->adminuser->present()->fullName()),
|
'name'=> e($consumable->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class DepartmentsTransformer
|
|||||||
] : null,
|
] : null,
|
||||||
'manager' => ($department->manager) ? [
|
'manager' => ($department->manager) ? [
|
||||||
'id' => (int) $department->manager->id,
|
'id' => (int) $department->manager->id,
|
||||||
'name' => e($department->manager->getFullNameAttribute()),
|
'name' => e($department->manager->display_name),
|
||||||
'first_name'=> e($department->manager->first_name),
|
'first_name'=> e($department->manager->first_name),
|
||||||
'last_name'=> e($department->manager->last_name),
|
'last_name'=> e($department->manager->last_name),
|
||||||
] : null,
|
] : null,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class DepreciationsTransformer
|
|||||||
'licenses_count' => ($depreciation->licenses_count > 0) ? (int) $depreciation->licenses_count : 0,
|
'licenses_count' => ($depreciation->licenses_count > 0) ? (int) $depreciation->licenses_count : 0,
|
||||||
'created_by' => ($depreciation->adminuser) ? [
|
'created_by' => ($depreciation->adminuser) ? [
|
||||||
'id' => (int) $depreciation->adminuser->id,
|
'id' => (int) $depreciation->adminuser->id,
|
||||||
'name'=> e($depreciation->adminuser->present()->fullName()),
|
'name'=> e($depreciation->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime')
|
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime')
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class GroupsTransformer
|
|||||||
'notes' => Helper::parseEscapedMarkedownInline($group->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($group->notes),
|
||||||
'created_by' => ($group->adminuser) ? [
|
'created_by' => ($group->adminuser) ? [
|
||||||
'id' => (int) $group->adminuser->id,
|
'id' => (int) $group->adminuser->id,
|
||||||
'name'=> e($group->adminuser->present()->fullName()),
|
'name'=> e($group->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use App\Models\License;
|
|||||||
use App\Models\LicenseSeat;
|
use App\Models\LicenseSeat;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
class LicenseSeatsTransformer
|
class LicenseSeatsTransformer
|
||||||
{
|
{
|
||||||
public function transformLicenseSeats(Collection $seats, $total)
|
public function transformLicenseSeats(Collection $seats, $total)
|
||||||
@@ -52,6 +51,7 @@ class LicenseSeatsTransformer
|
|||||||
'reassignable' => (bool) $seat->license->reassignable,
|
'reassignable' => (bool) $seat->license->reassignable,
|
||||||
'notes' => e($seat->notes),
|
'notes' => e($seat->notes),
|
||||||
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
|
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
|
||||||
|
'disabled' => $seat->unreassignable_seat || $seat->license->isInactive(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ class LicensesTransformer
|
|||||||
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
|
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
|
||||||
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
|
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
|
||||||
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
|
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
|
||||||
|
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
|
||||||
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
|
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost),
|
||||||
'purchase_cost_numeric' => $license->purchase_cost,
|
'purchase_cost_numeric' => $license->purchase_cost,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($license->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($license->notes),
|
||||||
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
|
|
||||||
'seats' => (int) $license->seats,
|
'seats' => (int) $license->seats,
|
||||||
'free_seats_count' => (int) $license->free_seats_count,
|
'free_seats_count' => (int) $license->free_seats_count - License::unReassignableCount($license),
|
||||||
'remaining' => (int) $license->free_seats_count,
|
'remaining' => (int) $license->free_seats_count,
|
||||||
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
|
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
|
||||||
'license_name' => ($license->license_name) ? e($license->license_name) : null,
|
'license_name' => ($license->license_name) ? e($license->license_name) : null,
|
||||||
@@ -48,13 +48,13 @@ class LicensesTransformer
|
|||||||
'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null,
|
'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null,
|
||||||
'created_by' => ($license->adminuser) ? [
|
'created_by' => ($license->adminuser) ? [
|
||||||
'id' => (int) $license->adminuser->id,
|
'id' => (int) $license->adminuser->id,
|
||||||
'name'=> e($license->adminuser->present()->fullName()),
|
'name'=> e($license->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
|
||||||
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
|
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
|
||||||
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
|
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
|
||||||
|
'disabled' => $license->isInactive(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ class LocationsTransformer
|
|||||||
'assets_count' => (int) $location->assets_count,
|
'assets_count' => (int) $location->assets_count,
|
||||||
'rtd_assets_count' => (int) $location->rtd_assets_count,
|
'rtd_assets_count' => (int) $location->rtd_assets_count,
|
||||||
'users_count' => (int) $location->users_count,
|
'users_count' => (int) $location->users_count,
|
||||||
|
'consumables_count' => (int) $location->consumables_count,
|
||||||
|
'components_count' => (int) $location->components_count,
|
||||||
|
'children_count' => (int) $location->children_count,
|
||||||
'currency' => ($location->currency) ? e($location->currency) : null,
|
'currency' => ($location->currency) ? e($location->currency) : null,
|
||||||
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
|
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($location->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($location->notes),
|
||||||
@@ -76,12 +79,13 @@ class LocationsTransformer
|
|||||||
];
|
];
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
'update' => Gate::allows('update', Location::class) ? true : false,
|
'update' => (Gate::allows('update', Location::class) && ($location->deleted_at == '')),
|
||||||
'delete' => $location->isDeletable(),
|
'delete' => $location->isDeletable(),
|
||||||
'bulk_selectable' => [
|
'bulk_selectable' => [
|
||||||
'delete' => $location->isDeletable()
|
'delete' => $location->isDeletable()
|
||||||
],
|
],
|
||||||
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
|
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
|
||||||
|
'restore' => (Gate::allows('create', Location::class) && ($location->deleted_at != '')),
|
||||||
];
|
];
|
||||||
|
|
||||||
$array += $permissions_array;
|
$array += $permissions_array;
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ class MaintenancesTransformer
|
|||||||
'completion_date' => Helper::getFormattedDateObject($assetmaintenance->completion_date, 'date'),
|
'completion_date' => Helper::getFormattedDateObject($assetmaintenance->completion_date, 'date'),
|
||||||
'user_id' => ($assetmaintenance->adminuser) ? [
|
'user_id' => ($assetmaintenance->adminuser) ? [
|
||||||
'id' => $assetmaintenance->adminuser->id,
|
'id' => $assetmaintenance->adminuser->id,
|
||||||
'name'=> e($assetmaintenance->adminuser->present()->fullName())
|
'name'=> e($assetmaintenance->adminuser->display_name)
|
||||||
] : null, // legacy to not change the shape of the API
|
] : null, // legacy to not change the shape of the API
|
||||||
'created_by' => ($assetmaintenance->adminuser) ? [
|
'created_by' => ($assetmaintenance->adminuser) ? [
|
||||||
'id' => (int) $assetmaintenance->adminuser->id,
|
'id' => (int) $assetmaintenance->adminuser->id,
|
||||||
'name'=> e($assetmaintenance->adminuser->present()->fullName()),
|
'name'=> e($assetmaintenance->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ManufacturersTransformer
|
|||||||
'notes' => Helper::parseEscapedMarkedownInline($manufacturer->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($manufacturer->notes),
|
||||||
'created_by' => ($manufacturer->adminuser) ? [
|
'created_by' => ($manufacturer->adminuser) ? [
|
||||||
'id' => (int) $manufacturer->adminuser->id,
|
'id' => (int) $manufacturer->adminuser->id,
|
||||||
'name'=> e($manufacturer->adminuser->present()->fullName()),
|
'name'=> e($manufacturer->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($manufacturer->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($manufacturer->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($manufacturer->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($manufacturer->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class PredefinedKitsTransformer
|
|||||||
'name' => e($kit->name),
|
'name' => e($kit->name),
|
||||||
'created_by' => ($kit->adminuser) ? [
|
'created_by' => ($kit->adminuser) ? [
|
||||||
'id' => (int) $kit->adminuser->id,
|
'id' => (int) $kit->adminuser->id,
|
||||||
'name'=> e($kit->adminuser->present()->fullName()),
|
'name'=> e($kit->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($kit->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($kit->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($kit->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($kit->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Http\Transformers;
|
|||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\Asset;
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
class ProfileTransformer
|
class ProfileTransformer
|
||||||
@@ -26,7 +25,7 @@ class ProfileTransformer
|
|||||||
'id' => (int) $file->id,
|
'id' => (int) $file->id,
|
||||||
'icon' => Helper::filetype_icon($file->filename),
|
'icon' => Helper::filetype_icon($file->filename),
|
||||||
'item' => ($file->item) ? [
|
'item' => ($file->item) ? [
|
||||||
'name' => ($file->itemType()=='user') ? e($file->item->getFullNameAttribute()) : e($file->item->getDisplayNameAttribute()),
|
'name' => $file->item->display_name ? e($file->item->display_name) : null,
|
||||||
'type' => e($file->itemType()),
|
'type' => e($file->itemType()),
|
||||||
] : null,
|
] : null,
|
||||||
'filename' => e($file->filename),
|
'filename' => e($file->filename),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class StatuslabelsTransformer
|
|||||||
'notes' => e($statuslabel->notes),
|
'notes' => e($statuslabel->notes),
|
||||||
'created_by' => ($statuslabel->adminuser) ? [
|
'created_by' => ($statuslabel->adminuser) ? [
|
||||||
'id' => (int) $statuslabel->adminuser->id,
|
'id' => (int) $statuslabel->adminuser->id,
|
||||||
'name'=> e($statuslabel->adminuser->present()->fullName()),
|
'name'=> e($statuslabel->adminuser->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($statuslabel->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($statuslabel->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($statuslabel->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($statuslabel->updated_at, 'datetime'),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class UploadedFilesTransformer
|
|||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
|
||||||
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
|
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
|
||||||
'inlineable' => StorageHelper::allowSafeInline($file->uploads_file_path()),
|
'inlineable' => StorageHelper::allowSafeInline($file->uploads_file_path()) ?? false,
|
||||||
'exists_on_disk' => (Storage::exists($file->uploads_file_path()) ? true : false),
|
'exists_on_disk' => (Storage::exists($file->uploads_file_path()) ? true : false),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class UsersTransformer
|
|||||||
public function transformUser(User $user)
|
public function transformUser(User $user)
|
||||||
{
|
{
|
||||||
|
|
||||||
$role = '';
|
$role = null;
|
||||||
if ($user->isSuperUser()) {
|
if ($user->isSuperUser()) {
|
||||||
$role = 'superadmin';
|
$role = 'superadmin';
|
||||||
} elseif ($user->isAdmin()) {
|
} elseif ($user->isAdmin()) {
|
||||||
@@ -31,16 +31,17 @@ class UsersTransformer
|
|||||||
$array = [
|
$array = [
|
||||||
'id' => (int) $user->id,
|
'id' => (int) $user->id,
|
||||||
'avatar' => e($user->present()->gravatar) ?? null,
|
'avatar' => e($user->present()->gravatar) ?? null,
|
||||||
'name' => e($user->getFullNameAttribute()),
|
'name' => e($user->getFullNameAttribute()) ?? null,
|
||||||
'first_name' => e($user->first_name),
|
'first_name' => e($user->first_name) ?? null,
|
||||||
'last_name' => e($user->last_name),
|
'last_name' => e($user->last_name) ?? null,
|
||||||
'username' => e($user->username),
|
'display_name' => ($user->getRawOriginal('display_name')) ? e($user->getRawOriginal('display_name')) : null,
|
||||||
|
'username' => e($user->username) ?? null,
|
||||||
'remote' => ($user->remote == '1') ? true : false,
|
'remote' => ($user->remote == '1') ? true : false,
|
||||||
'locale' => ($user->locale) ? e($user->locale) : null,
|
'locale' => ($user->locale) ? e($user->locale) : null,
|
||||||
'employee_num' => ($user->employee_num) ? e($user->employee_num) : null,
|
'employee_num' => ($user->employee_num) ? e($user->employee_num) : null,
|
||||||
'manager' => ($user->manager) ? [
|
'manager' => ($user->manager) ? [
|
||||||
'id' => (int) $user->manager->id,
|
'id' => (int) $user->manager->id,
|
||||||
'name'=> e($user->manager->first_name).' '.e($user->manager->last_name),
|
'name'=> e($user->manager->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
|
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
|
||||||
'vip' => ($user->vip == '1') ? true : false,
|
'vip' => ($user->vip == '1') ? true : false,
|
||||||
@@ -59,7 +60,7 @@ class UsersTransformer
|
|||||||
] : null,
|
] : null,
|
||||||
'department_manager' => ($user->department?->manager) ? [
|
'department_manager' => ($user->department?->manager) ? [
|
||||||
'id' => (int) $user->department->manager->id,
|
'id' => (int) $user->department->manager->id,
|
||||||
'name'=> e($user->department->manager->full_name),
|
'name'=> e($user->department->manager->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'location' => ($user->userloc) ? [
|
'location' => ($user->userloc) ? [
|
||||||
'id' => (int) $user->userloc->id,
|
'id' => (int) $user->userloc->id,
|
||||||
@@ -82,7 +83,7 @@ class UsersTransformer
|
|||||||
'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null,
|
'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null,
|
||||||
'created_by' => ($user->createdBy) ? [
|
'created_by' => ($user->createdBy) ? [
|
||||||
'id' => (int) $user->createdBy->id,
|
'id' => (int) $user->createdBy->id,
|
||||||
'name'=> e($user->createdBy->present()->fullName),
|
'name'=> e($user->createdBy->display_name),
|
||||||
] : null,
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($user->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($user->updated_at, 'datetime'),
|
||||||
@@ -138,6 +139,7 @@ class UsersTransformer
|
|||||||
'first_name' => e($user->first_name),
|
'first_name' => e($user->first_name),
|
||||||
'last_name' => e($user->last_name),
|
'last_name' => e($user->last_name),
|
||||||
'username' => e($user->username),
|
'username' => e($user->username),
|
||||||
|
'display_name' => e($user->display_name),
|
||||||
'created_by' => $user->adminuser ? [
|
'created_by' => $user->adminuser ? [
|
||||||
'id' => (int) $user->adminuser->id,
|
'id' => (int) $user->adminuser->id,
|
||||||
'name'=> e($user->adminuser->present()->fullName),
|
'name'=> e($user->adminuser->present()->fullName),
|
||||||
|
|||||||
@@ -40,11 +40,32 @@ class AssetModelImporter extends ItemImporter
|
|||||||
{
|
{
|
||||||
|
|
||||||
$editingAssetModel = false;
|
$editingAssetModel = false;
|
||||||
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
|
||||||
|
/**
|
||||||
|
* This part gets a little confusing, since folks might be importing multiple models with the same name and different model numbers for the first time
|
||||||
|
* or they might be wanting to update existing models with new model numbers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// They are not trying to update existing models, so we'll check for duplicates with model name *and* number
|
||||||
|
if (! $this->updating) {
|
||||||
|
$this->log('Finding model by name and model number: '.$this->findCsvMatch($row, 'name').' / '.$this->findCsvMatch($row, 'model_number'));
|
||||||
|
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->where('model_number', '=', $this->findCsvMatch($row, 'model_number'))->first();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($this->findCsvMatch($row, 'id')!='') {
|
||||||
|
// Override model if an ID was given
|
||||||
|
$this->log('Finding model by ID: '.$this->findCsvMatch($row, 'id'));
|
||||||
|
$assetModel = AssetModel::find($this->findCsvMatch($row, 'id'));
|
||||||
|
} else {
|
||||||
|
$this->log('Finding model by name: '.$this->findCsvMatch($row, 'name'));
|
||||||
|
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($assetModel) {
|
if ($assetModel) {
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
$this->log('A matching Model '.$this->item['name'].' already exists');
|
$this->log('A matching Model '.$this->item['name'].' already exists and we are not updating. Skipping.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +87,7 @@ class AssetModelImporter extends ItemImporter
|
|||||||
$this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset'));
|
$this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset'));
|
||||||
$this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation'));
|
$this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation'));
|
||||||
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0;
|
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0;
|
||||||
|
$this->item['require_serial'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'require_serial'))) == 1) ? 1 : 0;
|
||||||
|
|
||||||
if (!empty($this->item['category'])) {
|
if (!empty($this->item['category'])) {
|
||||||
if ($category = $this->createOrFetchCategory($this->item['category'])) {
|
if ($category = $this->createOrFetchCategory($this->item['category'])) {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ abstract class Importer
|
|||||||
'termination_date' => 'termination date',
|
'termination_date' => 'termination date',
|
||||||
'warranty_months' => 'warranty',
|
'warranty_months' => 'warranty',
|
||||||
'full_name' => 'full name',
|
'full_name' => 'full name',
|
||||||
|
'display_name' => 'display name',
|
||||||
'email' => 'email',
|
'email' => 'email',
|
||||||
'username' => 'username',
|
'username' => 'username',
|
||||||
'address' => 'address',
|
'address' => 'address',
|
||||||
@@ -299,6 +300,7 @@ abstract class Importer
|
|||||||
'full_name' => $this->findCsvMatch($row, 'full_name'),
|
'full_name' => $this->findCsvMatch($row, 'full_name'),
|
||||||
'first_name' => $this->findCsvMatch($row, 'first_name'),
|
'first_name' => $this->findCsvMatch($row, 'first_name'),
|
||||||
'last_name' => $this->findCsvMatch($row, 'last_name'),
|
'last_name' => $this->findCsvMatch($row, 'last_name'),
|
||||||
|
'display_name' => $this->findCsvMatch($row, 'display_name'),
|
||||||
'email' => $this->findCsvMatch($row, 'email'),
|
'email' => $this->findCsvMatch($row, 'email'),
|
||||||
'manager_id'=> '',
|
'manager_id'=> '',
|
||||||
'department_id' => '',
|
'department_id' => '',
|
||||||
@@ -369,6 +371,7 @@ abstract class Importer
|
|||||||
$user->first_name = $user_array['first_name'];
|
$user->first_name = $user_array['first_name'];
|
||||||
$user->last_name = $user_array['last_name'];
|
$user->last_name = $user_array['last_name'];
|
||||||
$user->username = $user_array['username'];
|
$user->username = $user_array['username'];
|
||||||
|
$user->display_name = $user_array['display_name'] ?? null;
|
||||||
$user->email = $user_array['email'];
|
$user->email = $user_array['email'];
|
||||||
$user->manager_id = $user_array['manager_id'] ?? null;
|
$user->manager_id = $user_array['manager_id'] ?? null;
|
||||||
$user->department_id = $user_array['department_id'] ?? null;
|
$user->department_id = $user_array['department_id'] ?? null;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a supplier if a duplicate does not exist.
|
* Create a manufacturer if a duplicate does not exist.
|
||||||
* @todo Investigate how this should interact with Importer::createManufacturerIfNotExists
|
* @todo Investigate how this should interact with Importer::createManufacturerIfNotExists
|
||||||
*
|
*
|
||||||
* @author A. Gianotto
|
* @author A. Gianotto
|
||||||
@@ -39,16 +39,16 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
|
|
||||||
$editingManufacturer = false;
|
$editingManufacturer = false;
|
||||||
|
|
||||||
$supplier = Manufacturer::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
$manufacturer = Manufacturer::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
||||||
|
|
||||||
if ($this->findCsvMatch($row, 'id')!='') {
|
if ($this->findCsvMatch($row, 'id')!='') {
|
||||||
// Override supplier if an ID was given
|
// Override manufacturer if an ID was given
|
||||||
\Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id'));
|
\Log::debug('Finding manufacturer by ID: '.$this->findCsvMatch($row, 'id'));
|
||||||
$supplier = Manufacturer::find($this->findCsvMatch($row, 'id'));
|
$manufacturer = Manufacturer::find($this->findCsvMatch($row, 'id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($supplier) {
|
if ($manufacturer) {
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
$this->log('A matching Manufacturer '.$this->item['name'].' already exists');
|
$this->log('A matching Manufacturer '.$this->item['name'].' already exists');
|
||||||
return;
|
return;
|
||||||
@@ -58,8 +58,8 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
$editingManufacturer = true;
|
$editingManufacturer = true;
|
||||||
} else {
|
} else {
|
||||||
$this->log('No Matching Manufacturer, Create a new one');
|
$this->log('No Matching Manufacturer, Create a new one');
|
||||||
$supplier = new Manufacturer;
|
$manufacturer = new Manufacturer;
|
||||||
$supplier->created_by = auth()->id();
|
$manufacturer->created_by = auth()->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the records from the CSV to determine their values
|
// Pull the records from the CSV to determine their values
|
||||||
@@ -79,21 +79,21 @@ class ManufacturerImporter extends ItemImporter
|
|||||||
|
|
||||||
|
|
||||||
if ($editingManufacturer) {
|
if ($editingManufacturer) {
|
||||||
Log::debug('Updating existing supplier');
|
Log::debug('Updating existing manufacturer');
|
||||||
$supplier->update($this->sanitizeItemForUpdating($supplier));
|
$manufacturer->update($this->sanitizeItemForUpdating($manufacturer));
|
||||||
} else {
|
} else {
|
||||||
Log::debug('Creating supplier');
|
Log::debug('Creating manufacturer');
|
||||||
$supplier->fill($this->sanitizeItemForStoring($supplier));
|
$manufacturer->fill($this->sanitizeItemForStoring($manufacturer));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($supplier->save()) {
|
if ($manufacturer->save()) {
|
||||||
$this->log('Manufacturer '.$supplier->name.' created or updated from CSV import');
|
$this->log('Manufacturer '.$manufacturer->name.' created or updated from CSV import');
|
||||||
return $supplier;
|
return $manufacturer;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log::debug($supplier->getErrors());
|
Log::debug($manufacturer->getErrors());
|
||||||
$this->logError($supplier, 'Manufacturer "'.$this->item['name'].'"');
|
$this->logError($manufacturer, 'Manufacturer "'.$this->item['name'].'"');
|
||||||
return $supplier->errors;
|
return $manufacturer->errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class UserImporter extends ItemImporter
|
|||||||
// Pull the records from the CSV to determine their values
|
// Pull the records from the CSV to determine their values
|
||||||
$this->item['id'] = trim($this->findCsvMatch($row, 'id'));
|
$this->item['id'] = trim($this->findCsvMatch($row, 'id'));
|
||||||
$this->item['username'] = trim($this->findCsvMatch($row, 'username'));
|
$this->item['username'] = trim($this->findCsvMatch($row, 'username'));
|
||||||
|
$this->item['display_name'] = trim($this->findCsvMatch($row, 'display_name'));
|
||||||
$this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name'));
|
$this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name'));
|
||||||
$this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name'));
|
$this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name'));
|
||||||
$this->item['email'] = trim($this->findCsvMatch($row, 'email'));
|
$this->item['email'] = trim($this->findCsvMatch($row, 'email'));
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ class CheckoutableListener
|
|||||||
|
|
||||||
if (!empty($to)) {
|
if (!empty($to)) {
|
||||||
try {
|
try {
|
||||||
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
|
$toMail = (clone $mailable)->locale($notifiable->locale);
|
||||||
|
Mail::to(array_flatten($to))->send($toMail);
|
||||||
Log::info('Checkout Mail sent to checkout target');
|
Log::info('Checkout Mail sent to checkout target');
|
||||||
} catch (ClientException $e) {
|
} catch (ClientException $e) {
|
||||||
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
@@ -104,6 +105,16 @@ class CheckoutableListener
|
|||||||
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!empty($cc)) {
|
||||||
|
try {
|
||||||
|
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
|
||||||
|
Mail::to(array_flatten($cc))->send($ccMail);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($shouldSendWebhookNotification) {
|
if ($shouldSendWebhookNotification) {
|
||||||
@@ -178,15 +189,26 @@ class CheckoutableListener
|
|||||||
|
|
||||||
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
|
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
|
||||||
|
|
||||||
try {
|
if (!empty($to)) {
|
||||||
if (!empty($to)) {
|
try {
|
||||||
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
|
$toMail = (clone $mailable)->locale($notifiable->locale);
|
||||||
Log::info('Checkin Mail sent to CC addresses');
|
Mail::to(array_flatten($to))->send($toMail);
|
||||||
|
Log::info('Checkin Mail sent to checkin target');
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($cc)) {
|
||||||
|
try {
|
||||||
|
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
|
||||||
|
Mail::to(array_flatten($cc))->send($ccMail);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
} catch (ClientException $e) {
|
|
||||||
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +262,12 @@ class CheckoutableListener
|
|||||||
$acceptance->checkoutable()->associate($event->checkoutable);
|
$acceptance->checkoutable()->associate($event->checkoutable);
|
||||||
$acceptance->assignedTo()->associate($event->checkedOutTo);
|
$acceptance->assignedTo()->associate($event->checkedOutTo);
|
||||||
|
|
||||||
|
$acceptance->qty = 1;
|
||||||
|
|
||||||
|
if (isset($event->checkoutable->checkout_qty)) {
|
||||||
|
$acceptance->qty = $event->checkoutable->checkout_qty;
|
||||||
|
}
|
||||||
|
|
||||||
$category = $this->getCategoryFromCheckoutable($event->checkoutable);
|
$category = $this->getCategoryFromCheckoutable($event->checkoutable);
|
||||||
|
|
||||||
if ($category?->alert_on_response) {
|
if ($category?->alert_on_response) {
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ class Importer extends Component
|
|||||||
'start_date' => trans('general.start_date'),
|
'start_date' => trans('general.start_date'),
|
||||||
'state' => trans('general.state'),
|
'state' => trans('general.state'),
|
||||||
'username' => trans('admin/users/table.username'),
|
'username' => trans('admin/users/table.username'),
|
||||||
|
'display_name' => trans('admin/users/table.display_name'),
|
||||||
'vip' => trans('general.importer.vip'),
|
'vip' => trans('general.importer.vip'),
|
||||||
'website' => trans('general.website'),
|
'website' => trans('general.website'),
|
||||||
'zip' => trans('general.zip'),
|
'zip' => trans('general.zip'),
|
||||||
@@ -402,6 +403,7 @@ class Importer extends Component
|
|||||||
|
|
||||||
|
|
||||||
$this->assetmodels_fields = [
|
$this->assetmodels_fields = [
|
||||||
|
'id' => trans('general.id'),
|
||||||
'category' => trans('general.category'),
|
'category' => trans('general.category'),
|
||||||
'eol' => trans('general.eol'),
|
'eol' => trans('general.eol'),
|
||||||
'fieldset' => trans('admin/models/general.fieldset'),
|
'fieldset' => trans('admin/models/general.fieldset'),
|
||||||
@@ -411,6 +413,7 @@ class Importer extends Component
|
|||||||
'model_number' => trans('general.model_no'),
|
'model_number' => trans('general.model_no'),
|
||||||
'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
|
'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
|
||||||
'requestable' => trans('admin/models/general.requestable'),
|
'requestable' => trans('admin/models/general.requestable'),
|
||||||
|
'require_serial' => trans('admin/hardware/general.require_serial'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -485,6 +488,13 @@ class Importer extends Component
|
|||||||
'username',
|
'username',
|
||||||
trans('general.importer.checked_out_to_username'),
|
trans('general.importer.checked_out_to_username'),
|
||||||
],
|
],
|
||||||
|
'display_name' =>
|
||||||
|
[
|
||||||
|
'display name',
|
||||||
|
'displayName',
|
||||||
|
'display',
|
||||||
|
trans('admin/users/table.display_name'),
|
||||||
|
],
|
||||||
'first_name' =>
|
'first_name' =>
|
||||||
[
|
[
|
||||||
'first name',
|
'first name',
|
||||||
@@ -527,6 +537,10 @@ class Importer extends Component
|
|||||||
'product key',
|
'product key',
|
||||||
'key',
|
'key',
|
||||||
],
|
],
|
||||||
|
'require_serial' =>
|
||||||
|
[
|
||||||
|
'serial required',
|
||||||
|
],
|
||||||
'model_number' =>
|
'model_number' =>
|
||||||
[
|
[
|
||||||
'model',
|
'model',
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class CheckinAssetMail extends Mailable
|
|||||||
|
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
from: $from,
|
from: $from,
|
||||||
subject: trans('mail.Asset_Checkin_Notification'),
|
subject: trans('mail.Asset_Checkin_Notification', ['tag' => $this->item->asset_tag]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Mail;
|
namespace App\Mail;
|
||||||
|
|
||||||
use App\Models\Accessory;
|
use App\Models\Accessory;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\Location;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@@ -41,7 +43,7 @@ class CheckoutAccessoryMail extends Mailable
|
|||||||
|
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
from: $from,
|
from: $from,
|
||||||
subject: (trans('mail.Accessory_Checkout_Notification')),
|
subject: trans_choice('mail.Accessory_Checkout_Notification', $this->checkout_qty),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +56,17 @@ class CheckoutAccessoryMail extends Mailable
|
|||||||
$eula = $this->item->getEula();
|
$eula = $this->item->getEula();
|
||||||
$req_accept = $this->item->requireAcceptance();
|
$req_accept = $this->item->requireAcceptance();
|
||||||
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
||||||
|
$name = null;
|
||||||
|
|
||||||
|
if($this->target instanceof User){
|
||||||
|
$name = $this->target->display_name;
|
||||||
|
}
|
||||||
|
else if($this->target instanceof Asset){
|
||||||
|
$name = $this->target->assignedto?->display_name;
|
||||||
|
}
|
||||||
|
else if($this->target instanceof Location){
|
||||||
|
$name = $this->target->manager->name;
|
||||||
|
}
|
||||||
|
|
||||||
return new Content(
|
return new Content(
|
||||||
markdown: 'mail.markdown.checkout-accessory',
|
markdown: 'mail.markdown.checkout-accessory',
|
||||||
@@ -61,15 +74,38 @@ class CheckoutAccessoryMail extends Mailable
|
|||||||
'item' => $this->item,
|
'item' => $this->item,
|
||||||
'admin' => $this->admin,
|
'admin' => $this->admin,
|
||||||
'note' => $this->note,
|
'note' => $this->note,
|
||||||
'target' => $this->target,
|
'target' => $name,
|
||||||
'eula' => $eula,
|
'eula' => $eula,
|
||||||
'req_accept' => $req_accept,
|
'req_accept' => $req_accept,
|
||||||
'accept_url' => $accept_url,
|
'accept_url' => $accept_url,
|
||||||
'checkout_qty' => $this->checkout_qty,
|
'checkout_qty' => $this->checkout_qty,
|
||||||
|
'introduction_line' => $this->introductionLine(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function introductionLine(): string
|
||||||
|
{
|
||||||
|
if ($this->target instanceof Location) {
|
||||||
|
return trans_choice('mail.new_item_checked_location', $this->checkout_qty, ['location' => $this->target->name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->requiresAcceptance()) {
|
||||||
|
return trans_choice('mail.new_item_checked_with_acceptance', $this->checkout_qty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->requiresAcceptance()) {
|
||||||
|
return trans_choice('mail.new_item_checked', $this->checkout_qty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we shouldn't get here but let's send a default message just in case
|
||||||
|
return trans('new_item_checked');
|
||||||
|
}
|
||||||
|
private function requiresAcceptance(): int|bool
|
||||||
|
{
|
||||||
|
return method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the attachments for the message.
|
* Get the attachments for the message.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Mail;
|
|||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
|
use App\Models\Location;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@@ -36,14 +37,6 @@ class CheckoutAssetMail extends Mailable
|
|||||||
$this->settings = Setting::getSettings();
|
$this->settings = Setting::getSettings();
|
||||||
$this->target = $checkedOutTo;
|
$this->target = $checkedOutTo;
|
||||||
|
|
||||||
// Location is a target option, but there are no emails currently associated with locations.
|
|
||||||
if($this->target instanceof User){
|
|
||||||
$this->target = $this->target->present()?->fullName();
|
|
||||||
}
|
|
||||||
else if($this->target instanceof Asset){
|
|
||||||
$this->target = $this->target->assignedto?->present()?->fullName();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->last_checkout = '';
|
$this->last_checkout = '';
|
||||||
$this->expected_checkin = '';
|
$this->expected_checkin = '';
|
||||||
|
|
||||||
@@ -85,6 +78,17 @@ class CheckoutAssetMail extends Mailable
|
|||||||
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
|
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
|
||||||
$req_accept = $this->requiresAcceptance();
|
$req_accept = $this->requiresAcceptance();
|
||||||
$fields = [];
|
$fields = [];
|
||||||
|
$name = null;
|
||||||
|
|
||||||
|
if($this->target instanceof User){
|
||||||
|
$name = $this->target->display_name;
|
||||||
|
}
|
||||||
|
else if($this->target instanceof Asset){
|
||||||
|
$name = $this->target->assignedto?->display_name;
|
||||||
|
}
|
||||||
|
else if($this->target instanceof Location){
|
||||||
|
$name = $this->target->manager->name;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the item has custom fields associated with it
|
// Check if the item has custom fields associated with it
|
||||||
if (($this->item->model) && ($this->item->model->fieldset)) {
|
if (($this->item->model) && ($this->item->model->fieldset)) {
|
||||||
@@ -100,7 +104,7 @@ class CheckoutAssetMail extends Mailable
|
|||||||
'admin' => $this->admin,
|
'admin' => $this->admin,
|
||||||
'status' => $this->item->assetstatus?->name,
|
'status' => $this->item->assetstatus?->name,
|
||||||
'note' => $this->note,
|
'note' => $this->note,
|
||||||
'target' => $this->target,
|
'target' => $name,
|
||||||
'fields' => $fields,
|
'fields' => $fields,
|
||||||
'eula' => $eula,
|
'eula' => $eula,
|
||||||
'req_accept' => $req_accept,
|
'req_accept' => $req_accept,
|
||||||
@@ -125,7 +129,7 @@ class CheckoutAssetMail extends Mailable
|
|||||||
private function getSubject(): string
|
private function getSubject(): string
|
||||||
{
|
{
|
||||||
if ($this->firstTimeSending) {
|
if ($this->firstTimeSending) {
|
||||||
return trans('mail.Asset_Checkout_Notification');
|
return trans('mail.Asset_Checkout_Notification', ['tag' => $this->item->asset_tag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return trans('mail.unaccepted_asset_reminder');
|
return trans('mail.unaccepted_asset_reminder');
|
||||||
@@ -133,12 +137,16 @@ class CheckoutAssetMail extends Mailable
|
|||||||
|
|
||||||
private function introductionLine(): string
|
private function introductionLine(): string
|
||||||
{
|
{
|
||||||
|
if ($this->firstTimeSending && $this->target instanceof Location) {
|
||||||
|
return trans_choice('mail.new_item_checked_location', 1, ['location' => $this->target->name]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->firstTimeSending && $this->requiresAcceptance()) {
|
if ($this->firstTimeSending && $this->requiresAcceptance()) {
|
||||||
return trans('mail.new_item_checked_with_acceptance');
|
return trans_choice('mail.new_item_checked_with_acceptance', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->firstTimeSending && !$this->requiresAcceptance()) {
|
if ($this->firstTimeSending && !$this->requiresAcceptance()) {
|
||||||
return trans('mail.new_item_checked');
|
return trans_choice('mail.new_item_checked', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->firstTimeSending && $this->requiresAcceptance()) {
|
if (!$this->firstTimeSending && $this->requiresAcceptance()) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class CheckoutComponentMail extends Mailable
|
|||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
$this->target = $checkedOutTo;
|
$this->target = $checkedOutTo;
|
||||||
$this->acceptance = $acceptance;
|
$this->acceptance = $acceptance;
|
||||||
$this->qty = $component->assets->first()?->pivot?->assigned_qty;
|
$this->qty = $component->checkout_qty;
|
||||||
|
|
||||||
$this->settings = Setting::getSettings();
|
$this->settings = Setting::getSettings();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ class CheckoutLicenseMail extends Mailable
|
|||||||
$this->target = $checkedOutTo;
|
$this->target = $checkedOutTo;
|
||||||
|
|
||||||
if($this->target instanceof User){
|
if($this->target instanceof User){
|
||||||
$this->target = $this->target->present()?->fullName();
|
$this->target = $this->target->display_name;
|
||||||
}
|
}
|
||||||
elseif($this->target instanceof Asset){
|
elseif($this->target instanceof Asset){
|
||||||
$this->target = $this->target->assignedto?->present()?->fullName();
|
$this->target = $this->target->display_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Models\Traits\Acceptable;
|
use App\Models\Traits\Acceptable;
|
||||||
|
use App\Models\Traits\CompanyableTrait;
|
||||||
use App\Models\Traits\HasUploads;
|
use App\Models\Traits\HasUploads;
|
||||||
use App\Models\Traits\Searchable;
|
use App\Models\Traits\Searchable;
|
||||||
use App\Presenters\Presentable;
|
use App\Presenters\Presentable;
|
||||||
@@ -65,7 +66,7 @@ class Accessory extends SnipeModel
|
|||||||
'company_id' => 'integer|nullable',
|
'company_id' => 'integer|nullable',
|
||||||
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
||||||
'min_amt' => 'integer|min:0|nullable',
|
'min_amt' => 'integer|min:0|nullable',
|
||||||
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99',
|
||||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -308,27 +309,6 @@ class Accessory extends SnipeModel
|
|||||||
return $this->category->require_acceptance ?? false;
|
return $this->category->require_acceptance ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
|
||||||
* checks for a settings level EULA
|
|
||||||
*
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @since [v3.0]
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getEula()
|
|
||||||
{
|
|
||||||
|
|
||||||
if ($this->category->eula_text) {
|
|
||||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
|
||||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
|
||||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check how many items within an accessory are checked out
|
* Check how many items within an accessory are checked out
|
||||||
*
|
*
|
||||||
@@ -377,6 +357,10 @@ class Accessory extends SnipeModel
|
|||||||
|
|
||||||
$accessory_checkout->limit(1)->delete();
|
$accessory_checkout->limit(1)->delete();
|
||||||
}
|
}
|
||||||
|
public function totalCostSum() {
|
||||||
|
|
||||||
|
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -----------------------------------------------
|
* -----------------------------------------------
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Traits\CompanyableTrait;
|
||||||
use App\Models\Traits\Searchable;
|
use App\Models\Traits\Searchable;
|
||||||
use App\Presenters\Presentable;
|
use App\Presenters\Presentable;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
@@ -245,19 +246,6 @@ class Actionlog extends SnipeModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establishes the actionlog -> uploads relationship
|
|
||||||
*
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @since [v3.0]
|
|
||||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
|
||||||
*/
|
|
||||||
public function uploads()
|
|
||||||
{
|
|
||||||
return $this->morphTo('item')
|
|
||||||
->where('action_type', '=', 'uploaded')
|
|
||||||
->withTrashed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes the actionlog -> userlog relationship
|
* Establishes the actionlog -> userlog relationship
|
||||||
@@ -455,6 +443,26 @@ class Actionlog extends SnipeModel
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Godfrey Martinez
|
||||||
|
* @since [v8.0.4]
|
||||||
|
* @return \App\Models\Actionlog
|
||||||
|
*/
|
||||||
|
public function logUploadDelete($object, $filename)
|
||||||
|
{
|
||||||
|
$log = new Actionlog;
|
||||||
|
$log->item_type = $object instanceof SnipeModel ? get_class($object) : $object;
|
||||||
|
$log->item_id = $object->id;
|
||||||
|
$log->created_by = auth()->id();
|
||||||
|
$log->target_id = null;
|
||||||
|
$log->filename = $filename;
|
||||||
|
$log->created_at = date('Y-m-d H:i:s');
|
||||||
|
$log->logaction('upload deleted');
|
||||||
|
|
||||||
|
return $log;
|
||||||
|
}
|
||||||
|
|
||||||
public function uploads_file_url()
|
public function uploads_file_url()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -7,19 +7,20 @@ use App\Exceptions\CheckoutNotAllowed;
|
|||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Traits\UniqueUndeletedTrait;
|
use App\Http\Traits\UniqueUndeletedTrait;
|
||||||
use App\Models\Traits\Acceptable;
|
use App\Models\Traits\Acceptable;
|
||||||
|
use App\Models\Traits\CompanyableTrait;
|
||||||
use App\Models\Traits\HasUploads;
|
use App\Models\Traits\HasUploads;
|
||||||
use App\Models\Traits\Searchable;
|
use App\Models\Traits\Searchable;
|
||||||
use App\Presenters\Presentable;
|
|
||||||
use App\Presenters\AssetPresenter;
|
use App\Presenters\AssetPresenter;
|
||||||
|
use App\Presenters\Presentable;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Watson\Validating\ValidatingTrait;
|
use Watson\Validating\ValidatingTrait;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for Assets.
|
* Model for Assets.
|
||||||
@@ -113,7 +114,7 @@ class Asset extends Depreciable
|
|||||||
'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
|
'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
|
||||||
'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
|
'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
|
||||||
'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'],
|
'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'],
|
||||||
'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'],
|
'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:99999999999999999.99'],
|
||||||
'supplier_id' => ['nullable', 'exists:suppliers,id'],
|
'supplier_id' => ['nullable', 'exists:suppliers,id'],
|
||||||
'asset_eol_date' => ['nullable', 'date'],
|
'asset_eol_date' => ['nullable', 'date'],
|
||||||
'eol_explicit' => ['nullable', 'boolean'],
|
'eol_explicit' => ['nullable', 'boolean'],
|
||||||
@@ -160,7 +161,6 @@ class Asset extends Depreciable
|
|||||||
'eol_explicit',
|
'eol_explicit',
|
||||||
'last_audit_date',
|
'last_audit_date',
|
||||||
'next_audit_date',
|
'next_audit_date',
|
||||||
'asset_eol_date',
|
|
||||||
'last_checkin',
|
'last_checkin',
|
||||||
'last_checkout',
|
'last_checkout',
|
||||||
];
|
];
|
||||||
@@ -823,21 +823,26 @@ class Asset extends Depreciable
|
|||||||
* @since [v2.0]
|
* @since [v2.0]
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public static function getExpiringWarrantee($days = 30)
|
public static function getExpiringWarrantyOrEol($days = 30)
|
||||||
{
|
{
|
||||||
$days = (is_null($days)) ? 30 : $days;
|
|
||||||
|
return self::where('archived', '=', '0')
|
||||||
return self::where('archived', '=', '0') // this can stay for right now, as `archived` defaults to 0 at the db level, but should probably be replaced with assetstatus->archived?
|
|
||||||
->whereNotNull('warranty_months')
|
|
||||||
->whereNotNull('purchase_date')
|
|
||||||
->whereNull('deleted_at')
|
|
||||||
->NotArchived()
|
->NotArchived()
|
||||||
->whereRaw(
|
->whereNull('deleted_at')
|
||||||
'DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) <= DATE_ADD(NOW(), INTERVAL '
|
->where(function ($query) use ($days) {
|
||||||
. $days
|
// Check for manual asset EOL first
|
||||||
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()'
|
$query->where(function ($query) use ($days) {
|
||||||
)
|
$query->whereNotNull('asset_eol_date')
|
||||||
->orderByRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH)')
|
->whereBetween('asset_eol_date', [Carbon::now(), Carbon::now()->addDays($days)]);
|
||||||
|
// Otherwise use the warranty months + purchase date + threshold
|
||||||
|
})->orWhere(function ($query) use ($days) {
|
||||||
|
$query->whereNotNull('purchase_date')
|
||||||
|
->whereNotNull('warranty_months')
|
||||||
|
->whereBetween('purchase_date', [Carbon::now(), Carbon::now()->addMonths('assets.warranty_months')->addDays($days)]);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->orderBy('asset_eol_date', 'ASC')
|
||||||
|
->orderBy('purchase_date', 'ASC')
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1018,31 +1023,6 @@ class Asset extends Depreciable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
|
||||||
* checks for a settings level EULA
|
|
||||||
*
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @since [v4.0]
|
|
||||||
* @return string | false
|
|
||||||
*/
|
|
||||||
public function getEula()
|
|
||||||
{
|
|
||||||
|
|
||||||
if (($this->model) && ($this->model->category)) {
|
|
||||||
if (($this->model->category->eula_text) && ($this->model->category->use_default_eula === 0)) {
|
|
||||||
return Helper::parseEscapedMarkedown($this->model->category->eula_text);
|
|
||||||
} elseif ($this->model->category->use_default_eula === 1) {
|
|
||||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public function getComponentCost()
|
public function getComponentCost()
|
||||||
{
|
{
|
||||||
$cost = 0;
|
$cost = 0;
|
||||||
@@ -1892,6 +1872,30 @@ class Asset extends Depreciable
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($fieldname == 'jobtitle') {
|
||||||
|
$query->where(function ($query) use ($search_val) {
|
||||||
|
if (is_array($search_val)) {
|
||||||
|
$query->whereHasMorph(
|
||||||
|
'assignedTo',
|
||||||
|
[User::class],
|
||||||
|
function ($query) use ($search_val) {
|
||||||
|
$query->whereIn('users.jobtitle', $search_val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$query->whereHasMorph(
|
||||||
|
'assignedTo',
|
||||||
|
[User::class],
|
||||||
|
function ($query) use ($search_val) {
|
||||||
|
$query->where(function ($query) use ($search_val) {
|
||||||
|
$query->where('users.jobtitle', 'LIKE', '%' . $search_val . '%');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* THIS CLUNKY BIT IS VERY IMPORTANT
|
* THIS CLUNKY BIT IS VERY IMPORTANT
|
||||||
@@ -1916,7 +1920,7 @@ class Asset extends Depreciable
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier')
|
if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier')
|
||||||
&& ($fieldname!='status_label') && ($fieldname!='assigned_to') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')
|
&& ($fieldname!='status_label') && ($fieldname!='assigned_to') && ($fieldname!='model') && ($fieldname!='jobtitle') && ($fieldname!='company') && ($fieldname!='manufacturer')
|
||||||
) {
|
) {
|
||||||
$query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
|
$query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class AssetModel extends SnipeModel
|
|||||||
'name',
|
'name',
|
||||||
'notes',
|
'notes',
|
||||||
'requestable',
|
'requestable',
|
||||||
|
'require_serial'
|
||||||
];
|
];
|
||||||
|
|
||||||
use Searchable;
|
use Searchable;
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use TCPDF;
|
||||||
|
|
||||||
class CheckoutAcceptance extends Model
|
class CheckoutAcceptance extends Model
|
||||||
{
|
{
|
||||||
@@ -32,7 +35,19 @@ class CheckoutAcceptance extends Model
|
|||||||
|
|
||||||
return array_filter($recipients);
|
return array_filter($recipients);
|
||||||
}
|
}
|
||||||
|
public function getCheckoutableItemTypeAttribute(): string
|
||||||
|
{
|
||||||
|
$type = $this->checkoutable_type;
|
||||||
|
|
||||||
|
return match ($type) {
|
||||||
|
Asset::class => trans('general.asset'),
|
||||||
|
LicenseSeat::class => trans('general.license'),
|
||||||
|
Accessory::class => trans('general.accessory'),
|
||||||
|
Component::class => trans('general.component'),
|
||||||
|
Consumable::class => trans('general.consumable'),
|
||||||
|
default => class_basename($type),
|
||||||
|
};
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* The resource that was is out
|
* The resource that was is out
|
||||||
*
|
*
|
||||||
@@ -117,8 +132,7 @@ class CheckoutAcceptance extends Model
|
|||||||
/**
|
/**
|
||||||
* Filter checkout acceptences by the user
|
* Filter checkout acceptences by the user
|
||||||
*
|
*
|
||||||
* @param Illuminate\Database\Eloquent\Builder $query
|
* @param User $user
|
||||||
* @param User $user
|
|
||||||
* @return \Illuminate\Database\Eloquent\Builder
|
* @return \Illuminate\Database\Eloquent\Builder
|
||||||
*/
|
*/
|
||||||
public function scopeForUser(Builder $query, User $user)
|
public function scopeForUser(Builder $query, User $user)
|
||||||
@@ -129,11 +143,111 @@ class CheckoutAcceptance extends Model
|
|||||||
/**
|
/**
|
||||||
* Filter to only get pending acceptances
|
* Filter to only get pending acceptances
|
||||||
*
|
*
|
||||||
* @param Illuminate\Database\Eloquent\Builder $query
|
|
||||||
* @return \Illuminate\Database\Eloquent\Builder
|
* @return \Illuminate\Database\Eloquent\Builder
|
||||||
*/
|
*/
|
||||||
public function scopePending(Builder $query)
|
public function scopePending(Builder $query)
|
||||||
{
|
{
|
||||||
return $query->whereNull('accepted_at')->whereNull('declined_at');
|
return $query->whereNull('accepted_at')->whereNull('declined_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function scopeDeclined(Builder $query)
|
||||||
|
{
|
||||||
|
return $query->whereNull('accepted_at')->whereNotNull('declined_at');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function displayCheckoutableType(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute:: make(
|
||||||
|
get: fn(mixed $value) => strtolower(str_replace('App\Models\\', '', $this->checkoutable_type)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateAcceptancePdf($data, $pdf_filename) {
|
||||||
|
|
||||||
|
// set some language dependent data:
|
||||||
|
$lg = Array();
|
||||||
|
$lg['a_meta_charset'] = 'UTF-8';
|
||||||
|
$lg['w_page'] = 'page';
|
||||||
|
|
||||||
|
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
||||||
|
$pdf->setRTL(false);
|
||||||
|
$pdf->setLanguageArray($lg);
|
||||||
|
$pdf->SetFontSubsetting(true);
|
||||||
|
$pdf->SetCreator('Snipe-IT Asset Management System');
|
||||||
|
$pdf->SetAuthor($data['assigned_to']);
|
||||||
|
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
|
||||||
|
$pdf->SetSubject('Asset Acceptance: '.$data['item_tag']);
|
||||||
|
$pdf->SetKeywords('Snipe-IT, assets, acceptance, eula, tos');
|
||||||
|
$pdf->SetFont('dejavusans', '', 8, '', true);
|
||||||
|
$pdf->SetPrintHeader(false);
|
||||||
|
$pdf->SetPrintFooter(false);
|
||||||
|
|
||||||
|
$pdf->AddPage();
|
||||||
|
if ($data['logo'] != null) {
|
||||||
|
$pdf->writeHTML('<img src="'.$data['logo'].'">', true, 0, true, 0, '');
|
||||||
|
} else {
|
||||||
|
$pdf->writeHTML('<h3>'.$data['site_name'].'</h3><br /><br />', true, 0, true, 0, 'C');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf->Ln();
|
||||||
|
$pdf->writeHTML(trans('general.date') . ': ' . Helper::getFormattedDateObject(now(), 'datetime', false), true, 0, true, 0, '');
|
||||||
|
|
||||||
|
if ($data['company_name'] != null) {
|
||||||
|
$pdf->writeHTML(trans('general.company') . ': ' . e($data['company_name']), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
if ($data['item_tag'] != null) {
|
||||||
|
$pdf->writeHTML(trans('general.asset_tag') . ': ' . e($data['item_tag']), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
if ($data['item_name'] != null) {
|
||||||
|
$pdf->writeHTML(trans('general.name') . ': ' . e($data['item_name']), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
if ($data['item_model'] != null) {
|
||||||
|
$pdf->writeHTML(trans('general.asset_model') . ': ' . e($data['item_model']), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
if ($data['item_serial'] != null) {
|
||||||
|
$pdf->writeHTML(trans('admin/hardware/form.serial').': '.e($data['item_serial']), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
if (($data['qty'] != null) && ($data['qty'] > 1)) {
|
||||||
|
$pdf->writeHTML(trans('general.qty').': '.e($data['qty']), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
$pdf->writeHTML(trans('general.assignee').': '.e($data['assigned_to']), true, 0, true, 0, '');
|
||||||
|
$pdf->Ln();
|
||||||
|
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
|
||||||
|
|
||||||
|
|
||||||
|
// Break the EULA into lines based on newlines, and check each line for RTL or CJK characters
|
||||||
|
$eula_lines = preg_split("/\r\n|\n|\r/", $data['eula']);
|
||||||
|
|
||||||
|
foreach ($eula_lines as $eula_line) {
|
||||||
|
Helper::hasRtl($eula_line) ? $pdf->setRTL(true) : $pdf->setRTL(false);
|
||||||
|
Helper::isCjk($eula_line) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
|
||||||
|
|
||||||
|
$pdf->writeHTML(Helper::parseEscapedMarkedown($eula_line), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
$pdf->Ln();
|
||||||
|
$pdf->Ln();
|
||||||
|
$pdf->setRTL(false);
|
||||||
|
$pdf->Ln();
|
||||||
|
|
||||||
|
if ($data['signature'] != null) {
|
||||||
|
$pdf->writeHTML('<img src="'.$data['signature'].'">', true, 0, true, 0, '');
|
||||||
|
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
|
||||||
|
$pdf->writeHTML(e($data['assigned_to']), true, 0, true, 0, 'C');
|
||||||
|
$pdf->Ln();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data['note'] != null) {
|
||||||
|
Helper::isCjk($data['note']) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
|
||||||
|
$pdf->writeHTML(trans('general.notes') . ': ' . e($data['note']), true, 0, true, 0, '');
|
||||||
|
$pdf->Ln();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$pdf->writeHTML(trans('general.assigned_date').': '.e($data['check_out_date']), true, 0, true, 0, '');
|
||||||
|
$pdf->writeHTML(trans('general.accepted_date').': '.e($data['accepted_date']), true, 0, true, 0, '');
|
||||||
|
|
||||||
|
return $pdf->Output($pdf_filename, 'S');
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class CheckoutRequest extends Model
|
|||||||
public function name()
|
public function name()
|
||||||
{
|
{
|
||||||
if ($this->itemType() == 'asset') {
|
if ($this->itemType() == 'asset') {
|
||||||
return $this->itemRequested()->present()->name();
|
return $this->itemRequested()->display_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->itemRequested()->name;
|
return $this->itemRequested()->name;
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Traits\CompanyableTrait;
|
||||||
use App\Models\Traits\Searchable;
|
use App\Models\Traits\Searchable;
|
||||||
use App\Presenters\Presentable;
|
use App\Presenters\Presentable;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Watson\Validating\ValidatingTrait;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Watson\Validating\ValidatingTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for Companies.
|
* Model for Companies.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
|
use App\Models\Traits\CompanyableTrait;
|
||||||
use App\Models\Traits\HasUploads;
|
use App\Models\Traits\HasUploads;
|
||||||
use App\Models\Traits\Searchable;
|
use App\Models\Traits\Searchable;
|
||||||
use App\Presenters\Presentable;
|
use App\Presenters\Presentable;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Watson\Validating\ValidatingTrait;
|
use Watson\Validating\ValidatingTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +43,7 @@ class Component extends SnipeModel
|
|||||||
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
||||||
'min_amt' => 'integer|min:0|nullable',
|
'min_amt' => 'integer|min:0|nullable',
|
||||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||||
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99',
|
||||||
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -217,24 +217,6 @@ class Component extends SnipeModel
|
|||||||
return $this->category->require_acceptance;
|
return $this->category->require_acceptance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
|
||||||
* checks for a settings level EULA
|
|
||||||
*
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @since [v4.0]
|
|
||||||
* @return string | false
|
|
||||||
*/
|
|
||||||
public function getEula()
|
|
||||||
{
|
|
||||||
if ($this->category->eula_text) {
|
|
||||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
|
||||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
|
||||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes the component -> action logs relationship
|
* Establishes the component -> action logs relationship
|
||||||
@@ -306,7 +288,10 @@ class Component extends SnipeModel
|
|||||||
return $this->qty - $this->numCheckedOut();
|
return $this->qty - $this->numCheckedOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function totalCostSum() {
|
||||||
|
|
||||||
|
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* -----------------------------------------------
|
* -----------------------------------------------
|
||||||
* BEGIN MUTATORS
|
* BEGIN MUTATORS
|
||||||
|
|||||||
@@ -4,22 +4,16 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Models\Traits\Acceptable;
|
use App\Models\Traits\Acceptable;
|
||||||
|
use App\Models\Traits\CompanyableTrait;
|
||||||
use App\Models\Traits\HasUploads;
|
use App\Models\Traits\HasUploads;
|
||||||
use App\Models\Traits\Searchable;
|
use App\Models\Traits\Searchable;
|
||||||
|
use App\Presenters\ConsumablePresenter;
|
||||||
use App\Presenters\Presentable;
|
use App\Presenters\Presentable;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Watson\Validating\ValidatingTrait;
|
use Watson\Validating\ValidatingTrait;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
|
||||||
use App\Presenters\ConsumablePresenter;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\ConsumableAssignment;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Location;
|
|
||||||
use App\Models\Manufacturer;
|
|
||||||
use App\Models\Supplier;
|
|
||||||
use App\Models\Category;
|
|
||||||
|
|
||||||
class Consumable extends SnipeModel
|
class Consumable extends SnipeModel
|
||||||
{
|
{
|
||||||
@@ -53,7 +47,7 @@ class Consumable extends SnipeModel
|
|||||||
'company_id' => 'integer|nullable',
|
'company_id' => 'integer|nullable',
|
||||||
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
||||||
'min_amt' => 'integer|min:0|max:99999|nullable',
|
'min_amt' => 'integer|min:0|max:99999|nullable',
|
||||||
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99',
|
||||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -291,25 +285,6 @@ class Consumable extends SnipeModel
|
|||||||
return $this->category->require_acceptance;
|
return $this->category->require_acceptance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
|
||||||
* checks for a settings level EULA
|
|
||||||
*
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @since [v4.0]
|
|
||||||
* @return string | false
|
|
||||||
*/
|
|
||||||
public function getEula()
|
|
||||||
{
|
|
||||||
if ($this->category->eula_text) {
|
|
||||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
|
||||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
|
||||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check how many items within a consumable are checked out
|
* Check how many items within a consumable are checked out
|
||||||
*
|
*
|
||||||
@@ -337,7 +312,10 @@ class Consumable extends SnipeModel
|
|||||||
|
|
||||||
return $remaining;
|
return $remaining;
|
||||||
}
|
}
|
||||||
|
public function totalCostSum() {
|
||||||
|
|
||||||
|
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* -----------------------------------------------
|
* -----------------------------------------------
|
||||||
* BEGIN MUTATORS
|
* BEGIN MUTATORS
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Traits\CompanyableTrait;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Watson\Validating\ValidatingTrait;
|
use Watson\Validating\ValidatingTrait;
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user