Compare commits

...

761 Commits

Author SHA1 Message Date
snipe
17cc632a3b Validator for users switching companies
Signed-off-by: snipe <snipe@snipe.net>
2024-08-29 14:50:07 +01:00
snipe
b64ed254e0 Fixed tests
Signed-off-by: snipe <snipe@snipe.net>
2024-08-29 13:49:09 +01:00
snipe
ba291edc42 Merge pull request #15412 from uberbrady/ImprovePatchPurchasePrice
Fix [FD-43836] PATCH of purchase_cost for assets for comma as decimal separator
2024-08-29 13:18:52 +01:00
Brady Wetherington
ec2ea955d8 Fix PATCH of purchase_cost for assets for comma as decimal separator 2024-08-29 12:35:14 +01:00
snipe
c197644ba7 Merge pull request #15284 from spencerrlongg/bug/sc-26584
[Multi-Company] Fixes Users Being Moved With Items Still Assigned
2024-08-29 11:38:15 +01:00
snipe
29b30cc5d3 Merge pull request #15411 from snipe/snyk/upgrade-webpack
Upgraded webpack
2024-08-29 11:29:44 +01:00
snipe
6af27516dc Upgraded webpack
Signed-off-by: snipe <snipe@snipe.net>
2024-08-29 11:29:00 +01:00
snipe
a89f17a145 Merge pull request #15410 from snipe/snyk/upgrade-jquery-ui
Updated jquery UI
2024-08-29 11:25:56 +01:00
snipe
de0565f5b3 Updated jquery UI
Signed-off-by: snipe <snipe@snipe.net>
2024-08-29 11:24:06 +01:00
snipe
68c708bdef Merge pull request #15409 from snipe/fixes-string
Corrected language string
2024-08-29 11:18:34 +01:00
snipe
5581950fee Corrected language string
Signed-off-by: snipe <snipe@snipe.net>
2024-08-29 11:17:17 +01:00
snipe
58e366a063 Merge pull request #15408 from snipe/redirect-on-print-if-user-invalid
Check that the user exists before trying to print
2024-08-29 11:08:02 +01:00
snipe
b06c527767 Check that the user exists before trying to print
Signed-off-by: snipe <snipe@snipe.net>
2024-08-29 11:06:30 +01:00
snipe
26f28a862a Merge pull request #15404 from snipe/bug/sc-26717-model-delete
Set image to null if model is deleted
2024-08-28 14:26:03 +01:00
snipe
94c981e22c Set image to null if model is deleted
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 14:23:49 +01:00
snipe
e8da7e2df2 Merge pull request #15403 from snipe/clean_up_depreciations
Fixed #15392 - filter by depreciation when showing models
2024-08-28 12:46:48 +01:00
snipe
d635c86e00 Added tests
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:46:30 +01:00
snipe
406ff6984b Added click to select on models table
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:42:23 +01:00
snipe
c6ddc501c5 Filter by depreciation_id
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:40:12 +01:00
snipe
4839181beb Added counts to API
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:40:01 +01:00
snipe
63a05c89a7 Added counts to show method
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:39:52 +01:00
snipe
385c4f69f7 Added counts to depreciation transformer
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:39:43 +01:00
snipe
486cd8c8c9 Nicer formatting for searchableAttributes
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:39:09 +01:00
snipe
eb5d93b3c2 Added assets relationship
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:38:55 +01:00
snipe
a5ff623484 Added asset, license, model count to API
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:38:43 +01:00
snipe
b5a4389815 Added badge count, fixed model bulk edit menu
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 12:38:31 +01:00
snipe
7b5b559baa Merge pull request #15401 from snipe/fixes-15397-new-window-for-label
Opens label new window
2024-08-28 11:57:29 +01:00
snipe
67a9929745 Opens label new window
Signed-off-by: snipe <snipe@snipe.net>
2024-08-28 11:56:54 +01:00
snipe
64c4433b98 Merge pull request #15396 from snipe/add_start_end_date_to_users_edit
Added `start_date` and `end_date` to user bulk edit
2024-08-27 07:36:37 +01:00
snipe
22bc088f6f Added start_date and end_date to user bulk edit
Signed-off-by: snipe <snipe@snipe.net>
2024-08-27 07:21:19 +01:00
snipe
d3e8e06638 Add @swift2512 as a contributor 2024-08-26 14:07:06 +01:00
snipe
1b933f7add Added s to fa icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-26 13:47:17 +01:00
snipe
3fe891a05b Removed a few more commas
Signed-off-by: snipe <snipe@snipe.net>
2024-08-26 10:25:27 +01:00
snipe
f4c5b712f4 Removed colons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-26 10:24:07 +01:00
snipe
4c8dc7818d Merge pull request #15389 from snipe/added_generate_label_to_button_stack
Fixed #15388 - Moved generate label  button
2024-08-23 17:57:00 +01:00
snipe
5c43a1f87c Use proper style for buttons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 17:54:49 +01:00
snipe
d3b265de8e Moved button on hardware
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 17:48:36 +01:00
snipe
263151658f Moved general label button
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 17:44:09 +01:00
snipe
aa86e07cd2 Fixed #15385 - small icon tweaks
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 15:22:39 +01:00
snipe
d92fa5de65 fa-fw on settings icon
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 15:17:25 +01:00
snipe
c589140ea0 Merge pull request #15383 from snipe/re-adds-checkout-button
Fixed #15378 - Corrected gate on checkin button on asset view
2024-08-23 08:42:04 +01:00
snipe
52894615ce More specific gate for checkin
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 08:40:41 +01:00
snipe
8546bbdd65 Fixed gate for checkout button
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 08:38:47 +01:00
snipe
cc2c8f76d0 Merge pull request #15375 from Godmartinz/fix-acceptance-reminder-command
Fixes the `acceptance-reminder` command
2024-08-23 07:31:29 +01:00
snipe
1ffa69c43c Merge pull request #15380 from snipe/fixed/fixed_nav_bracket
Added pull-right to angle bracket
2024-08-23 07:28:20 +01:00
snipe
f85ebd7ffd Added pull-right to angle bracket
Signed-off-by: snipe <snipe@snipe.net>
2024-08-23 07:27:39 +01:00
snipe
78d355f136 Merge pull request #15377 from marcusmoore/fixes/custom-field-values-on-validation-error
Fixed custom field defaults being prematurely updated
2024-08-23 07:20:23 +01:00
snipe
ec0346e4a8 Add @setpill as a contributor 2024-08-23 07:19:08 +01:00
snipe
fc5eb37776 Merge pull request #15379 from setpill/fix/load-trustproxies-middleware
fixed #15374: load TrustProxies middleware in Kernel.php
2024-08-23 07:18:41 +01:00
setpill
1d7853cbfe fixed #15374: load TrustProxies middleware in Kernel.php 2024-08-22 23:41:27 +02:00
Marcus Moore
af0a95be12 Simplify assertions 2024-08-22 10:18:23 -07:00
Marcus Moore
d67975cb62 Implement fix 2024-08-22 10:18:23 -07:00
Marcus Moore
663b2fd844 Add test case 2024-08-22 10:18:23 -07:00
Marcus Moore
bcace9d019 Point test to correct endpoint 2024-08-22 10:18:23 -07:00
Marcus Moore
b59bf3e7dc Add failing test 2024-08-22 10:18:23 -07:00
Godfrey M
3957d670d0 fixes send acceptance reminder query 2024-08-22 10:04:46 -07:00
snipe
833dace2b4 Merge pull request #15373 from snipe/fixes/#15366_custom_fields
Fixed #15366 use the non-admin edit encrypted custom fields permissions
2024-08-22 15:00:16 +01:00
snipe
56e31d2303 Fixed #15366 - use permission for encrypted custom fields
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 14:58:09 +01:00
snipe
ec365b0804 Merge pull request #15372 from StarlessNights/update-docker-compose-files
Fixed #15371: docker-compose files updated.
2024-08-22 14:49:13 +01:00
Iisakki Jaakkola
aef0ac68c3 Need to use the long format for redis too in docker-compose file. 2024-08-22 16:10:00 +03:00
Iisakki Jaakkola
f12f9a816f Update the official docker-compose file too while at it. 2024-08-22 15:47:12 +03:00
Iisakki Jaakkola
a000d6454f Fix non-functional development docker-compose file. 2024-08-22 15:46:40 +03:00
snipe
ca8864c061 Fixed small layout quirks in asset view
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 13:28:52 +01:00
snipe
423f4f9126 Merge pull request #14667 from Godmartinz/add_location_to_create_user_via_asset
Adds location select to the create new user via asset checkout
2024-08-22 13:25:41 +01:00
snipe
456c7d8d91 Merge pull request #15065 from Godmartinz/explicit_eol_removal_command
Adds `snipeit:remove-explicit-eols` command
2024-08-22 13:20:56 +01:00
snipe
54cfe3f6e6 Merge pull request #15156 from uberbrady/re-add-stalebot
Re-add stalebot-esque github Action
2024-08-22 13:20:10 +01:00
snipe
5e0b18104d Merge pull request #15369 from snipe/fixes/checkbox_on_restore
Fixed checkbox layout on modal from #15296
2024-08-22 12:59:20 +01:00
snipe
8b5d3f7fbd Fixed extra closing p tag
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 12:56:32 +01:00
snipe
744f43676d Fixed checkbox layout on modal from #15296
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 12:53:16 +01:00
snipe
ec0b9b198f Merge pull request #15296 from uberbrady/expose_restore_sanitize
Expose the 'sanitize' system for backup restores to the web GUI
2024-08-22 12:43:07 +01:00
snipe
94300d81d6 Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 11:19:32 +01:00
snipe
bc9ea5a2ec Merge pull request #15352 from Godmartinz/mobile_breakpoint
Fixed Mobile misalignment of info on Assets and Users view pages
2024-08-22 11:18:06 +01:00
snipe
4635a6efc3 Merge pull request #15360 from snipe/more_print_fixes
More print fixes for asset view
2024-08-22 11:17:49 +01:00
snipe
9608414eae Merge pull request #15367 from snipe/fixes/15344_added_freeform_to_country_select2
Fixed #15344 - make select2 for countries freeform-ish
2024-08-22 11:06:13 +01:00
snipe
5efddf6f5b Allow clear on country dropdown
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 11:01:39 +01:00
snipe
305dc049a4 Added asterisk help text
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 11:01:28 +01:00
snipe
3ac0702094 Fixed #15344 - make select2 for countries freeform-ish
Signed-off-by: snipe <snipe@snipe.net>
2024-08-22 10:13:22 +01:00
snipe
47d8e2f8b9 Add @Scarzy as a contributor 2024-08-22 09:42:05 +01:00
snipe
83dd9ce20e Merge pull request #15362 from r-xyz/model-files-api
Added #9413: AssetModel files endpoints to API
2024-08-21 23:15:24 +01:00
r-xyz
a8eb76fd8d Fixed model files API routes. 2024-08-21 22:25:41 +02:00
r-xyz
cd7db5c4a8 Fix some typos in models file handler. 2024-08-21 22:24:08 +02:00
r-xyz
da7313bc9d Fix models files API routes. 2024-08-21 20:24:22 +02:00
r-xyz
4ec361c718 Add AssetModel files endpoints to API 2024-08-21 19:49:51 +02:00
Brady Wetherington
738ef442fd Rename .env var to have DB_ prefix for sanitize-by-default setting 2024-08-21 13:16:51 +01:00
snipe
d29b3bfb9a Few more hidden-print classes
Signed-off-by: snipe <snipe@snipe.net>
2024-08-21 13:06:16 +01:00
snipe
6fdce3c536 Merge pull request #15358 from r-xyz/rename-docker-startup
Renamed  docker startup scripts coherently.
2024-08-21 13:02:50 +01:00
snipe
912bbf0e32 More print fixes
Signed-off-by: snipe <snipe@snipe.net>
2024-08-21 13:02:21 +01:00
snipe
01c4fe6113 Merge pull request #15359 from uberbrady/improve_windows_upgrade
Fixed #15190 - Improvements to upgrade.php script to improve Windows experience
2024-08-21 12:12:23 +01:00
Brady Wetherington
0fa9f57971 Improvements to upgrade.php script to improve Windows experience 2024-08-21 11:53:38 +01:00
r-xyz
1ab29ec3a4 Rename docker startup scripts coherently. 2024-08-21 11:52:09 +02:00
snipe
7e475a0786 Merge pull request #15357 from snipe/tighten_category_view_translations
Fixed #15330 - Cleaned up category title
2024-08-21 10:41:17 +01:00
snipe
26b3c62ab8 Cleaned up category title
Signed-off-by: snipe <snipe@snipe.net>
2024-08-21 10:29:51 +01:00
snipe
d0acf5b8a6 Merge pull request #15356 from snipe/validate_location_parent
Fixed #15341 - validate parent ID
2024-08-21 10:12:58 +01:00
snipe
74fbc23823 Updated tests
Signed-off-by: snipe <snipe@snipe.net>
2024-08-21 10:09:35 +01:00
snipe
a23dee52f2 Added tests
Signed-off-by: snipe <snipe@snipe.net>
2024-08-21 09:58:47 +01:00
snipe
4d03f1e110 Fixed #15341 - validate parent ID
Signed-off-by: snipe <snipe@snipe.net>
2024-08-21 09:46:18 +01:00
Godfrey M
963911f2e1 changed order of info stgack 2024-08-21 00:23:10 -07:00
Godfrey M
9d484077ae missed a file 2024-08-20 14:24:37 -07:00
Godfrey M
9858b0f37f fixes info tab for users and assets 2024-08-20 14:23:55 -07:00
snipe
09033b19a7 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2024-08-20 19:44:13 +01:00
snipe
5fdeb9c413 Merge pull request #15351 from snipe/localizations/new_translations_2024-08-20
Updated languages
2024-08-20 19:42:08 +01:00
snipe
22d3734075 Merge pull request #15350 from marcusmoore/icon-component-updates
Fixed icon not rotated and simplified component
2024-08-20 19:34:38 +01:00
Marcus Moore
eca6b03f44 Allow id to be rendered 2024-08-20 11:08:53 -07:00
Marcus Moore
bbdbec7197 Rely on $attributes behavior rendering passed attributes 2024-08-20 11:00:39 -07:00
snipe
6c450d1338 Updated languages
Signed-off-by: snipe <snipe@snipe.net>
2024-08-20 18:18:17 +01:00
spencerrlongg
a8cd1027f3 rm commented code 2024-08-20 11:40:15 -05:00
snipe
d99b306ae9 Merge pull request #15345 from snipe/add_trim_strings_middleware
Added TrimStrings middleware
2024-08-20 11:46:55 +01:00
snipe
74136761df Added TrimStrings middleware
Signed-off-by: snipe <snipe@snipe.net>
2024-08-20 11:45:13 +01:00
snipe
f597d64339 Merge pull request #15342 from snipe/snyk/bs-tables
[Snyk] Upgrade bootstrap-table from 1.23.0 to 1.23.2
2024-08-20 10:17:26 +01:00
snipe
0072f1500e [Snyk] Upgrade bootstrap-table from 1.23.0 to 1.23.2
Signed-off-by: snipe <snipe@snipe.net>
2024-08-20 10:16:27 +01:00
snipe
64bed01a91 Merge pull request #15327 from snipe/update_button_style
Update icons to use blade components, standardize button colors and format
2024-08-20 10:13:43 +01:00
snipe
f6319e11e7 Added fa-fw to sidebar elements
Signed-off-by: snipe <snipe@snipe.net>
2024-08-20 10:11:27 +01:00
snipe
5d9f988df3 Added fw class to topnav icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-20 10:01:49 +01:00
snipe
f8c72fb0ac Merge pull request #15112 from marcusmoore/livewire-importer-improvements
Improved handling attempted access of deleted files in importer
2024-08-20 09:57:03 +01:00
snipe
886514a25f Merge pull request #15336 from marcusmoore/fixes/remove-displayed-parentheses
Fixed `)` being added to expected asset checkin report email header
2024-08-19 22:46:08 +01:00
Marcus Moore
fa765667f2 Merge branch 'develop' into livewire-importer-improvements 2024-08-19 14:41:58 -07:00
Marcus Moore
434bdcd6d4 Add missing parentheses to conditional 2024-08-19 14:31:47 -07:00
snipe
be5f3f38f8 Settings pages
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 14:48:12 +01:00
snipe
08c3a25b39 Little more padding
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 14:16:57 +01:00
snipe
34cd4b6244 Added links to apple/google maps
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 14:15:36 +01:00
snipe
d89c8682be Reversed location button ordering
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 13:46:31 +01:00
snipe
6f024849e9 Few more
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 13:39:24 +01:00
snipe
e048f0955f More icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 13:27:21 +01:00
snipe
479b2b4fd3 More icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 13:23:08 +01:00
snipe
84f14a05bd Fixed logging for asset model restore
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 13:20:19 +01:00
snipe
8d52fa51b1 Fixed suppliers buttons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 13:20:06 +01:00
snipe
229d8b9bf5 Added location restore
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 13:19:59 +01:00
snipe
eb8d43a804 Updated buttons on models
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 12:32:44 +01:00
snipe
f82266fade Cleaned up presenters
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 11:58:31 +01:00
snipe
b4b6f7a35f Added css-consumable to overrides for table headers
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 11:58:21 +01:00
snipe
0bc995b87f Updated more icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 11:58:05 +01:00
snipe
59725f2031 Removed extra debugging in test
Signed-off-by: snipe <snipe@snipe.net>
2024-08-19 11:57:40 +01:00
snipe
00bc9ac806 Fixed spacing
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 22:39:14 +01:00
snipe
f200960a57 Aaaand more
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 22:26:57 +01:00
snipe
c700127f1a Updated user view
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 22:09:00 +01:00
snipe
ae2f9571b4 And still more
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 21:57:37 +01:00
snipe
a77dcad336 More links
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 20:30:53 +01:00
snipe
7ace9324b4 Added padlock sound
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 20:30:22 +01:00
snipe
d2e889e927 Still more
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 19:21:59 +01:00
snipe
803bdb457c Added calendar
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 18:48:19 +01:00
snipe
7c9b1f6e38 Added the plus icon
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 18:39:22 +01:00
snipe
d545537a43 More icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 18:34:13 +01:00
snipe
4d8904938d Added calendar icon for datepickers
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 18:24:26 +01:00
snipe
0c09f2b2c0 Moar icon replacement
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 18:13:49 +01:00
snipe
bffba02511 Updated icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 17:26:44 +01:00
snipe
901f4df7ee Added more icons
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 17:03:36 +01:00
snipe
9337cba340 Added download
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 15:53:58 +01:00
snipe
2b0c67c263 Load the icon helper
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 15:51:00 +01:00
snipe
da71f031f5 Use icon in hardware view
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 15:48:24 +01:00
snipe
d2e585baa7 Added icon blade
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 15:47:45 +01:00
snipe
a102c085df Added icon helper
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 15:47:37 +01:00
snipe
cb40a82e79 Merge pull request #15324 from snipe/jerk_prevention
Prevent passing an array as login
2024-08-18 04:54:23 +01:00
snipe
4253acad4c Prevent passing an array as login
Signed-off-by: snipe <snipe@snipe.net>
2024-08-18 04:51:36 +01:00
snipe
7e6ff3cbe6 Removed trailing rows
Signed-off-by: snipe <snipe@snipe.net>
2024-08-17 00:51:19 +01:00
snipe
e7ef3bf515 Merge pull request #15322 from snipe/importer_model_fixes
Importer model fixes
2024-08-17 00:37:37 +01:00
snipe
a25efe53aa Removed some debugging
Signed-off-by: snipe <snipe@snipe.net>
2024-08-17 00:35:42 +01:00
snipe
de059a715a Cleaner test CSV
Signed-off-by: snipe <snipe@snipe.net>
2024-08-17 00:30:49 +01:00
snipe
318aff1ef0 Fixed behavior for null model numbers
Signed-off-by: snipe <snipe@snipe.net>
2024-08-17 00:27:44 +01:00
snipe
e96b9b2f4f Added test for models with same name but no model number
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 23:04:11 +01:00
snipe
3ae4a5caf0 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 22:53:27 +01:00
snipe
559f0d2f90 Added string
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 22:53:21 +01:00
snipe
9b6a36c8aa Removed unusued parameter
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 22:45:57 +01:00
snipe
1a4aebf805 Added TwoColumnUniqueUndeletedTrait trait
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 22:45:47 +01:00
snipe
6ad7100aa3 Removed unused use statements
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 22:45:17 +01:00
snipe
5529669884 Added tests
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 22:44:03 +01:00
snipe
b57283d8d1 Removed commented code
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 22:43:56 +01:00
snipe
7658f7c41d First shot
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 20:29:53 +01:00
spencerrlongg
70e5e0f9df get rid of dd 2024-08-16 12:52:06 -05:00
spencerrlongg
dec4691c73 should be good to go now 2024-08-16 12:50:09 -05:00
snipe
48903b1402 Added Gate Pass Generator link
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 17:42:42 +01:00
snipe
7376c21f10 Removed dupe link, linked name
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 17:01:07 +01:00
snipe
f921400579 Added link to UnifiSnipeSync
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 16:55:16 +01:00
snipe
ad6d09b6ad Removed default label
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 16:43:51 +01:00
snipe
43b338d612 Fixed translation
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 16:15:58 +01:00
snipe
67655ad84b Dev assets
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 13:11:33 +01:00
snipe
ea2b1b0748 Merge pull request #15318 from snipe/localization/fixed_strings
Corrected some translations
2024-08-16 12:43:06 +01:00
snipe
856c57cb12 Corrected some translations
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 12:39:59 +01:00
snipe
90584ab803 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2024-08-16 11:11:00 +01:00
snipe
184f54a6cd Removed extra spaces
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 11:10:57 +01:00
snipe
f24031b974 Merge pull request #15206 from Godmartinz/dashboard_box_alignment
Fixed dashboard box overflow
2024-08-16 11:10:40 +01:00
snipe
0a7aaa54b6 Merge pull request #15282 from r-xyz/alpine-secrets
Added support for Docker secrets in Standard and alpine image.
2024-08-16 11:08:09 +01:00
snipe
3167ee91d1 Added a setting test and validation on settings
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 10:51:49 +01:00
snipe
e8f1190628 Added missing migration from #15314
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 10:44:28 +01:00
snipe
44f18d210e Remove MBP constraint
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 10:36:35 +01:00
snipe
e8deecc9b4 Removed MBP constraint on model
This was creating a validation error since the name already exists

Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 10:35:42 +01:00
snipe
a79fe69bf4 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2024-08-16 10:22:16 +01:00
snipe
d49c33fbe3 Add @Glukose1 as a contributor 2024-08-16 10:22:05 +01:00
snipe
de29c1d8a0 Merge pull request #15314 from Glukose1/features/add_setting_due_for_checkin_in_days
Added #15312: Add checkin due in days setting
2024-08-16 10:21:36 +01:00
snipe
f9172594b2 Clearer time example display
Signed-off-by: snipe <snipe@snipe.net>
2024-08-16 10:14:43 +01:00
Marcus Moore
94d8a547b8 Merge branch 'develop' into livewire-importer-improvements 2024-08-15 13:47:49 -07:00
snipe
b39a7c6f0c String constraint
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 15:42:11 +01:00
snipe
a3d847151a Model name uniqueness
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 15:19:27 +01:00
snipe
594f506641 Merge pull request #15305 from snipe/localizations/updated_languages_2024-08-15
Updated translation strings
2024-08-15 12:21:59 +01:00
snipe
dd458dfa7f Updated strings
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 12:21:06 +01:00
snipe
b917489f00 Corrected string
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 12:10:41 +01:00
snipe
75dfedcb82 Merge pull request #15304 from snipe/confetti_fun_mode_redux
Added confetti option
2024-08-15 11:37:34 +01:00
snipe
6216b4fc0d Use native browser control for required in addition to the css effect
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 11:35:56 +01:00
snipe
4356cb7b9b Added confetti option
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 11:32:42 +01:00
Glukose1
d8df52cc8f Implemented setting to specify the amount of days before an assets appers in the due for checkin page. Currently this uses the audit warning days but a sperated setting gives the possibility of defineing different values for each field. 2024-08-15 12:20:51 +02:00
snipe
10dc1f1368 Re-pull the accessory checkout
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 10:51:49 +01:00
snipe
8f7bce7aad Re-added UI for checkout to non-users for accessories
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 10:44:27 +01:00
snipe
4d66f7c93f Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 10:35:02 +01:00
snipe
381003eeab Cleaned up asset tag field on quickscan checkin
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 10:27:31 +01:00
snipe
c3d4c821f3 Merge pull request #15303 from snipe/features/#15301_optional_status_to_quickscan_checkin
Fixed #15301 - Added optional status to quickscan checkin
2024-08-15 10:26:04 +01:00
snipe
d8038ac483 Removed status label list
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 10:17:08 +01:00
snipe
d268f25f16 Make asset tag required
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 10:16:57 +01:00
snipe
01c69c8f8f Allow optional status label on quickscan checkin
Signed-off-by: snipe <snipe@snipe.net>
2024-08-15 10:08:48 +01:00
spencerrlongg
9622e05cf5 correct api test 2024-08-14 18:41:06 -05:00
spencerrlongg
afaf53cdfc failing ui test 2024-08-14 18:14:21 -05:00
spencerrlongg
f031309f8f set up api controller for route/model binding 2024-08-14 16:09:15 -05:00
spencerrlongg
20ec420ba3 not quite done, api side needs some work 2024-08-14 13:53:29 -05:00
spencerrlongg
a70b94e707 Merge branch 'refs/heads/develop' into bug/sc-26584 2024-08-14 11:36:19 -05:00
r-xyz
0d7cf55a3a Add support for Docker secrets in Standard and alpine image. 2024-08-14 17:40:52 +02:00
Brady Wetherington
008bf036b5 Got rid of weird namespace declaration and commented-out data-toggle line 2024-08-14 16:09:34 +01:00
Brady Wetherington
cc5ad456e6 Expose the 'sanitize' system for backup restores to the web GUI 2024-08-14 15:59:21 +01:00
snipe
3e29457094 Merge pull request #15292 from snipe/features/updated_phpinsights
Updated phpinsights
2024-08-14 12:12:40 +01:00
snipe
0b3ac2a9cd Updated phpinsights
Signed-off-by: snipe <snipe@snipe.net>
2024-08-14 12:08:49 +01:00
snipe
0f1e5181a6 Nicer input layout for bulk edit name
Signed-off-by: snipe <snipe@snipe.net>
2024-08-14 10:09:14 +01:00
snipe
e544007185 Merge pull request #15291 from snipe/features/bulk_update_asset_name
Added asset name to bulk asset edit
2024-08-14 10:02:09 +01:00
snipe
4b690e8e1f Updated test to check for nulling
Signed-off-by: snipe <snipe@snipe.net>
2024-08-14 10:01:56 +01:00
snipe
371afde52d Bulk edit asset name
Signed-off-by: snipe <snipe@snipe.net>
2024-08-14 09:52:25 +01:00
snipe
7a6a2e3d92 Merge pull request #15290 from marcusmoore/test-fixes
Fixed component check in test
2024-08-14 00:00:55 +01:00
Marcus Moore
3f8ac59f4c Reference the entry in the components_assets table 2024-08-13 15:31:16 -07:00
snipe
74e7b1cfa5 Merge pull request #15286 from marcusmoore/test-updates
Implemented artisan test case
2024-08-13 20:46:46 +01:00
Marcus Moore
ae0a6d66f3 Update tests
https://masteringlaravel.io/daily/2024-02-09-watch-out-for-this-when-testing-artisan-commands
2024-08-13 12:16:08 -07:00
Marcus Moore
321839e074 Fix namespace 2024-08-13 12:14:56 -07:00
spencerrlongg
120cfd13c5 translation 2024-08-13 14:07:40 -05:00
spencerrlongg
09f2739298 works, un-reassignable licenses are an issue 2024-08-13 13:45:41 -05:00
snipe
e9fc455a30 Merge pull request #15192 from Godmartinz/status_labels_check_emails
Added status labels to confirmation emails
2024-08-13 18:24:15 +01:00
Godfrey M
8af4126de1 removed indent from blades 2024-08-13 10:21:33 -07:00
Godfrey M
5d1c48c071 fixes alignment, and box issue with pie chart 2024-08-13 10:11:49 -07:00
snipe
3044af9410 Merge pull request #15280 from snipe/bug/sc-23294
Fixed select2 “x” color on multi-select and border color
2024-08-13 16:46:56 +01:00
snipe
4f0f0af83b Make the border radius match the rest of the site
Signed-off-by: snipe <snipe@snipe.net>
2024-08-13 16:46:26 +01:00
snipe
198afee946 Fixed select2 “x” color on mult-select and border color
Signed-off-by: snipe <snipe@snipe.net>
2024-08-13 16:42:23 +01:00
snipe
def0af652d Merge pull request #15279 from TelecomsSansFrontieres/fix_missing-translate-item-assets
Fix: add missing admin/hardware/table.name key
2024-08-13 16:35:25 +01:00
Florent Bervas
20e4ba0590 lang: relocated translation key to existing item in general 2024-08-13 15:07:54 +00:00
snipe
72fd9977e5 Merge pull request #15277 from uberbrady/silence_saml_errors
Fixed: [sc-26355] Attempt to de-escalate SAML login and logout errors
2024-08-13 15:51:03 +01:00
Brady Wetherington
10f35c682b Re-add space 2024-08-13 15:49:51 +01:00
snipe
6e84c29ce4 Merge pull request #15278 from snipe/print_view_improvements
Fixed #14821 - Hide UI elements from print view on assets
2024-08-13 15:45:30 +01:00
snipe
389a06aac5 Hide UI elements from print view on assets
Signed-off-by: snipe <snipe@snipe.net>
2024-08-13 15:40:51 +01:00
Brady Wetherington
4b96721393 Attempt to de-escalate SAML login and logout errors 2024-08-13 14:55:13 +01:00
snipe
e42ee0c0e1 Merge pull request #15276 from snipe/css_updates
Bold selected tab for better readability
2024-08-13 14:21:45 +01:00
snipe
277a0fd16a Make selected tab bold
Signed-off-by: snipe <snipe@snipe.net>
2024-08-13 14:19:41 +01:00
snipe
c163d6774f Merge pull request #15198 from spencerrlongg/feature/add_trait_to_request
Add MayContainCustomFields Trait to Asset Update Request
2024-08-13 14:12:52 +01:00
snipe
9e73eaf955 Merge pull request #15275 from snipe/added_tooltips_to_top_bar
Added tooltips to top nav shortcuts
2024-08-13 13:50:27 +01:00
snipe
1b2094f4e2 Added tooltips to top nav shortcuts
Signed-off-by: snipe <snipe@snipe.net>
2024-08-13 13:49:13 +01:00
spencerrlongg
ec863df007 rm conditional that might be unnecessary 2024-08-12 16:58:53 -05:00
spencerrlongg
cc3b8e0681 this should more or less work, but i need to determine if this is the best way 2024-08-12 16:58:21 -05:00
spencerrlongg
ff145abbe7 use array for eager loading, makes ide prettier 2024-08-12 16:13:03 -05:00
snipe
e6106aa7cf Merge pull request #15272 from snipe/fixes/added_selectlist_permission_for_reports
Added reporting-only access for selectlists
2024-08-12 20:47:17 +01:00
snipe
b45a8f4b5f Added reporting-only access for selectlists
Signed-off-by: snipe <snipe@snipe.net>
2024-08-12 20:44:17 +01:00
snipe
c9f8a84d48 Merge pull request #15270 from snipe/fixes/make_dashboard_pie_respect_show_in_list
Fixed #15247 - make pie chart on dashboard respect the “show archived…
2024-08-12 17:59:27 +01:00
snipe
ac1543d568 Fixed #15247 - make pie chart on dashboard respect the “show archived” setting
Signed-off-by: snipe <snipe@snipe.net>
2024-08-12 17:54:42 +01:00
snipe
9d354ca657 Merge pull request #15266 from uberbrady/checkout_to_fixer
Checkout-to fixer
2024-08-12 16:34:19 +01:00
snipe
3f2139349e Allow better fallthrough on image
Signed-off-by: snipe <snipe@snipe.net>
2024-08-12 16:30:21 +01:00
snipe
f56dd582f9 Merge pull request #15269 from snipe/fixes/smaller_email_logo
Fixes ##15236 - smaller email logo
2024-08-12 16:27:59 +01:00
snipe
225ab47b8e Simplified logic
Signed-off-by: snipe <snipe@snipe.net>
2024-08-12 16:23:09 +01:00
snipe
4c350bd5a3 Remove the regular logo fallback, make image capped by height
Signed-off-by: snipe <snipe@snipe.net>
2024-08-12 16:21:58 +01:00
snipe
e7fb29fced Fixed copy area for serial
Signed-off-by: snipe <snipe@snipe.net>
2024-08-12 15:58:30 +01:00
Brady Wetherington
a2d11b43de Whoops, these were accidentally committed 2024-08-12 13:50:46 +01:00
Brady Wetherington
f80cf666b1 Mark tests as incomplete for the new artisan assigned_to fixer 2024-08-12 13:46:56 +01:00
snipe
ce5be8ac24 Merge pull request #15100 from Godmartinz/depreciation_percentage
Added Depreciation Percentage as an option
2024-08-12 10:10:25 +01:00
snipe
1777bb4b93 Pulled searchable relation on model - needs revisit
Signed-off-by: snipe <snipe@snipe.net>
2024-08-11 10:26:02 +01:00
snipe
5c6c655cdc Added sounds to quickscan checkin
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 19:37:18 +01:00
snipe
60eb602156 Merge pull request #15264 from snipe/fixes/fixed_admin_ordering_on_report
Fixed #15252 - admin ordering on activity report
2024-08-10 18:26:15 +01:00
snipe
76b114d820 Added test
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 18:21:41 +01:00
snipe
ce33deed86 Remove unused use statement
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 17:14:34 +01:00
snipe
1e6c172f18 Removed unusued use statement
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 17:14:20 +01:00
snipe
a1ce9b9285 Added query scope for ordering by admin
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 17:00:50 +01:00
snipe
6ec3693030 Load admin on actionlog
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 17:00:34 +01:00
snipe
71729f0b16 Load admin and model on assets
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 17:00:20 +01:00
snipe
1c229a8e08 Made notes field sortable
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 17:00:11 +01:00
snipe
07b1e3fb90 Fixed tests
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 16:55:48 +01:00
snipe
6294516271 Merge pull request #15263 from snipe/really/remove_pound_sign_for_order_number
Fixed #15248 Removed pound symbol from order number on asset view
2024-08-10 16:18:40 +01:00
snipe
f3331b5045 Removed pound symbol from order number on asset view
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 16:17:07 +01:00
snipe
6a06a91fa2 Merge pull request #15262 from snipe/synk/jquery-validation-from-1.20.1-to-1.21.0
Upgrade jquery-validation from 1.20.1 to 1.21.0
2024-08-10 16:07:47 +01:00
snipe
0cba5ba67e Upgrade jquery-validation from 1.20.1 to 1.21.0
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 16:06:14 +01:00
snipe
b815352a45 Merge pull request #15261 from snipe/security/upgrade_fontawesome
[Snyk] Upgrade @fortawesome/fontawesome-free from 6.5.2 to 6.6.0
2024-08-10 15:57:10 +01:00
snipe
2f299dbb20 Upgrade fontawesome, generate dev assets
Signed-off-by: snipe <snipe@snipe.net>
2024-08-10 15:51:24 +01:00
snipe
f76fbe75e0 Merge pull request #15221 from marcusmoore/fixes/bulk-checkin-logging
Fixed accessories and consumables not being logged correctly during bulk check-in
2024-08-08 10:43:59 +01:00
snipe
cc1e356c35 Merge pull request #15232 from marcusmoore/shift-126036
Updated PHPUnit to v10
2024-08-08 01:23:07 +01:00
snipe
8094f3c0e1 Merge pull request #15245 from spencerrlongg/bug/acceptance_issue
Fixed Issue with Acceptances Being Created
2024-08-08 01:22:34 +01:00
snipe
900beaa955 Merge pull request #15244 from snipe/features/play_sound_on_audit
Fixes #10560 - optional ability to play sound on audit
2024-08-08 00:58:30 +01:00
Marcus Moore
6521c02526 Merge branch 'develop' into livewire-importer-improvements
# Conflicts:
#	resources/views/livewire/importer.blade.php
2024-08-07 15:20:39 -07:00
spencerrlongg
2412333152 added conditional to listener 2024-08-07 16:53:06 -05:00
Marcus Moore
4a2d2f7336 Improve variable name 2024-08-07 13:32:30 -07:00
Marcus Moore
d03baa4613 Make diff cleaner 2024-08-07 13:29:45 -07:00
Marcus Moore
41437e4d8f Merge branch 'develop' into fixes/bulk-checkin-logging
# Conflicts:
#	app/Http/Controllers/Users/BulkUsersController.php
2024-08-07 13:06:59 -07:00
snipe
7d220dd8c2 set default
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 20:58:04 +01:00
snipe
a99ec062e6 Added checkbox for preferences
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 20:49:07 +01:00
snipe
8fe6395287 Added migration
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 20:41:13 +01:00
snipe
b4d51599ee Added sound effects
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 20:38:50 +01:00
snipe
9350a20189 Include accessories count
This isn’t exactly right right now

Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 20:19:47 +01:00
snipe
889d5da71e Check for accessories on bulk items
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 20:04:32 +01:00
snipe
5546ab08c2 Only delete user records in accessories_checkouts for bulk delete
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 19:53:18 +01:00
snipe
ef183f256c Merge pull request #15242 from Godmartinz/shortcut_option
Fixed shortcuts to be optional now
2024-08-07 18:56:27 +01:00
Godfrey M
3a96e501cc use old school input 2024-08-07 10:54:06 -07:00
Godfrey M
6b1784f2ee makes shortcuts optional 2024-08-07 10:47:16 -07:00
snipe
1a872b1643 Merge remote-tracking branch 'origin/develop' 2024-08-07 18:15:44 +01:00
snipe
deef9ad181 Merge pull request #15241 from snipe/partial_revert_for_checkout_accessories_to_non_user
Partial revert for checkout accessories to non user
2024-08-07 18:15:08 +01:00
snipe
0dc7e8b8dc Temp removed other selector
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 18:04:52 +01:00
snipe
a5f075548a Default to user
Signed-off-by: snipe <snipe@snipe.net>
2024-08-07 18:04:39 +01:00
Brady Wetherington
1706ddd511 working fixup script, but failing tests. But I have tests! 2024-08-07 16:06:00 +01:00
Marcus Moore
a220986d16 Switch to snake case 2024-08-06 16:13:51 -07:00
Marcus Moore
2a8ac81eed Fix namespace 2024-08-06 15:22:13 -07:00
Marcus Moore
d3cb3c03d2 Rerun composer update using php 8.1 2024-08-06 15:01:01 -07:00
Marcus Moore
c60bc5cdbe Change indents back to 2 2024-08-06 13:51:44 -07:00
Marcus Moore
6cfbf835c7 Migrate phpunit.xml
vendor/bin/phpunit --migrate-configuration
2024-08-06 13:37:53 -07:00
Marcus Moore
047b77e038 Composer update for phpunit, collision, and paratest
composer update phpunit/phpunit nunomaduro/collision brianium/paratest --with-all-dependencies
2024-08-06 13:34:51 -07:00
snipe
766b370264 Merge pull request #15229 from marcusmoore/bug/sc-26552
Disallowed checking out components to different companies and fixed number remaining counts
2024-08-06 21:33:55 +01:00
snipe
da33277234 Merge pull request #15230 from marcusmoore/fixes/test-namespaces
Fixed handful of test namespaces
2024-08-06 21:32:11 +01:00
Marcus Moore
3e832e5e94 Revert "Define test classes as final"
This reverts commit 95516b0343.
2024-08-06 13:31:17 -07:00
Marcus Moore
82e795b642 Revert "Add return types to test methods"
This reverts commit 83fb6826ee.
2024-08-06 13:30:34 -07:00
Shift
95516b0343 Define test classes as final 2024-08-06 20:25:22 +00:00
Shift
83fb6826ee Add return types to test methods 2024-08-06 20:25:22 +00:00
Shift
b1e92ab866 Declare data providers as static 2024-08-06 20:25:21 +00:00
Shift
96241cb67c Adopt PHP attributes in test classes 2024-08-06 20:25:21 +00:00
Shift
b680dab5c9 Ignore PHPUnit cache folder 2024-08-06 20:25:19 +00:00
Shift
984840dc82 Bump PHPUnit dependencies 2024-08-06 20:25:18 +00:00
Marcus Moore
84e447af09 Fix test namespaces 2024-08-06 13:21:36 -07:00
Marcus Moore
c7ddabcc8b Check for FMCS when checking out component 2024-08-06 12:27:15 -07:00
Marcus Moore
bee80fcf8a Remove global scope when counting check outs 2024-08-06 12:16:06 -07:00
Marcus Moore
0aff35b622 Scaffold additional failed tests 2024-08-06 12:07:34 -07:00
Godfrey M
46a6a84ecb adds migration and translations 2024-08-06 11:07:39 -07:00
Marcus Moore
374812f8fe Add failing test 2024-08-06 10:48:38 -07:00
Marcus Moore
94e00b8a3e Use new accessory checkout relationship 2024-08-05 17:26:11 -07:00
Marcus Moore
f2b78d18a4 Merge branch 'develop' into fixes/bulk-checkin-logging
# Conflicts:
#	app/Http/Controllers/Users/BulkUsersController.php
2024-08-05 17:25:54 -07:00
Marcus Moore
17eccfcd8b Formatting 2024-08-05 16:58:27 -07:00
Marcus Moore
01e4382d20 Formatting 2024-08-05 16:58:17 -07:00
Marcus Moore
1c664af326 Remove todo outside of scope 2024-08-05 16:58:08 -07:00
Marcus Moore
392d34422a Remove code handled by ConsumableAssignment:: call above 2024-08-05 16:57:40 -07:00
Marcus Moore
35e7a3163c Implement test case 2024-08-05 16:54:31 -07:00
Marcus Moore
96fafa6952 Improve readability 2024-08-05 16:36:29 -07:00
Marcus Moore
a55693211f Move test class 2024-08-05 16:27:04 -07:00
Marcus Moore
1acd24fdbe Re-order helpers 2024-08-05 16:26:25 -07:00
Marcus Moore
59b7d5db4b Remove comment 2024-08-05 16:25:14 -07:00
Marcus Moore
16aa47509b Implement test for assets 2024-08-05 16:24:52 -07:00
Marcus Moore
d1bb3ef6bf Scaffold two tests 2024-08-05 16:19:09 -07:00
Marcus Moore
5cd9dd4a67 Add assertion to ensure user cannot perform bulk actions on self 2024-08-05 16:17:24 -07:00
Marcus Moore
fc405d9d73 Assert redirected to correct place 2024-08-05 16:14:01 -07:00
Marcus Moore
5786ff5035 Add assertion for success message 2024-08-05 16:11:07 -07:00
Marcus Moore
8a206a6d92 Add assertion 2024-08-05 16:10:12 -07:00
Marcus Moore
67b3ab820f Add todo comments 2024-08-05 16:10:08 -07:00
Marcus Moore
b06501dd02 Add assertions 2024-08-05 16:02:59 -07:00
Marcus Moore
e3049fffd4 Remove comment 2024-08-05 15:50:53 -07:00
Marcus Moore
5ecdb7e07c Add validation test 2024-08-05 15:50:09 -07:00
Marcus Moore
4ed3347f52 Add permission check 2024-08-05 15:43:38 -07:00
Marcus Moore
364775dcfe Improve readability 2024-08-05 15:42:03 -07:00
Marcus Moore
bfebcdc7ed Improve variable name 2024-08-05 15:22:35 -07:00
Marcus Moore
480e4f3a69 Improve readability 2024-08-05 15:16:40 -07:00
Marcus Moore
78a0417ee9 Add another user into the mix 2024-08-05 15:11:18 -07:00
snipe
b83d148b37 Merge pull request #15217 from TelecomsSansFrontieres/feature_extend-search-by-tag
Feature: Extend search capabilities to other assets attributes
2024-08-05 15:38:51 +01:00
Florent Bervas
ad794248fe add a warning message if asset tag not found 2024-08-05 13:54:33 +00:00
Florent Bervas
b804791ff6 feature: extend search capabilities to other assets attributes 2024-08-05 12:26:29 +00:00
snipe
c45bf870b7 Merge pull request #15211 from snipe/feaures/15205_copy_asset_tag
Fixed #15205 - adds copy to asset tag
2024-08-02 20:59:53 +01:00
snipe
a0bd8b6049 Fixed #15205 - adds copy to asset tag
Signed-off-by: snipe <snipe@snipe.net>
2024-08-02 20:56:31 +01:00
Godfrey M
ae4f278df1 edited terenaries on notifs 2024-08-01 11:13:20 -07:00
Godfrey M
bc0ff706b0 fixes dashboard box overflow 2024-08-01 10:31:39 -07:00
snipe
28abb8d8cc Hotfix for 405 update asset
Signed-off-by: snipe <snipe@snipe.net>
2024-08-01 12:30:35 +01:00
snipe
24d948a3f6 Merge pull request #15204 from snipe/fixes/405_on_api_asset_update
Fixed (temp) put route
2024-08-01 12:28:23 +01:00
snipe
c1c9c3554e Fixed (temp) put route
Signed-off-by: snipe <snipe@snipe.net>
2024-08-01 12:27:02 +01:00
snipe
fcea564afa Fixed button label
Signed-off-by: snipe <snipe@snipe.net>
2024-07-31 21:44:27 +01:00
snipe
f44abd0b28 Fixed button label
Signed-off-by: snipe <snipe@snipe.net>
2024-07-31 21:43:09 +01:00
Godfrey M
854903805b one more spot 2024-07-31 12:16:41 -07:00
Godfrey M
868f117b67 Merge branch 'status_labels_check_emails' of github.com:Godmartinz/snipe-it into status_labels_check_emails 2024-07-31 12:03:56 -07:00
Godfrey M
b6cac4baae corrects ? usage 2024-07-31 12:03:36 -07:00
snipe
4fcb3df1d9 Merge pull request #15199 from Godmartinz/fix-signature-pad
adds closing brackets to signature pad
2024-07-31 19:23:33 +01:00
Godfrey M
b60e22bcb4 adds closing brackets to signature pad 2024-07-31 11:10:33 -07:00
spencerrlongg
61312c2eec oops, mysql 2024-07-31 12:07:50 -05:00
spencerrlongg
b0063b1d4a test written 2024-07-31 11:57:35 -05:00
akemidx
fffcbdc44d Merge remote-tracking branch 'upstream/develop' into upstream/dev 2024-07-31 07:14:06 -04:00
akemidx
8d1fa362f7 restoring code 2024-07-31 07:13:42 -04:00
snipe
31a2765b30 Merge pull request #15145 from Godmartinz/purge-storage
Added user storage files to purge command
2024-07-31 10:16:08 +01:00
snipe
e79a5b7efe Merge pull request #15193 from Godmartinz/admin-settings
Fixed admin boxes alignment
2024-07-31 08:54:44 +01:00
snipe
ca531e85f3 Merge pull request #15194 from spencerrlongg/bug/sc-26247
Fixed A Translation Issue With Auth
2024-07-31 08:54:09 +01:00
spencerrlongg
0941c0944a ok, found issue, but need to test some things now... 2024-07-30 22:55:15 -05:00
spencerrlongg
9b80843c77 tests a little broken, added some nullchecks 2024-07-30 21:44:22 -05:00
spencerrlongg
8684a3efc3 delete note, add trait to other request 2024-07-30 21:22:46 -05:00
akemidx
fccfce2ae8 first thought 2024-07-30 19:27:37 -04:00
Godfrey M
935d2ce29a fixes admin box alignment 2024-07-30 15:05:09 -07:00
spencerrlongg
437ddc01b4 removed extraneous $request->validate() arguments 2024-07-30 16:26:24 -05:00
Godfrey Martinez
f19e58b352 Merge pull request #21 from Godmartinz/status_labels_check_emails_checkout
adds Status labels to check in and out emails
2024-07-30 09:41:05 -07:00
Godfrey M
eb6f330e67 adds status to check in and out 2024-07-30 09:38:39 -07:00
Godfrey M
f19899543d changed error to info 2024-07-30 09:18:17 -07:00
snipe
3a2611f8e1 Merge pull request #14565 from spencerrlongg/bug/sc-23936
Checks that custom fields exist before saving
2024-07-30 11:36:55 +01:00
Godfrey M
53ad312700 added missing closing bracket 2024-07-29 15:17:32 -07:00
Godfrey M
89d375daad add indents 2024-07-29 15:15:41 -07:00
Godfrey M
d46f9776fe remove text 2024-07-29 15:14:58 -07:00
Godfrey M
f9a47c8a9f adds a try catch 2024-07-29 15:12:48 -07:00
Godfrey M
e395ee1878 adds a try catch 2024-07-29 15:12:32 -07:00
snipe
4971c54b05 Fixed seeder
Signed-off-by: snipe <snipe@snipe.net>
2024-07-29 20:17:25 +01:00
snipe
4c5b82ae37 Merge pull request #15188 from Godmartinz/1d_barcode_select_bug
Fixed 1d barcode targeting.
2024-07-29 20:00:21 +01:00
Spencer Long
fd7082c30f Merge branch 'develop' into bug/sc-23936 2024-07-29 14:00:00 -05:00
snipe
8d0b72293f Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2024-07-29 18:51:19 +01:00
snipe
f3d98f90c0 Add @arne-kroeger as a contributor 2024-07-29 18:51:12 +01:00
snipe
4eccb5ffc6 Merge pull request #15185 from arne-kroeger/feat/accesspories-checkout-to-location-or-asset
Added #14979: add checkout to location and assets functionality to accessories
2024-07-29 18:50:48 +01:00
Godfrey M
86049624c7 retargeted new label engine to correct 1d column 2024-07-29 10:42:26 -07:00
snipe
3972782033 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2024-07-29 18:27:56 +01:00
snipe
33b86057d1 Version bump
Signed-off-by: snipe <snipe@snipe.net>
2024-07-29 18:27:01 +01:00
arne-kroeger
3c3b922eae Adjusted newly added tests to new checkout form 2024-07-29 19:15:01 +02:00
arne-kroeger
e8d0147075 Adjusted missing down on migration and code smells 2024-07-29 19:04:10 +02:00
snipe
c2bcc2e2d9 Merge remote-tracking branch 'origin/develop' 2024-07-29 14:39:13 +01:00
snipe
55f9886412 Merge pull request #15187 from snipe/feature/sc-26458
Use the `pwd_secure_min` value (plus 5) for generated password
2024-07-29 14:38:39 +01:00
snipe
a77ece01a6 Fixed test name
Signed-off-by: snipe <snipe@snipe.net>
2024-07-29 14:34:10 +01:00
snipe
1c1101aeac Use the pwd_secure_min value (plus 5) for generated password
Signed-off-by: snipe <snipe@snipe.net>
2024-07-29 14:26:18 +01:00
snipe
de04ead55d Merge remote-tracking branch 'origin/develop' 2024-07-29 10:11:39 +01:00
snipe
bc18fac97e Fixed missing div
Signed-off-by: snipe <snipe@snipe.net>
2024-07-29 10:11:11 +01:00
arne-kroeger
3a0b03348e added additional tests 2024-07-29 11:06:36 +02:00
arne-kroeger
b18baf74d2 added options to checkout accessoires to locations and assets
Added #14979: add checkout functionality to accessoires
2024-07-29 10:54:53 +02:00
snipe
28c7355697 Fixed missing div
Signed-off-by: snipe <snipe@snipe.net>
2024-07-29 09:52:38 +01:00
snipe
0dd0baa511 Merge remote-tracking branch 'origin/develop' 2024-07-27 10:12:23 +01:00
snipe
c3a296c19f Added qr code back
Signed-off-by: snipe <snipe@snipe.net>
2024-07-27 10:12:13 +01:00
snipe
f6cc923e01 Merge remote-tracking branch 'origin/develop' 2024-07-26 17:29:46 +01:00
snipe
aab29fbb6b Use translated string for restore
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 16:47:40 +01:00
snipe
d9d3ff27ae Merge remote-tracking branch 'origin/develop' 2024-07-26 15:31:52 +01:00
snipe
614d05acb5 Merge pull request #15170 from uberbrady/simplify_css_skin_building
Dynamically iterate through the skin listing to build skins
2024-07-26 15:14:24 +01:00
snipe
e4a82edd3f Merge pull request #15172 from snipe/features/blade_component_for_submit
Use blade component for submit redirect on asset edit/create
2024-07-26 15:13:30 +01:00
snipe
eceaa72781 And more tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 15:07:43 +01:00
snipe
ddf45c5ee9 Renamed test
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 15:00:16 +01:00
snipe
ff100c0b9f Added more tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 14:58:10 +01:00
snipe
037cc4d098 More tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 14:27:07 +01:00
snipe
ff6e6ef88c Added more tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 13:43:10 +01:00
snipe
e8ec11652f Very basic checkin tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 13:16:31 +01:00
snipe
cd7f276c40 Fixed logging for component
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 13:09:21 +01:00
snipe
fc8bb82a02 Made dropdown wider
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 12:48:18 +01:00
snipe
5d7f1f77a3 Added redirect to checkin/checkout controllers
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 12:48:07 +01:00
snipe
5dea3f4495 Added blade component to checkin/checkout pages
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 12:47:33 +01:00
snipe
c6e709cd36 Added parameters
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 12:45:06 +01:00
Brady Wetherington
daf5a80081 Use a similar loop for the minification step for CSS 2024-07-26 12:03:03 +01:00
snipe
759ab78f80 Added more redirects
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 11:27:36 +01:00
snipe
b6d9f736e3 Pulled use session
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 10:41:28 +01:00
snipe
243ab51def Fixed back button
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 10:40:48 +01:00
snipe
c5603a0e10 Merge remote-tracking branch 'origin/develop' 2024-07-26 10:26:23 +01:00
snipe
57f6ecb1da Added created_by to license view
Signed-off-by: snipe <snipe@snipe.net>
2024-07-26 10:24:13 +01:00
snipe
64082ada1e Updated test
Signed-off-by: snipe <snipe@snipe.net>
2024-07-25 22:08:51 +01:00
snipe
068535a80c Removed logging
Signed-off-by: snipe <snipe@snipe.net>
2024-07-25 22:08:46 +01:00
snipe
08f4fe5f35 Add status messages to redirect
Signed-off-by: snipe <snipe@snipe.net>
2024-07-25 22:02:34 +01:00
snipe
018b5684fc Refactor helper method for redirection
Signed-off-by: snipe <snipe@snipe.net>
2024-07-25 22:02:03 +01:00
snipe
73a80a5fbc Use blade component for redirect option
Signed-off-by: snipe <snipe@snipe.net>
2024-07-25 21:35:13 +01:00
Brady Wetherington
4c8bf9ae19 Dynamically iterate through the skin listing to build skins 2024-07-25 21:20:06 +01:00
snipe
f77d300549 Merge pull request #15169 from uberbrady/create_directories_when_needed_on_restore
Create intermediate directories on restore if needed - Fixes [SC-25950]
2024-07-25 18:45:54 +01:00
snipe
62655be2d0 Fixed BYOD label
Signed-off-by: snipe <snipe@snipe.net>
2024-07-25 18:41:37 +01:00
Brady Wetherington
eb938bdba3 Create intermediate directories on restore if needed 2024-07-25 18:40:26 +01:00
snipe
129d3b35fb Merge pull request #15168 from uberbrady/improve_restore_sanitization
Improve restore sanitization - Fixes [sc-24840]
2024-07-25 18:20:05 +01:00
Brady Wetherington
84df23e1f6 Better handle older SQL dumps that got created in Windows format 2024-07-25 18:07:25 +01:00
snipe
a439d8abe8 Use fully qualified use statements
Signed-off-by: snipe <snipe@snipe.net>
2024-07-25 16:08:46 +01:00
snipe
24b7659c23 Merge pull request #15150 from marcusmoore/chore/sc-26113
Registered custom anonymous blade component directory
2024-07-25 16:08:01 +01:00
Marcus Moore
44dbbeb608 Add accessory and consumable specific checkin methods 2024-07-24 14:17:49 -07:00
Marcus Moore
7161b6416e Add failing test for accessories and consumables checkin 2024-07-24 14:16:42 -07:00
snipe
1331bffaca Merge remote-tracking branch 'origin/develop' 2024-07-24 21:42:41 +01:00
snipe
9b422e5c97 Baremetrics BMPay breaks with CSP turned [sc-25011]
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 21:42:20 +01:00
snipe
0c86855392 Merge remote-tracking branch 'origin/develop' 2024-07-24 21:04:20 +01:00
snipe
962ae5231d Merge pull request #15162 from snipe/fixes/added_location_assets_endpoint
Added assets endpoint for locations
2024-07-24 21:03:43 +01:00
snipe
590d13061a One more test
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 20:57:26 +01:00
snipe
c3a2cdeee9 Added assets endpoint for locations
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 20:53:58 +01:00
snipe
ef77fb91c0 Merge remote-tracking branch 'origin/develop' 2024-07-24 20:20:37 +01:00
snipe
02bd8d7ea1 Merge pull request #15161 from snipe/fixes/500_when_depreciation_is_active_but_no_purchase_date
Fixes 500 when depreciation is active but no purchase date
2024-07-24 20:20:10 +01:00
snipe
da4ec145d7 Removed test no longer needed due to validation
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 20:17:30 +01:00
snipe
ef145e47b4 Added tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 20:13:15 +01:00
snipe
adf58a06da Added check for purchase date
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 20:13:11 +01:00
snipe
63f0b5279d Added check for purchase_date
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 20:13:01 +01:00
Godfrey Martinez
c96ccbb6cb Merge branch 'develop' into depreciation_percentage 2024-07-24 12:12:28 -07:00
Godfrey M
950fff31ed fix conflicts 2024-07-24 12:12:04 -07:00
Godfrey M
f05d8281f3 fixes alignment for error msg 2024-07-24 12:06:56 -07:00
Godfrey M
d5c9fa823e adds translations 2024-07-24 11:39:33 -07:00
Godfrey M
99741db645 adds status labels to confirmation emails 2024-07-24 11:24:30 -07:00
snipe
90d5a6d8ab Merge remote-tracking branch 'origin/develop' 2024-07-24 19:16:56 +01:00
snipe
5b8529bc83 Fixed pluralization
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 19:16:08 +01:00
snipe
f76c68f0ed Merge remote-tracking branch 'origin/develop' 2024-07-24 18:10:23 +01:00
snipe
8368fb5c41 Merge pull request #15160 from snipe/fixes/allow_cloning_of_deleted_assets
Allow cloning of deleted assets
2024-07-24 18:09:55 +01:00
snipe
d1c39a737f Fixed double semicolon
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 18:09:32 +01:00
snipe
425bfa4318 Added test
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 18:06:21 +01:00
snipe
293abbd1d0 Allow cloning of deleted assets
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 17:49:38 +01:00
snipe
b64b774bd5 Use route model binding on clone, removed unneeded route
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 17:49:21 +01:00
snipe
466ab1e3c0 Removed the banner since we already warn
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 17:40:24 +01:00
snipe
daa9e7810e Merge pull request #15158 from snipe/fixes/ui_cleanup_for_asset_view
Cleaned up UI on asset view
2024-07-24 17:34:17 +01:00
snipe
95a0f3dbe5 Cleaned up UI on asset view
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 17:26:55 +01:00
Brady Wetherington
bb465dbfaa Cut down run number to 100 from 1000 2024-07-24 15:09:23 +01:00
snipe
e5ab12496f Merge pull request #15151 from snipe/snyk-fix-067da5564c4d29b025cd356b125e6ad9
[Snyk] Security upgrade alpine from 3.18.6 to 3.19
2024-07-24 14:22:57 +01:00
snipe
4b906e39be Merge pull request #15155 from snipe/fix_new_alpine
Switch dockerfile to using php8.2 instead of 8.1 due to Alpine changes
2024-07-24 14:00:21 +01:00
Brady Wetherington
0dc8f27240 Switch dockerfile to using php8.2 instead of 8.1 due to Alpine changes 2024-07-24 13:56:27 +01:00
Brady Wetherington
8cbcc237c0 Cleanup of Stale workflow for GH - set to debug-mode to start 2024-07-24 13:27:31 +01:00
snipe
c4135c71d4 Merge remote-tracking branch 'origin/develop' 2024-07-24 13:05:16 +01:00
snipe
172da4d655 Merge pull request #15154 from snipe/fixes/15103_show_eol_to_users
Fixes #15103 - Added EOL and audit into to user profile assets
2024-07-24 13:03:50 +01:00
snipe
6f8ccd4e10 Fixes #15103 - Added EOL and audit into to user profile assets
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 12:54:33 +01:00
snipe
8d36a75342 Merge remote-tracking branch 'origin/develop' 2024-07-24 12:41:20 +01:00
snipe
9457e5b890 Merge pull request #15153 from snipe/fixes/mail_backup_config
Fixed env var for backup config - related to #14964
2024-07-24 12:40:46 +01:00
snipe
bce7a278ae Fixed env var for backup config - related to #14964
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 12:38:58 +01:00
Brady Wetherington
97e3af8fc9 WIP on new GitHub-backed stalebot settings 2024-07-24 12:33:26 +01:00
Brady Wetherington
914e29210a Initial rough stab at re-implementing stalebot 2024-07-24 12:33:26 +01:00
snipe
a6041ae525 Merge remote-tracking branch 'origin/develop' 2024-07-24 10:41:36 +01:00
snipe
ab1e097b7c Early return for requiredness
Signed-off-by: snipe <snipe@snipe.net>
2024-07-24 10:41:14 +01:00
snipe
a781f84aea Merge remote-tracking branch 'origin/develop' 2024-07-24 10:36:23 +01:00
snipe
90a2a808d6 Merge pull request #15149 from spencerrlongg/bug/check_requiredness
Fixed: Requiredness Check Bug
2024-07-24 10:33:45 +01:00
snipe
73e2d8e305 Merge remote-tracking branch 'origin/develop' 2024-07-24 10:32:54 +01:00
snyk-bot
0e1e13f54a fix: Dockerfile.alpine to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-6913411
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249236
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249265
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249419
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249419
2024-07-24 09:11:01 +00:00
Marcus Moore
869c06f454 Register anonymous blade component namespace 2024-07-23 15:41:58 -07:00
spencerrlongg
b61ab423ca ok NOW it should be fine 2024-07-23 17:35:48 -05:00
spencerrlongg
090a595c99 conflict 2024-07-23 17:34:17 -05:00
Spencer Long
bc417308c7 Merge branch 'develop' into bug/check_requiredness 2024-07-23 17:32:43 -05:00
spencerrlongg
e77e16b9aa this works 2024-07-23 17:22:20 -05:00
spencerrlongg
0ae297634e ugh 2024-07-23 16:59:58 -05:00
spencerrlongg
0f2f559d7a test works, helper still needs work. pushing to check something 2024-07-23 16:46:29 -05:00
snipe
57b5b12952 Merge pull request #15148 from marcusmoore/fixes/dates-in-user-import
Fixed start_date and end_date in user importer
2024-07-23 22:37:19 +01:00
snipe
9baaaae1a3 Merge remote-tracking branch 'origin/develop' 2024-07-23 22:15:57 +01:00
snipe
1f4118a146 Fixed rule
Signed-off-by: snipe <snipe@snipe.net>
2024-07-23 22:15:37 +01:00
Marcus Moore
f4a3823d88 Handle empty strings for dates by converting them to null 2024-07-23 13:57:28 -07:00
snipe
b2a69efc9d Merge remote-tracking branch 'origin/develop' 2024-07-23 21:42:12 +01:00
snipe
00ebc8b64d Merge pull request #15136 from snipe/fixes/cookie_serialization
Remove cookie serialization
2024-07-23 21:41:44 +01:00
snipe
effd273245 Merge pull request #14458 from spencerrlongg/bug/sc-24884
Add Form Request and Tests for Update Asset API Method
2024-07-23 20:57:47 +01:00
snipe
7858c72b98 Fixed string
Signed-off-by: snipe <snipe@snipe.net>
2024-07-23 20:45:12 +01:00
snipe
09e2c0beab Merge pull request #15147 from uberbrady/fix_setup_ssl_check
Fix setup ssl check
2024-07-23 20:32:13 +01:00
spencerrlongg
e8864ffb01 test added, permission fixed 2024-07-23 14:29:17 -05:00
Brady Wetherington
d893de2b7e Merge branch 'develop' into fix_setup_ssl_check 2024-07-23 20:24:38 +01:00
Brady Wetherington
7f7cfef81b Mark test as Incomplete, and downgrade error back to debug for tests 2024-07-23 20:24:05 +01:00
Godfrey M
d01972bbe5 adds tests for amount and percent 2024-07-23 11:53:59 -07:00
spencerrlongg
b9fdb5880a quick push 2024-07-23 13:48:43 -05:00
spencerrlongg
1139ed676a $id to $asset->id (thanks tests) 2024-07-23 13:03:25 -05:00
Spencer Long
64be353156 Merge branch 'develop' into bug/sc-24884 2024-07-23 12:55:19 -05:00
Godfrey M
750015684d purges user storage files 2024-07-23 10:42:50 -07:00
snipe
992d16664d Merge remote-tracking branch 'origin/develop' 2024-07-23 18:26:54 +01:00
snipe
900c19b76d Merge pull request #15143 from snipe/fixes/no-NO-language
Switch to nb-NO from no-NO for Norwegian
2024-07-23 18:25:27 +01:00
snipe
effd2bce24 Fixed test to use nb-NO
Signed-off-by: snipe <snipe@snipe.net>
2024-07-23 18:22:13 +01:00
snipe
de8816d837 Merge pull request #15144 from snipe/localization/updated_strings_2024_07_23
Updated translations
2024-07-23 18:17:45 +01:00
snipe
593554daed Updated translations
Signed-off-by: snipe <snipe@snipe.net>
2024-07-23 18:14:19 +01:00
snipe
fecd877e8b Migration
Signed-off-by: snipe <snipe@snipe.net>
2024-07-23 17:35:06 +01:00
snipe
ba6d8ae8c7 Switch to nb-NO from no-NO
Signed-off-by: snipe <snipe@snipe.net>
2024-07-23 17:15:44 +01:00
snipe
175862d3c9 Added route param to message
Signed-off-by: snipe <snipe@snipe.net>
2024-07-23 16:37:01 +01:00
Brady Wetherington
5eea08088d Improve .env file checking to not validate SSL certificates 2024-07-23 16:03:02 +01:00
snipe
c752c2a125 Make the passport cookie name configurable
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 14:22:19 +01:00
snipe
4ac4f9b0a9 Customize cookie name
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 14:17:16 +01:00
snipe
09abcb44bb Remove cookie serialization
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 13:49:47 +01:00
snipe
7f566b9152 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/all.css
#	public/css/dist/bootstrap-table.css
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2024-07-22 10:56:37 +01:00
snipe
30835fe9ba Merge pull request #15134 from snipe/upgrade_bs_table
[Snyk] Upgrade bootstrap-table from 1.22.5 to 1.23.0 #15131
2024-07-22 10:50:53 +01:00
snipe
910e98b573 [Snyk] Upgrade bootstrap-table from 1.22.5 to 1.23.0 #15131
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 10:45:35 +01:00
snipe
4623e40077 Merge pull request #15133 from snipe/fixes/load_english_separately_for_table_locale
Load the english file again in case BS table doesn’t have a translation
2024-07-22 10:40:01 +01:00
snipe
20868b9ede Load the english file again in case BS table doesn’t have a translation
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 10:30:42 +01:00
snipe
efc84efabf Merge pull request #15132 from snipe/fixes/disable_remote_login_force_via_env
Hides the “Disable Other Login Mechanisms” option via env
2024-07-22 10:20:34 +01:00
snipe
7c7fa96334 Pulled from the app.php as well
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 10:19:55 +01:00
snipe
e08acb851c Removed env part, just check for the remote user login
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 10:18:33 +01:00
snipe
bbad84c6cc Hides the “Disable Other Login Mechanisms” option via env
Signed-off-by: snipe <snipe@snipe.net>
2024-07-22 10:13:59 +01:00
snipe
f2acb98afa Merge pull request #15128 from snipe/fixes/nicer_consumables_layout
Nicer consumables layout
2024-07-20 17:01:15 +01:00
snipe
38affea3f3 More updates
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 16:57:10 +01:00
snipe
cea31c5b11 Added more generic buttons with variables
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 16:53:01 +01:00
snipe
254e2f120b Added clone to consumables
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 16:40:32 +01:00
snipe
2bfee0c29a Few more tweaks
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 16:21:11 +01:00
snipe
c08bd8f88b Nicer consumables layout
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 16:11:45 +01:00
snipe
9818c2f033 Merge remote-tracking branch 'origin/develop' 2024-07-20 08:08:51 +01:00
snipe
f63fd25ce0 Fixed default
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 08:08:29 +01:00
snipe
57ecdd6765 Merge remote-tracking branch 'origin/develop' 2024-07-20 07:43:37 +01:00
snipe
9d890b35c6 Merge pull request #15127 from snipe/fixes/small_default_avatar_tweaks
Fixes #15076 - Removes ability to remove the default avatar from disk
2024-07-20 07:42:39 +01:00
snipe
8fc5c0b5be Use other default avatar, not in the avatars directory
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 07:34:40 +01:00
snipe
c4e7448d31 Updated strings
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 07:25:56 +01:00
snipe
ffd2687734 Updated language
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 07:25:46 +01:00
snipe
6f3eedf5e6 Removed restoreDefaultAvatar method
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 07:25:27 +01:00
snipe
241eb7b031 One more bad sig
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 06:06:27 +01:00
snipe
ca399794c3 Fixed incorrect signature
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 06:05:01 +01:00
snipe
736d4cc59a Use new deleting method in ImageUploadRequest
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 05:55:34 +01:00
snipe
ad85f8be2f Cleaned up
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 05:55:12 +01:00
snipe
be17ef4047 Added option to restore from default demo
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 05:55:06 +01:00
snipe
c6c006f143 Added restore from demo method
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 05:54:40 +01:00
snipe
10856516ac Split out the image deletion into a separate method
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 03:40:16 +01:00
snipe
beac4c8b8a Updated tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 03:39:54 +01:00
snipe
b39b39bc8a Ignore the test uploads directory
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 03:39:05 +01:00
snipe
e373be7dde Skip deleting default avatar from disk on delete
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 01:01:27 +01:00
snipe
ca7c0aa47c Added default avatar to seeder
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 00:59:25 +01:00
snipe
48a8cf6b70 Removed unused default avatar
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 00:58:57 +01:00
snipe
7ba1646703 Remove unusued default avatar
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 00:58:42 +01:00
snipe
daa550cc82 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2024-07-20 00:31:08 +01:00
snipe
e9f9d3c259 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 00:30:03 +01:00
snipe
63377853b8 Merge pull request #15125 from snipe/fixes/display_avatar_on_user_edit
Show existing images on user edit page
2024-07-20 00:29:28 +01:00
snipe
fc86eefeac Show existing images on user edit page
Signed-off-by: snipe <snipe@snipe.net>
2024-07-20 00:27:28 +01:00
snipe
a98ad76c6a Merge remote-tracking branch 'origin/develop' 2024-07-19 22:55:09 +01:00
snipe
32c768792a Merge pull request #15124 from snipe/fixes/path_for_crypt_in_importer
Fixed use statement for Crypt in the importer, removed unused statements
2024-07-19 22:54:18 +01:00
snipe
793cf27318 Fixed use statement
Signed-off-by: snipe <snipe@snipe.net>
2024-07-19 22:50:24 +01:00
snipe
ba568b7975 Merge pull request #15123 from snipe/tests/added_bulk_delete_assets
Added tests for bulk asset deletion and restore
2024-07-19 22:36:09 +01:00
snipe
4b5bd76225 Fixed notification use statement
Signed-off-by: snipe <snipe@snipe.net>
2024-07-19 22:27:03 +01:00
snipe
a54403ef01 Added bulk asset delete test
Signed-off-by: snipe <snipe@snipe.net>
2024-07-19 22:26:52 +01:00
snipe
13262c5125 Merge remote-tracking branch 'origin/develop' 2024-07-19 20:05:47 +01:00
snipe
9ea38fbb3a Merge pull request #15122 from snipe/fixes/15121_bulk_delete_and_restore
Fixed #15121 - bulk delete restore logging
2024-07-19 20:03:32 +01:00
snipe
94619e3284 Fixed #15121 - bulk delete restore logging
Signed-off-by: snipe <snipe@snipe.net>
2024-07-19 19:56:58 +01:00
snipe
8b05ef6db4 Updated url checks
Signed-off-by: snipe <snipe@snipe.net>
2024-07-19 19:48:03 +01:00
snipe
8e03083b83 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2024-07-19 19:07:53 +01:00
snipe
dc2debcd83 Add @DrekiDegga as a contributor 2024-07-19 19:07:41 +01:00
snipe
b7ab8b9e8f Merge pull request #14530 from DrekiDegga/develop
Added #14426: Makes all Manufacturer links dynamic, not just warranty lookup
2024-07-19 19:07:25 +01:00
snipe
c50ab1af67 Merge branch 'develop' into develop 2024-07-19 19:00:56 +01:00
snipe
7773d334ba Merge pull request #15120 from snipe/updated_localizations
Updated strings
2024-07-19 17:54:30 +01:00
snipe
2a0697022e Updated strings
Signed-off-by: snipe <snipe@snipe.net>
2024-07-19 17:47:22 +01:00
Marcus Moore
8d1cc22c58 Turn on legacy binding since other components still use it 2024-07-18 12:00:01 -07:00
snipe
e1eb963962 Updated prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 19:11:51 +01:00
snipe
87c2dc5bb5 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/build/app.js
#	public/js/dist/all.js
#	public/mix-manifest.json
2024-07-18 19:11:41 +01:00
snipe
06efcd3c46 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2024-07-18 18:55:18 +01:00
snipe
faa791e94c Merge pull request #15101 from snipe/localizations/more_strings
More localizations
2024-07-18 18:51:35 +01:00
snipe
e328dec9f9 Merge branch 'develop' into localizations/more_strings 2024-07-18 18:51:17 +01:00
snipe
8e60a7b22b Merge pull request #15110 from marcusmoore/fixes/test-namespace-fixes
Fixed a coupled test namespaces
2024-07-18 18:50:27 +01:00
snipe
958b6035e1 Add @r-xyz as a contributor 2024-07-18 18:49:59 +01:00
snipe
94e0739a74 Merge pull request #15115 from r-xyz/alpine-upload-limit
Improved Docker environment settings: implement `PHP_UPLOAD_LIMIT` for Alpine images
2024-07-18 18:49:00 +01:00
snipe
4465aef991 Merge pull request #15114 from snipe/checkout_multiple_accessories
Checkout multiple of an accessory in one checkout
2024-07-18 17:38:19 +01:00
snipe
822bc6f085 Removed response in form request
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 17:37:45 +01:00
snipe
19bd99d159 Updated count
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 17:33:24 +01:00
r-xyz
5c17fefb08 Implement PHP_UPLOAD_LIMIT in Alpine images. 2024-07-18 11:54:58 +02:00
snipe
f984b45de2 Merge branch 'develop' into checkout_multiple_accessories 2024-07-18 05:11:51 +01:00
snipe
d56252c6b3 Added more back :(
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 05:10:45 +01:00
snipe
44b950cb8e Added back in missing validation
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 05:07:50 +01:00
snipe
985714d504 Test passing now - I hope
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 05:04:17 +01:00
snipe
97ead7120e Use from routes for posting
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 04:54:07 +01:00
snipe
670021a482 Apply the optimize fix
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 04:43:46 +01:00
snipe
9858cc5baf Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 04:43:30 +01:00
snipe
fa5b59cf21 Added qty
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 04:29:49 +01:00
snipe
0ef58a9aef Added translation
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 03:48:42 +01:00
snipe
0c4e498df3 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 03:48:35 +01:00
snipe
b5b60f22d5 Removed int
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 03:48:21 +01:00
snipe
2f0c74aef0 Updated tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 03:48:06 +01:00
snipe
f56006fb6b More refactoring
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 01:46:53 +01:00
Marcus Moore
7685de45f2 Turn off legacy binding 2024-07-17 17:12:14 -07:00
Marcus Moore
199e68ff29 Simplify computed propery 2024-07-17 17:10:12 -07:00
Marcus Moore
81bffccf01 Use better error message 2024-07-17 16:28:26 -07:00
snipe
79a13e3618 Added numCheckedOut method
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:26:58 +01:00
snipe
d9b7df5b85 Added form requests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:26:42 +01:00
snipe
0c933bcc5d Cleaned up controllers, use form requests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:25:42 +01:00
snipe
4c4b0f722a Added qty to email notification
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:25:29 +01:00
snipe
6c3cafa72f Added tests
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:25:07 +01:00
snipe
16ae23dbeb Updated validation strings
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:25:00 +01:00
snipe
4f3064bdb1 Added store accesstory form request
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:24:51 +01:00
snipe
2b0627c1f6 Added accessory checkout request
Signed-off-by: snipe <snipe@snipe.net>
2024-07-18 00:24:28 +01:00
Marcus Moore
cfca1514c0 Swap file_id for activeFileId 2024-07-17 16:22:44 -07:00
snipe
345a4306e8 Added SubstituteBindings
Signed-off-by: snipe <snipe@snipe.net>
2024-07-17 23:02:10 +01:00
snipe
beb0836d69 Updated route
Signed-off-by: snipe <snipe@snipe.net>
2024-07-17 23:01:44 +01:00
Marcus Moore
b4ed01243b Add more details 2024-07-17 13:40:41 -07:00
Marcus Moore
3772a21a51 Move comment up 2024-07-17 13:37:24 -07:00
snipe
05e278d08b Added qty to email
Signed-off-by: snipe <snipe@snipe.net>
2024-07-17 20:58:21 +01:00
snipe
c21821a864 Added new form fields
Signed-off-by: snipe <snipe@snipe.net>
2024-07-17 20:58:12 +01:00
Marcus Moore
ba13b9924b Fix test namespaces 2024-07-17 11:04:35 -07:00
Marcus Moore
04b6cb763f Add todo 2024-07-17 10:58:48 -07:00
snipe
2f8306cba8 Merge remote-tracking branch 'origin/develop' 2024-07-17 16:27:16 +01:00
Godfrey M
baa7e7d561 clean up 2024-07-16 20:27:38 -07:00
Godfrey M
ffaacc04ef cleaned up calculateDepreciation method 2024-07-16 20:24:18 -07:00
Marcus Moore
dd32341502 Display message if attempting to delete non-existent file 2024-07-16 17:21:18 -07:00
Marcus Moore
f58e3114a2 Simplify destroy method and update list 2024-07-16 17:17:45 -07:00
Marcus Moore
eba494ad8c Make $activeFile a computed property 2024-07-16 17:03:42 -07:00
Marcus Moore
7e89b58746 Move files to computed property 2024-07-16 16:08:42 -07:00
Marcus Moore
b7744105a0 Migrate import type to component 2024-07-16 14:20:41 -07:00
snipe
d5e1dc54c6 Use translated strings for action log meta
This is only partially complete and I mostly hate it

Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 21:35:45 +01:00
Marcus Moore
69263f0e5b Migrate header row to component 2024-07-16 13:30:29 -07:00
snipe
a3b2912e89 Updated string
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 21:16:23 +01:00
snipe
7f412ec3f5 Missing string
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 21:14:41 +01:00
snipe
94e881e5f0 Cleared up language
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 21:11:03 +01:00
Godfrey M
5bb47e290f validates percentage on store and new depreciations 2024-07-16 13:07:12 -07:00
snipe
d53a032402 More consistent UI for indented checkboxes
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 21:07:03 +01:00
snipe
70d95877d8 Translations for license checkout errors
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 21:02:58 +01:00
Godfrey M
48821f8391 updates transformer, api controller 2024-07-16 12:58:23 -07:00
snipe
bb00edda4e Added basic jquery validation translations
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 20:54:48 +01:00
Godfrey M
aaa2858337 battling with handling depreciation percentage and amount 2024-07-16 12:25:19 -07:00
Marcus Moore
017530ba4b Make updating hook more specific 2024-07-16 12:02:50 -07:00
snipe
4895023f3b Translate import button
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 19:56:02 +01:00
snipe
0ed637f0ce Translate importer header
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 19:41:47 +01:00
snipe
45bf428092 Translated “seat” for license seat view
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 19:04:51 +01:00
snipe
e6f2c457a8 Handle localization better, clearer indenting on addresses
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 19:01:50 +01:00
snipe
04f723327e Fixed translation
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 19:01:23 +01:00
snipe
4a9bd95ed3 Added language support for select2
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 18:40:10 +01:00
snipe
78bd950daa Added select2 language files
Signed-off-by: snipe <snipe@snipe.net>
2024-07-16 18:39:42 +01:00
snipe
d3070a0be1 Merge remote-tracking branch 'origin/develop' 2024-07-16 17:24:16 +01:00
snipe
0a70bcc0a9 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	.all-contributorsrc
#	CONTRIBUTORS.md
2024-07-16 16:12:26 +01:00
Marcus Moore
c8dad528a8 Migrate a couple items out of mount 2024-07-15 16:15:58 -07:00
Marcus Moore
256e989ba1 Add test for importer 2024-07-15 15:49:18 -07:00
snipe
39810f9ba5 Merge remote-tracking branch 'origin/develop' 2024-07-12 11:05:10 +01:00
Marcus Moore
9793016603 Remove unneeded AuthorizesRequests 2024-07-11 13:32:16 -07:00
Marcus Moore
9e06f2d17f Remove commented code 2024-07-11 13:22:10 -07:00
snipe
7464c3622e Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2024-07-11 19:07:54 +01:00
Godfrey M
d4d19569ee adds execution timer 2024-07-11 10:52:47 -07:00
snipe
1a541ce220 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	.all-contributorsrc
#	CONTRIBUTORS.md
2024-07-11 18:38:57 +01:00
Godfrey M
43c7504f89 adds an all option, adds help context for all, seperates logic 2024-07-11 10:03:51 -07:00
snipe
c1937b6ea8 Merge remote-tracking branch 'origin/develop' 2024-07-11 12:01:28 +01:00
snipe
7584c30722 Merge remote-tracking branch 'origin/develop' 2024-07-11 12:00:25 +01:00
snipe
62f4b75156 Merge remote-tracking branch 'origin/develop' 2024-07-11 10:08:25 +01:00
Godfrey M
1fa6a763bc updated description 2024-07-10 11:09:57 -07:00
Godfrey M
726308bfd5 removed typo 2024-07-10 10:58:45 -07:00
Godfrey M
22ddb695f2 better description 2024-07-10 10:57:47 -07:00
Godfrey M
09b2feac54 updates assets with selected model to inherit asset model eol 2024-07-10 10:56:44 -07:00
Godfrey M
57e1df86c8 finds model and collects assets with such model 2024-07-10 10:21:24 -07:00
snipe
3d5b1b6c6d Merge remote-tracking branch 'origin/develop' 2024-07-10 17:09:56 +01:00
snipe
5f4ee39f2b Merge remote-tracking branch 'origin/develop' 2024-07-10 16:13:00 +01:00
snipe
45d7e2dc42 Merge remote-tracking branch 'origin/develop' 2024-07-10 08:56:29 +01:00
snipe
18f7ca860d Merge remote-tracking branch 'origin/develop' 2024-07-09 19:52:20 +01:00
snipe
ea881ff95e Merge remote-tracking branch 'origin/develop' 2024-07-09 13:42:03 +01:00
snipe
c1916d6fe4 Merge remote-tracking branch 'origin/develop' 2024-07-09 13:39:07 +01:00
snipe
da9932d147 Merge remote-tracking branch 'origin/develop' 2024-07-09 13:36:06 +01:00
snipe
da42127a7c Merge remote-tracking branch 'origin/develop' 2024-07-09 11:54:17 +01:00
snipe
090410edb5 Merge remote-tracking branch 'origin/develop' 2024-07-09 11:48:30 +01:00
snipe
43d4bc8dc4 Merge remote-tracking branch 'origin/develop' 2024-07-08 22:13:19 +01:00
snipe
ebcc48ec80 Merge remote-tracking branch 'origin/develop' 2024-07-08 22:02:25 +01:00
snipe
5ca62f3ef8 Merge remote-tracking branch 'origin/develop' 2024-07-08 21:39:31 +01:00
snipe
3ae16ff8ca Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
#	public/js/build/vendor.js
#	public/js/dist/all.js
#	public/mix-manifest.json
2024-07-08 15:53:42 +01:00
snipe
2e2f59b290 Updated production assets
Signed-off-by: snipe <snipe@snipe.net>
2024-07-08 15:29:36 +01:00
snipe
c16db436d3 Merge remote-tracking branch 'origin/develop' 2024-07-08 13:21:38 +01:00
snipe
8eb68d3901 Merge remote-tracking branch 'origin/develop' 2024-07-04 17:10:43 +01:00
snipe
fc7809192d Merge remote-tracking branch 'origin/develop' 2024-07-03 23:20:24 +01:00
spencerrlongg
e79111fed5 fix test :( 2024-07-02 16:01:18 -05:00
spencerrlongg
423b48279b merge 2024-07-02 15:53:28 -05:00
spencerrlongg
6f29c0a7cf Merge branch 'refs/heads/develop' into bug/sc-24884
# Conflicts:
#	routes/api.php
2024-07-02 15:48:39 -05:00
snipe
27bc16604b Add @Galaxy102 as a contributor 2024-07-02 09:48:32 +01:00
snipe
86ae4a3513 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2024-07-02 09:37:40 +01:00
spencerrlongg
0b60cbc531 remove $rules2 2024-07-01 19:37:49 -05:00
snipe
e502968707 Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-07-01 10:00:28 +01:00
snipe
12dcac4994 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/all.css
#	public/css/dist/bootstrap-table.css
#	public/js/build/app.js
#	public/js/dist/all.js
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2024-07-01 10:00:15 +01:00
snipe
1837da4508 Merge branch 'develop' 2024-06-26 13:46:40 +01:00
snipe
e1a6b441d7 Merge remote-tracking branch 'origin/develop' 2024-06-26 12:53:18 +01:00
snipe
23c1d664fe Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-06-24 22:07:01 +01:00
snipe
c891342fdb Bumoed version
Signed-off-by: snipe <snipe@snipe.net>
2024-06-24 22:06:52 +01:00
spencerrlongg
8ce577db37 adds @snipe's rules for undeleted assigned targets 2024-05-23 15:51:26 -05:00
Spencer Long
cdb1140f10 Merge branch 'develop' into bug/sc-24884 2024-05-23 13:53:00 -05:00
Godfrey M
5fa0c87ab0 null debugbar 2024-04-30 12:02:11 -07:00
Godfrey M
75aa01791a adds location select to the create new user vice asset checkout 2024-04-30 11:54:33 -07:00
spencerrlongg
97a6152ea9 update comment per @uberbrady 2024-04-25 17:47:55 -05:00
spencerrlongg
03f091a77f ammended note, got real rule in there 2024-04-25 17:17:42 -05:00
spencerrlongg
8696a423b0 another option 2024-04-23 17:38:40 -05:00
spencerrlongg
107f8db9bc another option 2024-04-23 17:35:33 -05:00
spencerrlongg
b11c900a4c fix bracket, + overwrite required rulesets 2024-04-23 16:29:07 -05:00
Spencer Long
53ccd196d7 Merge branch 'develop' into bug/sc-24884 2024-04-23 13:57:59 -05:00
spencerrlongg
99d7155729 translation strings 2024-04-07 19:50:53 -05:00
spencerrlongg
f30439a544 small refactor, pretty much good to go now though 2024-04-07 17:07:46 -05:00
spencerrlongg
997eddfed1 cleanup + notes for monday 2024-04-04 18:23:03 -05:00
spencerrlongg
52340aca78 just about wrapped up 2024-04-04 17:41:10 -05:00
spencerrlongg
e1fb446888 this is a pretty good start, need to know about other PR 2024-04-04 14:20:03 -05:00
steve@degga.net
83443ad2b5 Sometimes I should just slow down. 2024-04-04 11:36:21 -04:00
steve@degga.net
3fcc067481 Removes accidental changes on two lines 2024-04-04 10:27:44 -04:00
steve@degga.net
3e564eaf19 fixed accidentally forgot update a call to the old dynamicWarrantyUrl() method in hardware/view.blade.php 2024-04-03 20:09:37 -04:00
spencerrlongg
0a90df2b14 alright conflicts resolved 2024-04-03 14:40:14 -05:00
Spencer Long
4ab75c1c03 Merge branch 'develop' into bug/sc-24884 2024-04-03 14:08:41 -05:00
steve@degga.net
bf10fd0cf0 Rename dynamicUrl() method 2024-03-31 22:37:41 -04:00
steve@degga.net
83ce04dc8d Fixes syntax error. 2024-03-31 22:31:06 -04:00
steve@degga.net
f7bbec6be4 Makes all manufacturer URLs dynamic, not just warranty lookup. 2024-03-31 22:18:44 -04:00
spencerrlongg
cec84b857b fixed last audit date + test 2024-03-27 14:39:23 -05:00
spencerrlongg
1d4a7a7b02 added audit dates 2024-03-27 14:05:30 -05:00
spencerrlongg
701411c1b9 get rid of a couple unnecessary changes 2024-03-27 13:53:57 -05:00
Spencer Long
013463aafc Merge branch 'develop' into bug/sc-24884 2024-03-27 12:45:45 -05:00
spencerrlongg
39c15b2868 reformat array 2024-03-27 10:35:25 -05:00
spencerrlongg
60ca634eff remove interactswithsettings 2024-03-21 12:04:53 -05:00
spencerrlongg
be282dd038 resolve a couple issues 2024-03-21 09:26:45 -05:00
spencerrlongg
8cc1397ace rm a couple unnecessary 2024-03-20 15:36:53 -05:00
spencerrlongg
e7b9903341 delete some extra lines 2024-03-20 15:29:06 -05:00
spencerrlongg
e3e01e07b1 final cleanup 2024-03-20 15:23:45 -05:00
spencerrlongg
d18aa1db98 some more cleanup + tests 2024-03-20 15:18:15 -05:00
spencerrlongg
c155e4a7c9 new test for not found assets 2024-03-20 13:52:22 -05:00
spencerrlongg
fdf0be09db all tests passing 2024-03-20 13:43:01 -05:00
spencerrlongg
e1addc5aef oops, typo from conflict resolve 2024-03-20 13:16:36 -05:00
Spencer Long
b4b4927370 Merge branch 'develop' into bug/sc-24884 2024-03-20 12:36:39 -05:00
spencerrlongg
0ffceb9691 some notes and a little progress 2024-03-20 00:07:52 -05:00
spencerrlongg
1e810d2426 most tests now passing, still one broken 2024-03-19 19:47:26 -05:00
spencerrlongg
c0110e7f29 some more tests and refinement 2024-03-19 19:27:35 -05:00
spencerrlongg
86ab880c90 buncha progress 2024-03-19 15:34:59 -05:00
spencerrlongg
f6ab0f8f46 lots of cleanup to do, but this DOES work 2024-03-13 15:57:10 -05:00
spencerrlongg
f01b205486 some changes 2024-03-13 15:10:51 -05:00
spencerrlongg
8962ced038 push to switch branches 2024-03-13 10:40:50 -05:00
spencerrlongg
04d7884af8 some more testing stuff 2024-03-12 23:17:40 -05:00
spencerrlongg
6732b6601e some cool progress, but something with unique not working 2024-03-12 18:33:59 -05:00
spencerrlongg
eb8f1dd553 some cleanup 2024-03-09 12:29:26 -06:00
spencerrlongg
c8341d9dc4 aha, got it working. 2024-03-08 19:48:47 -06:00
spencerrlongg
b239b3a4db some good progress, lots of testing needs to be done on the new inclusion of SubstituteBindings 2024-03-08 18:24:41 -06:00
spencerrlongg
eac01868ca not all working, but pushing to work on something else 2024-03-05 11:02:55 -06:00
spencerrlongg
c025e25839 just the basics and notes, pushing to keep track 2024-02-26 14:32:50 -06:00
2001 changed files with 50739 additions and 20537 deletions

View File

@@ -3154,6 +3154,69 @@
"contributions": [
"code"
]
},
{
"login": "r-xyz",
"name": "r-xyz",
"avatar_url": "https://avatars.githubusercontent.com/u/100710244?v=4",
"profile": "https://github.com/r-xyz",
"contributions": [
"code"
]
},
{
"login": "DrekiDegga",
"name": "Steven Mainor",
"avatar_url": "https://avatars.githubusercontent.com/u/47491036?v=4",
"profile": "https://github.com/DrekiDegga",
"contributions": [
"code"
]
},
{
"login": "arne-kroeger",
"name": "arne-kroeger",
"avatar_url": "https://avatars.githubusercontent.com/u/65785975?v=4",
"profile": "https://github.com/arne-kroeger",
"contributions": [
"code"
]
},
{
"login": "Glukose1",
"name": "Glukose1",
"avatar_url": "https://avatars.githubusercontent.com/u/167117705?v=4",
"profile": "https://github.com/Glukose1",
"contributions": [
"code"
]
},
{
"login": "Scarzy",
"name": "Scarzy",
"avatar_url": "https://avatars.githubusercontent.com/u/1197791?v=4",
"profile": "https://github.com/Scarzy",
"contributions": [
"code"
]
},
{
"login": "setpill",
"name": "setpill",
"avatar_url": "https://avatars.githubusercontent.com/u/37372069?v=4",
"profile": "https://github.com/setpill",
"contributions": [
"code"
]
},
{
"login": "swift2512",
"name": "swift2512",
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
"profile": "https://github.com/swift2512",
"contributions": [
"bug"
]
}
]
}

View File

@@ -1,6 +1,8 @@
# --------------------------------------------
# REQUIRED: DB SETUP
# --------------------------------------------
# https://mariadb.com/kb/en/mariadb-server-docker-official-image-environment-variables/
MYSQL_DATABASE=snipeit
MYSQL_USER=snipeit
MYSQL_PASSWORD=changeme1234

View File

@@ -14,7 +14,7 @@ APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
APP_URL=http://localhost:8000
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier
APP_TIMEZONE='UTC'
APP_LOCALE=en
APP_LOCALE=en-US
MAX_RESULTS=500
# --------------------------------------------

View File

@@ -6,7 +6,7 @@ APP_DEBUG=false
APP_KEY=base64:hTUIUh9CP6dQx+6EjSlfWTgbaMaaRvlpEwk45vp+xmk=
APP_URL=http://127.0.0.1:8000
APP_TIMEZONE='US/Eastern'
APP_LOCALE=en
APP_LOCALE=en-US
APP_LOCKED=false
MAX_RESULTS=200

View File

@@ -32,6 +32,8 @@ DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_SANITIZE_BY_DEFAULT=false
# --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS
@@ -87,6 +89,7 @@ SESSION_LIFETIME=12000
EXPIRE_ON_CLOSE=false
ENCRYPT=false
COOKIE_NAME=snipeit_session
PASSPORT_COOKIE_NAME='snipeit_passport_token'
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15

43
.github/stale.yml vendored
View File

@@ -1,43 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- :woman_technologist: ready for dev
- :moneybag: bounty
- :hand: bug
- "🔐 security"
- "👩‍💻 ready for dev"
- "💰 bounty"
- "✋ bug"
exemptMilestones: true
# Label to use when marking an issue as stale
staleLabel: stale
only: issues
# Comment to post when removing the stale label.
unmarkComment: >
Okay, it looks like this issue or feature request might still be important. We'll re-open
it for now. Thank you for letting us know!
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Is this still relevant? We haven't heard from anyone in a bit. If so,
please comment with any updates or additional detail.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Don't
take it personally, we just need to keep a handle on things. Thank you
for your contributions!
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been automatically closed because it has not had
recent activity. If you believe this is still an issue, please confirm that
this issue is still happening in the most recent version of Snipe-IT and reply
to this thread to re-open it.

39
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: 'Close stale issues'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
# contents: write # only for delete-branch option
issues: write
# pull-requests: write
steps:
- uses: actions/stale@v9
with:
debug-only: true
operations-per-run: 100 # just while we're debugging
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
days-before-close: 7
exempt-all-milestones: true
stale-issue-message: >
Is this still relevant? We haven't heard from anyone in a bit. If so,
please comment with any updates or additional detail.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Don't
take it personally, we just need to keep a handle on things. Thank you
for your contributions!
close-issue-message: >
This issue has been automatically closed because it has not had
recent activity. If you believe this is still an issue, please confirm that
this issue is still happening in the most recent version of Snipe-IT and reply
to this thread to re-open it.
# There doesn't seem to be a 'reopen issue message'?
# Since there is no 'stale-pr-message' - PR's should not be stale'd
stale-issue-label: stale
exempt-issue-labels: >
pinned,security,:woman_technologist: ready for dev,:moneybag: bounty,:hand: bug,🔐 security,👩‍💻 ready for dev,💰 bounty,✋ bug

3
.gitignore vendored
View File

@@ -67,3 +67,6 @@ _ide_helper_models.php
/.phplint-cache
storage/ldap_client_tls.cert
storage/ldap_client_tls.key
/storage/framework/testing
/.phpunit.cache

View File

@@ -51,7 +51,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") |
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -1,35 +1,35 @@
FROM alpine:3.18.6
FROM alpine:3.19
# Apache + PHP
RUN apk add --no-cache \
apache2 \
php81 \
php81-common \
php81-apache2 \
php81-curl \
php81-ldap \
php81-mysqli \
php81-gd \
php81-xml \
php81-mbstring \
php81-zip \
php81-ctype \
php81-tokenizer \
php81-pdo_mysql \
php81-openssl \
php81-bcmath \
php81-phar \
php81-json \
php81-iconv \
php81-fileinfo \
php81-simplexml \
php81-session \
php81-dom \
php81-xmlwriter \
php81-xmlreader \
php81-sodium \
php81-redis \
php81-pecl-memcached \
php81-exif \
php82 \
php82-common \
php82-apache2 \
php82-curl \
php82-ldap \
php82-mysqli \
php82-gd \
php82-xml \
php82-mbstring \
php82-zip \
php82-ctype \
php82-tokenizer \
php82-pdo_mysql \
php82-openssl \
php82-bcmath \
php82-phar \
php82-json \
php82-iconv \
php82-fileinfo \
php82-simplexml \
php82-session \
php82-dom \
php82-xmlwriter \
php82-xmlreader \
php82-sodium \
php82-redis \
php82-pecl-memcached \
php82-exif \
curl \
wget \
vim \
@@ -42,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite
@@ -79,12 +79,12 @@ USER root
VOLUME ["/var/lib/snipeit"]
# Entrypoints
COPY docker/entrypoint_alpine.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Startup script
COPY docker/startup_alpine.sh /startup.sh
RUN chmod +x /startup.sh
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/entrypoint.sh"]
CMD ["/startup.sh"]
EXPOSE 80

View File

@@ -97,7 +97,7 @@ RUN set -eux; \
VOLUME [ "/var/lib/snipeit" ]
COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
COPY --chmod=655 docker/startup_alpine_fpm.sh /startup.sh
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
ENTRYPOINT [ "/startup.sh" ]
CMD [ "/startup.sh", "php-fpm" ]

View File

@@ -72,12 +72,13 @@ Since the release of the JSON REST API, several third-party developers have been
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT.
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT.
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
-----

View File

@@ -20,7 +20,7 @@ APP_DEBUG=true
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
APP_LOCALE=en
APP_LOCALE=en-US
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use Illuminate\Console\Command;
class FixupAssignedToWithoutAssignedType extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:assigned-to-fixup
{--debug : Display debugging output}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fixes up assets that have an assigned_to but no assigned_type';
/**
* Execute the console command.
*/
public function handle()
{
$assets = Asset::whereNull("assigned_type")->whereNotNull("assigned_to")->withTrashed();
$this->withProgressBar($assets->get(), function (Asset $asset) {
//now check each action log, from the most recent backwards, to find the last checkin or checkout
foreach($asset->log()->orderBy("id","desc")->get() as $action_log) {
if($this->option("debug")) {
$this->info("Asset id: " . $asset->id . " action log, action type is: " . $action_log->action_type);
}
switch($action_log->action_type) {
case 'checkin from':
if($this->option("debug")) {
$this->info("Doing a checkin for ".$asset->id);
}
$asset->assigned_to = null;
// if you have a required custom field, we still want to save, and we *don't* want an action_log
$asset->saveQuietly();
return;
case 'checkout':
if($this->option("debug")) {
$this->info("Doing a checkout for " . $asset->id . " picking target type: " . $action_log->target_type);
}
if($asset->assigned_to != $action_log->target_id) {
$this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to." vs. \$action_log->target_id=".$action_log->target_id);
//FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up...
}
$asset->assigned_type = $action_log->target_type;
$asset->saveQuietly(); // see above
return;
}
}
$asset->assigned_to = null; //asset was never checked in or out in its lifetime - it stays 'checked in'
$asset->saveQuietly(); //see above
});
$this->newLine();
$this->info("Assets assigned_type are fixed");
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
@@ -15,6 +16,8 @@ use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class Purge extends Command
{
@@ -141,6 +144,20 @@ class Purge extends Command
$this->info($users->count().' users purged.');
$user_assoc = 0;
foreach ($users as $user) {
$rel_path = 'private_uploads/users';
$filenames = Actionlog::where('action_type', 'uploaded')
->where('item_id', $user->id)
->pluck('filename');
foreach($filenames as $filename) {
try {
if (Storage::exists($rel_path . '/' . $filename)) {
Storage::delete($rel_path . '/' . $filename);
}
} catch (\Exception $e) {
Log::info('An error occurred while deleting files: ' . $e->getMessage());
}
}
$this->info('- User "'.$user->username.'" deleted.');
$user_assoc += $user->userlog()->count();
$user->userlog()->forceDelete();

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\AssetModel;
use Illuminate\Console\Command;
class RemoveExplicitEols extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:remove-explicit-eols {--model_name= : The name of the asset model to update (use "all" to update all models)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes explicit EOLs on assets with selected model so they may inherit the asset model EOL';
/**
* Execute the console command.
*/
public function handle()
{
$startTime = microtime(true);
if ($this->option('model_name') == 'all') {
$assets = Asset::all();
$this->updateAssets($assets);
} else {
$assetModel = AssetModel::where('name', '=', $this->option('model_name'))->first();
if ($assetModel) {
$assets = Asset::where('model_id', '=', $assetModel->id)->get();
$this->updateAssets($assets);
} else {
$this->error('Asset model not found');
}
}
$endTime = microtime(true);
$executionTime = ($endTime - $startTime);
$this->info('Command executed in ' . round($executionTime, 2) . ' seconds.');
}
private function updateAssets($assets)
{
foreach ($assets as $asset) {
$asset->eol_explicit = 0;
$asset->asset_eol_date = null;
$asset->save();
}
$this->info($assets->count() . ' Assets updated successfully');
}
}

View File

@@ -73,6 +73,7 @@ class ResetDemoSettings extends Command
$settings->saml_forcelogin = '0';
$settings->saml_slo = null;
$settings->saml_custom_settings = null;
$settings->default_avatar = 'default.png';
$settings->save();

View File

@@ -30,8 +30,11 @@ class SQLStreamer {
public function parse_sql(string $line): string {
// take into account the 'start of line or not' setting as an instance variable?
// 'continuation' lines for a permitted statement are PERMITTED.
// remove *only* line-feeds & carriage-returns; helpful for regexes against lines from
// Windows dumps
$line = trim($line, "\r\n");
if($this->statement_is_permitted && $line[0] === ' ') {
return $line;
return $line . "\n"; //re-add the newline
}
$table_regex = '`?([a-zA-Z0-9_]+)`?';
@@ -42,8 +45,12 @@ class SQLStreamer {
"/^(INSERT INTO )$table_regex(.*)$/" => false,
"/^UNLOCK TABLES/" => false,
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
"/^\\)[a-zA-Z0-9_= ]*;$/" => false
// ^^^^^^ that bit should *exit* the 'perimitted' black
"/^\\)[a-zA-Z0-9_= ]*;$/" => false,
// ^^^^^^ that bit should *exit* the 'permitted' block
"/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line
/* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with
a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to
assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */
];
foreach($allowed_statements as $statement => $statechange) {
@@ -67,7 +74,7 @@ class SQLStreamer {
}
//how do we *replace* the tablename?
// print "RETURNING LINE: $line";
return $line;
return $line . "\n"; //re-add newline
}
}
// all that is not allowed is denied.
@@ -85,7 +92,7 @@ class SQLStreamer {
$parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
//can't use 'users' because the 'accessories_users' table?
//can't use 'users' because the 'accessories_checkout' table?
// can't use 'assets' because 'ver1_components_assets'
foreach($check_tables as $check_table => $_ignore) {
foreach ($parser->tablenames as $tablename => $_count) {
@@ -164,7 +171,8 @@ class RestoreFromBackup extends Command
{filename : The zip file to be migrated}
{--no-progress : Don\'t show a progress bar}
{--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL}
{--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}';
{--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}
{--sql-stdout-only : ONLY "Sanitize" the SQL and print it to stdout - useful for debugging - probably requires --sanitize-with-prefix= }';
/**
* The console command description.
@@ -365,6 +373,15 @@ class RestoreFromBackup extends Command
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL.");
}
// If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness
if ($this->option('sql-stdout-only')) {
$sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix'));
$bytes_read = $sql_importer->line_aware_piping();
return $this->warn("$bytes_read total bytes read");
//TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
// which would be good for redirecting to a file, and not having to trim the last line off of it
}
//how to invoke the restore?
$pipes = [];
@@ -466,6 +483,9 @@ class RestoreFromBackup extends Command
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
$fp = $za->getStream($ugly_file_name);
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
if (!is_dir($file_details['dest'])) {
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
}
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
fwrite($migrated_file, $buffer);

View File

@@ -47,9 +47,10 @@ class SendAcceptanceReminder extends Command
{
$pending = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')
->whereHas('checkoutable', function($query) {
$query->where('archived', 0);
$query->where('accepted_at', null)
->where('declined_at', null);
})
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.admin'])
->get();
$count = 0;

View File

@@ -62,8 +62,9 @@ class Helper
'mn' => 'mn-MN', // Mongolian
'ms' => 'ms-MY', // Malay
'nl' => 'nl-NL', // Dutch
'no' => 'no-NO', // Norwegian
'no' => 'nb-NO', // Norwegian Bokmål
'pl' => 'pl-PL', // Polish
'pt' => 'pt-PT', // Portuguese
'ro' => 'ro-RO', // Romanian
'ru' => 'ru-RU', // Russian
'sk' => 'sk-SK', // Slovak
@@ -720,7 +721,7 @@ class Helper
{
$alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('checkouts as checkouts_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
$licenses = License::where('min_amt', '>', 0)->get();
@@ -748,7 +749,7 @@ class Helper
}
foreach ($accessories as $accessory) {
$avail = $accessory->qty - $accessory->users_count;
$avail = $accessory->qty - $accessory->checkouts_count;
if ($avail < ($accessory->min_amt) + $alert_threshold) {
if ($accessory->qty > 0) {
$percent = number_format((($avail / $accessory->qty) * 100), 0);
@@ -913,13 +914,22 @@ class Helper
$rules = $class::rules();
foreach ($rules as $rule_name => $rule) {
if ($rule_name == $field) {
if (strpos($rule, 'required') === false) {
return false;
if (is_array($rule)) {
if (in_array('required', $rule)) {
return true;
} else {
return false;
}
} else {
return true;
}
if (strpos($rule, 'required') === false) {
return false;
} else {
return true;
}
}
}
}
return false;
}
/**
@@ -1440,7 +1450,6 @@ class Helper
foreach (self::$language_map as $legacy => $new) {
if ($language_code == $legacy) {
Log::debug('Current language is '.$legacy.', using '.$new.' instead');
return $new;
}
}
@@ -1451,6 +1460,7 @@ class Helper
public static function mapBackToLegacyLocale($new_locale = null)
{
if (strlen($new_locale) <= 4) {
return $new_locale; //"new locale" apparently wasn't quite so new
}
@@ -1458,42 +1468,73 @@ class Helper
// This does a *reverse* search against our new language map array - given the value, find the *key* for it
$legacy_locale = array_search($new_locale, self::$language_map);
if($legacy_locale !== false) {
if ($legacy_locale !== false) {
return $legacy_locale;
}
return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void'
}
public static function determineLanguageDirection() {
return in_array(app()->getLocale(),
[
'ar-SA',
'fa-IR',
'he-IL'
]) ? 'rtl' : 'ltr';
}
static public function getRedirectOption($request, $id, $table, $asset_id = null)
static public function getRedirectOption($request, $id, $table, $item_id = null)
{
$redirect_option = Session::get('redirect_option');
$checkout_to_type = Session::get('checkout_to_type');
//return to index
if ($redirect_option == '0') {
// return to index
if ($redirect_option == 'index') {
switch ($table) {
case "Assets":
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
return route('hardware.index');
case "Users":
return route('users.index');
case "Licenses":
return route('licenses.index');
case "Accessories":
return route('accessories.index');
case "Components":
return route('components.index');
case "Consumables":
return route('consumables.index');
}
}
//return to thing being assigned
if ($redirect_option == '1') {
// return to thing being assigned
if ($redirect_option == 'item') {
switch ($table) {
case "Assets":
return redirect()->route('hardware.show', $id ? $id : $asset_id)->with('success', trans('admin/hardware/message.checkout.success'));
return route('hardware.show', $id ?? $item_id);
case "Users":
return route('users.show', $id ?? $item_id);
case "Licenses":
return route('licenses.show', $id ?? $item_id);
case "Accessories":
return route('accessories.show', $id ?? $item_id);
case "Components":
return route('components.show', $id ?? $item_id);
case "Consumables":
return route('consumables.show', $id ?? $item_id);
}
}
//return to thing being assigned to
if ($redirect_option == '2') {
// return to assignment target
if ($redirect_option == 'target') {
switch ($checkout_to_type) {
case 'user':
return redirect()->route('users.show', $request->assigned_user)->with('success', trans('admin/hardware/message.checkout.success'));
return route('users.show', ['user' => $request->assigned_user]);
case 'location':
return redirect()->route('locations.show', $request->assigned_location)->with('success', trans('admin/hardware/message.checkout.success'));
return route('locations.show', ['location' => $request->assigned_location]);
case 'asset':
return redirect()->route('hardware.show', $request->assigned_asset)->with('success', trans('admin/hardware/message.checkout.success'));
return route('hardware.show', ['hardware' => $request->assigned_asset]);
}
}
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));

179
app/Helpers/IconHelper.php Normal file
View File

@@ -0,0 +1,179 @@
<?php
namespace App\Helpers;
class IconHelper
{
public static function icon($type) {
switch ($type) {
case 'checkout':
return 'fa-solid fa-rotate-left';
case 'checkin':
return 'fa-solid fa-rotate-right';
case 'edit':
return 'fas fa-pencil-alt';
case 'clone':
return 'far fa-clone';
case 'delete':
return 'fas fa-trash';
case 'create':
return 'fa-solid fa-plus';
case 'audit':
return 'fa-solid fa-clipboard-check';
case '2fa reset':
return 'fa-solid fa-mobile-screen';
case 'new-user':
return 'fa-solid fa-user-plus';
case 'merged-user':
return 'fa-solid fa-people-arrows';
case 'delete-user':
return 'fa-solid fa-user-minus';
case 'update-user':
return 'fa-solid fa-user-pen';
case 'user':
return 'fa-solid fa-user';
case 'users':
return 'fas fa-users';
case 'restore':
return 'fa-solid fa-trash-arrow-up';
case 'external-link':
return 'fa fa-external-link';
case 'email':
return 'fa-regular fa-envelope';
case 'phone':
return 'fa-solid fa-phone';
case 'long-arrow-right':
return 'fas fa-long-arrow-alt-right';
case 'download':
return 'fas fa-download';
case 'checkmark':
return 'fas fa-check icon-white';
case 'x':
return 'fas fa-times';
case 'logout':
return 'fa fa-sign-out';
case 'admin-settings':
return 'fas fa-cogs';
case 'settings':
return 'fas fa-cog';
case 'angle-left':
return 'fas fa-angle-left';
case 'warning':
return 'fas fa-exclamation-triangle';
case 'kits':
return 'fas fa-object-group';
case 'assets':
return 'fas fa-barcode';
case 'accessories':
return 'far fa-keyboard';
case 'components':
return 'far fa-hdd';
case 'consumables':
return 'fas fa-tint';
case 'licenses':
return 'far fa-save';
case 'requestable':
return 'fas fa-laptop';
case 'reports':
return 'fas fa-chart-bar';
case 'heart':
return 'fas fa-heart';
case 'circle':
return 'fa-regular fa-circle';
case 'circle-solid':
return 'fa-solid fa-circle';
case 'due':
return 'fas fa-history';
case 'import':
return 'fas fa-cloud-upload-alt';
case 'search':
return 'fas fa-search';
case 'alerts':
return 'far fa-flag';
case 'password':
return 'fa-solid fa-key';
case 'api-key':
return 'fa-solid fa-user-secret';
case 'nav-toggle':
return 'fas fa-bars';
case 'dashboard':
return 'fas fa-tachometer-alt';
case 'info-circle':
return 'fas fa-info-circle';
case 'caret-right':
return 'fa fa-caret-right';
case 'caret-up':
return 'fa fa-caret-up';
case 'caret-down':
return 'fa fa-caret-down';
case 'arrow-circle-right':
return 'fa fa-arrow-circle-right';
case 'minus':
return 'fas fa-minus';
case 'spinner':
return 'fas fa-spinner fa-spin';
case 'copy-clipboard':
return 'fa-regular fa-clipboard';
case 'paperclip':
return 'fas fa-paperclip';
case 'files':
return 'fa-regular fa-file';
case 'more-info':
return 'far fa-life-ring';
case 'calendar':
return 'fas fa-calendar';
case 'plus':
return 'fas fa-plus';
case 'history':
return 'fas fa-history';
case 'more-files':
return 'fa-solid fa-laptop-file';
case 'maintenances':
return 'fas fa-wrench';
case 'seats':
return 'far fa-list-alt';
case 'globe-us':
return 'fas fa-globe-americas';
case 'locked':
return 'fas fa-lock';
case 'unlocked':
return 'fas fa-lock';
case 'locations':
return 'fas fa-map-marker-alt';
case 'superadmin':
return 'fas fa-crown';
case 'print':
return 'fa-solid fa-print';
case 'checkin-and-delete':
return 'fa-solid fa-user-xmark';
case 'branding':
return 'fas fa-copyright';
case 'general-settings':
return 'fa-solid fa-list-check';
case 'groups':
return 'fa-solid fa-user-group';
case 'bell':
return 'fa-solid fa-bell';
case 'hashtag':
return 'fa-solid fa-hashtag';
case 'asset-tags':
return 'fas fa-list-ol';
case 'labels':
return 'fas fa-tags';
case 'ldap':
return 'fas fa-sitemap';
case 'google':
return 'fa-brands fa-google';
case 'saml':
return 'fas fa-sign-in-alt';
case 'backups':
return 'fas fa-file-archive';
case 'logins':
return 'fas fa-crosshairs';
case 'oauth':
return 'fas fa-user-secret';
}
}
}

View File

@@ -79,10 +79,11 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the accessory created?
if ($accessory->save()) {
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
@@ -143,12 +144,12 @@ class AccessoriesController extends Controller
*/
public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse
{
if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$accessory->users_count"
"qty" => "required|numeric|min:$accessory->checkouts_count"
]);
if ($validator->fails()) {
@@ -176,9 +177,10 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success'));
}
} else {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
@@ -231,7 +233,7 @@ class AccessoriesController extends Controller
*/
public function show($accessoryID = null) : View | RedirectResponse
{
$accessory = Accessory::withCount('users as users_count')->find($accessoryID);
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryID);
$this->authorize('view', $accessory);
if (isset($accessory->id)) {
return view('accessories/view', compact('accessory'));

View File

@@ -3,8 +3,10 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -23,7 +25,7 @@ class AccessoryCheckinController extends Controller
*/
public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse
{
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
@@ -38,16 +40,16 @@ class AccessoryCheckinController extends Controller
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param null $accessoryUserId
* @param null $accessoryCheckoutId
* @param string $backto
*/
public function store(Request $request, $accessoryUserId = null, $backto = null) : RedirectResponse
public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse
{
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$accessory = Accessory::find($accessory_checkout->accessory_id);
$this->authorize('checkin', $accessory);
@@ -58,12 +60,12 @@ class AccessoryCheckinController extends Controller
}
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
$return_to = e($accessory_user->assigned_to);
if ($accessory_checkout->delete()) {
event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at));
event(new CheckoutableCheckedIn($accessory, User::find($return_to), auth()->user(), $request->input('note'), $checkin_at));
session()->put(['redirect_option' => $request->get('redirect_option')]);
return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success'));
}
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));

View File

@@ -3,18 +3,24 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Http\Requests\AccessoryCheckoutRequest;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AccessoryCheckoutController extends Controller
{
use CheckInOutRequest;
/**
* Return the form to checkout an Accessory to a user.
*
@@ -24,7 +30,7 @@ class AccessoryCheckoutController extends Controller
public function create($id) : View | RedirectResponse
{
if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) {
$this->authorize('checkout', $accessory);
@@ -57,44 +63,38 @@ class AccessoryCheckoutController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param int $accessoryId
* @param Accessory $accessory
*/
public function store(Request $request, $accessoryId) : RedirectResponse
public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
{
// Check if the accessory exists
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
}
$this->authorize('checkout', $accessory);
if (!$user = User::find($request->input('assigned_to'))) {
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
$target = $this->determineCheckoutTarget();
$accessory->checkout_qty = $request->input('checkout_qty', 1);
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
AccessoryCheckout::create([
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $target->id,
'assigned_type' => $target::class,
'note' => $request->input('note'),
]);
}
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
// Set this as user since we only allow checkout to user for this item type
$request->request->add(['checkout_to_type' => request('checkout_to_type')]);
$request->request->add(['assigned_user' => $target->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
// Update the accessory data
$accessory->assigned_to = e($request->input('assigned_to'));
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to'),
'note' => $request->input('note'),
]);
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))
->with('success', trans('admin/accessories/message.checkout.success'));
}
}

View File

@@ -218,6 +218,7 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(),
'note' => $request->input('note'),
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
@@ -308,6 +309,7 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'note' => $request->input('note'),
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,

View File

@@ -4,7 +4,10 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Http\Requests\AccessoryCheckoutRequest;
use App\Http\Requests\StoreAccessoryRequest;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Accessory;
@@ -15,10 +18,12 @@ use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use App\Models\AccessoryCheckout;
class AccessoriesController extends Controller
{
use CheckInOutRequest;
/**
* Display a listing of the resource.
*
@@ -46,13 +51,13 @@ class AccessoriesController extends Controller
'min_amt',
'company_id',
'notes',
'users_count',
'checkouts_count',
'qty',
];
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
->withCount('users as users_count');
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier')
->withCount('checkouts as checkouts_count');
if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search'));
@@ -121,12 +126,12 @@ class AccessoriesController extends Controller
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\JsonResponse
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/
public function store(ImageUploadRequest $request)
public function store(StoreAccessoryRequest $request)
{
$this->authorize('create', Accessory::class);
$accessory = new Accessory;
@@ -144,15 +149,15 @@ class AccessoriesController extends Controller
/**
* Display the specified resource.
*
* @param int $id
* @return array
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$this->authorize('view', Accessory::class);
$accessory = Accessory::withCount('users as users_count')->findOrFail($id);
$accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id);
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
@@ -161,10 +166,10 @@ class AccessoriesController extends Controller
/**
* Display the specified resource.
*
* @param int $id
* @return array
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @param int $id
* @return \Illuminate\Http\Response
*/
public function accessory_detail($id)
{
@@ -195,28 +200,23 @@ class AccessoriesController extends Controller
$offset = request('offset', 0);
$limit = request('limit', 50);
$accessory_users = $accessory->users;
$total = $accessory_users->count();
$accessory_checkouts = $accessory->checkouts;
$total = $accessory_checkouts->count();
if ($total < $offset) {
$offset = 0;
}
$accessory_users = $accessory->users()->skip($offset)->take($limit)->get();
$accessory_checkouts = $accessory->checkouts()->skip($offset)->take($limit)->get();
if ($request->filled('search')) {
$accessory_users = $accessory->users()
->where(function ($query) use ($request) {
$search_str = '%' . $request->input('search') . '%';
$query->where('first_name', 'like', $search_str)
->orWhere('last_name', 'like', $search_str)
->orWhere('note', 'like', $search_str);
})
$accessory_checkouts = $accessory->checkouts()->TextSearch($request->input('search'))
->get();
$total = $accessory_users->count();
$total = $accessory_checkouts->count();
}
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_checkouts, $total);
}
@@ -273,43 +273,31 @@ class AccessoriesController extends Controller
* If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
* @return \Illuminate\Http\JsonResponse
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function checkout(Request $request, $accessoryId)
public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
$this->authorize('checkout', $accessory);
$target = $this->determineCheckoutTarget();
$accessory->checkout_qty = $request->input('checkout_qty', 1);
if ($accessory->numRemaining() > 0) {
if (! $user = User::find($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
}
// Update the accessory data
$accessory->assigned_to = $request->input('assigned_to');
$accessory->users()->attach($accessory->id, [
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
AccessoryCheckout::create([
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to'),
'note' => $request->get('note'),
'assigned_to' => $target->id,
'assigned_type' => $target::class,
'note' => $request->input('note'),
]);
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
// Set this value to be able to pass the qty through to the event
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
@@ -326,19 +314,19 @@ class AccessoriesController extends Controller
*/
public function checkin(Request $request, $accessoryUserId = null)
{
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$accessory = Accessory::find($accessory_checkout->accessory_id);
$this->authorize('checkin', $accessory);
$logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note'));
$logaction = $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note'));
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
if (! is_null($accessory_user->assigned_to)) {
$user = User::find($accessory_user->assigned_to);
if ($accessory_checkout->delete()) {
if (! is_null($accessory_checkout->assigned_to)) {
$user = User::find($accessory_checkout->assigned_to);
}
$data['log_id'] = $logaction->id;

View File

@@ -0,0 +1,200 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\AssetModel;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetModelFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $assetModel);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assetmodels')) {
Storage::makeDirectory('private_uploads/assetmodels', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
$assetModel->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function list($assetModelId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('view', $assetModel);
// Check that there are some uploads on this asset that can be listed
if ($assetModel->uploads->count() > 0) {
$files = array();
foreach ($assetModel->uploads as $upload) {
array_push($files, $upload);
}
// Give the list of files back to the user
return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/models/message.upload.success')));
}
// There are no files.
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/models/message.upload.success')));
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error')), 500);
}
/**
* Check for permissions and display the file.
*
* @param int $assetModelId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v7.0.12]
* @author [r-xyz]
*/
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('view', $assetModel);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assetmodels/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetModelId
* @param int $fileId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assetmodels';
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('update', $assetModel);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
}

View File

@@ -78,6 +78,10 @@ class AssetModelsController extends Controller
$assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id'));
}
if ($request->filled('depreciation_id')) {
$assetmodels = $assetmodels->where('models.depreciation_id', '=', $request->input('depreciation_id'));
}
if ($request->filled('search')) {
$assetmodels->TextSearch($request->input('search'));
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
@@ -601,7 +602,7 @@ class AssetsController extends Controller
if ($field->field_encrypted == '1') {
Log::debug('This model field is encrypted in this fieldset.');
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
// If input value is null, use custom field's default value
if (($field_val == null) && ($request->has('model_id') != '')) {
@@ -651,36 +652,35 @@ class AssetsController extends Controller
* Accepts a POST request to update an asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
*/
public function update(ImageUploadRequest $request, $id) : JsonResponse
public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse
{
$this->authorize('update', Asset::class);
$asset->fill($request->validated());
if ($asset = Asset::find($id)) {
$asset->fill($request->all());
if ($request->has('model_id')) {
$asset->model()->associate(AssetModel::find($request->validated()['model_id']));
}
if ($request->has('company_id')) {
$asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']);
}
if ($request->has('rtd_location_id') && !$request->has('location_id')) {
$asset->location_id = $request->validated()['rtd_location_id'];
}
if ($request->input('last_audit_date')) {
$asset->last_audit_date = Carbon::parse($request->input('last_audit_date'))->startOfDay()->format('Y-m-d H:i:s');
}
($request->filled('model_id')) ?
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
($request->filled('rtd_location_id')) ?
$asset->location_id = $request->get('rtd_location_id') : '';
($request->filled('company_id')) ?
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
/**
* this is here just legacy reasons. Api\AssetController
* used image_source once to allow encoded image uploads.
*/
if ($request->has('image_source')) {
$request->offsetSet('image', $request->offsetGet('image_source'));
}
($request->filled('rtd_location_id')) ?
$asset->location_id = $request->get('rtd_location_id') : null;
/**
* this is here just legacy reasons. Api\AssetController
* used image_source once to allow encoded image uploads.
*/
if ($request->has('image_source')) {
$request->offsetSet('image', $request->offsetGet('image_source'));
}
$asset = $request->handleImages($asset);
$model = AssetModel::find($asset->model_id);
$asset = $request->handleImages($asset);
$model = $asset->model;
// Update custom fields
$problems_updating_encrypted_custom_fields = false;
@@ -695,7 +695,7 @@ class AssetsController extends Controller
}
}
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
$field_val = Crypt::encrypt($field_val);
} else {
$problems_updating_encrypted_custom_fields = true;
@@ -706,15 +706,13 @@ class AssetsController extends Controller
}
}
}
if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
$location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
$location = $target->location_id;
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $id)
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
->update(['location_id' => $target->location_id]);
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
$location = $target->id;
@@ -728,17 +726,13 @@ class AssetsController extends Controller
$asset->image = $asset->getImageUrl();
}
if ($problems_updating_encrypted_custom_fields) {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
} else {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
}
if ($problems_updating_encrypted_custom_fields) {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
} else {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
@@ -934,7 +928,7 @@ class AssetsController extends Controller
}
}
if ($request->has('status_id')) {
if ($request->filled('status_id')) {
$asset->status_id = $request->input('status_id');
}
@@ -984,7 +978,7 @@ class AssetsController extends Controller
public function checkinByTag(Request $request, $tag = null) : JsonResponse
{
$this->authorize('checkin', Asset::class);
if(null == $tag && null !== ($request->input('asset_tag'))) {
if (null == $tag && null !== ($request->input('asset_tag'))) {
$tag = $request->input('asset_tag');
}
$asset = Asset::where('asset_tag', $tag)->first();

View File

@@ -20,9 +20,22 @@ class DepreciationsController extends Controller
public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Depreciation::class);
$allowed_columns = ['id','name','months','depreciation_min','created_at'];
$allowed_columns = [
'id',
'name',
'months',
'depreciation_min',
'depreciation_type',
'created_at',
'assets_count',
'models_count',
'licenses_count',
];
$depreciations = Depreciation::select('id','name','months','depreciation_min','user_id','created_at','updated_at');
$depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','user_id','created_at','updated_at')
->withCount('assets as assets_count')
->withCount('models as models_count')
->withCount('licenses as licenses_count');
if ($request->filled('search')) {
$depreciations = $depreciations->TextSearch($request->input('search'));

View File

@@ -24,7 +24,7 @@ class LicensesController extends Controller
{
$this->authorize('view', License::class);
$licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count');
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
if ($request->filled('company_id')) {
$licenses->where('company_id', '=', $request->input('company_id'));
@@ -70,6 +70,9 @@ class LicensesController extends Controller
$licenses->where('depreciation_id', '=', $request->input('depreciation_id'));
}
if ($request->filled('user_id')) {
$licenses->where('user_id', '=', $request->input('user_id'));
}
if (($request->filled('maintained')) && ($request->input('maintained')=='true')) {
$licenses->where('maintained','=',1);
@@ -113,6 +116,9 @@ class LicensesController extends Controller
case 'company':
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
break;
case 'created_by':
$licenses = $licenses->OrderCreatedBy($order);
break;
default:
$allowed_columns =
[

View File

@@ -5,8 +5,10 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Asset;
use App\Models\Location;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@@ -222,6 +224,15 @@ class LocationsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors()));
}
public function assets(Request $request, Location $location) : JsonResponse | array
{
$this->authorize('view', Asset::class);
$this->authorize('view', $location);
$assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
$assets = $assets->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
}
/**
* Remove the specified resource from storage.
*
@@ -237,6 +248,7 @@ class LocationsController extends Controller
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withCount('accessories as accessories_count')
->findOrFail($id);
if (! $location->isDeletable()) {

View File

@@ -246,7 +246,7 @@ class PredefinedKitsController extends Controller
$relation = $kit->models();
if ($relation->find($model_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => 'Model already attached to kit']));
return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => trans('admin/kits/general.model_already_attached')]));
}
$relation->attach($model_id, ['quantity' => $quantity]);

View File

@@ -83,11 +83,19 @@ class ReportsController extends Controller
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
switch ($request->input('sort')) {
case 'admin':
$actionlogs->OrderAdmin($order);
break;
default:
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$actionlogs = $actionlogs->orderBy($sort, $order);
break;
}
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
$actionlogs = $actionlogs->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}

View File

@@ -8,6 +8,7 @@ use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\StatuslabelsTransformer;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\Statuslabel;
use Illuminate\Http\Request;
use App\Http\Transformers\PieChartTransformer;
@@ -187,8 +188,14 @@ class StatuslabelsController extends Controller
public function getAssetCountByStatuslabel() : array
{
$this->authorize('view', Statuslabel::class);
$statuslabels = Statuslabel::withCount('assets')->get();
$total = Array();
if (Setting::getSettings()->show_archived_in_list == 0 ) {
$statuslabels = Statuslabel::withCount('assets')->where('archived','0')->get();
} else {
$statuslabels = Statuslabel::withCount('assets')->get();
}
$total = [];
foreach ($statuslabels as $statuslabel) {

View File

@@ -427,13 +427,10 @@ class UsersController extends Controller
* @param \Illuminate\Http\Request $request
* @param int $id
*/
public function update(SaveUserRequest $request, $id) : JsonResponse
public function update(SaveUserRequest $request, User $user): JsonResponse
{
$this->authorize('update', User::class);
if ($user = User::find($id)) {
$this->authorize('update', $user);
/**
@@ -443,12 +440,10 @@ class UsersController extends Controller
*
*/
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
}
$user->fill($request->all());
if ($user->id == $request->input('manager_id')) {
@@ -473,16 +468,13 @@ class UsersController extends Controller
$user->permissions = $permissions_array;
}
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
if ($user->save()) {
// Check if the request has groups passed and has a value, AND that the user us a superuser
if (($request->has('groups')) && (auth()->user()->isSuperUser())) {
@@ -496,18 +488,10 @@ class UsersController extends Controller
// Sync the groups since the user is a superuser and the groups pass validation
$user->groups()->sync($request->input('groups'));
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
}
/**

View File

@@ -151,17 +151,17 @@ class AssetModelsController extends Controller
$model->notes = $request->input('notes');
$model->requestable = $request->input('requestable', '0');
$this->removeCustomFieldsDefaultValues($model);
$model->fieldset_id = $request->input('fieldset_id');
if ($this->shouldAddDefaultValues($request->input())) {
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
}
}
if ($model->save()) {
$this->removeCustomFieldsDefaultValues($model);
if ($this->shouldAddDefaultValues($request->input())) {
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))) {
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
}
}
if ($model->wasChanged('eol')) {
if ($model->eol > 0) {
$newEol = $model->eol;
@@ -202,6 +202,7 @@ class AssetModelsController extends Controller
if ($model->image) {
try {
Storage::disk('public')->delete('models/'.$model->image);
$model->update(['image' => null]);
} catch (\Exception $e) {
Log::info($e);
}
@@ -233,7 +234,7 @@ class AssetModelsController extends Controller
if ($model->restore()) {
$logaction = new Actionlog();
$logaction->item_type = User::class;
$logaction->item_type = AssetModel::class;
$logaction->item_id = $model->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->user_id = auth()->id();

View File

@@ -11,7 +11,6 @@ use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
@@ -83,7 +82,6 @@ class AssetCheckinController extends Controller
}
$asset->expected_checkin = null;
//$asset->last_checkout = null;
$asset->last_checkin = now();
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
@@ -128,12 +126,12 @@ class AssetCheckinController extends Controller
$acceptance->delete();
});
Session::put('redirect_option', $request->get('redirect_option'));
// Was the asset updated?
session()->put('redirect_option', $request->get('redirect_option'));
if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
return Helper::getRedirectOption($asset, $assetId, 'Assets');
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success'));
}
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());

View File

@@ -109,10 +109,11 @@ class AssetCheckoutController extends Controller
}
}
Session::put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
return Helper::getRedirectOption($request, $assetId, 'Assets');
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success', trans('admin/hardware/message.checkout.success'));
}
// Redirect to the asset management page with error
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());

View File

@@ -165,7 +165,7 @@ class AssetsController extends Controller
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
@@ -204,9 +204,13 @@ class AssetsController extends Controller
}
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($success) {
return redirect()->route('hardware.index')
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
}
@@ -289,6 +293,7 @@ class AssetsController extends Controller
*/
public function update(ImageUploadRequest $request, $assetId = null) : RedirectResponse
{
// Check if the asset exists
if (! $asset = Asset::find($assetId)) {
// Redirect to the asset management page with error
@@ -331,7 +336,7 @@ class AssetsController extends Controller
$status = Statuslabel::find($asset->status_id);
if($status->archived){
if ($status && $status->archived) {
$asset->assigned_to = null;
}
@@ -350,14 +355,26 @@ class AssetsController extends Controller
}
// Update the asset data
$asset_tag = $request->input('asset_tags');
$serial = $request->input('serials');
$asset->serial = $request->input('serials');
if (is_array($request->input('serials'))) {
$asset->serial = $serial[1];
}
$asset->name = $request->input('name');
$asset->serial = $serial[1];
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->asset_tag = $asset_tag[1];
$asset_tags = $request->input('asset_tags');
$asset->asset_tag = $request->input('asset_tags');
if (is_array($request->input('asset_tags'))) {
$asset->asset_tag = $asset_tags[1];
}
$asset->notes = $request->input('notes');
$asset = $request->handleImages($asset);
@@ -369,8 +386,9 @@ class AssetsController extends Controller
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
@@ -387,9 +405,10 @@ class AssetsController extends Controller
}
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->save()) {
return redirect()->route('hardware.show', $assetId)
return redirect()->to(Helper::getRedirectOption($request, $assetId, 'Assets'))
->with('success', trans('admin/hardware/message.update.success'));
}
@@ -459,9 +478,16 @@ class AssetsController extends Controller
$tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true');
if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
// Search for an exact and unique asset tag match
$assets = Asset::where('asset_tag', '=', $tag);
// If not a unique result, redirect to the index view
if ($assets->count() != 1) {
return redirect()->route('hardware.index')
->with('search', $tag)
->with('warning', trans('admin/hardware/message.does_not_exist_var', [ 'asset_tag' => $tag ]));
}
$asset = $assets->first();
$this->authorize('view', $asset);
return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch);
@@ -576,26 +602,20 @@ class AssetsController extends Controller
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function getClone($assetId = null)
public function getClone(Asset $asset)
{
// Check if the asset exists
if (is_null($asset_to_clone = Asset::find($assetId))) {
// Redirect to the asset management page
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('create', $asset_to_clone);
$asset = clone $asset_to_clone;
$asset->id = null;
$asset->asset_tag = '';
$asset->serial = '';
$asset->assigned_to = '';
$this->authorize('create', $asset);
$cloned = clone $asset;
$cloned->id = null;
$cloned->asset_tag = '';
$cloned->serial = '';
$cloned->assigned_to = '';
$cloned->deleted_at = '';
return view('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList())
->with('statuslabel_types', Helper::statusTypeList())
->with('item', $asset);
->with('item', $cloned);
}
/**
@@ -824,7 +844,7 @@ class AssetsController extends Controller
{
$this->authorize('checkin', Asset::class);
return view('hardware/quickscan-checkin');
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
}
public function audit($id)

View File

@@ -92,7 +92,9 @@ class BulkAssetsController extends Controller
// This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id';
$assets = Asset::with('assignedTo', 'location', 'model')->whereIn('assets.id', $asset_ids);
$assets = Asset::with('assignedTo', 'location', 'model')
->whereIn('assets.id', $asset_ids)
->withTrashed();
$assets = $assets->get();
@@ -225,7 +227,8 @@ class BulkAssetsController extends Controller
* its checkout status.
*/
if (($request->filled('purchase_date'))
if (($request->filled('name'))
|| ($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost'))
|| ($request->filled('supplier_id'))
@@ -237,6 +240,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('status_id'))
|| ($request->filled('model_id'))
|| ($request->filled('next_audit_date'))
|| ($request->filled('null_name'))
|| ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
@@ -249,13 +253,14 @@ class BulkAssetsController extends Controller
$this->update_array = [];
/**
* Leave out model_id and status here because we do math on that later. We have to do some extra
* validation and checks on those two.
* Leave out model_id and status here because we do math on that later. We have to do some
* extra validation and checks on those two.
*
* It's tempting to make these match the request check above, but some of these values require
* extra work to make sure the data makes sense.
*/
$this->conditionallyAddItem('purchase_date')
$this->conditionallyAddItem('name')
->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin')
->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable')
@@ -269,6 +274,11 @@ class BulkAssetsController extends Controller
/**
* Blank out fields that were requested to be blanked out via checkbox
*/
if ($request->input('null_name')=='1') {
$this->update_array['name'] = null;
}
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
}
@@ -483,12 +493,7 @@ class BulkAssetsController extends Controller
if ($request->filled('ids')) {
$assets = Asset::find($request->get('ids'));
foreach ($assets as $asset) {
$update_array['deleted_at'] = date('Y-m-d H:i:s');
$update_array['assigned_to'] = null;
DB::table('assets')
->where('id', $asset->id)
->update($update_array);
$asset->delete();
} // endforeach
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));

View File

@@ -508,8 +508,8 @@ class LoginController extends Controller
protected function validator(array $data)
{
return Validator::make($data, [
'username' => 'required',
'password' => 'required',
'username' => 'required|not_array',
'password' => 'required|not_array',
]);
}

View File

@@ -87,7 +87,7 @@ class ResetPasswordController extends Controller
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
];
$request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
$request->validate($this->rules());
Log::debug('Checking if '.$request->input('username').' exists');
// Check to see if the user even exists - we'll treat the response the same to prevent user sniffing

View File

@@ -99,12 +99,18 @@ class SamlController extends Controller
{
$saml = $this->saml;
$auth = $saml->getAuth();
$auth->processResponse();
$saml_exception = false;
try {
$auth->processResponse();
} catch (\Exception $e) {
Log::warning("Exception caught in SAML login: " . $e->getMessage());
$saml_exception = true;
}
$errors = $auth->getErrors();
if (! empty($errors)) {
Log::error('There was an error with SAML ACS: '.implode(', ', $errors));
Log::error('Reason: '.$auth->getLastErrorReason());
if (!empty($errors) || $saml_exception) {
Log::warning('There was an error with SAML ACS: ' . implode(', ', $errors));
Log::warning('Reason: ' . $auth->getLastErrorReason());
return redirect()->route('login')->with('error', trans('auth/message.signin.error'));
}
@@ -132,12 +138,18 @@ class SamlController extends Controller
{
$auth = $this->saml->getAuth();
$retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false);
$sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
$saml_exception = false;
try {
$sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
} catch (\Exception $e) {
Log::warning("Exception caught in SAML single-logout: " . $e->getMessage());
$saml_exception = true;
}
$errors = $auth->getErrors();
if (! empty($errors)) {
Log::error('There was an error with SAML SLS: '.implode(', ', $errors));
Log::error('Reason: '.$auth->getLastErrorReason());
if (!empty($errors) || $saml_exception) {
Log::warning('There was an error with SAML SLS: ' . implode(', ', $errors));
Log::warning('Reason: ' . $auth->getLastErrorReason());
return view('errors.403');
}

View File

@@ -20,7 +20,7 @@ trait CheckInOutRequest
return Location::findOrFail(request('assigned_location'));
case 'asset':
return Asset::findOrFail(request('assigned_asset'));
case 'user':
default:
return User::findOrFail(request('assigned_user'));
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
@@ -96,12 +97,10 @@ class ComponentCheckinController extends Controller
$asset = Asset::find($component_assets->asset_id);
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
if ($backto == 'asset'){
return redirect()->route('hardware.show', $asset->id)->with('success',
trans('admin/components/message.checkin.success'));
}
return redirect()->route('components.index')->with('success',
session()->put(['redirect_option' => $request->get('redirect_option')]);
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success',
trans('admin/components/message.checkin.success'));
}

View File

@@ -4,9 +4,11 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
@@ -93,14 +95,18 @@ class ComponentCheckoutController extends Controller
->withInput();
}
// Check if the user exists
// Check if the asset exists
$asset = Asset::find($request->input('asset_id'));
if ((Setting::getSettings()->full_multiple_companies_support) && $component->company_id !== $asset->company_id) {
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
}
// Update the component data
$component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'user_id' => auth()->user(),
'user_id' => auth()->user()->id,
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => $request->input('assigned_qty'),
'asset_id' => $request->input('asset_id'),
@@ -109,6 +115,11 @@ class ComponentCheckoutController extends Controller
event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note')));
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
$request->request->add(['checkout_to_type' => 'asset']);
$request->request->add(['assigned_asset' => $asset->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success'));
}
}

View File

@@ -86,8 +86,10 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
@@ -160,8 +162,10 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Consumable;
use App\Models\User;
@@ -33,7 +34,7 @@ class ConsumableCheckoutController extends Controller
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0){
return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.checkout.unavailable'));
->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()]));
}
// Return the checkout view
@@ -76,7 +77,7 @@ class ConsumableCheckoutController extends Controller
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => $quantity, 'remaining' => $consumable->numRemaining() ]));
}
$admin_user = auth()->user();
@@ -101,7 +102,13 @@ class ConsumableCheckoutController extends Controller
}
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
$request->request->add(['checkout_to_type' => 'user']);
$request->request->add(['assigned_user' => $user->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
// Redirect to the new consumable page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success'));
}
}

View File

@@ -87,8 +87,10 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($consumable->save()) {
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
@@ -160,8 +162,10 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($consumable->save()) {
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
@@ -200,7 +204,7 @@ class ConsumablesController extends Controller
*/
public function show($consumableId = null)
{
$consumable = Consumable::find($consumableId);
$consumable = Consumable::withCount('users as users_consumables')->find($consumableId);
$this->authorize($consumable);
if (isset($consumable->id)) {
return view('consumables/view', compact('consumable'));
@@ -209,4 +213,16 @@ class ConsumablesController extends Controller
return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.does_not_exist'));
}
public function clone(Consumable $consumable) : View
{
$this->authorize('create', $consumable);
$consumable_to_close = $consumable;
$consumable = clone $consumable_to_close;
$consumable->id = null;
$consumable->image = null;
$consumable->user_id = null;
return view('consumables/edit')->with('item', $consumable);
}
}

View File

@@ -211,7 +211,7 @@ class CustomFieldsetsController extends Controller
return redirect()->route('fieldsets.show', [$id])->with('success', trans('admin/custom_fields/message.field.create.assoc_success'));
}
return redirect()->route('fieldsets.show', [$id])->with('error', 'No field selected.');
return redirect()->route('fieldsets.show', [$id])->with('error', trans('admin/custom_fields/message.field.none_selected'));
}
/**

View File

@@ -62,6 +62,20 @@ class DepreciationsController extends Controller
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
$depreciation->user_id = Auth::id();
$request->validate([
'depreciation_min' => [
'required',
'numeric',
function ($attribute, $value, $fail) use ($request) {
if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
$fail(trans('validation.percent'));
}
},
],
'depreciation_type' => 'required|in:amount,percent',
]);
$depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created?
@@ -116,6 +130,20 @@ class DepreciationsController extends Controller
// Depreciation data
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
$request->validate([
'depreciation_min' => [
'required',
'numeric',
function ($attribute, $value, $fail) use ($request) {
if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
$fail(trans('validation.percent'));
}
},
],
'depreciation_type' => 'required|in:amount,percent',
]);
$depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created?
@@ -165,13 +193,20 @@ class DepreciationsController extends Controller
*/
public function show($id) : View | RedirectResponse
{
if (is_null($depreciation = Depreciation::find($id))) {
// Redirect to the blogs management page
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
}
$depreciation = Depreciation::withCount('assets as assets_count')
->withCount('models as models_count')
->withCount('licenses as licenses_count')
->find($id);
$this->authorize('view', $depreciation);
return view('depreciations/view', compact('depreciation'));
if ($depreciation) {
return view('depreciations/view', compact('depreciation'));
}
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
}
}

View File

@@ -62,10 +62,10 @@ class CheckoutKitController extends Controller
$checkout_result = $this->kitService->checkout($request, $kit, $user);
if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) {
return redirect()->back()->with('error', trans('general.checkout_error'))->with('error_messages', $checkout_result['errors']);
return redirect()->back()->with('error', trans('admin/kits/general.checkout_error'))->with('error_messages', $checkout_result['errors']);
}
return redirect()->back()->with('success', trans('general.checkout_success'))
return redirect()->back()->with('success', trans('admin/kits/general.checkout_success'))
->with('assets', Arr::get($checkout_result, 'assets', null))
->with('accessories', Arr::get($checkout_result, 'accessories', null))
->with('consumables', Arr::get($checkout_result, 'consumables', null));

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\License;
use App\Models\LicenseSeat;
@@ -100,15 +101,15 @@ class LicenseCheckinController extends Controller
$licenseSeat->asset_id = null;
$licenseSeat->notes = $request->input('notes');
session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the asset updated?
if ($licenseSeat->save()) {
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
if ($backTo == 'user') {
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
}
return redirect()->route('licenses.show', $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success'));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success'));
}
// Redirect to the license page with error

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\LicenseCheckoutRequest;
use App\Models\Accessory;
@@ -81,10 +82,27 @@ class LicenseCheckoutController extends Controller
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
if ($this->$checkoutMethod($licenseSeat)) {
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.checkout.success'));
if ($request->filled('asset_id')) {
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
} elseif ($request->filled('assigned_to')) {
$checkoutTarget = $this->checkoutToUser($licenseSeat);
$request->request->add(['assigned_user' => $checkoutTarget->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
}
if ($checkoutTarget) {
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success'));
}
return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.'));
}
@@ -94,14 +112,14 @@ class LicenseCheckoutController extends Controller
if (! $licenseSeat) {
if ($seatId) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.'));
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.unavailable')));
}
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.'));
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
}
if (! $licenseSeat->license->is($license)) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.'));
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
}
return $licenseSeat;
@@ -120,8 +138,7 @@ class LicenseCheckoutController extends Controller
}
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
return true;
return $target;
}
return false;
@@ -137,8 +154,7 @@ class LicenseCheckoutController extends Controller
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
return true;
return $target;
}
return false;

View File

@@ -102,8 +102,10 @@ class LicensesController extends Controller
$license->user_id = Auth::id();
$license->min_amt = $request->input('min_amt');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) {
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($license->getErrors());
@@ -180,8 +182,10 @@ class LicensesController extends Controller
$license->category_id = $request->input('category_id');
$license->min_amt = $request->input('min_amt');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) {
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success'));
}
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
return redirect()->back()->withInput()->withErrors($license->getErrors());

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Location;
use App\Models\User;
@@ -193,7 +194,13 @@ class LocationsController extends Controller
*/
public function show($id = null) : View | RedirectResponse
{
$location = Location::find($id);
$location = Location::withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withTrashed()
->find($id);
if (isset($location->id)) {
return view('locations/view', compact('location'));
@@ -249,6 +256,41 @@ class LocationsController extends Controller
}
/**
* Restore a given Asset Model (mark as un-deleted)
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $id
*/
public function postRestore($id) : RedirectResponse
{
$this->authorize('create', Location::class);
if ($location = Location::withTrashed()->find($id)) {
if ($location->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.location')]));
}
if ($location->restore()) {
$logaction = new Actionlog();
$logaction->item_type = Location::class;
$logaction->item_id = $location->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->user_id = auth()->id();
$logaction->logaction('restore');
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
}
// Check validation
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
}
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}
public function print_all_assigned($id) : View | RedirectResponse
{
if ($location = Location::where('id', $id)->first()) {

View File

@@ -49,6 +49,8 @@ class ProfileController extends Controller
$user->gravatar = $request->input('gravatar');
$user->skin = $request->input('skin');
$user->phone = $request->input('phone');
$user->enable_sounds = $request->input('enable_sounds', false);
$user->enable_confetti = $request->input('enable_confetti', false);
if (! config('app.lock_passwords')) {
$user->locale = $request->input('locale', 'en-US');

View File

@@ -14,7 +14,6 @@ use App\Models\Asset;
use App\Models\User;
use App\Notifications\FirstAdminNotification;
use App\Notifications\MailTest;
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
@@ -129,11 +128,11 @@ class SettingsController extends Controller
protected function dotEnvFileIsExposed() : bool
{
try {
return Http::timeout(10)
return Http::withoutVerifying()->timeout(10)
->accept('*/*')
->get(URL::to('.env'))
->successful();
} catch (HttpClientException $e) {
} catch (\Exception $e) {
Log::debug($e->getMessage());
return true;
}
@@ -325,6 +324,7 @@ class SettingsController extends Controller
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
$setting->unique_serial = $request->input('unique_serial', '0');
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
$setting->show_archived_in_list = $request->input('show_archived_in_list', '0');
$setting->dashboard_message = $request->input('dashboard_message');
@@ -414,10 +414,7 @@ class SettingsController extends Controller
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
if ($request->input('clear_logo') == '1') {
if (($setting->logo) && (Storage::exists($setting->logo))) {
Storage::disk('public')->delete($setting->logo);
}
$setting = $request->deleteExistingImage($setting, '', 'logo');
$setting->logo = null;
$setting->brand = 1;
}
@@ -425,43 +422,38 @@ class SettingsController extends Controller
// Email logo upload
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
if ($request->input('clear_email_logo') == '1') {
if (($setting->email_logo) && (Storage::exists($setting->email_logo))) {
Storage::disk('public')->delete($setting->email_logo);
}
$setting = $request->deleteExistingImage($setting, '', 'email_logo');
$setting->email_logo = null;
// If they are uploading an image, validate it and upload it
}
// Label logo upload
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
if ($request->input('clear_label_logo') == '1') {
if (($setting->label_logo) && (Storage::exists($setting->label_logo))) {
Storage::disk('public')->delete($setting->label_logo);
}
if ($request->input('clear_label_logo') == '1') {
$setting = $request->deleteExistingImage($setting, '', 'label_logo');
$setting->label_logo = null;
}
// Favicon upload
$setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon');
if ('1' == $request->input('clear_favicon')) {
if (($setting->favicon) && (Storage::exists($setting->favicon))) {
Storage::disk('public')->delete($setting->favicon);
}
$setting = $request->deleteExistingImage($setting, '', 'favicon');
$setting->favicon = null;
}
// Default avatar upload
$setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar');
if ($request->input('clear_default_avatar') == '1') {
if (($setting->default_avatar) && (Storage::exists('avatars/'.$setting->default_avatar))) {
Storage::disk('public')->delete('avatars/'.$setting->default_avatar);
if ($request->input('clear_default_avatar') == '1') {
// Don't delete the file, just update the field if this is the default
if ($setting->default_avatar!='default.png') {
$setting = $request->deleteExistingImage($setting, 'avatars', 'default_avatar');
}
$setting->default_avatar = null;
}
if ($request->input('restore_default_avatar') == '1') {
$setting->default_avatar = 'default.png';
}
}
if ($setting->save()) {
@@ -645,6 +637,7 @@ class SettingsController extends Controller
$setting->alert_threshold = $request->input('alert_threshold');
$setting->audit_interval = $request->input('audit_interval');
$setting->audit_warning_days = $request->input('audit_warning_days');
$setting->due_checkin_days = $request->input('due_checkin_days');
$setting->show_alerts_in_menu = $request->input('show_alerts_in_menu', '0');
if ($setting->save()) {
@@ -1211,7 +1204,7 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0]
*/
public function postRestore($filename = null) : RedirectResponse
public function postRestore(Request $request, $filename = null): RedirectResponse
{
if (! config('app.lock_passwords')) {
@@ -1231,13 +1224,29 @@ class SettingsController extends Controller
Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename);
// run the restore command
Artisan::call('snipeit:restore',
[
$restore_params = [
'--force' => true,
'--no-progress' => true,
'filename' => storage_path($path).'/'.$filename
]);
'filename' => storage_path($path) . '/' . $filename
];
if ($request->input('clean')) {
Log::debug("Attempting 'clean' - first, guessing prefix...");
Artisan::call('snipeit:restore', [
'--sanitize-guess-prefix' => true,
'filename' => storage_path($path) . '/' . $filename
]);
$guess_prefix_output = Artisan::output();
Log::debug("Sanitize output is: $guess_prefix_output");
list($prefix, $_output) = explode("\n", $guess_prefix_output);
Log::debug("prefix is: '$prefix'");
$restore_params['--sanitize-with-prefix'] = $prefix;
}
// run the restore command
Artisan::call('snipeit:restore',
$restore_params
);
// If it's greater than 300, it probably worked
$output = Artisan::output();
@@ -1264,7 +1273,7 @@ class SettingsController extends Controller
DB::table('users')->update(['remember_token' => null]);
Auth::logout();
return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.');
return redirect()->route('login')->with('success', trans('admin/settings/message.restore.success'));
} else {
return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found'));
}

View File

@@ -16,6 +16,7 @@ use App\Models\Consumable;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Password;
@@ -29,7 +30,7 @@ class BulkUsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7]
* @param Request $request
* @return \Illuminate\Contracts\View\View
* @return \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit(Request $request)
@@ -115,6 +116,8 @@ class BulkUsersController extends Controller
->conditionallyAddItem('remote')
->conditionallyAddItem('ldap_import')
->conditionallyAddItem('activated')
->conditionallyAddItem('start_date')
->conditionallyAddItem('end_date')
->conditionallyAddItem('autoassign_licenses');
@@ -145,7 +148,14 @@ class BulkUsersController extends Controller
$this->update_array['company_id'] = null;
}
if ($request->input('null_start_date')=='1') {
$this->update_array['start_date'] = null;
}
if ($request->input('null_end_date')=='1') {
$this->update_array['end_date'] = null;
}
if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id');
}
@@ -218,21 +228,19 @@ class BulkUsersController extends Controller
}
$users = User::whereIn('id', $user_raw_array)->get();
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get();
$accessories = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get();
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', User::class)->get();
$accessoryUserRows = DB::table('accessories_checkout')->where('assigned_type', User::class)->whereIn('assigned_to', $user_raw_array)->get();
$licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get();
$consumables = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get();
$consumableUserRows = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get();
if ((($assets->count() > 0) && ((!$request->filled('status_id')) || ($request->input('status_id') == '')))) {
return redirect()->route('users.index')->with('error', 'No status selected');
}
$this->logItemCheckinAndDelete($assets, Asset::class);
$this->logItemCheckinAndDelete($accessories, Accessory::class);
$this->logAccessoriesCheckin($accessoryUserRows);
$this->logItemCheckinAndDelete($licenses, License::class);
$this->logItemCheckinAndDelete($consumables, Consumable::class);
$this->logConsumablesCheckin($consumableUserRows);
Asset::whereIn('id', $assets->pluck('id'))->update([
'status_id' => e(request('status_id')),
@@ -241,19 +249,14 @@ class BulkUsersController extends Controller
'expected_checkin' => null,
]);
LicenseSeat::whereIn('id', $licenses->pluck('id'))->update(['assigned_to' => null]);
ConsumableAssignment::whereIn('id', $consumables->pluck('id'))->delete();
ConsumableAssignment::whereIn('id', $consumableUserRows->pluck('id'))->delete();
foreach ($users as $user) {
$user->consumables()->sync([]);
$user->accessories()->sync([]);
if ($request->input('delete_user')=='1') {
$user->delete();
}
}
$msg = trans('general.bulk_checkin_success');
@@ -279,7 +282,7 @@ class BulkUsersController extends Controller
if ($itemType == License::class){
$item_id = $item->license_id;
}
$logAction->item_id = $item_id;
// We can't rely on get_class here because the licenses/accessories fetched above are not eloquent models, but simply arrays.
$logAction->item_type = $itemType;
@@ -291,6 +294,34 @@ class BulkUsersController extends Controller
}
}
private function logAccessoriesCheckin(Collection $accessoryUserRows): void
{
foreach ($accessoryUserRows as $accessoryUserRow) {
$logAction = new Actionlog();
$logAction->item_id = $accessoryUserRow->accessory_id;
$logAction->item_type = Accessory::class;
$logAction->target_id = $accessoryUserRow->assigned_to;
$logAction->target_type = User::class;
$logAction->user_id = Auth::id();
$logAction->note = 'Bulk checkin items';
$logAction->logaction('checkin from');
}
}
private function logConsumablesCheckin(Collection $consumableUserRows): void
{
foreach ($consumableUserRows as $consumableUserRow) {
$logAction = new Actionlog();
$logAction->item_id = $consumableUserRow->consumable_id;
$logAction->item_type = Consumable::class;
$logAction->target_id = $consumableUserRow->assigned_to;
$logAction->target_type = User::class;
$logAction->user_id = Auth::id();
$logAction->note = 'Bulk checkin items';
$logAction->logaction('checkin from');
}
}
/**
* Save bulk-edited users
*

View File

@@ -133,6 +133,8 @@ class UsersController extends Controller
// we have to invoke the
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($user->save()) {
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
@@ -152,7 +154,7 @@ class UsersController extends Controller
$user->notify(new WelcomeNotification($data));
}
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.create'));
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))->with('success', trans('admin/users/message.success.create'));
}
return redirect()->back()->withInput()->withErrors($user->getErrors());
@@ -184,7 +186,7 @@ class UsersController extends Controller
{
$this->authorize('update', User::class);
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id);
$user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($id);
if ($user) {
@@ -212,83 +214,79 @@ class UsersController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(SaveUserRequest $request, $id = null)
public function update(SaveUserRequest $request, User $user)
{
$this->authorize('update', User::class);
// This is a janky hack to prevent people from changing admin demo user data on the public demo.
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
// Thanks, jerks. You are why we can't have nice things. - snipe
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) {
return redirect()->route('users.index')->with('error', trans('general.permission_denied_superuser_demo'));
}
// We need to reverse the UI specific logic for our
// permissions here before we update the user.
$permissions = $request->input('permissions', []);
app('request')->request->set('permissions', $permissions);
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id);
$user->load(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed();
// User is valid - continue...
if ($user) {
$this->authorize('update', $user);
$this->authorize('update', $user);
// Figure out of this user was an admin before this edit
$orig_permissions_array = $user->decodePermissions();
$orig_superuser = '0';
if (is_array($orig_permissions_array)) {
if (array_key_exists('superuser', $orig_permissions_array)) {
$orig_superuser = $orig_permissions_array['superuser'];
}
// Figure out of this user was an admin before this edit
$orig_permissions_array = $user->decodePermissions();
$orig_superuser = '0';
if (is_array($orig_permissions_array)) {
if (array_key_exists('superuser', $orig_permissions_array)) {
$orig_superuser = $orig_permissions_array['superuser'];
}
}
// Only save groups if the user is a superuser
if (auth()->user()->isSuperUser()) {
$user->groups()->sync($request->input('groups'));
}
// Only save groups if the user is a superuser
if (auth()->user()->isSuperUser()) {
$user->groups()->sync($request->input('groups'));
}
// Update the user fields
$user->username = trim($request->input('username'));
$user->email = trim($request->input('email'));
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
$user->locale = $request->input('locale');
$user->employee_num = $request->input('employee_num');
$user->activated = $request->input('activated', 0);
$user->jobtitle = $request->input('jobtitle', null);
$user->phone = $request->input('phone');
$user->location_id = $request->input('location_id', null);
$user->company_id = Company::getIdForUser($request->input('company_id', null));
$user->manager_id = $request->input('manager_id', null);
$user->notes = $request->input('notes');
$user->department_id = $request->input('department_id', null);
$user->address = $request->input('address', null);
$user->city = $request->input('city', null);
$user->state = $request->input('state', null);
$user->country = $request->input('country', null);
// if a user is editing themselves we should always keep activated true
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
$user->zip = $request->input('zip', null);
$user->remote = $request->input('remote', 0);
$user->vip = $request->input('vip', 0);
$user->website = $request->input('website', null);
$user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null);
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
// Update the user fields
$user->username = trim($request->input('username'));
$user->email = trim($request->input('email'));
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
$user->locale = $request->input('locale');
$user->employee_num = $request->input('employee_num');
$user->activated = $request->input('activated', 0);
$user->jobtitle = $request->input('jobtitle', null);
$user->phone = $request->input('phone');
$user->location_id = $request->input('location_id', null);
$user->company_id = Company::getIdForUser($request->input('company_id', null));
$user->manager_id = $request->input('manager_id', null);
$user->notes = $request->input('notes');
$user->department_id = $request->input('department_id', null);
$user->address = $request->input('address', null);
$user->city = $request->input('city', null);
$user->state = $request->input('state', null);
$user->country = $request->input('country', null);
// if a user is editing themselves we should always keep activated true
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
$user->zip = $request->input('zip', null);
$user->remote = $request->input('remote', 0);
$user->vip = $request->input('vip', 0);
$user->website = $request->input('website', null);
$user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null);
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)
->update(['location_id' => $request->input('location_id', null)]);
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)
->update(['location_id' => $request->input('location_id', null)]);
// Do we want to update the user password?
if ($request->filled('password')) {
$user->password = bcrypt($request->input('password'));
}
// Do we want to update the user password?
if ($request->filled('password')) {
$user->password = bcrypt($request->input('password'));
}
// Update the location of any assets checked out to this user
@@ -309,19 +307,14 @@ class UsersController extends Controller
// Handle uploaded avatar
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($user->save()) {
// Redirect to the user page
return redirect()->route('users.index')
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))
->with('success', trans('admin/users/message.success.update'));
}
return redirect()->back()->withInput()->withErrors($user->getErrors());
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
}
/**
@@ -598,29 +591,29 @@ class UsersController extends Controller
/**
* Print inventory
*
* @author Aladin Alaily
* @since [v1.8]
* @return \Illuminate\Http\RedirectResponse
* @author Aladin Alaily
*/
public function printInventory($id)
{
$this->authorize('view', User::class);
$user = User::where('id', $id)->withTrashed()->first();
if ($user = User::where('id', $id)->withTrashed()->first()) {
// Make sure they can view this particular user
$this->authorize('view', $user);
$this->authorize('view', $user);
$assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get();
$accessories = $user->accessories()->get();
$consumables = $user->consumables()->get();
$assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get();
$accessories = $user->accessories()->get();
$consumables = $user->consumables()->get();
return view('users/print')->with('assets', $assets)
->with('licenses', $user->licenses()->get())
->with('accessories', $accessories)
->with('consumables', $consumables)
->with('show_user', $user)
->with('settings', Setting::getSettings());
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
return view('users/print')->with('assets', $assets)
->with('licenses', $user->licenses()->get())
->with('accessories', $accessories)
->with('consumables', $consumables)
->with('show_user', $user)
->with('settings', Setting::getSettings());
}
/**

View File

@@ -14,6 +14,7 @@ class Kernel extends HttpKernel
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\NoSessionStore::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Session\Middleware\StartSession::class,
@@ -21,6 +22,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\CheckForSetup::class,
\App\Http\Middleware\CheckForDebug::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrimStrings::class,
\App\Http\Middleware\SecurityHeaders::class,
\App\Http\Middleware\PreventBackHistory::class,
\Illuminate\Http\Middleware\HandleCors::class,
@@ -43,10 +45,13 @@ class Kernel extends HttpKernel
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
\App\Http\Middleware\AssetCountForSidebar::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'auth:api',
\App\Http\Middleware\CheckLocale::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

View File

@@ -20,5 +20,5 @@ class EncryptCookies extends BaseEncrypter
*
* @var bool
*/
protected static $serialize = true;
protected static $serialize = false;
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Http\Requests;
use App\Models\Accessory;
use Illuminate\Support\Facades\Gate;
class AccessoryCheckoutRequest extends ImageUploadRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Gate::allows('checkout', new Accessory);
}
public function prepareForValidation(): void
{
if ($this->accessory) {
$this->diff = ($this->accessory->numRemaining() - $this->checkout_qty);
$this->merge([
'checkout_qty' => $this->checkout_qty ?? 1,
'number_remaining_after_checkout' => (int) ($this->accessory->numRemaining() - $this->checkout_qty),
'number_currently_remaining' => (int) $this->accessory->numRemaining(),
'checkout_difference' => (int) $this->diff,
]);
\Log::debug('---------------------------------------------');
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return array_merge(
[
'assigned_user' => 'required_without_all:assigned_asset,assigned_location',
'assigned_asset' => 'required_without_all:assigned_user,assigned_location',
'assigned_location' => 'required_without_all:assigned_user,assigned_asset',
'number_remaining_after_checkout' => [
'min:0',
'required',
'integer',
],
'checkout_qty' => [
'integer',
'lte:number_currently_remaining',
'min:1',
],
],
);
}
public function messages(): array
{
$messages = [
'checkout_qty.lte' => trans_choice('admin/accessories/message.checkout.checkout_qty.lte', $this->number_currently_remaining, [
'number_currently_remaining' => $this->number_currently_remaining,
'checkout_qty' => $this->checkout_qty,
]),
];
return $messages;
}
}

View File

@@ -10,6 +10,7 @@ use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exception\NotReadableException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class ImageUploadRequest extends Request
{
@@ -123,7 +124,7 @@ class ImageUploadRequest extends Request
} catch(NotReadableException $e) {
Log::debug($e);
$validator = \Validator::make([], []);
$validator = Validator::make([], []);
$validator->errors()->add($form_fieldname, trans('general.unaccepted_image_type', ['mimetype' => $image->getClientMimeType()]));
throw new \Illuminate\Validation\ValidationException($validator);
@@ -135,28 +136,28 @@ class ImageUploadRequest extends Request
}
// Remove Current image if exists
if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
try {
Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
} catch (\Exception $e) {
Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
}
}
$item = $this->deleteExistingImage($item, $path, $db_fieldname);
$item->{$db_fieldname} = $file_name;
}
// If the user isn't uploading anything new but wants to delete their old image, do so
} elseif ($this->input('image_delete') == '1') {
Log::debug('Deleting image');
$item = $this->deleteExistingImage($item, $path, $db_fieldname);
}
return $item;
}
public function deleteExistingImage($item, $path = null, $db_fieldname = 'image') {
if ($item->{$db_fieldname}!='') {
try {
Storage::disk('public')->delete($path.'/'.$item->{$db_fieldname});
$item->{$db_fieldname} = null;
$item->{$db_fieldname} = null;
} catch (\Exception $e) {
Log::debug($e);
}
}
return $item;

View File

@@ -6,6 +6,7 @@ use App\Models\Setting;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use App\Rules\UserCannotSwitchCompaniesIfItemsAssigned;
class SaveUserRequest extends FormRequest
{
@@ -34,6 +35,11 @@ class SaveUserRequest extends FormRequest
$rules = [
'department_id' => 'nullable|exists:departments,id',
'manager_id' => 'nullable|exists:users,id',
'company_id' => [
'nullable',
'exists:companies,id',
]
];
switch ($this->method()) {
@@ -52,11 +58,13 @@ class SaveUserRequest extends FormRequest
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
$rules['password'] = Setting::passwordComplexityRulesSaving('update').'|confirmed';
$rules['company_id'] = [new UserCannotSwitchCompaniesIfItemsAssigned()];
break;
// Save only what's passed
case 'PATCH':
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
$rules['company_id'] = [new UserCannotSwitchCompaniesIfItemsAssigned()];
break;
default:

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Http\Requests;
use App\Models\Accessory;
use App\Models\Category;
use Illuminate\Support\Facades\Gate;
class StoreAccessoryRequest extends ImageUploadRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Gate::allows('create', new Accessory);
}
public function prepareForValidation(): void
{
if ($this->category_id) {
if ($category = Category::find($this->category_id)) {
$this->merge([
'category_type' => $category->category_type ?? null,
]);
}
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return array_merge(
['category_type' => 'in:accessory'],
parent::rules(),
);
}
public function messages(): array
{
$messages = ['category_type.in' => trans('admin/accessories/message.invalid_category_type')];
return $messages;
}
public function response(array $errors)
{
return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Requests;
use App\Http\Requests\Traits\MayContainCustomFields;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Setting;
@@ -11,6 +12,7 @@ use Illuminate\Support\Facades\Gate;
class StoreAssetRequest extends ImageUploadRequest
{
use MayContainCustomFields;
/**
* Determine if the user is authorized to make this request.
*

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests\Traits;
use App\Models\AssetModel;
use App\Models\CustomField;
trait MayContainCustomFields
{
// this gets called automatically on a form request
public function withValidator($validator)
{
// find the model
if ($this->method() == 'POST') {
$asset_model = AssetModel::find($this->model_id);
}
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
$asset_model = $this->asset->model;
}
// collect the custom fields in the request
$validator->after(function ($validator) use ($asset_model) {
$request_fields = $this->collect()->keys()->filter(function ($attributes) {
return str_starts_with($attributes, '_snipeit_');
});
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
if (count($request_fields) > 0) {
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
->each(function ($request_field_name) use ($request_fields, $validator) {
if (CustomField::where('db_column', $request_field_name)->exists()) {
$validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found_on_model'));
} else {
$validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found'));
}
});
}
});
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Requests;
use App\Http\Requests\Traits\MayContainCustomFields;
use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class UpdateAssetRequest extends ImageUploadRequest
{
use MayContainCustomFields;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('update', $this->asset);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = array_merge(
parent::rules(),
(new Asset)->getRules(),
// this is to overwrite rulesets that include required, and rewrite unique_undeleted
[
'model_id' => ['integer', 'exists:models,id,deleted_at,NULL', 'not_array'],
'status_id' => ['integer', 'exists:status_labels,id'],
'asset_tag' => [
'min:1', 'max:255', 'not_array',
Rule::unique('assets', 'asset_tag')->ignore($this->asset)->withoutTrashed()
],
],
);
// if the purchase cost is passed in as a string **and** the digit_separator is ',' (as is common in the EU)
// then we tweak the purchase_cost rule to make it a string
if (Setting::getSettings()->digit_separator === '1.234,56' && is_string($this->input('purchase_cost'))) {
$rules['purchase_cost'] = ['nullable', 'string'];
}
return $rules;
}
}

View File

@@ -11,15 +11,17 @@ trait TwoColumnUniqueUndeletedTrait
* @param string $field
* @return string
*/
protected function prepareTwoColumnUniqueUndeletedRule($parameters, $field)
protected function prepareTwoColumnUniqueUndeletedRule($parameters)
{
$column = $parameters[0];
$value = $this->{$parameters[0]};
// This is an existing model we're updating so ignore the current ID ($this->getKey())
if ($this->exists) {
return 'two_column_unique_undeleted:'.$this->table.','.$this->getKey().','.$column.','.$value;
}
// This is a new record, so we can ignore the current ID
return 'two_column_unique_undeleted:'.$this->table.',0,'.$column.','.$value;
}
}

View File

@@ -39,7 +39,7 @@ class AccessoriesTransformer
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
'remaining_qty' => (int) $accessory->numRemaining(),
'users_count' => $accessory->users_count,
'checkouts_count' => $accessory->checkouts_count,
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
@@ -66,27 +66,42 @@ class AccessoriesTransformer
return $array;
}
public function transformCheckedoutAccessory($accessory, $accessory_users, $total)
public function transformCheckedoutAccessory($accessory, $accessory_checkouts, $total)
{
$array = [];
foreach ($accessory_users as $user) {
foreach ($accessory_checkouts as $checkout) {
$array[] = [
'assigned_pivot_id' => $user->pivot->id,
'id' => (int) $user->id,
'username' => e($user->username),
'name' => e($user->getFullNameAttribute()),
'first_name'=> e($user->first_name),
'last_name'=> e($user->last_name),
'employee_number' => e($user->employee_num),
'checkout_notes' => e($user->pivot->note),
'last_checkout' => Helper::getFormattedDateObject($user->pivot->created_at, 'datetime'),
'type' => 'user',
'id' => $checkout->id,
'assigned_to' => $this->transformAssignedTo($checkout),
'checkout_notes' => e($checkout->note),
'last_checkout' => Helper::getFormattedDateObject($checkout->created_at, 'datetime'),
'available_actions' => ['checkin' => true],
];
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformAssignedTo($accessoryCheckout)
{
if ($accessoryCheckout->checkedOutToUser()) {
return [
'id' => (int) $accessoryCheckout->assigned->id,
'username' => e($accessoryCheckout->assigned->username),
'name' => e($accessoryCheckout->assigned->getFullNameAttribute()),
'first_name'=> e($accessoryCheckout->assigned->first_name),
'last_name'=> ($accessoryCheckout->assigned->last_name) ? e($accessoryCheckout->assigned->last_name) : null,
'email'=> ($accessoryCheckout->assigned->email) ? e($accessoryCheckout->assigned->email) : null,
'employee_number' => ($accessoryCheckout->assigned->employee_num) ? e($accessoryCheckout->assigned->employee_num) : null,
'type' => 'user',
];
}
return $accessoryCheckout->assigned ? [
'id' => $accessoryCheckout->assigned->id,
'name' => e($accessoryCheckout->assigned->display_name),
'type' => $accessoryCheckout->assignedType(),
] : null;
}
}

View File

@@ -205,11 +205,11 @@ class ActionlogsTransformer
public function transformCheckedoutActionlog (Collection $accessories_users, $total)
public function transformCheckedoutActionlog (Collection $accessories_checkout, $total)
{
$array = array();
foreach ($accessories_users as $user) {
foreach ($accessories_checkout as $user) {
$array[] = (new UsersTransformer)->transformUser($user);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
@@ -256,7 +256,7 @@ class ActionlogsTransformer
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $oldRtdName : '';
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $newRtdName : '';
$clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
$clean_meta[trans('admin/hardware/form.default_location')] = $clean_meta['rtd_location_id'];
unset($clean_meta['rtd_location_id']);
}
@@ -272,7 +272,7 @@ class ActionlogsTransformer
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ". $oldLocationName : '';
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ". $newLocationName : '';
$clean_meta['Current Location'] = $clean_meta['location_id'];
$clean_meta[trans('admin/locations/message.current_location')] = $clean_meta['location_id'];
unset($clean_meta['location_id']);
}
@@ -287,7 +287,7 @@ class ActionlogsTransformer
$clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".$oldModelName;
$clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".$newModelName; /** model is required at asset creation */
$clean_meta['Model'] = $clean_meta['model_id'];
$clean_meta[trans('admin/hardware/form.model')] = $clean_meta['model_id'];
unset($clean_meta['model_id']);
}
if(array_key_exists('company_id', $clean_meta)) {
@@ -300,7 +300,7 @@ class ActionlogsTransformer
$clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."] ". $oldCompanyName : trans('general.unassigned');
$clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ". $newCompanyName : trans('general.unassigned');
$clean_meta['Company'] = $clean_meta['company_id'];
$clean_meta[trans('general.company')] = $clean_meta['company_id'];
unset($clean_meta['company_id']);
}
if(array_key_exists('supplier_id', $clean_meta)) {
@@ -313,7 +313,7 @@ class ActionlogsTransformer
$clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ". $oldSupplierName : trans('general.unassigned');
$clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ". $newSupplierName : trans('general.unassigned');
$clean_meta['Supplier'] = $clean_meta['supplier_id'];
$clean_meta[trans('general.supplier')] = $clean_meta['supplier_id'];
unset($clean_meta['supplier_id']);
}
if(array_key_exists('status_id', $clean_meta)) {
@@ -326,11 +326,11 @@ class ActionlogsTransformer
$clean_meta['status_id']['old'] = $clean_meta['status_id']['old'] ? "[id: ".$clean_meta['status_id']['old']."] ". $oldStatusName : trans('general.unassigned');
$clean_meta['status_id']['new'] = $clean_meta['status_id']['new'] ? "[id: ".$clean_meta['status_id']['new']."] ". $newStatusName : trans('general.unassigned');
$clean_meta['Status'] = $clean_meta['status_id'];
$clean_meta[trans('general.status_label')] = $clean_meta['status_id'];
unset($clean_meta['status_id']);
}
if(array_key_exists('asset_eol_date', $clean_meta)) {
$clean_meta['EOL date'] = $clean_meta['asset_eol_date'];
$clean_meta[trans('admin/hardware/form.eol_date')] = $clean_meta['asset_eol_date'];
unset($clean_meta['asset_eol_date']);
}

View File

@@ -86,7 +86,7 @@ class AssetsTransformer
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'),
'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'),
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '',
'age' => $asset->purchase_date ? $asset->purchase_date->locale(app()->getLocale())->diffForHumans() : '',
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
'last_checkin' => Helper::getFormattedDateObject($asset->last_checkin, 'datetime'),
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),

View File

@@ -55,6 +55,7 @@ class ConsumablesTransformer
'checkin' => Gate::allows('checkin', Consumable::class),
'update' => Gate::allows('update', Consumable::class),
'delete' => Gate::allows('delete', Consumable::class),
'clone' => (Gate::allows('create', Consumable::class) && ($consumable->deleted_at == '')),
];
$array += $permissions_array;

View File

@@ -7,6 +7,7 @@ use App\Models\Depreciable;
use App\Models\Depreciation;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
class DepreciationsTransformer
{
@@ -26,7 +27,10 @@ class DepreciationsTransformer
'id' => (int) $depreciation->id,
'name' => e($depreciation->name),
'months' => $depreciation->months.' '.trans('general.months'),
'depreciation_min' => $depreciation->depreciation_min,
'depreciation_min' => $depreciation->depreciation_type === 'percent' ? $depreciation->depreciation_min.'%' : $depreciation->depreciation_min,
'assets_count' => $depreciation->assets_count,
'models_count' => $depreciation->models_count,
'licenses_count' => $depreciation->licenses_count,
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime')
];

View File

@@ -51,7 +51,7 @@ class LicenseSeatsTransformer
];
if ($seat_count != 0) {
$array['name'] = 'Seat '.$seat_count;
$array['name'] = trans('admin/licenses/general.seat_count', ['count' => $seat_count]);
}
$permissions_array['available_actions'] = [

View File

@@ -45,6 +45,10 @@ class LicensesTransformer
'maintained' => ($license->maintained == 1) ? true : false,
'supplier' => ($license->supplier) ? ['id' => (int) $license->supplier->id, 'name'=> e($license->supplier->name)] : null,
'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null,
'created_by' => ($license->adminuser) ? [
'id' => (int) $license->adminuser->id,
'name'=> e($license->adminuser->present()->fullName()),
] : null,
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),

View File

@@ -3,14 +3,10 @@
namespace App\Importer;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\User;
use App\Events\CheckoutableCheckedIn;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Crypt;
class AssetImporter extends ItemImporter
{
@@ -75,8 +71,10 @@ class AssetImporter extends ItemImporter
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
if ($asset) {
if (! $this->updating) {
$this->log('A matching Asset '.$asset_tag.' already exists');
return;
$exists_error = trans('general.import_asset_tag_exists', ['asset_tag' => $asset_tag]);
$this->log($exists_error);
$this->addErrorToBag($asset, 'asset_tag', $exists_error);
return $exists_error;
}
$this->log('Updating Asset');

View File

@@ -281,6 +281,13 @@ abstract class Importer
}
}
protected function addErrorToBag($item, $field, $error_message)
{
if ($this->errorCallback) {
call_user_func($this->errorCallback, $item, $field, [$field => [$error_message]]);
}
}
/**
* Finds the user matching given data, or creates a new one if there is no match.
* This is NOT used by the User Import, only for Asset/Accessory/etc where

View File

@@ -196,64 +196,77 @@ class ItemImporter extends Importer
{
$condition = array();
$asset_model_name = $this->findCsvMatch($row, 'asset_model');
$asset_model_category = $this->findCsvMatch($row, 'category');
$asset_modelNumber = $this->findCsvMatch($row, 'model_number');
// TODO: At the moment, this means we can't update the model number if the model name stays the same.
if (! $this->shouldUpdateField($asset_model_name)) {
return;
}
if ((empty($asset_model_name)) && (! empty($asset_modelNumber))) {
$asset_model_name = $asset_modelNumber;
} elseif ((empty($asset_model_name)) && (empty($asset_modelNumber))) {
$asset_model_name = 'Unknown';
}
if ((!empty($asset_model_name)) && (empty($asset_modelNumber))) {
$condition[] = ['name', '=', $asset_model_name];
} elseif ((!empty($asset_model_name)) && (!empty($asset_modelNumber))) {
$condition[] = ['name', '=', $asset_model_name];
$condition[] = ['model_number', '=', $asset_modelNumber];
$asset_model = AssetModel::select('id');
if (!empty($asset_model_name)) {
$asset_model = $asset_model->where('name', '=', $asset_model_name);
if (!empty($asset_modelNumber)) {
$asset_model = $asset_model->where('model_number', '=', $asset_modelNumber);
}
}
$editingModel = $this->updating;
$asset_model = AssetModel::where($condition)->first();
$asset_model = $asset_model->first();
if ($asset_model) {
if (! $this->updating) {
$this->log('A matching model already exists, returning it.');
return $asset_model->id;
}
$this->log('Matching Model found, updating it.');
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
$item['name'] = $asset_model_name;
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
if(!empty($asset_modelNumber)){
if (!empty($asset_modelNumber)){
$item['model_number'] = $asset_modelNumber;
}
$asset_model->update($item);
$asset_model->save();
$this->log('Asset Model Updated');
return $asset_model->id;
}
$this->log('No Matching Model, Creating a new one');
}
$this->log('No Matching Model, Creating a new one');
$asset_model = new AssetModel();
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
$item['name'] = $asset_model_name;
$item['model_number'] = $asset_modelNumber;
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
$item['category_id'] = $this->createOrFetchCategory($asset_model_category);
$asset_model->fill($item);
//$asset_model = AssetModel::firstOrNew($item);
$item = null;
if ($asset_model->save()) {
$this->log('Asset Model '.$asset_model_name.' with model number '.$asset_modelNumber.' was created');
return $asset_model->id;
}
$this->log('Asset Model Errors: '.$asset_model->getErrors());
$this->logError($asset_model, 'Asset Model "'.$asset_model_name.'"');
return null;

View File

@@ -67,6 +67,7 @@ class UserImporter extends ItemImporter
$this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0;
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0;
$this->handleEmptyStringsForDates();
$user_department = trim($this->findCsvMatch($row, 'department'));
if ($this->shouldUpdateField($user_department)) {
@@ -179,4 +180,22 @@ class UserImporter extends ItemImporter
{
$this->send_welcome = $send;
}
/**
* Since the findCsvMatch() method will set '' for columns that are present but empty,
* we need to set those empty strings to null to avoid passing bad data to the database
* (ie ending up with 0000-00-00 instead of the intended null).
*
* @return void
*/
private function handleEmptyStringsForDates(): void
{
if ($this->item['start_date'] === '') {
$this->item['start_date'] = null;
}
if ($this->item['end_date'] === '') {
$this->item['end_date'] = null;
}
}
}

View File

@@ -137,7 +137,11 @@ class CheckoutableListener
*/
private function getCheckoutAcceptance($event)
{
if (! $event->checkoutable->requireAcceptance()) {
$checkedOutToType = get_class($event->checkedOutTo);
if ($checkedOutToType != "App\Models\User") {
return null;
}
if (!$event->checkoutable->requireAcceptance()) {
return null;
}

View File

@@ -3,30 +3,25 @@
namespace App\Livewire;
use App\Models\CustomField;
use Livewire\Component;
use App\Models\Import;
use Illuminate\Support\Facades\Storage;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Computed;
use Livewire\Component;
class Importer extends Component
{
use AuthorizesRequests;
public $files;
public $progress; //upload progress - '-1' means don't show
public $progress = -1; //upload progress - '-1' means don't show
public $progress_message;
public $progress_bar_class;
public $progress_bar_class = 'progress-bar-warning';
public $message; //status/error message?
public $message_type; //success/error?
//originally from ImporterFile
public $import_errors; //
public ?Import $activeFile = null;
public $activeFileId;
public $headerRow = [];
public $typeOfImport;
public $importTypes;
public $columnOptions;
public $statusType;
@@ -35,7 +30,6 @@ class Importer extends Component
public $send_welcome;
public $run_backup;
public $field_map; // we need a separate variable for the field-mapping, because the keys in the normal array are too complicated for Livewire to understand
public $file_id; // TODO: I can't figure out *why* we need this, but it really seems like we do. I can't seem to pull the id from the activeFile for some reason?
// Make these variables public - we set the properties in the constructor so we can localize them (versus the old static arrays)
public $accessories_fields;
@@ -51,10 +45,8 @@ class Importer extends Component
'files.*.file_path' => 'required|string',
'files.*.created_at' => 'required|string',
'files.*.filesize' => 'required|integer',
'activeFile' => 'Import',
'activeFile.import_type' => 'string',
'activeFile.field_map' => 'array',
'activeFile.header_row' => 'array',
'headerRow' => 'array',
'typeOfImport' => 'string',
'field_map' => 'array'
];
@@ -68,15 +60,13 @@ class Importer extends Component
{
$tmp = array();
if ($this->activeFile) {
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
$tmp = array_combine($this->headerRow, $this->field_map);
$tmp = array_filter($tmp);
}
return json_encode($tmp);
}
private function getColumns($type)
{
switch ($type) {
@@ -115,76 +105,66 @@ class Importer extends Component
return $results;
}
public function updating($name, $new_import_type)
public function updatingTypeOfImport($type)
{
if ($name == "activeFile.import_type") {
// go through each header, find a matching field to try and map it to.
foreach ($this->activeFile->header_row as $i => $header) {
// do we have something mapped already?
if (array_key_exists($i, $this->field_map)) {
// yes, we do. Is it valid for this type of import?
// (e.g. the import type might have been changed...?)
if (array_key_exists($this->field_map[$i], $this->columnOptions[$new_import_type])) {
//yes, this key *is* valid. Continue on to the next field.
continue;
} else {
//no, this key is *INVALID* for this import type. Better set it to null
// and we'll hope that the $aliases_fields or something else picks it up.
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
} // TODO - strictly speaking, this isn't necessary here I don't think.
// go through each header, find a matching field to try and map it to.
foreach ($this->headerRow as $i => $header) {
// do we have something mapped already?
if (array_key_exists($i, $this->field_map)) {
// yes, we do. Is it valid for this type of import?
// (e.g. the import type might have been changed...?)
if (array_key_exists($this->field_map[$i], $this->columnOptions[$type])) {
//yes, this key *is* valid. Continue on to the next field.
continue;
} else {
//no, this key is *INVALID* for this import type. Better set it to null
// and we'll hope that the $aliases_fields or something else picks it up.
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
} // TODO - strictly speaking, this isn't necessary here I don't think.
}
// first, check for exact matches
foreach ($this->columnOptions[$type] as $v => $text) {
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
$this->field_map[$i] = $v;
continue 2; //don't bother with the alias check, go to the next header
}
// first, check for exact matches
foreach ($this->columnOptions[$new_import_type] as $value => $text) {
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
$this->field_map[$i] = $value;
continue 2; //don't bother with the alias check, go to the next header
}
}
// if you got here, we didn't find a match. Try the $aliases_fields
foreach ($this->aliases_fields as $key => $alias_values) {
foreach ($alias_values as $alias_value) {
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
// Make *absolutely* sure that this key actually _exists_ in this import type -
// you can trigger this by importing accessories with a 'Warranty' column (which don't exist
// in "Accessories"!)
if (array_key_exists($key, $this->columnOptions[$new_import_type])) {
$this->field_map[$i] = $key;
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
}
}
// if you got here, we didn't find a match. Try the $aliases_fields
foreach ($this->aliases_fields as $key => $alias_values) {
foreach ($alias_values as $alias_value) {
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
// Make *absolutely* sure that this key actually _exists_ in this import type -
// you can trigger this by importing accessories with a 'Warranty' column (which don't exist
// in "Accessories"!)
if (array_key_exists($key, $this->columnOptions[$type])) {
$this->field_map[$i] = $key;
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
}
}
}
// and if you got here, we got nothing. Let's recommend 'null'
$this->field_map[$i] = null; // Booooo :(
}
// and if you got here, we got nothing. Let's recommend 'null'
$this->field_map[$i] = null; // Booooo :(
}
}
public function boot() { // FIXME - delete or undelete.
///////$this->activeFile = null; // I do *not* understand why I have to do this, but, well, whatever.
}
public function mount()
{
$this->authorize('import');
$this->progress = -1; // '-1' means 'don't show the progressbar'
$this->progress_bar_class = 'progress-bar-warning';
$this->importTypes = [
'asset' => trans('general.assets'),
'accessory' => trans('general.accessories'),
'asset' => trans('general.assets'),
'accessory' => trans('general.accessories'),
'consumable' => trans('general.consumables'),
'component' => trans('general.components'),
'license' => trans('general.licenses'),
'user' => trans('general.users'),
'location' => trans('general.locations'),
'component' => trans('general.components'),
'license' => trans('general.licenses'),
'user' => trans('general.users'),
'location' => trans('general.locations'),
];
/**
* These are the item-type specific columns
*/
$this->accessories_fields = [
$this->accessories_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
@@ -307,7 +287,7 @@ class Importer extends Component
'manufacturer' => trans('general.manufacturer'),
];
$this->users_fields = [
$this->users_fields = [
'id' => trans('general.id'),
'company' => trans('general.company'),
'location' => trans('general.location'),
@@ -332,12 +312,12 @@ class Importer extends Component
'website' => trans('general.website'),
'avatar' => trans('general.image'),
'gravatar' => trans('general.importer.gravatar'),
'start_date' => trans('general.start_date'),
'end_date' => trans('general.end_date'),
'employee_num' => trans('general.employee_number'),
'start_date' => trans('general.start_date'),
'end_date' => trans('general.end_date'),
'employee_num' => trans('general.employee_number'),
];
$this->locations_fields = [
$this->locations_fields = [
'name' => trans('general.item_name_var', ['item' => trans('general.location')]),
'address' => trans('general.address'),
'address2' => trans('general.importer.address2'),
@@ -374,6 +354,12 @@ class Importer extends Component
'model name',
'model',
],
'eol_date' =>
[
'eol',
'eol date',
'asset eol date',
],
'gravatar' =>
[
'gravatar',
@@ -504,19 +490,16 @@ class Importer extends Component
];
$this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean
foreach($this->importTypes AS $type => $name) {
foreach ($this->importTypes as $type => $name) {
$this->columnOptions[$type] = $this->getColumns($type);
}
if ($this->activeFile) {
$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : [];
}
}
public function selectFile($id)
{
$this->clearMessage();
$this->activeFile = Import::find($id);
$this->activeFileId = $id;
if (!$this->activeFile) {
$this->message = trans('admin/hardware/message.import.file_missing');
@@ -525,15 +508,17 @@ class Importer extends Component
return;
}
$this->headerRow = $this->activeFile->header_row;
$this->typeOfImport = $this->activeFile->import_type;
$this->field_map = null;
foreach($this->activeFile->header_row as $element) {
if(isset($this->activeFile->field_map[$element])) {
foreach ($this->headerRow as $element) {
if (isset($this->activeFile->field_map[$element])) {
$this->field_map[] = $this->activeFile->field_map[$element];
} else {
$this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings
}
}
$this->file_id = $id;
$this->import_errors = null;
$this->statusText = null;
@@ -541,21 +526,33 @@ class Importer extends Component
public function destroy($id)
{
// TODO: why don't we just do File::find($id)? This seems dumb.
foreach($this->files as $file) {
if ($id == $file->id) {
if (Storage::delete('private_uploads/imports/'.$file->file_path)) {
$file->delete();
$this->authorize('import');
$this->message = trans('admin/hardware/message.import.file_delete_success');
$this->message_type = 'success';
return;
} else {
$this->message = trans('admin/hardware/message.import.file_delete_error');
$this->message_type = 'danger';
}
}
$import = Import::find($id);
// Check that the import wasn't deleted after while page was already loaded...
// @todo: next up...handle the file being missing for other interactions...
// for example having an import open in two tabs, deleting it, and then changing
// the import type in the other tab. The error message below wouldn't display in that case.
if (!$import) {
$this->message = trans('admin/hardware/message.import.file_already_deleted');
$this->message_type = 'danger';
return;
}
if (Storage::delete('private_uploads/imports/' . $import->file_path)) {
$import->delete();
$this->message = trans('admin/hardware/message.import.file_delete_success');
$this->message_type = 'success';
unset($this->files);
return;
}
$this->message = trans('admin/hardware/message.import.file_delete_error');
$this->message_type = 'danger';
}
public function clearMessage()
@@ -564,11 +561,22 @@ class Importer extends Component
$this->message_type = null;
}
#[Computed]
public function files()
{
return Import::orderBy('id', 'desc')->get();
}
#[Computed]
public function activeFile()
{
return Import::find($this->activeFileId);
}
public function render()
{
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
return view('livewire.importer')
->extends('layouts.default')
->section('content');
->extends('layouts.default')
->section('content');
}
}

View File

@@ -63,7 +63,7 @@ class Accessory extends SnipeModel
'company_id' => 'integer|nullable',
'min_amt' => 'integer|min:0|nullable',
'purchase_cost' => 'numeric|nullable|gte:0',
'purchase_date' => 'date_format:Y-m-d|nullable',
'purchase_date' => 'date_format:Y-m-d|nullable',
];
@@ -253,9 +253,10 @@ class Accessory extends SnipeModel
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function users()
public function checkouts()
{
return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->withPivot('id', 'created_at', 'note')->withTrashed();
return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id')
->with('assignedTo');
}
/**
@@ -267,7 +268,9 @@ class Accessory extends SnipeModel
*/
public function hasUsers()
{
return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->count();
return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id')
->where('assigned_type', User::class)
->count();
}
/**
@@ -329,11 +332,24 @@ class Accessory extends SnipeModel
}
/**
* Check how many items within an accessory are checked out
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0]
* @return int
*/
public function numCheckedOut()
{
return $this->checkouts_count ?? $this->checkouts()->count();
}
/**
* Check how many items of an accessory remain.
*
* In order to use this model method, you MUST call withCount('users as users_count')
* on the eloquent query in the controller, otherwise $this->>users_count will be null and
* In order to use this model method, you MUST call withCount('checkouts as checkouts_count')
* on the eloquent query in the controller, otherwise $this->checkouts_count will be null and
* bad things happen.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
@@ -342,11 +358,11 @@ class Accessory extends SnipeModel
*/
public function numRemaining()
{
$checkedout = $this->users_count;
$checkedout = $this->numCheckedOut();
$total = $this->qty;
$remaining = $total - $checkedout;
return (int) $remaining;
return $remaining;
}
/**
@@ -357,12 +373,12 @@ class Accessory extends SnipeModel
*/
public function declinedCheckout(User $declinedBy, $signature)
{
if (is_null($accessory_user = \DB::table('accessories_users')->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) {
if (is_null($accessory_checkout = AccessoryCheckout::userAssigned()->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$accessory_user->limit(1)->delete();
$accessory_checkout->limit(1)->delete();
}
/**

148
app/Models/AccessoryCheckout.php Executable file
View File

@@ -0,0 +1,148 @@
<?php
namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
/**
* Model for Accessories.
*
* @version v1.0
*/
class AccessoryCheckout extends Model
{
use Searchable;
protected $fillable = ['user_id', 'accessory_id', 'assigned_to', 'assigned_type', 'note'];
protected $table = 'accessories_checkout';
/**
* Establishes the accessory checkout -> accessory relationship
*
* @author [A. Kroeger]
* @since [v7.0.9]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function accessory()
{
return $this->hasOne(\App\Models\Accessory::class, 'accessory_id');
}
/**
* Establishes the accessory checkout -> user relationship
*
* @author [A. Kroeger]
* @since [v7.0.9]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function user()
{
return $this->hasOne(\App\Models\User::class, 'user_id');
}
/**
* Get the target this asset is checked out to
*
* @author [A. Kroeger]
* @since [v7.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assignedTo()
{
return $this->morphTo('assigned', 'assigned_type', 'assigned_to')->withTrashed();
}
/**
* Gets the lowercased name of the type of target the asset is assigned to
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return string
*/
public function assignedType()
{
return $this->assigned_type ? strtolower(class_basename($this->assigned_type)) : null;
}
/**
* Determines whether the accessory is checked out to a user
*
* Even though we allow allow for checkout to things beyond users
* this method is an easy way of seeing if we are checked out to a user.
*
* @author [A. Kroeger]
* @since [v7.0]
*/
public function checkedOutToUser(): bool
{
return $this->assignedType() === Asset::USER;
}
public function scopeUserAssigned(Builder $query): void
{
$query->where('assigned_type', '=', User::class);
}
/**
* Run additional, advanced searches.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array $terms The search terms
* @return \Illuminate\Database\Eloquent\Builder
*/
public function advancedTextSearch(Builder $query, array $terms)
{
$userQuery = User::where(function ($query) use ($terms) {
foreach ($terms as $term) {
$search_str = '%' . $term . '%';
$query->where('first_name', 'like', $search_str)
->orWhere('last_name', 'like', $search_str)
->orWhere('note', 'like', $search_str);
}
})->select('id');
$locationQuery = Location::where(function ($query) use ($terms) {
foreach ($terms as $term) {
$search_str = '%' . $term . '%';
$query->where('name', 'like', $search_str);
}
})->select('id');
$assetQuery = Asset::where(function ($query) use ($terms) {
foreach ($terms as $term) {
$search_str = '%' . $term . '%';
$query->where('name', 'like', $search_str);
}
})->select('id');
$query->where(function ($query) use ($userQuery) {
$query->where('assigned_type', User::class)
->whereIn('assigned_to', $userQuery);
})->orWhere(function($query) use ($locationQuery) {
$query->where('assigned_type', Location::class)
->whereIn('assigned_to', $locationQuery);
})->orWhere(function($query) use ($assetQuery) {
$query->where('assigned_type', Asset::class)
->whereIn('assigned_to', $assetQuery);
})->orWhere(function($query) use ($terms) {
foreach ($terms as $term) {
$search_str = '%' . $term . '%';
$query->where('note', 'like', $search_str);
}
});
return $query;
}
}

View File

@@ -7,7 +7,6 @@ use App\Presenters\Presentable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Auth;
/**
* Model for the Actionlog (the table that keeps a historical log of
@@ -17,10 +16,12 @@ use Illuminate\Support\Facades\Auth;
*/
class Actionlog extends SnipeModel
{
use CompanyableTrait;
use HasFactory;
// This is to manually set the source (via setActionSource()) for determineActionSource()
protected ?string $source = null;
protected $with = ['admin'];
protected $presenter = \App\Presenters\ActionlogPresenter::class;
use SoftDeletes;
@@ -372,4 +373,9 @@ class Actionlog extends SnipeModel
{
$this->source = $source;
}
public function scopeOrderAdmin($query, $order)
{
return $query->leftJoin('users as admin_sort', 'action_logs.user_id', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
}
}

View File

@@ -2,7 +2,6 @@
namespace App\Models;
use App\Events\AssetCheckedOut;
use App\Events\CheckoutableCheckedOut;
use App\Exceptions\CheckoutNotAllowed;
use App\Helpers\Helper;
@@ -31,6 +30,7 @@ class Asset extends Depreciable
{
protected $presenter = AssetPresenter::class;
protected $with = ['model', 'admin'];
use CompanyableTrait;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
@@ -97,35 +97,38 @@ class Asset extends Depreciable
];
protected $rules = [
'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array',
'status_id' => 'required|integer|exists:status_labels,id',
'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag|not_array',
'name' => 'nullable|max:255',
'company_id' => 'nullable|integer|exists:companies,id',
'warranty_months' => 'nullable|numeric|digits_between:0,240',
'last_checkout' => 'nullable|date_format:Y-m-d H:i:s',
'last_checkin' => 'nullable|date_format:Y-m-d H:i:s',
'expected_checkin' => 'nullable|date',
'last_audit_date' => 'nullable|date_format:Y-m-d H:i:s',
// 'next_audit_date' => 'nullable|date|after:last_audit_date',
'next_audit_date' => 'nullable|date',
'location_id' => 'nullable|exists:locations,id',
'rtd_location_id' => 'nullable|exists:locations,id',
'purchase_date' => 'nullable|date|date_format:Y-m-d',
'serial' => 'nullable|unique_undeleted:assets,serial',
'purchase_cost' => 'nullable|numeric|gte:0',
'supplier_id' => 'nullable|exists:suppliers,id',
'asset_eol_date' => 'nullable|date',
'eol_explicit' => 'nullable|boolean',
'byod' => 'nullable|boolean',
'order_number' => 'nullable|string|max:191',
'notes' => 'nullable|string|max:65535',
'assigned_to' => 'nullable|integer',
'requestable' => 'nullable|boolean',
'model_id' => ['required', 'integer', 'exists:models,id,deleted_at,NULL', 'not_array'],
'status_id' => ['required', 'integer', 'exists:status_labels,id'],
'asset_tag' => ['required', 'min:1', 'max:255', 'unique_undeleted:assets,asset_tag', 'not_array'],
'name' => ['nullable', 'max:255'],
'company_id' => ['nullable', 'integer', 'exists:companies,id'],
'warranty_months' => ['nullable', 'numeric', 'digits_between:0,240'],
'last_checkout' => ['nullable', 'date_format:Y-m-d H:i:s'],
'last_checkin' => ['nullable', 'date_format:Y-m-d H:i:s'],
'expected_checkin' => ['nullable', 'date'],
'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'],
'next_audit_date' => ['nullable', 'date'],
//'after:last_audit_date'],
'location_id' => ['nullable', 'exists:locations,id'],
'rtd_location_id' => ['nullable', 'exists:locations,id'],
'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
'serial' => ['nullable', 'unique_undeleted:assets,serial'],
'purchase_cost' => ['nullable', 'numeric', 'gte:0'],
'supplier_id' => ['nullable', 'exists:suppliers,id'],
'asset_eol_date' => ['nullable', 'date'],
'eol_explicit' => ['nullable', 'boolean'],
'byod' => ['nullable', 'boolean'],
'order_number' => ['nullable', 'string', 'max:191'],
'notes' => ['nullable', 'string', 'max:65535'],
'assigned_to' => ['nullable', 'integer'],
'requestable' => ['nullable', 'boolean'],
'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'],
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL'],
'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL']
];
/**
/**
* The attributes that are mass assignable.
*
* @var array
@@ -713,7 +716,7 @@ class Asset extends Depreciable
* @since [v1.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function adminuser()
public function admin()
{
return $this->belongsTo(\App\Models\User::class, 'user_id');
}
@@ -1312,7 +1315,7 @@ class Asset extends Depreciable
public function scopeDueForCheckin($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
$interval = $settings->due_checkin_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');

View File

@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
use \App\Presenters\AssetModelPresenter;
use App\Http\Traits\TwoColumnUniqueUndeletedTrait;
/**
* Model for Asset Models. Asset Models contain higher level
@@ -21,21 +22,8 @@ class AssetModel extends SnipeModel
{
use HasFactory;
use SoftDeletes;
protected $presenter = AssetModelPresenter::class;
use Loggable, Requestable, Presentable;
protected $table = 'models';
protected $hidden = ['user_id', 'deleted_at'];
// Declare the rules for the model validation
protected $rules = [
'name' => 'required|min:1|max:255',
'model_number' => 'max:255|nullable',
'min_amt' => 'integer|min:0|nullable',
'category_id' => 'required|integer|exists:categories,id',
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
'eol' => 'integer:min:0|max:240|nullable',
];
use TwoColumnUniqueUndeletedTrait;
/**
* Whether the model should inject its identifier to the unique
@@ -44,8 +32,26 @@ class AssetModel extends SnipeModel
*
* @var bool
*/
protected $injectUniqueIdentifier = true;
use ValidatingTrait;
protected $table = 'models';
protected $hidden = ['user_id', 'deleted_at'];
protected $presenter = AssetModelPresenter::class;
// Declare the rules for the model validation
protected $rules = [
'name' => 'string|required|min:1|max:255|two_column_unique_undeleted:model_number',
'model_number' => 'string|max:255|nullable|two_column_unique_undeleted:name',
'min_amt' => 'integer|min:0|nullable',
'category_id' => 'required|integer|exists:categories,id',
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
'eol' => 'integer:min:0|max:240|nullable',
];
/**
* The attributes that are mass assignable.
@@ -73,7 +79,12 @@ class AssetModel extends SnipeModel
*
* @var array
*/
protected $searchableAttributes = ['name', 'model_number', 'notes', 'eol'];
protected $searchableAttributes = [
'name',
'model_number',
'notes',
'eol'
];
/**
* The relations and their attributes that should be included when searching the model.
@@ -86,6 +97,9 @@ class AssetModel extends SnipeModel
'manufacturer' => ['name'],
];
/**
* Establishes the model -> assets relationship
*

View File

@@ -205,7 +205,11 @@ class Component extends SnipeModel
public function numCheckedOut()
{
$checkedout = 0;
foreach ($this->assets as $checkout) {
// In case there are elements checked out to assets that belong to a different company
// than this asset and full multiple company support is on we'll remove the global scope,
// so they are included in the count.
foreach ($this->assets()->withoutGlobalScope(new CompanyableScope)->get() as $checkout) {
$checkedout += $checkout->pivot->assigned_qty;
}

View File

@@ -7,7 +7,7 @@ use EasySlugger\Utf8Slugger;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Rule;
use Schema;
use Illuminate\Support\Facades\Schema;
use Watson\Validating\ValidatingTrait;
class CustomField extends Model
{

View File

@@ -76,9 +76,9 @@ class Depreciable extends SnipeModel
if ($months_passed >= $this->get_depreciation()->months){
//if there is a floor use it
if(!$this->get_depreciation()->depreciation_min == null) {
if($this->get_depreciation()->depreciation_min) {
$current_value = $this->get_depreciation()->depreciation_min;
$current_value = $this->calculateDepreciation();
}else{
$current_value = 0;
@@ -86,7 +86,7 @@ class Depreciable extends SnipeModel
}
else {
// The equation here is (Purchase_Cost-Floor_min)*(Months_passed/Months_til_depreciated)
$current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->get_depreciation()->depreciation_min)) * ($months_passed / $this->get_depreciation()->months)), 2);
$current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->calculateDepreciation())) * ($months_passed / $this->get_depreciation()->months)), 2);
}
@@ -95,7 +95,7 @@ class Depreciable extends SnipeModel
public function getMonthlyDepreciation(){
return ($this->purchase_cost-$this->get_depreciation()->depreciation_min)/$this->get_depreciation()->months;
return ($this->purchase_cost-$this->calculateDepreciation())/$this->get_depreciation()->months;
}
@@ -158,17 +158,20 @@ class Depreciable extends SnipeModel
public function time_until_depreciated()
{
// @link http://www.php.net/manual/en/class.datetime.php
$d1 = new \DateTime();
$d2 = $this->depreciated_date();
if ($this->depreciated_date()) {
// @link http://www.php.net/manual/en/class.datetime.php
$d1 = new \DateTime();
$d2 = $this->depreciated_date();
// @link http://www.php.net/manual/en/class.dateinterval.php
$interval = $d1->diff($d2);
if (! $interval->invert) {
return $interval;
} else {
return new \DateInterval('PT0S'); //null interval (zero seconds from now)
// @link http://www.php.net/manual/en/class.dateinterval.php
$interval = $d1->diff($d2);
if (! $interval->invert) {
return $interval;
} else {
return new \DateInterval('PT0S'); //null interval (zero seconds from now)
}
}
return false;
}
public function depreciated_date()
@@ -188,4 +191,16 @@ class Depreciable extends SnipeModel
{
return new \DateTime($time);
}
private function calculateDepreciation()
{
if($this->get_depreciation()->depreciation_type === 'percent') {
$depreciation_percent= $this->get_depreciation()->depreciation_min / 100;
$depreciation_min= $this->purchase_cost * $depreciation_percent;
return $depreciation_min;
}
$depreciation_min = $this->get_depreciation()->depreciation_min;
return $depreciation_min;
}
}

View File

@@ -75,4 +75,17 @@ class Depreciation extends SnipeModel
{
return $this->hasMany(\App\Models\License::class, 'depreciation_id');
}
/**
* Establishes the depreciation -> assets relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v5.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assets()
{
return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'depreciation_id', 'model_id');
}
}

View File

@@ -50,7 +50,7 @@ class License extends Depreciable
'category_id' => 'required|exists:categories,id',
'company_id' => 'integer|nullable',
'purchase_cost'=> 'numeric|nullable|gte:0',
'purchase_date' => 'date_format:Y-m-d|nullable|max:10',
'purchase_date' => 'date_format:Y-m-d|nullable|max:10|required_with:depreciation_id',
'expiration_date' => 'date_format:Y-m-d|nullable|max:10',
'termination_date' => 'date_format:Y-m-d|nullable|max:10',
'min_amt' => 'numeric|nullable|gte:0',
@@ -736,4 +736,17 @@ class License extends Depreciable
return $query->leftJoin('companies as companies', 'licenses.company_id', '=', 'companies.id')->select('licenses.*')
->orderBy('companies.name', $order);
}
/**
* Query builder scope to order on the user that created it
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderCreatedBy($query, $order)
{
return $query->leftJoin('users as users_sort', 'licenses.user_id', '=', 'users_sort.id')->select('licenses.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order);
}
}

View File

@@ -33,7 +33,7 @@ class Location extends SnipeModel
'country' => 'min:2|max:191|nullable',
'zip' => 'max:10|nullable',
'manager_id' => 'exists:users,id|nullable',
'parent_id' => 'non_circular:locations,id',
'parent_id' => 'nullable|exists:locations,id|non_circular:locations,id',
];
protected $casts = [
@@ -108,10 +108,11 @@ class Location extends SnipeModel
{
return Gate::allows('delete', $this)
&& ($this->assets_count === 0)
&& ($this->assigned_assets_count === 0)
&& ($this->children_count === 0)
&& ($this->users_count === 0);
&& ($this->assets_count == 0)
&& ($this->assigned_assets_count == 0)
&& ($this->children_count == 0)
&& ($this->accessories_count == 0)
&& ($this->users_count == 0);
}
/**

View File

@@ -22,9 +22,9 @@ class Manufacturer extends SnipeModel
// Declare the rules for the form validation
protected $rules = [
'name' => 'required|min:2|max:255|unique:manufacturers,name,NULL,id,deleted_at,NULL',
'url' => 'url|nullable',
'url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://',
'support_email' => 'email|nullable',
'support_url' => 'nullable|url',
'support_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://',
'warranty_lookup_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://'
];

View File

@@ -9,6 +9,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use App\Helpers\Helper;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
use Illuminate\Support\Facades\Log;
@@ -73,7 +74,10 @@ class Setting extends Model
'login_remote_user_header_name' => 'string|nullable',
'thumbnail_max_h' => 'numeric|max:500|min:25',
'pwd_secure_min' => 'numeric|required|min:8',
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable',
'audit_warning_days' => 'numeric|nullable',
'due_checkin_days' => 'numeric|nullable',
'audit_interval' => 'numeric|nullable',
'custom_forgot_pass_url' => 'url|nullable',
'privacy_policy_link' => 'nullable|url',

View File

@@ -21,6 +21,11 @@ class SnipeModel extends Model
*/
public function setPurchaseCostAttribute($value)
{
if (is_float($value)) {
//value is *already* a floating-point number. Just assign it directly
$this->attributes['purchase_cost'] = $value;
return;
}
$value = Helper::ParseCurrency($value);
if ($value == 0) {

View File

@@ -331,7 +331,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function accessories()
{
return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_users', 'assigned_to', 'accessory_id')
return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_checkout', 'assigned_to', 'accessory_id')
->withPivot('id', 'created_at', 'note')->withTrashed()->orderBy('accessory_id');
}

View File

@@ -24,6 +24,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
$this->item_tag = $params['item_tag'];
$this->item_model = $params['item_model'];
$this->item_serial = $params['item_serial'];
$this->item_status = $params['item_status'];
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false);
$this->assigned_to = $params['assigned_to'];
$this->note = $params['note'];
@@ -65,6 +66,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
'item_tag' => $this->item_tag,
'item_model' => $this->item_model,
'item_serial' => $this->item_serial,
'item_status' => $this->item_status,
'note' => $this->note,
'accepted_date' => $this->accepted_date,
'assigned_to' => $this->assigned_to,

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