Compare commits

...

459 Commits

Author SHA1 Message Date
snipe
89a232ae14 Merge pull request #18266 from Valinwolf/develop
Added endpoint & use_path_style_endpoint configs for public/private S3
2025-11-28 17:39:23 +00:00
Patrick Thomas
6eaefa0bdd Added endpoint & use_path_style_endpoint configs for public/private S3 2025-11-28 17:29:02 +00:00
snipe
a272bdc796 Merge pull request #18251 from uberbrady/improve_component_asset_counts
Optimize queries for Components listing
2025-11-26 14:40:14 +00:00
Brady Wetherington
416b32cbc8 Optimize queries for Components listing 2025-11-26 12:36:44 +00:00
snipe
b3996f1970 In the sea, @uberbrady! (fixed missing semicolon) 2025-11-26 00:35:16 +00:00
snipe
ca4ed605a8 Merge pull request #18246 from Godmartinz/resize-label-fields-conditionally-L6009_A
Fixes [FD-52064] Avery `L6009_A` & `L4736_A` label field overflow with scaling
2025-11-25 18:37:06 +00:00
Godfrey M
d3e6d7442f typo bonanza 2025-11-25 10:30:56 -08:00
Godfrey M
b558bc5334 change variable" 2025-11-25 10:30:06 -08:00
Godfrey M
204d7b5be6 added scaling to L4736_A 2025-11-25 10:23:08 -08:00
Godfrey M
7dccfec332 adds notes 2025-11-25 10:12:27 -08:00
snipe
dfb59d8a55 Added link to rudder2snipe repo 2025-11-25 18:01:29 +00:00
snipe
a12ee3c0da Merge pull request #18245 from uberbrady/redirect_upgrader
Add new 'git remote' management to change Snipe-IT URLs
2025-11-25 13:38:31 +00:00
Brady Wetherington
a657c479be Add new 'git remote' management to change Snipe-IT URL's 2025-11-25 13:17:56 +00:00
Godfrey M
ab82c5fd88 resizes label field box to size if needed 2025-11-24 11:04:23 -08:00
snipe
f2334082ee Added tag color to location query 2025-11-24 17:40:13 +00:00
snipe
c7cb4674f5 Bumped version 2025-11-24 12:31:50 +00:00
snipe
82314076a9 Updated translations 2025-11-24 12:30:07 +00:00
snipe
a1c67b5154 Fixed user details toggle 2025-11-24 11:37:14 +00:00
snipe
3e343fe8b7 Merge pull request #18236 from grokability/dependabot/github_actions/develop/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-24 08:33:46 +00:00
dependabot[bot]
e288c942ee Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 08:04:19 +00:00
snipe
7a1ccb0d53 Nicer query 2025-11-23 14:09:55 +00:00
snipe
7f76198139 Fixed RB-20501 - correctly return error response when license+seat don’t match
TypeError: App\Http\Transformers\LicenseSeatsTransformer::transformLicenseSeat(): Argument #1 ($seat) must be of type App\Models\LicenseSeat, bool given, called in /snipe-it/app/Http/Controllers/Api/LicenseSeatsController.php on line 92
2025-11-23 14:05:24 +00:00
snipe
69994e0c11 Merge pull request #18232 from grokability/check-for-valid-json-on-filter
Added filter form request to validate JSON
2025-11-22 14:22:33 +00:00
snipe
aa959fbe92 Added filter form request to validate JSON 2025-11-22 14:03:58 +00:00
snipe
644ef040d0 Merge pull request #18226 from Godmartinz/audit-notification-rb-19608
Fixes RB-19608 adds safe guards, adds Audit notification for google workspace, adds tests
2025-11-22 11:41:57 +00:00
snipe
33839d7244 Re-added the horizontal class 2025-11-22 11:14:59 +00:00
Godfrey M
48270cb9b4 url trim 2025-11-21 10:11:28 -08:00
Godfrey M
cd42760b68 notes 2025-11-20 14:25:13 -08:00
Godfrey M
1116da389e add google chat to audit notification test 2025-11-20 14:24:03 -08:00
Godfrey M
24d7ae4a2f added google chat to audit notifications" 2025-11-20 14:22:08 -08:00
Godfrey M
e598ef6e05 adds tests, webhook enable settings 2025-11-20 13:51:14 -08:00
snipe
7f2a80552b Fixed form layout for SAML 2025-11-20 20:15:34 +00:00
Godfrey M
eceeb4aa3b return if there is no item for the audit notification 2025-11-20 10:56:43 -08:00
snipe
9d08e2d297 Clearer language 2025-11-20 13:44:09 +00:00
snipe
4d44fd47c3 Merge pull request #18217 from Godmartinz/unaccepted-row-null-fix
Fixes FD-52005 Adds null safe operators to unacceptable items report
2025-11-20 13:27:24 +00:00
snipe
88635cb6c3 Merge pull request #18223 from grokability/#18114-fixed-declined-notification-added-x-header
Fixed #18114 - include declined item name, added app headers
2025-11-20 13:27:01 +00:00
snipe
1687fcc035 Small subject tweaks 2025-11-20 13:23:17 +00:00
snipe
39e7937458 Fixed #18114 - include declined item name, added app headers 2025-11-20 12:58:53 +00:00
Godfrey M
60ff2970f0 safeguard null rows by filtering rows in query 2025-11-19 11:35:24 -08:00
Godfrey M
212cd026a3 add null safe operators to company and csv plain values 2025-11-18 12:36:07 -08:00
snipe
cf47f8fea9 Merge pull request #18216 from Godmartinz/fix-license-patch-to-allow-update-of-other-fields
Fixed #18024 License Seat update/patch method
2025-11-18 20:29:18 +00:00
Godfrey M
4b45ffd841 updating fields of checkout now works 2025-11-18 11:35:26 -08:00
snipe
5daba6034d Merge pull request #18213 from grokability/#18202-copy-to-clipboard-spaces
Fixed #18202 - copy to clipboard adding spaces in FF
2025-11-18 13:13:29 +00:00
snipe
4e5c19e932 Fixed #18202 - copy to clipboard adding spaces in FF 2025-11-18 13:11:40 +00:00
snipe
5eb73baf5e Removed duplicate formatter 2025-11-18 12:54:21 +00:00
snipe
a97c453706 Small fixes for user profiles 2025-11-18 11:59:54 +00:00
snipe
57224f7304 Fixed #18209 - translate asset filters 2025-11-18 11:40:15 +00:00
snipe
c564ee6093 Fixed #18211 - limit regex field to 191 characters 2025-11-18 11:15:15 +00:00
snipe
caaa9ab23e Merge pull request #18122 from chruoss/master
Renamed L6009 -> L4736 and added correct L6009
2025-11-17 21:40:08 +00:00
snipe
b6d2c6d28c Merge pull request #18208 from marcusmoore/fixes/null-array-filter
Fixed potential exception while filtering in users index endpoint
2025-11-17 21:39:08 +00:00
Marcus Moore
c36c8968d3 Avoid passing null to array_filter in user controller 2025-11-17 10:26:29 -08:00
snipe
bcd32da2bc Remove loading user count since we don’t use it for depts 2025-11-17 17:34:43 +00:00
snipe
c56c7ddd03 Merge pull request #18201 from grokability/#18185-added-tag-color-to-companies
Added #18185 added tag color to companies
2025-11-17 13:33:17 +00:00
snipe
9a0dd604c9 Small layout tweaks 2025-11-17 13:21:01 +00:00
snipe
a1dec176a1 Added mobile 2025-11-17 13:09:13 +00:00
snipe
67032d068d Removed space 2025-11-17 13:09:08 +00:00
snipe
401c83945d Updated gate check on presenters 2025-11-17 13:07:28 +00:00
snipe
ed6d020edb Fixed permission name for locations 2025-11-17 12:31:18 +00:00
snipe
8eb5600b1e Fixed manufacturer link 2025-11-17 12:19:27 +00:00
snipe
9c4cd69106 Updated checkout blades 2025-11-17 12:15:38 +00:00
snipe
64cbe0c960 Use dept presenter 2025-11-17 12:15:25 +00:00
snipe
be70b217d9 Updated presenters 2025-11-17 12:11:38 +00:00
snipe
b4f5260dda Updated controllers to use tag_color 2025-11-17 11:36:11 +00:00
snipe
83e446f99c Updated formatters to use tag_color if given 2025-11-17 11:18:38 +00:00
snipe
767139cf0b Updated select2 to use tag_color if available 2025-11-17 11:18:21 +00:00
snipe
e3ac60111f Added tag_color to factories 2025-11-17 11:18:02 +00:00
snipe
c694c11724 Added presenter reference 2025-11-15 14:58:02 +00:00
snipe
b50765a151 Added more to the views 2025-11-15 14:50:51 +00:00
snipe
590f77bdb4 Updated controllers and presenters 2025-11-14 17:58:27 +00:00
snipe
09575e5312 Added tag color to transformer 2025-11-14 15:58:07 +00:00
snipe
1693825dd0 Added translations 2025-11-14 15:57:57 +00:00
snipe
5a89056112 Added migration 2025-11-14 15:57:47 +00:00
snipe
2ee51bb282 Added tag color to transformer 2025-11-14 15:57:39 +00:00
snipe
ea7cffc1a3 Made tag_color fillable 2025-11-14 15:57:27 +00:00
snipe
c20d1b82ae Added tag color to controllers 2025-11-14 15:57:18 +00:00
snipe
4e8c1e853b Fixed markdown in upcoming audits email 2025-11-14 14:09:53 +00:00
snipe
4509e1d1dc Merge pull request #18195 from grokability/#18189-day-of-week
Fixed #18189 - added option to pick the day the week starts on
2025-11-13 16:46:15 +00:00
snipe
6b9d4941be Language tweaks 2025-11-13 16:35:40 +00:00
snipe
308cd6b91d Fixed #18189 - added option to pick the day the week starts on 2025-11-13 16:33:08 +00:00
snipe
bd742aec9c Merge pull request #18192 from Godmartinz/fix-requestable-asset-model-query
Fixes FD51910 requestable asset model available quantity count
2025-11-13 15:37:24 +00:00
snipe
d2ab3071a6 Merge pull request #18193 from ubc-cpsc/bugfix/CVE-2025-64500
Fixes CVE-2025-64500: Incorrect parsing of PATH_INFO can lead to limited authorization
2025-11-13 09:52:50 +00:00
Joël Pittet
1dd4c161f0 Fixes CVE-2025-64500 2025-11-12 18:09:37 -08:00
Godfrey M
5e48dd45b2 remove unnecessary code 2025-11-12 16:27:04 -08:00
Godfrey M
601d6e7377 fixes requestable models query 2025-11-12 16:25:08 -08:00
snipe
d43be271e6 Small tweaks to user group listings 2025-11-12 20:59:06 +00:00
snipe
92a3bdf4e9 Merge pull request #18191 from grokability/limit-adding-users-to-group-if-over-limit
Set a limit on number of users for group user loading
2025-11-12 20:24:24 +00:00
snipe
fa2aafe41f Set a limit on number of users for group user loading 2025-11-12 20:19:52 +00:00
snipe
a7be1acbd8 Merge pull request #18190 from grokability/improved-checkin-reminders
Improved overdue checkin alert
2025-11-12 16:01:32 +00:00
snipe
fbe2ae03ff Use due_checkin_days setting instead of audit warning days 2025-11-12 15:52:16 +00:00
snipe
d1a492f953 Improved overdue checkin alert 2025-11-12 15:38:47 +00:00
snipe
352807c2d7 Fixed RB-20498 - Check that logo file exists on acceptance 2025-11-11 18:43:39 +00:00
snipe
3aa046bfa7 Include trashed in other acceptance tasks 2025-11-11 14:00:48 +00:00
snipe
9454ff677b Get deleted objects in unaccepted asset report 2025-11-11 13:53:43 +00:00
snipe
a5c7b8f609 Small unaccepted items report fixes 2025-11-11 13:17:56 +00:00
snipe
9365d1adc6 Merge pull request #18164 from Godmartinz/fix-accessories-redirect-previous-page
Fixes #18138 redirect to Previous Page from accessories edit screen
2025-11-11 12:29:23 +00:00
snipe
ddd2a96ac5 Clearer message on no inventory to report 2025-11-11 11:52:05 +00:00
snipe
bf8ff51234 Make suppliers notes nullable by default 2025-11-10 21:31:14 +00:00
snipe
7891f52bd4 Merge pull request #18177 from grokability/add-files-to-suppliers
Fixed #12451: Add file upload support to suppliers, changed notes field to text (from varchar)
2025-11-10 20:57:21 +00:00
snipe
baa4a8a461 Migration to change notes on suppliers to text from varchar 2025-11-10 20:53:21 +00:00
snipe
34daffcdf4 Added supplier file uploads 2025-11-10 20:47:45 +00:00
snipe
21baea27a8 Merge pull request #18141 from uberbrady/actiontype_enum_redux
Actiontype enum redux
2025-11-10 18:55:01 +00:00
snipe
e1d3714445 Add @MarvelousAnything as a contributor 2025-11-10 14:15:10 +00:00
snipe
8b7e0a0d78 Add @mohammad-ahmadi1 as a contributor 2025-11-10 14:15:01 +00:00
snipe
a1cc427c9c Add @smarsching as a contributor 2025-11-10 14:14:51 +00:00
snipe
5dc675040d Bumped version 2025-11-10 13:48:59 +00:00
snipe
1258eb6533 Merge pull request #17913 from Godmartinz/add_types_to_unaccepted_asset_report
Fixed #15664 - Adds Accessories, Components, Consumables, and Licenses to Unaccepted Assets report
2025-11-10 12:24:13 +00:00
snipe
83abfc9ca6 Updated language files 2025-11-07 13:41:46 +00:00
snipe
b42d6677cc Updated assets 2025-11-07 13:25:45 +00:00
snipe
e8c644a600 Merge pull request #18165 from Godmartinz/border-color-create-new-button-fix
Fixes #18140 Changes border color of create New in Dark modes
2025-11-07 13:21:52 +00:00
snipe
9f6a73b290 Merge pull request #18170 from grokability/fix-for-groups
Fixed #18157 - only partial information stored on group save if lower `max_input_vars` and/or `max_multipart_body_parts`
2025-11-06 21:09:42 +00:00
snipe
4d38bd1c62 Renamed variable 2025-11-06 21:07:52 +00:00
snipe
138262114d Added enctype back in 2025-11-06 21:04:55 +00:00
snipe
4073c9e638 Updated ordering 2025-11-06 21:01:20 +00:00
snipe
f1b4877a98 A few small tweaks for new groups 2025-11-06 20:36:51 +00:00
snipe
c39c92d0d7 Mash the ids into a string, fixed disclosure arrows 2025-11-06 20:12:25 +00:00
snipe
90fc48d959 Shim workaround to avoid max_input_vars and max_multipart_body_parts limits
max_input_vars = 2000
max_multipart_body_parts = 2048
2025-11-06 18:17:40 +00:00
Godfrey M
7f10a53105 dark blue 2025-11-05 12:47:10 -08:00
Godfrey M
7ae1b7a765 change border-color of create new 2025-11-05 12:40:29 -08:00
Godfrey M
22a43a0463 adds redirect option fix to accessories update method 2025-11-05 12:19:18 -08:00
snipe
dea399398a Merge pull request #18161 from Godmartinz/TZE_24mm_E_adjustment
FD-50838: Fixes 24mm_E label sizing
2025-11-05 18:50:42 +00:00
Godfrey M
7434dd9458 final adjustments to 24mm_E label 2025-11-05 09:32:21 -08:00
snipe
88e532dbc4 Fixed #18157 - reports permission glitch 2025-11-05 15:24:15 +00:00
snipe
c5ad451c39 Merge pull request #18156 from grokability/#18119-fix-double-helpering-on-dates-for-asset-acceptance
Fixed #18119 - double formatting for acceptance/decline date
2025-11-04 21:40:58 +00:00
snipe
44bfceeb0f Fixed #18119 - double formatting for acceptance/decline date 2025-11-04 21:36:27 +00:00
snipe
37eb63837b Merge pull request #18155 from grokability/#18021-fix-patch-api-with-unique-serial
Override unique_undeleted in the form request
2025-11-04 21:23:00 +00:00
snipe
4ada47e3b0 Use new setting variable since we already have it 2025-11-04 21:12:49 +00:00
snipe
e5c55c9ab3 Fixed typo in the comment 2025-11-04 21:11:54 +00:00
snipe
547b3df7b4 Added more commentary on why we’re intefering with the request 2025-11-04 21:08:48 +00:00
snipe
4100f2600c Override unique_undeleted in the form request 2025-11-04 20:43:31 +00:00
snipe
a9574e8fd6 Fixed #18133 - make the disabled toggle JS so it’s clearer 2025-11-04 19:39:35 +00:00
snipe
c6269d6bbc Merge pull request #18151 from MarvelousAnything/fixes/test_webhook_content_type
Fix Content-Type Header not being set correctly for testWebhook
2025-11-04 19:03:53 +00:00
snipe
c1204a5301 Merge pull request #18152 from grokability/#18020-rework-pr
Re-apply #18020, fixed #15107 (mostly) - added prefix and more options to 2D barcodes
2025-11-04 18:55:54 +00:00
snipe
cc5ac65909 Re-apply #18020, fixed #15107 (mostly) - added prefix and more options to label 2D tags 2025-11-04 18:43:35 +00:00
Owen Voskuhl Hayes
9ddc48e3d7 fix the headers field for Guzzle request 2025-11-04 12:03:21 -05:00
snipe
4a39d7c67a Use transformer for dept update responses 2025-11-04 16:06:19 +00:00
snipe
b7a6706591 Merge pull request #18150 from grokability/#18148-dept-api-request-user-count
Fixed #18148 and #17451 - return int for user_count, fixed validation
2025-11-04 16:05:42 +00:00
snipe
ddb031f091 Fixed #18148 and #17451 - return int for user_count, fixed validation 2025-11-04 15:56:23 +00:00
snipe
e906d25776 Merge pull request #16973 from spencerrlongg/bug/sc-29245
Adds Form Request for Creating Departments
2025-11-04 15:09:38 +00:00
Brady Wetherington
27d7449459 Fix comment to properly reflext the current state of the database 2025-11-04 14:04:37 +00:00
snipe
5d8905c997 Merge pull request #18143 from grokability/#18101-make-copy-to-clipboard-more-consistent
Fixed #18101: Make copy to clipboard alignment more consistent
2025-11-03 17:36:54 +00:00
snipe
517f4ce121 Fixed #18101: Make copy to clipboard alignment more consistent 2025-11-03 17:29:39 +00:00
snipe
6809bbd3d5 Merge pull request #18142 from grokability/#18136-copy-asset-name
Fixed #18136 - adds copy to clipbpard to asset name
2025-11-03 15:48:59 +00:00
snipe
ab555d05e1 Fixed #18136 - adds copy to clipbpard to asset name 2025-11-03 15:47:32 +00:00
Brady Wetherington
f7bc538fdf Intorduce ActionType enum and ensure all logactions are using it 2025-11-03 15:39:31 +00:00
Brady Wetherington
2de66ad5db Merge branch 'fix_incorrect_action_types' into actiontype_enum_redux 2025-11-03 15:31:14 +00:00
snipe
30e16b6213 Added int 2025-11-03 14:10:36 +00:00
snipe
92b50ca7ae Addresses #17994, #16925 2025-11-03 14:10:27 +00:00
snipe
9ee36df979 Merge pull request #18139 from grokability/#18082-add-company-to-seat-view
Fixed #18082: Added user company to checked out licenses
2025-11-03 12:51:29 +00:00
snipe
46d1c14e1a Added user company to checked out licenses 2025-11-03 12:45:33 +00:00
snipe
61895011fb Merge pull request #18131 from Godmartinz/fix-inventory-alert-notification-misfire
Adds a null check for items and threshold in inventory alert notification
2025-10-30 20:11:24 +00:00
Godfrey M
32d43034bd add a null check for items and threshold in inventory alert notification 2025-10-30 10:47:17 -07:00
snipe
dfc6cdc127 Merge pull request #18128 from marcusmoore/fixes/17738-category-edit-form-fix
Fixed #17738 - accurately represent checkbox on category edit screen
2025-10-30 16:43:17 +00:00
snipe
16e93f9e18 Merge remote-tracking branch 'origin/master' into develop 2025-10-30 14:03:23 +00:00
snipe
7395b1a4eb Track permission changes 2025-10-30 13:40:24 +00:00
snipe
fa98557225 Check that the permissions are really an array
This accounts for weird data in the permissions column
2025-10-30 13:34:50 +00:00
Marcus Moore
894606b62e Remove old tests 2025-10-29 13:16:03 -07:00
Marcus Moore
f0a6a0026a Improve phrasing 2025-10-29 13:03:37 -07:00
Marcus Moore
070e0c93be Improve phrasing 2025-10-29 12:46:26 -07:00
Marcus Moore
2b27b733e5 Improve wording 2025-10-29 12:45:45 -07:00
Marcus Moore
0355c2b642 Dynamically adjust checkbox wording 2025-10-29 12:44:34 -07:00
Marcus Moore
3bad19fb56 Improve translation key name 2025-10-29 12:40:39 -07:00
Marcus Moore
0f84d51a48 Improve property name 2025-10-29 12:37:17 -07:00
Marcus Moore
2e8572d9c5 Use v3 syntax for computed properties 2025-10-29 12:35:30 -07:00
Marcus Moore
df53d5d966 Skip computing sendCheckInEmail 2025-10-29 12:32:01 -07:00
Marcus Moore
23838959ca Never disable email checkbox 2025-10-29 12:27:12 -07:00
snipe
6dbb836a01 Merge pull request #18125 from grokability/form-row-again
Added form row component
2025-10-29 16:33:15 +00:00
snipe
3426afe5a8 Account for input styles 2025-10-29 16:15:22 +00:00
snipe
4bbf923eb6 Smaller default row for textarea 2025-10-29 16:11:56 +00:00
snipe
e2c3480194 Apply form rows to manufacturers 2025-10-29 16:11:47 +00:00
snipe
73159076f6 Better handle input groups in js validator 2025-10-29 16:11:37 +00:00
snipe
90d040573d Added regular link icon 2025-10-29 16:07:16 +00:00
chruoss
6904ad02a2 Renamed L6009.php -> L4736.php
Renamed L6009_A.php -> L4736_A.php
Added correct L6009.php
Added correct L6009_A.php
2025-10-29 15:13:12 +01:00
snipe
155481a442 Merge pull request #18120 from grokability/small-form-fixes-settings
Fixed form label alignments in settings section
2025-10-29 11:16:59 +00:00
snipe
2f31bfc5fe Fixed some HTML labels in settings 2025-10-29 11:13:07 +00:00
snipe
8d0c88dc74 Merge pull request #18116 from akemidx/auditwarningthreshold
Fixed #17329 Audit Warning Threshold could be negative
2025-10-28 20:44:58 +00:00
snipe
07256fd833 Merge pull request #18044 from mohammad-ahmadi1/ISSUE-17004-fix-db-dump-ssl-issue
fix: update mysqldump options to use --ssl-mode=DISABLED for modern versions
2025-10-28 20:37:08 +00:00
snipe
776cd43a58 Change text string for item (versus asset) 2025-10-28 20:36:05 +00:00
akemidx
acb5309aab min = 0 2025-10-28 16:32:21 -04:00
snipe
c68f81db3c Merge pull request #18113 from Godmartinz/customfieldvalue-in-importer-fix
Adds #17433  `is_null` check to import handler for custom fields
2025-10-28 20:27:22 +00:00
snipe
ac8e341b37 Merge pull request #18115 from marcusmoore/fixes/rb-20449-remove-admin-from-acceptance-email
Fixed #18112 - fix consumable and license acceptances
2025-10-28 20:26:59 +00:00
snipe
36122b3966 Added BACKUP_TIME_LIMIT to env example 2025-10-28 20:26:23 +00:00
akemidx
79bcf472f0 greater than or equal to zero 2025-10-28 16:13:33 -04:00
Marcus Moore
55d86da846 Remove references to administrator in acceptance notification 2025-10-28 13:07:00 -07:00
Godfrey M
e4f8c1ba3f fix custom field value conditional check 2025-10-28 12:24:35 -07:00
Godfrey M
c36236b7dc add is_null acheck to import hanlder for custom fields 2025-10-28 11:37:38 -07:00
snipe
63994333d0 Merge pull request #18111 from grokability/#16914-better-ldap-sync-phrasing
Fixed #16914: better ldap sync phrasing
2025-10-28 15:51:10 +00:00
snipe
da4c7d8934 One more tweak 2025-10-28 15:47:56 +00:00
snipe
186721eca0 Added breadcrumbs 2025-10-28 15:42:59 +00:00
snipe
f53d939c86 Better explanation for location sync, nicer look 2025-10-28 15:42:53 +00:00
snipe
23e6909708 Merge pull request #18110 from grokability/#18107-normalize-to-strings
Fixed #18107: normalize "to" strings
2025-10-28 15:03:16 +00:00
snipe
cf421fe1c1 Added user-specific “to” 2025-10-28 15:02:15 +00:00
snipe
4d80e806e4 Use “-“ instead of “to” string, added placeholders 2025-10-28 15:02:03 +00:00
snipe
60a7b7f7ff Merge pull request #18109 from grokability/audit-improvements
Limit the upcoming audit email to 30 records, added optional --with-output
2025-10-28 14:31:55 +00:00
snipe
90263eab06 Added note for —with-option flag 2025-10-28 14:20:40 +00:00
snipe
9d8f251fc4 Limit the email to 30 records, added optional --with-output 2025-10-28 14:15:15 +00:00
snipe
2b4986571c Merge pull request #18108 from uberbrady/fix_ldap_tests
Fixed - LDAP test needs to be fixed to match new behavior
2025-10-28 12:49:11 +00:00
Brady Wetherington
890d13bd52 LDAP test needs to be fixed to match new behavior 2025-10-28 12:30:30 +00:00
snipe
e698e71137 Merge pull request #18100 from marcusmoore/fixes/20318-license-seat-display-name
Added null safe operator in case of missing license
2025-10-28 09:29:25 +00:00
snipe
d064a5530a Merge remote-tracking branch 'origin/master' into develop 2025-10-28 02:10:07 +00:00
snipe
ab4fbf6c19 Merge pull request #18105 from grokability/ldap-fast-find-and-bind
Possible fix for 504 gateway timeout on unreachable LDAP server
2025-10-28 02:09:41 +00:00
snipe
728afa8361 Possible fix for 504 gateway timeout on unreachable LDAP server 2025-10-27 23:45:12 +00:00
snipe
b77019c16e Merge remote-tracking branch 'origin/develop' 2025-10-27 19:32:28 +00:00
snipe
6703448b80 Merge pull request #18102 from marcusmoore/fixes/rb-20434-undefined-permissions-variable
Fixed issue when viewing user that does not have permissions set
2025-10-27 19:31:54 +00:00
Marcus Moore
776ba19a1f Define default permissions array 2025-10-27 12:28:55 -07:00
Marcus Moore
1f499e0d44 Add null safe operator in case of missing license 2025-10-27 10:47:55 -07:00
snipe
0a6eb61103 Merge remote-tracking branch 'origin/develop' 2025-10-27 13:20:18 +00:00
snipe
32a2eed5ec Fixed #18075 - make require_serial boolean in API transformer 2025-10-27 13:19:37 +00:00
snipe
40a70d39d0 Merge remote-tracking branch 'origin/develop' 2025-10-27 12:42:40 +00:00
snipe
5697054e98 Merge pull request #18099 from grokability/fix-cjk-for-acceptance-translated-strings
Fixed #18097 - check for CJK in field labels as well as content
2025-10-27 12:41:59 +00:00
snipe
def5969e1c Fixed #18097 - check for CJK in field labels as well as content 2025-10-27 12:34:30 +00:00
snipe
78a418630d Merge remote-tracking branch 'origin/develop' 2025-10-27 12:21:25 +00:00
snipe
6d76e7b2d4 Added dumb pverride for reports :( 2025-10-27 12:21:16 +00:00
snipe
f052c8b44c Merge pull request #18076 from uberbrady/move_traits_into_directories
Moved Traits into its directory and modify the FCO's to point to them
2025-10-27 11:43:31 +00:00
snipe
0e6991d56d Merge remote-tracking branch 'origin/develop' 2025-10-27 11:42:05 +00:00
snipe
06eab5f8a4 Merge pull request #18093 from Godmartinz/fix-warranty-part-of-expiring-asset-query
Fixed expiring warranties not being included in the expiring alerts notification
2025-10-27 11:41:51 +00:00
snipe
4a481e79c4 Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-10-27 11:14:05 +00:00
snipe
ee4abbcbaa Updated dev assets 2025-10-27 11:12:13 +00:00
snipe
dcc82d742f Fixed RB-20430 - 500 on LDAP if baseDN is not set 2025-10-27 11:09:24 +00:00
snipe
19cb2089d7 Merge pull request #18098 from grokability/dependabot/github_actions/develop/actions/upload-artifact-5
Bump actions/upload-artifact from 4 to 5
2025-10-27 10:47:59 +00:00
snipe
04923b06b0 Merge pull request #18078 from grokability/groups-ui-improvements
Groups UI improvements, ability to add users from the group edit screen
2025-10-27 10:47:11 +00:00
dependabot[bot]
e16755d491 Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 08:19:26 +00:00
snipe
742b0769a4 Use translation string 2025-10-26 12:10:08 +00:00
snipe
df68dca9dc Warn if user has individual permission overrides 2025-10-25 18:57:17 +01:00
snipe
4a5bf78d58 Merge branch 'develop' into groups-ui-improvements 2025-10-25 18:31:22 +01:00
snipe
7947237489 Double check the admin status when toggling the superadmin 2025-10-25 18:29:52 +01:00
snipe
1115205164 Normalize the JS 2025-10-25 18:20:22 +01:00
snipe
d5d01136c4 Fixed js errors 2025-10-25 17:57:59 +01:00
snipe
3d47277614 Added cursor style 2025-10-25 17:57:44 +01:00
snipe
b937bea04f Working, but there’s a bit of a jitter I need to fix 2025-10-25 15:59:47 +01:00
snipe
fff14632bc Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-10-25 14:59:31 +01:00
snipe
9bdf1a620f Built assets 2025-10-25 14:58:27 +01:00
snipe
60099aa989 FAFOing on disclosure arrows being remembered 2025-10-25 14:56:01 +01:00
snipe
f3976e5dd8 Merge pull request #18094 from Godmartinz/custom_field_color_fixx
Fixed fieldset colors on dark themes
2025-10-23 20:32:05 +01:00
Godfrey M
d7d6893304 fix Custom Fields section header font color 2025-10-23 12:05:13 -07:00
Godfrey M
99549ce805 rewrite query for expired warranties on assets, concat queries" 2025-10-23 11:01:51 -07:00
snipe
7eb15fe04d Merge pull request #18091 from uberbrady/fix_backup_durations
Moved import time limit inside class, added new backup time limit
2025-10-23 18:51:35 +01:00
Godfrey M
e2019a13ab rewrite expired assets collection query 2025-10-23 10:49:58 -07:00
Brady Wetherington
4b7a06761a Moved import time limit inside class, added new backup time limit 2025-10-23 16:05:09 +01:00
Brady Wetherington
b96e2fb52c Added migration to fix incorrect action_type in action_log 2025-10-23 15:40:54 +01:00
snipe
8f4a1f5801 Moved JS and styles into js and css files 2025-10-23 13:24:26 +01:00
snipe
891bec9cdb Styling fixes 2025-10-23 13:06:45 +01:00
Godfrey M
c5252ea583 skip in one more spot 2025-10-22 12:23:14 -07:00
Godfrey M
82d553c180 skip test if sqllite 2025-10-22 12:19:13 -07:00
Godfrey M
71e34355b9 remove unwanted changes 2025-10-22 11:51:00 -07:00
Godfrey M
2bad8c72e4 fixes warranty part of expiring alerts query 2025-10-22 11:43:54 -07:00
Godfrey M
6134ca01ac Merge remote-tracking branch 'origin/develop' into develop 2025-10-22 10:48:05 -07:00
snipe
be8193ebff Fixed inherit permissions 2025-10-22 15:00:04 +01:00
snipe
430ee46645 Small tweaks to inherit js 2025-10-22 13:41:59 +01:00
snipe
76fbbf29e8 Replace generate text with icon button 2025-10-22 13:37:45 +01:00
snipe
3108159d95 Jitter CSS tweaks 2025-10-22 12:37:39 +01:00
snipe
f282a1ead7 Removed duplicated permissions js code 2025-10-22 12:03:55 +01:00
snipe
324bc4957d Merge pull request #18080 from Godmartinz/add-null-safe-operator-to-manager
Fixes admin alerts when a location doesnt have a manager
2025-10-22 11:56:59 +01:00
snipe
c3b5c4dfae Moved the logic into the permissions partial, added translations 2025-10-21 22:10:09 +01:00
Godfrey M
4ab5d97e86 add nullsafe operator to location manager 2025-10-21 14:06:08 -07:00
Godfrey M
b4696ef11e added alert_email to send conditional in listener 2025-10-21 13:11:21 -07:00
snipe
294ffb72a4 Translations!!! 2025-10-21 20:36:09 +01:00
snipe
8c534d29d3 WTF tower?? 2025-10-21 19:36:28 +01:00
snipe
d6cb262f9d Bumped version 2025-10-21 19:22:36 +01:00
snipe
5211e2ae20 Some UI refinement 2025-10-21 19:22:36 +01:00
snipe
90832fd1ad Checked check 2025-10-21 19:22:36 +01:00
snipe
889cbc69e1 Starter improvements - needs testing 2025-10-21 19:22:36 +01:00
snipe
15948370d4 Updated assets 2025-10-21 19:22:24 +01:00
snipe
63c5177b37 Merge pull request #18077 from Godmartinz/acceptance-notif-fixes-pt2
Adds admin to decline notification, fix asset and model name translations on asset notification
2025-10-21 18:31:28 +01:00
Godfrey M
4fbfaf6b9f add admin to decline, fix asset and model name translations 2025-10-21 10:22:34 -07:00
Brady Wetherington
9e68497b63 Moved Traits into directory and modify the users to point to them 2025-10-21 16:45:58 +01:00
snipe
0b9e13bf1e Merge remote-tracking branch 'origin/develop' 2025-10-21 12:58:06 +01:00
snipe
485d343e0f Merge pull request #18058 from uberbrady/ldap_fetch_dn_before_login_FIXED
FIXED - perform Ldap fetch for dn (Distinguished Name) before logging-in (fixed)
2025-10-21 12:56:31 +01:00
snipe
07f0e8a3be Merge pull request #18070 from Godmartinz/fix-admin-in-user-acceptance-notif
Fixes `admin` parameter acceptance notifications
2025-10-21 12:54:03 +01:00
snipe
cc5afb1cd8 Merge pull request #18074 from grokability/smaller-audit-image-fix
Fixed audit images not displaying inline
2025-10-21 12:51:20 +01:00
snipe
c69f1c0890 Smaller fix for missing audit images 2025-10-21 12:43:59 +01:00
Godfrey M
a8733bdedf remove nullsafe operator 2025-10-20 11:01:38 -07:00
Godfrey M
4222e4eb51 add nullsafe operator after admin 2025-10-20 11:00:59 -07:00
Godfrey M
5db4441f5c fix admin param in user accepted notification 2025-10-20 10:51:24 -07:00
snipe
2bcfe97211 Merge remote-tracking branch 'origin/develop' 2025-10-20 16:03:55 +01:00
snipe
4e74c97c84 Merge pull request #18066 from smarsching/issue-18065
Fixed #18065: Ensure that private_key_bits is always an int
2025-10-20 13:51:42 +01:00
Sebastian Marsching
71a46c9bd6 Fixed #18065: ensure that private_key_bits is always an int. 2025-10-18 18:39:43 +02:00
snipe
1a7c7fdebf Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	config/version.php
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-10-17 18:37:18 +01:00
snipe
65ff3d414a Bumped version 2025-10-17 18:34:54 +01:00
snipe
9fa38b70c8 Bumped version 2025-10-17 18:26:44 +01:00
snipe
470172f53f Re-run assets 2025-10-17 18:26:05 +01:00
snipe
81261d9e36 Put the session check back in - possible fix for #18004 2025-10-17 18:25:50 +01:00
snipe
3ab2e20119 Merge remote-tracking branch 'origin/develop' 2025-10-17 17:12:47 +01:00
snipe
b773d576ea Updated cipher-base library 2025-10-17 17:12:31 +01:00
snipe
23feb64b5a Fixed potential XSS on locations 2025-10-17 17:07:57 +01:00
snipe
87fe9d9d3d Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/css/dist/skins/_all-skins.css
#	public/css/dist/skins/_all-skins.css.map
#	public/css/dist/skins/_all-skins.min.css
#	public/css/dist/skins/skin-contrast.css
#	public/css/dist/skins/skin-contrast.css.map
#	public/css/dist/skins/skin-contrast.min.css
#	public/mix-manifest.json
2025-10-17 17:00:10 +01:00
snipe
b1ef3f51cb Merge pull request #18064 from grokability/new-fieldsets
Use new fieldset component
2025-10-17 16:58:17 +01:00
snipe
e1b6488f8e Use new fieldset component 2025-10-17 16:55:36 +01:00
snipe
a472dede2b Merge pull request #18063 from grokability/improved-fieldset-UI-and-better-help-test-for-ldap-mapping
New legend styles and additional help hints for LDAP
2025-10-17 15:45:05 +01:00
snipe
263cc3f7a1 New styles and additional help hints for LDAP 2025-10-17 15:29:24 +01:00
Brady Wetherington
598612d4bf Got tests to pass? Not very happy about it, tbh. 2025-10-16 19:11:49 +01:00
Brady Wetherington
a07d83e583 Improved find-and-bind for complex LDAP directory structures 2025-10-16 19:11:49 +01:00
snipe
d355812433 Updated assets 2025-10-16 19:06:22 +01:00
snipe
1afc14f5ab Merge remote-tracking branch 'origin/develop' 2025-10-16 19:04:30 +01:00
snipe
8947b667ae Fixed status labels skin for visited link buttons 2025-10-16 19:04:15 +01:00
snipe
6b41796d44 Fixed restore button on asset view page 2025-10-16 19:04:01 +01:00
snipe
6efe8eb55b Small fix for high contrast skin 2025-10-16 18:33:33 +01:00
snipe
4c8f8918e8 Merge remote-tracking branch 'origin/develop' 2025-10-16 15:49:59 +01:00
snipe
4cb5bb1855 Merge pull request #18057 from grokability/user-selector-fix
Fixed #18053 - user selector missing on page load without cookies
2025-10-16 15:49:39 +01:00
snipe
eb007e025a Fixed #18053 - user selector missing on page load without cookies 2025-10-16 15:47:25 +01:00
snipe
18aefa9dee Merge remote-tracking branch 'origin/develop' 2025-10-16 15:10:29 +01:00
snipe
2ec540bd36 Updated language translations 2025-10-16 15:10:17 +01:00
snipe
1374ec408a Merge pull request #18019 from akemidx/emdashcharfix
Fixed #17363 - Emdash Character Encoding error
2025-10-16 15:07:31 +01:00
snipe
8f8fed2b79 Merge remote-tracking branch 'origin/develop' 2025-10-16 14:33:30 +01:00
snipe
a1e6f01fe9 Fixed updating notes in bulk edit 2025-10-16 14:33:21 +01:00
snipe
d7496f22e5 Merge remote-tracking branch 'origin/develop' 2025-10-16 14:04:44 +01:00
snipe
7de25a1c37 Merge pull request #18056 from grokability/add-expected-checkin-to-view-assets
Added expected_checkin to user’s View Assigned page
2025-10-16 14:04:31 +01:00
snipe
78da89340c Added expected_checkin to user’s View Assigned page 2025-10-16 13:59:59 +01:00
snipe
60606115fe Merge remote-tracking branch 'origin/develop' 2025-10-16 12:46:47 +01:00
snipe
57b49fc31c Check for array key for license report/formatters 2025-10-16 12:46:17 +01:00
snipe
6e90c8f6e6 Bumped hash for master 2025-10-16 11:28:37 +01:00
snipe
a7ff2595a5 Bumped hash 2025-10-16 11:28:01 +01:00
snipe
1edea6abef Fixed RB-20347 - ambigious field in custom fields 2025-10-16 11:27:25 +01:00
snipe
a3bd58bda6 Foundation 2025-10-16 11:17:48 +01:00
snipe
3339d1999f Merge remote-tracking branch 'origin/develop' 2025-10-16 11:12:56 +01:00
akemidx
df3e8ec0f3 cleaning up notes 2025-10-15 17:35:25 -04:00
snipe
2dbec867d9 Merge pull request #18038 from boteroTradebe/develop
Fixed LDAP sync not syncing all user fields
2025-10-15 17:25:33 +01:00
Mohammad Ahmadi
3fc651d659 fix: update mysqldump options to use --ssl-mode=DISABLED for modern versions 2025-10-15 00:30:53 +02:00
snipe
b91d23023d Fix blade template syntax for accessory fields 2025-10-14 13:45:58 +01:00
boteroTradebe
cc234c60b8 Update LdapSync.php to populate/update user fields
Added missing IF statements for address, city, state, and ZIP code. Previously, this information would be pulled via LDAP sync but would not update the user data due to the missing IF statements for these attributes.
2025-10-13 13:42:11 -05:00
snipe
f1883c8004 Merge remote-tracking branch 'origin/develop' 2025-10-13 14:48:46 +01:00
snipe
79f6ddc8ee Merge pull request #18036 from grokability/better-messaging-for-bulk-deletes
Fixed #18034 - Shows success message on partial bulk delete success
2025-10-13 14:30:26 +01:00
snipe
89f439a18d Shows success message on partial bulk delete success 2025-10-13 14:25:05 +01:00
snipe
79eb5bfad9 Merge pull request #18035 from grokability/fixes-bulk-error-notification
Fixed bulk error display to be more consistent and correct HTML
2025-10-13 14:02:11 +01:00
snipe
3a36b7dafd Fixed display to be more consistent and correct HTML 2025-10-13 13:57:04 +01:00
snipe
1fe2fd9891 Merge pull request #17573 from spencerrlongg/feature/8709-bulk-deletion-of-asset-categories-suppliers-manufacturers
Fixed #8709 - Bulk Deletion of Categories, Suppliers, Manufa...
2025-10-13 13:20:13 +01:00
snipe
5fdb999ece Merge branch 'develop' into feature/8709-bulk-deletion-of-asset-categories-suppliers-manufacturers 2025-10-13 13:07:53 +01:00
snipe
5a38d9c2b6 Merge pull request #18029 from grokability/dependabot/github_actions/develop/github/codeql-action-4
Bump github/codeql-action from 3 to 4
2025-10-13 12:00:49 +01:00
snipe
b1c7dc6cbb Merge remote-tracking branch 'origin/develop' 2025-10-13 11:32:19 +01:00
snipe
b21b2ac41c Merge pull request #18032 from grokability/move-asset-private-files
Added migration to move asset model private uploads
2025-10-13 11:31:55 +01:00
snipe
1396c597ef Move files 2025-10-13 11:19:25 +01:00
dependabot[bot]
46af72ee4e Bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 08:19:22 +00:00
snipe
e3e8f553c8 Merge remote-tracking branch 'origin/develop' 2025-10-09 18:39:13 +01:00
snipe
549928d3d1 Updated assets 2025-10-09 18:38:59 +01:00
snipe
abefec9628 Upgraded jdpdf to ^3.0.3 2025-10-09 18:37:59 +01:00
snipe
b483eeded4 Merge remote-tracking branch 'origin/develop' 2025-10-09 11:24:47 +01:00
snipe
fab85dafa8 Added employee number and email to acceptance PDF 2025-10-09 11:24:16 +01:00
snipe
955faed919 Merge pull request #18018 from grokability/advanced-user-search
Normalize advanced search
2025-10-09 11:04:28 +01:00
snipe
3ff516180d Merge remote-tracking branch 'origin/develop' 2025-10-09 10:56:08 +01:00
snipe
0c8ca6d6b0 Search on status label name 2025-10-09 10:54:11 +01:00
snipe
a5de077e04 Merge pull request #17975 from marcusmoore/replace-date-and-time-display-macros
Removed date and time display format form macros
2025-10-09 04:54:05 +01:00
akemidx
c0a99d6b52 notes 2025-10-08 19:20:32 -04:00
Marcus Moore
a1e65cd897 Merge branch 'develop' into replace-date-and-time-display-macros
# Conflicts:
#	resources/macros/macros.php
2025-10-08 13:10:56 -07:00
snipe
5d65f1ffc5 Merge pull request #17976 from marcusmoore/17204-replace-form-digit-separator
Fixed #17204 - replace Form::digit_separator macro
2025-10-08 21:01:11 +01:00
snipe
b7193a06fd Normalize advanced search 2025-10-08 20:51:35 +01:00
akemidx
cba090f8eb notes 2025-10-08 15:50:00 -04:00
snipe
0d6baa1081 Merge remote-tracking branch 'origin/develop' 2025-10-08 15:29:02 +01:00
snipe
bc60d796a3 Fixed RB-20329 - ambiguous clause on deleted_at 2025-10-08 15:28:48 +01:00
snipe
85a208526b Merge remote-tracking branch 'origin/develop' 2025-10-08 10:38:08 +01:00
snipe
c3ac0a750d Added licenses checkin permission 2025-10-08 10:37:58 +01:00
snipe
0f111127a5 Merge pull request #18005 from marcusmoore/fixes/drop-index-migration
Fixed exception when rolling back migrations
2025-10-08 05:26:22 +01:00
snipe
cd266a6bef Merge remote-tracking branch 'origin/develop' 2025-10-08 05:13:14 +01:00
snipe
0f4945621c Merge pull request #18009 from grokability/#18002-audit-image-location
Fixed #18002 - use correct path for audit images to determine validity
2025-10-08 05:12:54 +01:00
snipe
9a477f227b Fixed #18002 - use correct path for audit images to determine validity 2025-10-08 05:09:04 +01:00
akemidx
3c81257325 fix? 2025-10-07 20:23:46 -04:00
Marcus Moore
218fe9ebdc Allow Laravel to calculate index name 2025-10-07 16:04:12 -07:00
spencerrlongg
24bb45ab97 change translation 2025-10-07 13:26:08 -05:00
snipe
85f39de540 Merge pull request #18001 from grokability/#17670-fixes-advanced-search-on-assets
Fixed #17670 - advanced search on relationships not working
2025-10-07 17:35:05 +01:00
snipe
5cc3277e2d Merge pull request #18003 from uberbrady/fix_file_upload_base64
Fixed [FD-50921] - base64-encoded image files for asset creation was broken
2025-10-07 17:34:53 +01:00
snipe
d5ef7f3204 Merge remote-tracking branch 'origin/develop' 2025-10-07 17:29:24 +01:00
snipe
cb47d01f51 Fixed variable name in location print 2025-10-07 17:29:12 +01:00
Brady Wetherington
ee499c1385 Fix base64-encoded image files for asset creation; add test 2025-10-07 16:00:16 +01:00
snipe
4ae4af5c10 Fixed #17670 - advanced search on relationships not working 2025-10-07 15:39:09 +01:00
snipe
b0f5fe7e25 Merge remote-tracking branch 'origin/develop' 2025-10-07 14:32:33 +01:00
snipe
fd32585efc Added notes back to audit email 2025-10-07 14:32:24 +01:00
snipe
c675bb7252 Merge remote-tracking branch 'origin/develop' 2025-10-07 14:29:21 +01:00
snipe
e65d11d71e Pulled incorrect button label 2025-10-07 14:29:10 +01:00
snipe
c2680334f1 Clearer title on maintenances within asset context 2025-10-07 14:28:38 +01:00
snipe
1217a02ec1 Merge pull request #17992 from marcusmoore/17963-undelete-files-command
Added command to remove invalid "upload deleted" entries from the action log
2025-10-07 14:17:56 +01:00
snipe
143a091f45 Merge remote-tracking branch 'origin/develop' 2025-10-07 14:15:59 +01:00
snipe
49ecc93dbe Merge pull request #17999 from grokability/updated-audit-report
Improved upcoming audit email layout and cli feedback
2025-10-07 14:15:44 +01:00
snipe
1735fb6bed Improved audit email 2025-10-07 14:08:33 +01:00
snipe
8fefc11b4d Merge remote-tracking branch 'origin/develop' 2025-10-07 12:09:32 +01:00
snipe
1a3b22171c Merge pull request #17997 from grokability/#17924-add-url-to-maintenances
Fixed #17924 - added url to maintenances
2025-10-07 12:09:06 +01:00
snipe
5e773be260 Enhanced tests 2025-10-07 12:05:37 +01:00
snipe
c317a1dc8b Added url to factory 2025-10-07 12:03:35 +01:00
snipe
9401ffc83c Added link to view 2025-10-07 11:55:32 +01:00
snipe
479a852446 Fixed #17924 - added url to maintenances 2025-10-07 11:49:10 +01:00
snipe
9188fb03f0 Merge remote-tracking branch 'origin/develop' 2025-10-07 10:49:59 +01:00
snipe
36bb91cbc3 Merge pull request #17995 from grokability/#17910-show-counts-on-mobile
Fixed #17910 - added counts to mobile view for assets
2025-10-07 10:47:41 +01:00
snipe
56e5ab8bc6 Fixed #17910 - added counts to mobile view for assets 2025-10-07 10:41:03 +01:00
snipe
607eb6ca03 Merge remote-tracking branch 'origin/develop' 2025-10-07 09:42:59 +01:00
snipe
ad745cc84b Fixed #17911 - updated language string 2025-10-07 09:42:30 +01:00
Marcus Moore
5a13d5ea6f Update command description 2025-10-06 15:23:55 -07:00
Marcus Moore
0b065eb7fe Improve command 2025-10-06 15:20:43 -07:00
Marcus Moore
e2c0e4bc66 WIP build out command 2025-10-06 15:14:15 -07:00
Marcus Moore
655b0e6778 Scaffold command 2025-10-06 14:59:25 -07:00
Marcus Moore
cd785c9fc3 Remove name_display_format macro that was accidentally re-added 2025-10-06 13:06:52 -07:00
Marcus Moore
23885f5166 Merge branch 'develop' into 17204-replace-form-digit-separator
# Conflicts:
#	resources/macros/macros.php
2025-10-06 13:04:34 -07:00
Marcus Moore
689d5a2d58 Remove digit_separator form macro 2025-10-02 15:39:28 -07:00
Marcus Moore
b2e9eb866c Replace digit_separator form macro
Fixes #17204
2025-10-02 15:38:59 -07:00
Marcus Moore
c8b7782d1d Remove date_display_format and time_display_format macros 2025-10-02 15:27:20 -07:00
Marcus Moore
673f936689 Replace Form:: time_display_format on localization screen 2025-10-02 15:26:31 -07:00
Marcus Moore
2ca0d39e51 Replace Form:: date_display_format on localization screen 2025-10-02 15:21:51 -07:00
spencerrlongg
b9f4dc1e9d translation 2025-10-02 12:00:52 -05:00
akemidx
b082fb6692 investigation and notes 2025-10-01 18:21:25 -04:00
akemidx
a384245368 investigation and notes 2025-09-30 16:33:33 -04:00
spencerrlongg
32882f81e7 replace box-default 2025-09-29 15:52:43 -05:00
spencerrlongg
36f5099932 added new counts and throw new exceptions and catch them 2025-09-29 15:16:41 -05:00
Godfrey Martinez
ca44ee94a4 Merge pull request #28 from Godmartinz/add_types_to_command
update the snipeit reminder command
2025-09-25 10:44:53 -07:00
Godfrey M
3ae7a77032 update the snipeit reminder command 2025-09-25 10:43:41 -07:00
Godfrey M
881c789a75 add usuage of withoutactionlog 2025-09-24 15:45:01 -07:00
Godfrey M
fea0189479 Merge branch 'fix-factory-auto-gen-action-logs' into add_types_to_unaccepted_asset_report 2025-09-24 15:43:27 -07:00
Godfrey M
6ca0e19819 uncomment code 2025-09-24 13:50:41 -07:00
Godfrey M
bf6964ee62 tests passing, CheckoutAcceptance Factory needs eyes though 2025-09-24 13:41:04 -07:00
Godfrey M
9ac2ea2a52 fix test to check all mailable types for reminders 2025-09-24 13:22:33 -07:00
Godfrey M
0ce20c1edd fix send reminder method to handle other types 2025-09-23 18:40:03 -07:00
Godfrey M
d31d99b40f remove else and blank lines 2025-09-23 17:00:08 -07:00
Godfrey M
3527c357cc whoops 2025-09-23 16:57:06 -07:00
Godfrey M
2b5254e68f fixed, eager loading, variable names, clean up 2025-09-23 16:55:10 -07:00
Godfrey M
6f3323c195 fix data in view model 2025-09-23 12:50:26 -07:00
Godfrey M
5af85bfe7d further clean up 2025-09-23 12:10:50 -07:00
Godfrey M
20adad3c6b fix company name link, clean up query 2025-09-23 12:09:21 -07:00
Godfrey M
ca8eae4064 updated the csv values to be plain 2025-09-23 11:40:29 -07:00
Godfrey M
7077faaf4a updated the postassetAcceptanceReport query and rows 2025-09-23 11:08:37 -07:00
Godfrey M
ab30df10ff remove sorter from blade 2025-09-22 12:20:32 -07:00
Godfrey M
a6cb75c481 remove sorter, didnt work 2025-09-22 12:19:34 -07:00
Godfrey M
51ce570eb3 attempt to sort chronologically, can not resort still 2025-09-18 11:01:40 -07:00
Godfrey M
dcbb09bbd7 added checkoutable class 2025-09-16 15:59:53 -07:00
Godfrey M
58eac619ea add checkoutable class, rework blade for unaccepted items 2025-09-16 15:52:41 -07:00
spencerrlongg
1d24b7985b another translation change 2025-08-27 17:25:17 -05:00
spencerrlongg
526bb2c650 more translation changes 2025-08-27 17:23:46 -05:00
spencerrlongg
c450c0ddb8 lots of translation changes 2025-08-27 17:17:18 -05:00
spencerrlongg
51f6927076 add details block for more than 3 errors to notification 2025-08-26 21:50:08 -05:00
spencerrlongg
1d88cf443f revert SuppliersController as well 2025-08-25 18:45:31 -05:00
spencerrlongg
7b6c0c3a40 Revert "tests passing, needs some manual testing"
This reverts commit fdb0651bf4.
2025-08-25 18:42:11 -05:00
spencerrlongg
fdb0651bf4 tests passing, needs some manual testing 2025-08-25 18:25:36 -05:00
spencerrlongg
c39d484611 rm unused import, new prop 2025-08-25 16:30:53 -05:00
spencerrlongg
c42996429f rm unused import 2025-08-25 14:10:12 -05:00
spencerrlongg
a091baf5a6 component finally working 2025-08-25 14:06:56 -05:00
spencerrlongg
643d44af22 change \Throwable to \Exceptionm, add missing report()s 2025-08-20 23:09:59 -05:00
spencerrlongg
b934f43db0 rename all exceptions 2025-08-20 22:58:39 -05:00
spencerrlongg
e33b1b6c90 fixed maintenances 2025-08-12 13:34:40 -05:00
Spencer Long
30520297e8 Merge branch 'develop' into feature/8709-bulk-deletion-of-asset-categories-suppliers-manufacturers 2025-08-12 11:58:11 -06:00
spencerrlongg
78ca1d1335 some cleanup 2025-08-11 21:07:08 -05:00
spencerrlongg
6159ee8c2c category done! 2025-08-11 20:16:43 -05:00
spencerrlongg
5cd5392958 manufacturer completed, just categories left 2025-08-11 19:42:09 -05:00
spencerrlongg
0dcdfc5d14 fix tests after routing change 2025-08-11 19:11:40 -05:00
spencerrlongg
d0e068f1c0 suppliers completely done, rinse and repeat for the other two 2025-08-11 19:04:36 -05:00
spencerrlongg
3eefeec4ce partials made, need to figure out all this jquery, button disabled 2025-08-09 22:40:00 -05:00
spencerrlongg
b61419c1ce oop, revert delete 2025-08-09 22:19:28 -05:00
spencerrlongg
f590fcffbc some tests, a component i probably won't use, beginning of front end 2025-08-09 22:16:23 -05:00
spencerrlongg
7cdfaa93ec a couple more tests and cleanup 2025-07-10 16:45:15 -05:00
spencerrlongg
59ccc70303 bulk changes that should make this work 2025-07-10 16:02:39 -05:00
spencerrlongg
f1584b722d work on bulk tests, switching branches to check something 2025-07-10 15:29:42 -05:00
spencerrlongg
4d8c5a86a4 routes added, tests scratched but need writing 2025-07-09 23:01:58 -05:00
spencerrlongg
5f835aa009 delete unused imports 2025-07-07 18:07:26 -05:00
spencerrlongg
d5ca543719 start introducing parent exception 2025-07-07 18:05:23 -05:00
spencerrlongg
db63ad1cf4 add image check to supplier action 2025-06-17 18:13:37 -05:00
spencerrlongg
5da79cd5ca destroy manufacturer action, bulk manufacturer controller 2025-06-12 17:11:45 -05:00
spencerrlongg
13c971b171 some refinements, bulk categories controller 2025-06-12 15:19:49 -05:00
spencerrlongg
1cee7e43ed more (reusable) exceptions, a couple notes 2025-06-12 00:47:57 -05:00
spencerrlongg
5e81c63d6e some notes and things moved 2025-06-11 11:39:14 -05:00
spencerrlongg
17456482d6 category destroy action 2025-06-04 21:42:58 -05:00
spencerrlongg
a0431e1912 supplier actions working 2025-06-04 20:44:18 -05:00
spencerrlongg
ee5aac8008 supplier destroy action creaated and a lot scratched out 2025-06-03 20:25:07 -05:00
spencerrlongg
1b397cd780 assertDbHas 2025-05-21 18:17:45 -05:00
spencerrlongg
120316bae0 wrong location import 2025-05-21 13:36:31 -05:00
spencerrlongg
7571ff007f add fillable properties to rules, some tests, move authorization to request 2025-05-21 12:51:21 -05:00
spencerrlongg
7afd7da2b4 initial work, more testing/tests needed 2025-05-21 11:44:54 -05:00
1486 changed files with 195819 additions and 7048 deletions

View File

@@ -4235,6 +4235,33 @@
"contributions": [
"code"
]
},
{
"login": "smarsching",
"name": "Sebastian Marsching",
"avatar_url": "https://avatars.githubusercontent.com/u/2880129?v=4",
"profile": "http://sebastian.marsching.com/",
"contributions": [
"code"
]
},
{
"login": "mohammad-ahmadi1",
"name": "Mo",
"avatar_url": "https://avatars.githubusercontent.com/u/40658372?v=4",
"profile": "https://github.com/mohammad-ahmadi1",
"contributions": [
"code"
]
},
{
"login": "MarvelousAnything",
"name": "Owen V. Hayes",
"avatar_url": "https://avatars.githubusercontent.com/u/20994684?v=4",
"profile": "https://github.com/MarvelousAnything",
"contributions": [
"code"
]
}
]
}

View File

@@ -137,6 +137,8 @@ PUBLIC_AWS_ACCESS_KEY_ID=null
PUBLIC_AWS_DEFAULT_REGION=null
PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_ENDPOINT=null
PUBLIC_AWS_PATH_STYLE=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -147,6 +149,8 @@ PRIVATE_AWS_SECRET_ACCESS_KEY=null
PRIVATE_AWS_DEFAULT_REGION=null
PRIVATE_AWS_BUCKET=null
PRIVATE_AWS_URL=null
PRIVATE_AWS_ENDPOINT=null
PRIVATE_AWS_PATH_STYLE=null
PRIVATE_AWS_BUCKET_ROOT=null
# --------------------------------------------

View File

@@ -144,6 +144,8 @@ PUBLIC_AWS_ACCESS_KEY_ID=null
PUBLIC_AWS_DEFAULT_REGION=null
PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_ENDPOINT=null
PUBLIC_AWS_PATH_STYLE=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -154,6 +156,8 @@ PRIVATE_AWS_SECRET_ACCESS_KEY=null
PRIVATE_AWS_DEFAULT_REGION=null
PRIVATE_AWS_BUCKET=null
PRIVATE_AWS_URL=null
PRIVATE_AWS_ENDPOINT=null
PRIVATE_AWS_PATH_STYLE=null
PRIVATE_AWS_BUCKET_ROOT=null
# --------------------------------------------

View File

@@ -143,6 +143,8 @@ PUBLIC_AWS_ACCESS_KEY_ID=null
PUBLIC_AWS_DEFAULT_REGION=null
PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_ENDPOINT=null
PUBLIC_AWS_PATH_STYLE=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -153,6 +155,8 @@ PRIVATE_AWS_SECRET_ACCESS_KEY=null
PRIVATE_AWS_DEFAULT_REGION=null
PRIVATE_AWS_BUCKET=null
PRIVATE_AWS_URL=null
PRIVATE_AWS_ENDPOINT=null
PRIVATE_AWS_PATH_STYLE=null
PRIVATE_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -190,13 +194,14 @@ APP_ALLOW_INSECURE_HOSTS=false
GOOGLE_MAPS_API=
LDAP_MEM_LIM=500M
LDAP_TIME_LIM=600
BACKUP_TIME_LIMIT=600
IMPORT_TIME_LIMIT=600
IMPORT_MEMORY_LIMIT=500M
REPORT_TIME_LIMIT=12000
API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true
LIVEWIRE_URL_PREFIX=null
MAX_UNPAGINATED=5000
# --------------------------------------------
# OPTIONAL: SAML SETTINGS

View File

@@ -26,14 +26,14 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4

View File

@@ -32,7 +32,7 @@ jobs:
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
@@ -52,6 +52,6 @@ jobs:
# Upload the SARIF file generated in the previous step
- name: Upload SARIF results file
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Crowdin push
uses: crowdin/github-action@v2

View File

@@ -42,7 +42,7 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v5
uses: actions/checkout@v6
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx

View File

@@ -42,7 +42,7 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v5
uses: actions/checkout@v6
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx

View File

@@ -11,7 +11,7 @@ jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Docker Hub Description
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4

View File

@@ -37,7 +37,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get Composer Cache Directory
id: composer-cache
@@ -82,7 +82,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |

View File

@@ -34,7 +34,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get Composer Cache Directory
id: composer-cache
@@ -81,7 +81,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |

View File

@@ -25,7 +25,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get Composer Cache Directory
id: composer-cache
@@ -67,7 +67,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |

View File

@@ -68,7 +68,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [<img src="https://avatars.githubusercontent.com/u/15015119?v=4" width="110px;"/><br /><sub>Johannes Pollitt</sub>](https://github.com/FlorestanII)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [<img src="https://avatars.githubusercontent.com/u/14185442?v=4" width="110px;"/><br /><sub>Michael Strobel</sub>](https://strobelm.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [<img src="https://avatars.githubusercontent.com/u/634790?v=4" width="110px;"/><br /><sub>Nicky West</sub>](http://nickwest.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [<img src="https://avatars.githubusercontent.com/u/1347327?v=4" width="110px;"/><br /><sub>akaspeh1</sub>](https://github.com/akaspeh1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [<img src="https://avatars.githubusercontent.com/u/15015119?v=4" width="110px;"/><br /><sub>Johannes Pollitt</sub>](https://github.com/FlorestanII)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [<img src="https://avatars.githubusercontent.com/u/14185442?v=4" width="110px;"/><br /><sub>Michael Strobel</sub>](https://strobelm.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [<img src="https://avatars.githubusercontent.com/u/634790?v=4" width="110px;"/><br /><sub>Nicky West</sub>](http://nickwest.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [<img src="https://avatars.githubusercontent.com/u/1347327?v=4" width="110px;"/><br /><sub>akaspeh1</sub>](https://github.com/akaspeh1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | [<img src="https://avatars.githubusercontent.com/u/2880129?v=4" width="110px;"/><br /><sub>Sebastian Marsching</sub>](http://sebastian.marsching.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smarsching "Code") | [<img src="https://avatars.githubusercontent.com/u/40658372?v=4" width="110px;"/><br /><sub>Mo</sub>](https://github.com/mohammad-ahmadi1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mohammad-ahmadi1 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/20994684?v=4" width="110px;"/><br /><sub>Owen V. Hayes</sub>](https://github.com/MarvelousAnything)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MarvelousAnything "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -83,6 +83,7 @@ Since the release of the JSON REST API, several third-party developers have been
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Rudder2Snipe](https://github.com/norbertoaquino/rudder2snipe) by [@norbertoaquino](https://github.com/norbertoaquino) - Rudder.io integration for Snipe-IT
- [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.

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Actions\Categories;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Category;
use Illuminate\Support\Facades\Storage;
class DestroyCategoryAction
{
/**
* @throws ItemStillHasAssets
* @throws ItemStillHasAssetModels
* @throws ItemStillHasComponents
* @throws ItemStillHasAccessories
* @throws ItemStillHasLicenses
* @throws ItemStillHasConsumables
*/
static function run(Category $category): bool
{
$category->loadCount([
'assets as assets_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
'licenses as licenses_count',
'models as models_count'
]);
if ($category->assets_count > 0) {
throw new ItemStillHasAssets($category);
}
if ($category->accessories_count > 0) {
throw new ItemStillHasAccessories($category);
}
if ($category->consumables_count > 0) {
throw new ItemStillHasConsumables($category);
}
if ($category->components_count > 0) {
throw new ItemStillHasComponents($category);
}
if ($category->licenses_count > 0) {
throw new ItemStillHasLicenses($category);
}
if ($category->models_count > 0) {
throw new ItemStillHasAssetModels($category);
}
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
return true;
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Actions\Manufacturers;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Manufacturer;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class DeleteManufacturerAction
{
/**
* @throws ItemStillHasAssets
* @throws ItemStillHasComponents
* @throws ItemStillHasAccessories
* @throws ItemStillHasLicenses
* @throws ItemStillHasConsumables
*/
static function run(Manufacturer $manufacturer): bool
{
$manufacturer->loadCount([
'assets as assets_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
'licenses as licenses_count',
]);
if ($manufacturer->assets_count > 0) {
throw new ItemStillHasAssets($manufacturer);
}
if ($manufacturer->accessories_count > 0) {
throw new ItemStillHasAccessories($manufacturer);
}
if ($manufacturer->consumables_count > 0) {
throw new ItemStillHasConsumables($manufacturer);
}
if ($manufacturer->components_count > 0) {
throw new ItemStillHasComponents($manufacturer);
}
if ($manufacturer->licenses_count > 0) {
throw new ItemStillHasLicenses($manufacturer);
}
if ($manufacturer->image) {
try {
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
Log::info($e);
}
}
$manufacturer->delete();
//dd($manufacturer);
return true;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Actions\Suppliers;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Models\Supplier;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasLicenses;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class DestroySupplierAction
{
/**
*
* @throws ItemStillHasLicenses
* @throws ItemStillHasAssets
* @throws ItemStillHasMaintenances
* @throws ItemStillHasAccessories
* @throws ItemStillHasConsumables
* @throws ItemStillHasComponents
*/
static function run(Supplier $supplier): bool
{
$supplier->loadCount([
'maintenances as maintenances_count',
'assets as assets_count',
'licenses as licenses_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
]);
if ($supplier->assets_count > 0) {
throw new ItemStillHasAssets($supplier);
}
if ($supplier->maintenances_count > 0) {
throw new ItemStillHasMaintenances($supplier);
}
if ($supplier->licenses_count > 0) {
throw new ItemStillHasLicenses($supplier);
}
if ($supplier->accessories_count > 0) {
throw new ItemStillHasAccessories($supplier);
}
if ($supplier->consumables_count > 0) {
throw new ItemStillHasConsumables($supplier);
}
if ($supplier->components_count > 0) {
throw new ItemStillHasComponents($supplier);
}
if ($supplier->image) {
try {
Storage::disk('public')->delete('suppliers/'.$supplier->image);
} catch (\Exception $e) {
Log::info($e->getMessage());
}
}
$supplier->delete();
return true;
}
}

View File

@@ -317,9 +317,21 @@ class LdapSync extends Command
if($ldap_map["jobtitle"] != null){
$user->jobtitle = $item['jobtitle'];
}
if($ldap_map["address"] != null){
$user->address = $item['address'];
}
if($ldap_map["city"] != null){
$user->city = $item['city'];
}
if($ldap_map["state"] != null){
$user->state = $item['state'];
}
if($ldap_map["country"] != null){
$user->country = $item['country'];
}
if($ldap_map["zip"] != null){
$user->zip = $item['zip'];
}
if($ldap_map["dept"] != null){
$user->department_id = $department->id;
}

View File

@@ -8,8 +8,6 @@ use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Helper\ProgressIndicator;
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
/**
* Class ObjectImportCommand
@@ -52,6 +50,9 @@ class ObjectImportCommand extends Command
*/
public function handle()
{
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
$this->progressIndicator = new ProgressIndicator($this->output);
$filename = $this->argument('filename');

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands;
use App\Models\Actionlog;
use Illuminate\Console\Command;
class RemoveInvalidUploadDeleteActionLogItems extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:remove-invalid-upload-delete-action-log-items';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Permanently remove invalid "upload deleted" action log items that have a null filename. This command can potentially result in deleted files being "resurrected" in the UI.';
/**
* Execute the console command.
*/
public function handle()
{
$invalidLogs = Actionlog::query()
->where('action_type', 'upload deleted')
->whereNull('filename')
->withTrashed()
->get();
$this->info("{$invalidLogs->count()} invalid log items found.");
if ($invalidLogs->count() === 0) {
return 0;
}
$this->table(['ID', 'Action Type', 'Item Type', 'Item ID', 'Created At', 'Deleted At'], $invalidLogs->map(fn($log) => [
$log->id,
$log->action_type,
$log->item_type,
$log->item_id,
$log->created_at,
$log->deleted_at,
])->toArray());
if ($this->confirm("Do you wish to remove {$invalidLogs->count()} log items?")) {
$invalidLogs->each(fn($log) => $log->forceDelete());
}
return 0;
}
}

View File

@@ -3,13 +3,18 @@
namespace App\Console\Commands;
use App\Mail\UnacceptedAssetReminderMail;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CurrentInventory;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Facades\Mail;
class SendAcceptanceReminder extends Command
@@ -26,7 +31,7 @@ class SendAcceptanceReminder extends Command
*
* @var string
*/
protected $description = 'This will resend users with unaccepted assets a reminder to accept or decline them.';
protected $description = 'This will resend users with unaccepted items a reminder to accept or decline them.';
/**
* Create a new command instance.
@@ -45,19 +50,30 @@ class SendAcceptanceReminder extends Command
*/
public function handle()
{
$pending = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')
->whereHas('checkoutable', function($query) {
$query->where('accepted_at', null)
->where('declined_at', null);
})
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
->get();
$pending = CheckoutAcceptance::query()
->with([
'checkoutable' => function (MorphTo $morph) {
$morph->morphWith([
Asset::class => ['model.category', 'assignedTo', 'adminuser', 'company', 'checkouts'],
Accessory::class => ['category', 'company', 'checkouts'],
LicenseSeat::class => ['user', 'license', 'checkouts'],
Component::class => ['assignedTo', 'company', 'checkouts'],
Consumable::class => ['company', 'checkouts'],
]);
},
'assignedTo',
])
->whereHasMorph(
'checkoutable',
[Asset::class, Accessory::class, LicenseSeat::class, Component::class, Consumable::class],
fn ($q) => $q->whereNull('accepted_at')
->whereNull('declined_at')
)
->pending()
->get();
$count = 0;
$unacceptedAssetGroups = $pending
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
})

View File

@@ -9,6 +9,8 @@ use App\Notifications\ExpectedCheckinAdminNotification;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
use App\Helpers\Helper;
class SendExpectedCheckinAlerts extends Command
{
@@ -17,7 +19,7 @@ class SendExpectedCheckinAlerts extends Command
*
* @var string
*/
protected $name = 'snipeit:expected-checkin';
protected $signature = 'snipeit:expected-checkin {--with-output : Display the results in a table in your console in addition to sending the email}';
/**
* The console command description.
@@ -42,19 +44,47 @@ class SendExpectedCheckinAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
$interval = $settings->audit_warning_days ?? 0;
$interval = $settings->due_checkin_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$count = 0;
if (!$this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
}
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get();
$this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline');
$this->info($assets->count().' assets must be checked on or before '.Helper::getFormattedDateObject($interval_date, 'date', false));
foreach ($assets as $asset) {
if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) {
$this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email);
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
$count++;
}
}
if ($this->option('with-output')) {
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
$this->table(
[
trans('general.id'),
trans('admin/hardware/form.tag'),
trans('admin/hardware/form.model'),
trans('general.model_no'),
trans('general.purchase_date'),
trans('admin/hardware/form.expected_checkin'),
],
$assets->map(fn($assets) => [
trans('general.id') => $assets->id,
trans('admin/hardware/form.tag') => $assets->asset_tag,
trans('admin/hardware/form.model') => $assets->model->name,
trans('general.model_no') => $assets->model->model_number,
trans('general.purchase_date') => $assets->purchase_date_formatted,
trans('admin/hardware/form.eol_date') => $assets->expected_checkin_formattedDate ? $assets->expected_checkin_formattedDate . ' (' . $assets->expected_checkin_diff_for_humans . ')' : '',
])
);
}
}
@@ -63,10 +93,11 @@ class SendExpectedCheckinAlerts extends Command
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
$this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email);
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
}
$this->info('Sent checkin reminders to to '.$count.' users.');
}
}

View File

@@ -107,7 +107,7 @@ class SendExpirationAlerts extends Command
trans('general.name') => $item->name,
trans('general.purchase_date') => $item->purchase_date_formatted,
trans('admin/licenses/form.expiration') => $item->expires_formatted_date,
trans('mail.expires') => $item->expires_diff_for_humans,
trans('mail.expires') => $item->expires_formatted_date ? $item->expires_diff_for_humans : '',
trans('admin/licenses/form.termination_date') => $item->terminates_formatted_date,
trans('mail.terminates') => $item->terminates_diff_for_humans
])

View File

@@ -52,7 +52,9 @@ class SendInventoryAlerts extends Command
return new AlertRecipient($item);
});
\Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
} else {
$this->info('No low inventory items found. No mail sent.');
}
} else {
if ($settings->alert_email == '') {

View File

@@ -16,7 +16,7 @@ class SendUpcomingAuditReport extends Command
*
* @var string
*/
protected $signature = 'snipeit:upcoming-audits';
protected $signature = 'snipeit:upcoming-audits {--with-output : Display the results in a table in your console in addition to sending the email}';
/**
* The console command description.
@@ -47,21 +47,69 @@ class SendUpcomingAuditReport extends Command
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->filter(fn($item) => !empty($item))
->all();
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
$assets_query = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'asc')->with('supplier');
$asset_count = $assets_query->count();
$this->info(number_format($asset_count) . ' assets must be audited on or before ' . $interval_date);
if (!$this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
}
if ($asset_count > 0) {
$assets_for_email = $assets_query->limit(30)->get();
// Send a rollup to the admin, if settings dictate
if ($settings->alert_email != '') {
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->filter(fn($item) => !empty($item))
->all();
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets_for_email, $settings->audit_warning_days, $asset_count));
$this->info('Audit notification sent to: ' . $settings->alert_email);
} else {
$this->info('There is no admin alert email set so no email will be sent.');
}
if ($this->option('with-output')) {
// Get the full list if the user wants output in the console
$assets_for_output = $assets_query->limit(null)->get();
$this->table(
[
trans('general.id'),
trans('general.name'),
trans('general.last_audit'),
trans('general.next_audit_date'),
trans('mail.Days'),
trans('mail.supplier'),
trans('mail.assigned_to'),
],
$assets_for_output->map(fn($item) => [
trans('general.id') => $item->id,
trans('general.name') => $item->display_name,
trans('general.last_audit') => $item->last_audit_formatted_date,
trans('general.next_audit_date') => $item->next_audit_formatted_date,
trans('mail.Days') => round($item->next_audit_diff_in_days),
trans('mail.supplier') => $item->supplier ? $item->supplier->name : '',
trans('mail.assigned_to') => $item->assignedTo ? $item->assignedTo->display_name : '',
])
);
}
} else {
$this->info('There are no assets due for audit in the next ' . $interval . ' days.');
}
}
}

View File

@@ -37,6 +37,8 @@ class SystemBackup extends Command
*/
public function handle()
{
ini_set('max_execution_time', env('BACKUP_TIME_LIMIT', 600)); //600 seconds = 10 minutes
if ($this->option('filename')) {
$filename = $this->option('filename');

33
app/Enums/ActionType.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace App\Enums;
enum ActionType: string
{
// General
case Create = 'create';
case Update = 'update';
case Delete = 'delete';
case Restore = 'restore';
// Assets/Accessories/Components/Licenses/Consumables
case Checkout = 'checkout';
case CheckinFrom = 'checkin from';
case Requested = 'requested';
case RequestCanceled = 'request canceled';
case Accepted = 'accepted';
case Declined = 'declined';
case Audit = 'audit';
case NoteAdded = 'note added';
// Users
case TwoFactorReset = '2FA reset';
case Merged = 'merged';
// Licenses
case DeleteSeats = 'delete seats';
case AddSeats = 'add seats';
// File Uploads
case Uploaded = 'uploaded';
case UploadDeleted = 'upload deleted';
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasAccessories extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasAssetModels extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasAssets extends ItemStillHasChildren
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasChildren extends Exception
{
//public function __construct($message, $code = 0, Exception $previous = null, $parent, $children)
//{
// trans()
//
//}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasComponents extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasConsumables extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasLicenses extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasMaintenances extends ItemStillHasChildren
{
//
}

View File

@@ -40,6 +40,8 @@ class IconHelper
return 'fa-solid fa-trash-arrow-up';
case 'external-link':
return 'fa fa-external-link';
case 'link':
return 'fa fa-link';
case 'email':
return 'fa-regular fa-envelope';
case 'phone':
@@ -195,6 +197,10 @@ class IconHelper
case 'note':
case 'notes':
return 'fas fa-sticky-note';
case 'tip':
return 'fa-solid fa-lightbulb';
case 'highlight':
return 'fa-solid fa-highlighter';
}
}
}

View File

@@ -182,7 +182,11 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
}
if ($accessory->save()) {
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')

View File

@@ -13,9 +13,9 @@ use App\Models\Company;
use App\Models\Contracts\Acceptable;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
use App\Notifications\AcceptanceItemAcceptedNotification;
use App\Notifications\AcceptanceItemAcceptedToUserNotification;
use App\Notifications\AcceptanceItemDeclinedNotification;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
@@ -116,8 +116,6 @@ class AcceptanceController extends Controller
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
// If signatures are required, make sure we have one
if (Setting::getSettings()->require_accept_signature == '1') {
@@ -140,14 +138,14 @@ class AcceptanceController extends Controller
// Convert PDF logo to base64 for TCPDF
// This is needed for TCPDF to properly embed the image if it's a png and the cache isn't writable
$encoded_logo = null;
if ($settings->acceptance_pdf_logo) {
if (($settings->acceptance_pdf_logo) && (Storage::disk('public')->exists($settings->acceptance_pdf_logo))) {
$encoded_logo = base64_encode(file_get_contents(public_path() . '/uploads/' . $settings->acceptance_pdf_logo));
}
// Get the data array ready for the notifications and PDF generation
$data = [
'item_tag' => $item->asset_tag,
'item_name' => $item->name, // this handles licenses seats, which don't have a 'name' field
'item_name' => $item->display_name, // this handles licenses seats, which don't have a 'name' field
'item_model' => $item->model?->name,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
@@ -157,16 +155,16 @@ class AcceptanceController extends Controller
'accepted_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'declined_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'assigned_to' => $assigned_user->display_name,
'email' => $assigned_user->email,
'employee_num' => $assigned_user->employee_num,
'site_name' => $settings->site_name,
'company_name' => $item->company?->name?? $settings->site_name,
'signature' => (($sig_filename && array_key_exists('1', $encoded_image))) ? $encoded_image[1] : null,
'logo' => ($encoded_logo) ?? null,
'date_settings' => $settings->date_display_format,
'admin' => auth()->user()->present()?->fullName,
'qty' => $acceptance->qty ?? 1,
];
if ($request->input('asset_acceptance') == 'accepted') {
@@ -185,13 +183,13 @@ class AcceptanceController extends Controller
// Add the attachment for the signing user into the $data array
$data['file'] = $pdf_filename;
try {
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($assigned_user->locale));
$assigned_user->notify((new AcceptanceItemAcceptedToUserNotification($data))->locale($assigned_user->locale));
} catch (\Exception $e) {
Log::warning($e);
}
}
try {
$acceptance->notify((new AcceptanceAssetAcceptedNotification($data))->locale(Setting::getSettings()->locale));
$acceptance->notify((new AcceptanceItemAcceptedNotification($data))->locale(Setting::getSettings()->locale));
} catch (\Exception $e) {
Log::warning($e);
}
@@ -206,7 +204,7 @@ class AcceptanceController extends Controller
$acceptance->decline($sig_filename, $request->input('note'));
}
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
$acceptance->notify(new AcceptanceItemDeclinedNotification($data));
Log::debug('New event acceptance.');
event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined');

View File

@@ -54,6 +54,15 @@ class AccessoriesController extends Controller
'notes',
'checkouts_count',
'qty',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'category',
'supplier',
'manufacturer',
];
@@ -61,10 +70,23 @@ class AccessoriesController extends Controller
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
->withCount('checkouts as checkouts_count');
if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search'));
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$accessories->ByFilter($filter);
} elseif ($request->filled('search')) {
$accessories->TextSearch($request->input('search'));
}
if ($request->filled('company_id')) {
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
}

View File

@@ -54,6 +54,12 @@ class AssetModelsController extends Controller
'deleted_at',
'updated_at',
'require_serial',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'manufacturer',
'category',
];
$assetmodels = AssetModel::select([
@@ -81,6 +87,24 @@ class AssetModelsController extends Controller
->withCount('assignedAssets as assets_assigned_count')
->withCount('archivedAssets as assets_archived_count');
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$assetmodels->ByFilter($filter);
} elseif ($request->filled('search')) {
$assetmodels->TextSearch($request->input('search'));
}
if ($request->input('status')=='deleted') {
$assetmodels->onlyTrashed();
}

View File

@@ -3,37 +3,38 @@
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\Http\Transformers\ComponentsTransformer;
use App\Models\AccessoryCheckout;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Requests\FilterRequest;
use App\Http\Requests\StoreAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\ComponentsTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\AccessoryCheckout;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use App\View\Label;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use App\View\Label;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
@@ -56,7 +57,7 @@ class AssetsController extends Controller
* @param int $assetId
* @since [v4.0]
*/
public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array
public function index(FilterRequest $request, $action = null, $upcoming_status = null) : JsonResponse | array
{
@@ -116,6 +117,22 @@ class AssetsController extends Controller
'asset_eol_date',
'requestable',
'jobtitle',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'model',
'location',
'rtd_location',
'category',
'status_label',
'manufacturer',
'supplier',
'jobtitle',
'assigned_to',
'created_by',
];
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
@@ -132,6 +149,7 @@ class AssetsController extends Controller
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
$assets = Asset::select('assets.*')
@@ -166,7 +184,7 @@ class AssetsController extends Controller
// Search custom fields by column name
foreach ($all_custom_fields as $field) {
if ($request->filled($field->db_column_name()) && $field->db_column_name()) {
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
$assets->where('assets.'.$field->db_column_name(), '=', $request->input($field->db_column_name()));
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Http\Controllers\Api;
use App\Actions\Categories\DestroyCategoryAction;
use App\Exceptions\ItemStillHasChildren;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\CategoriesTransformer;
@@ -41,6 +43,7 @@ class CategoriesController extends Controller
'created_at',
'updated_at',
'image',
'tag_color',
'notes',
];
@@ -55,12 +58,30 @@ class CategoriesController extends Controller
'require_acceptance',
'checkin_email',
'image',
'tag_color',
'notes',
])
->with('adminuser')
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$categories->ByFilter($filter);
} elseif ($request->filled('search')) {
$categories->TextSearch($request->input('search'));
}
/*
* This checks to see if we should override the Admin Setting to show archived assets in list.
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
@@ -74,10 +95,6 @@ class CategoriesController extends Controller
$categories = $categories->withCount('showableAssets as assets_count');
}
if ($request->filled('search')) {
$categories = $categories->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$categories->where('name', '=', $request->input('name'));
}
@@ -211,17 +228,21 @@ class CategoriesController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id) : JsonResponse
public function destroy(Category $category): JsonResponse
{
$this->authorize('delete', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
if (! $category->isDeletable()) {
try {
DestroyCategoryAction::run(category: $category);
} catch (ItemStillHasChildren $e) {
return response()->json(
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>$category->category_type]))
Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['asset_type' => $category->category_type]))
);
} catch (\Exception $e) {
report($e);
return response()->json(
Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong'))
);
}
$category->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
}

View File

@@ -38,6 +38,7 @@ class CompaniesController extends Controller
'accessories_count',
'consumables_count',
'components_count',
'tag_color',
'notes',
];
@@ -64,6 +65,11 @@ class CompaniesController extends Controller
$companies->where('created_by', '=', $request->input('created_by'));
}
if ($request->filled('tag_color')) {
$companies->where('tag_color', '=', $request->input('tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
@@ -191,6 +197,7 @@ class CompaniesController extends Controller
'companies.name',
'companies.email',
'companies.image',
'companies.tag_color',
]);

View File

@@ -45,16 +45,40 @@ class ComponentsController extends Controller
'qty',
'image',
'notes',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'category',
'manufacturer',
'supplier',
];
$components = Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
->with('company', 'location', 'category', 'supplier', 'adminuser', 'manufacturer')
->withSum('uncontrainedAssets as sum_unconstrained_assets', 'components_assets.assigned_qty');
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search'));
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$components->ByFilter($filter);
} elseif ($request->filled('search')) {
$components->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$components->where('name', '=', $request->input('name'));
}
@@ -88,7 +112,8 @@ class ComponentsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $components->count()) ? $components->count() : app('api_offset_value');
$components_count = $components->count();
$offset = ($request->input('offset') > $components_count) ? $components_count : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -119,7 +144,7 @@ class ComponentsController extends Controller
break;
}
$total = $components->count();
$total = $components_count;
$components = $components->skip($offset)->take($limit)->get();
return (new ComponentsTransformer)->transformComponents($components, $total);

View File

@@ -31,10 +31,53 @@ class ConsumablesController extends Controller
$consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
->withCount('users as consumables_users_count');
if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'manufacturer',
'location',
'qty',
'image',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'category',
'supplier',
'manufacturer',
];
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$consumables->ByFilter($filter);
} elseif ($request->filled('search')) {
$consumables->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$consumables->where('name', '=', $request->input('name'));
}
@@ -96,25 +139,6 @@ class ConsumablesController extends Controller
$consumables = $consumables->OrderByCreatedBy($order);
break;
default:
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'manufacturer',
'location',
'qty',
'image'
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$consumables = $consumables->orderBy($sort, $order);
break;

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreDepartmentRequest;
use App\Http\Transformers\DepartmentsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Department;
@@ -23,21 +24,23 @@ class DepartmentsController extends Controller
public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count', 'notes'];
$allowed_columns = ['id', 'name', 'image', 'users_count', 'notes', 'tag_color'];
$departments = Department::select(
'departments.id',
'departments.name',
'departments.phone',
'departments.fax',
'departments.location_id',
'departments.company_id',
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
'departments.image',
'departments.notes',
)->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
[
'departments.id',
'departments.name',
'departments.phone',
'departments.fax',
'departments.location_id',
'departments.company_id',
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
'departments.image',
'departments.tag_color',
'departments.notes'
])->with('location')->with('manager')->with('company')->withCount('users as users_count');
if ($request->filled('search')) {
$departments = $departments->TextSearch($request->input('search'));
@@ -59,6 +62,10 @@ class DepartmentsController extends Controller
$departments->where('location_id', '=', $request->input('location_id'));
}
if ($request->filled('tag_color')) {
$departments->where('tag_color', '=', $request->input('departments.tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $departments->count()) ? $departments->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -94,18 +101,17 @@ class DepartmentsController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*/
public function store(ImageUploadRequest $request) : JsonResponse
public function store(StoreDepartmentRequest $request): JsonResponse
{
$this->authorize('create', Department::class);
$department = new Department;
$department->fill($request->all());
$department->fill($request->validated());
$department = $request->handleImages($department);
$department->created_by = auth()->id();
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
if ($department->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.create.success')));
return response()->json(Helper::formatStandardApiResponse('success', (new DepartmentsTransformer)->transformDepartment($department), trans('admin/departments/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
@@ -121,7 +127,7 @@ class DepartmentsController extends Controller
public function show($id) : array
{
$this->authorize('view', Department::class);
$department = Department::findOrFail($id);
$department = Department::withCount('users as users_count')->findOrFail($id);
return (new DepartmentsTransformer)->transformDepartment($department);
}
@@ -141,7 +147,7 @@ class DepartmentsController extends Controller
$department = $request->handleImages($department);
if ($department->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.update.success')));
return response()->json(Helper::formatStandardApiResponse('success', (new DepartmentsTransformer)->transformDepartment($department), trans('admin/departments/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
@@ -185,6 +191,7 @@ class DepartmentsController extends Controller
'id',
'name',
'image',
'tag_color',
]);
if ($request->filled('search')) {

View File

@@ -24,7 +24,7 @@ class GroupsController extends Controller
$this->authorize('view', Group::class);
$groups = Group::select('id', 'name', 'permissions', 'notes', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count');
$groups = Group::select(['id', 'name', 'permissions', 'notes', 'created_at', 'updated_at', 'created_by'])->with('adminuser')->withCount('users as users_count');
if ($request->filled('search')) {
$groups = $groups->TextSearch($request->input('search'));
@@ -50,6 +50,7 @@ class GroupsController extends Controller
'id',
'name',
'created_at',
'updated_at',
'users_count',
];

View File

@@ -26,11 +26,11 @@ class LicenseSeatsController extends Controller
if ($license = License::find($licenseId)) {
$this->authorize('view', $license);
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department', 'user.company', 'asset.company')
->where('license_seats.license_id', $licenseId);
if ($request->input('status') == 'available') {
$seats->whereNull('license_seats.assigned_to');
$seats->whereNull('license_seats.assigned_to')->whereNull('license_seats.asset_id');
}
if ($request->input('status') == 'assigned') {
@@ -40,8 +40,10 @@ class LicenseSeatsController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
if ($request->input('sort') == 'department') {
if ($request->input('sort') == 'assigned_user.department') {
$seats->OrderDepartments($order);
} elseif ($request->input('sort') == 'assigned_user.company') {
$seats->OrderCompany($order);
} else {
$seats->orderBy('updated_at', $order);
}
@@ -77,17 +79,14 @@ class LicenseSeatsController extends Controller
{
$this->authorize('view', License::class);
// sanity checks:
// 1. does the license seat exist?
if (! $licenseSeat = LicenseSeat::find($seatId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
}
// 2. does the seat belong to the specified license?
if (! $license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
if ($licenseSeat = LicenseSeat::where('license_id', $licenseId)->find($seatId)) {
return (new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat);
}
return (new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat);
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat ID or license not found or the seat does not belong to this license'));
}
/**
@@ -120,17 +119,22 @@ class LicenseSeatsController extends Controller
// check if this update is a checkin operation
// 1. are relevant fields touched at all?
$touched = $licenseSeat->isDirty('assigned_to') || $licenseSeat->isDirty('asset_id');
// 2. are they cleared? if yes then this is a checkin operation
$is_checkin = ($touched && $licenseSeat->assigned_to === null && $licenseSeat->asset_id === null);
$assignmentTouched = $licenseSeat->isDirty('assigned_to') || $licenseSeat->isDirty('asset_id');
$anythingTouched = $licenseSeat->isDirty();
if (! $touched) {
// nothing to update
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
if (! $anythingTouched) {
return response()->json(
Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success'))
);
}
if( $touched && $licenseSeat->unreassignable_seat) {
if( $assignmentTouched && $licenseSeat->unreassignable_seat) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.checkout.unavailable')));
}
// 2. are they cleared? if yes then this is a checkin operation
$is_checkin = ($assignmentTouched && $licenseSeat->assigned_to === null && $licenseSeat->asset_id === null);
$target = null;
// the logging functions expect only one "target". if both asset and user are present in the request,
// we simply let assets take precedence over users...
if ($licenseSeat->isDirty('assigned_to')) {
@@ -140,25 +144,23 @@ class LicenseSeatsController extends Controller
$target = $is_checkin ? $oldAsset : Asset::find($licenseSeat->asset_id);
}
if (is_null($target)){
if ($assignmentTouched && is_null($target)){
return response()->json(Helper::formatStandardApiResponse('error', null, 'Target not found'));
}
if ($licenseSeat->save()) {
if ($is_checkin) {
if(!$licenseSeat->license->reassignable){
$licenseSeat->unreassignable_seat = true;
$licenseSeat->save();
if($assignmentTouched) {
if ($is_checkin) {
if (!$licenseSeat->license->reassignable) {
$licenseSeat->unreassignable_seat = true;
$licenseSeat->save();
}
$licenseSeat->logCheckin($target, $licenseSeat->notes);
} else {
// in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation.
$licenseSeat->logCheckout($request->input('notes'), $target);
}
$licenseSeat->logCheckin($target, $licenseSeat->notes);
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
// in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation.
$licenseSeat->logCheckout($request->input('notes'), $target);
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}

View File

@@ -59,6 +59,7 @@ class LocationsController extends Controller
'state',
'updated_at',
'zip',
'tag_color',
'notes',
];
@@ -81,6 +82,8 @@ class LocationsController extends Controller
'locations.ldap_ou',
'locations.currency',
'locations.company_id',
'locations.tag_color',
'locations.tag_color',
'locations.notes',
'locations.created_by',
'locations.deleted_at',
@@ -145,6 +148,10 @@ class LocationsController extends Controller
$locations->onlyTrashed();
}
if ($request->filled('tag_color')) {
$locations->where('tag_color', '=', $request->input('locations.tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -235,6 +242,7 @@ class LocationsController extends Controller
'locations.currency',
'locations.company_id',
'locations.notes',
'locations.tag_color',
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
@@ -402,6 +410,7 @@ class LocationsController extends Controller
'locations.name',
'locations.parent_id',
'locations.image',
'locations.tag_color',
]);
// Only scope locations if the setting is enabled

View File

@@ -52,6 +52,10 @@ class MaintenancesController extends Controller
$maintenances->where('maintenances.created_by', '=', $request->input('created_by'));
}
if ($request->filled('url')) {
$maintenances->where('maintenances.url', '=', $request->input('url'));
}
if ($request->filled('asset_maintenance_type')) {
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
}

View File

@@ -2,6 +2,13 @@
namespace App\Http\Controllers\Api;
use App\Actions\Manufacturers\DeleteManufacturerAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ManufacturersTransformer;
@@ -40,6 +47,7 @@ class ManufacturersController extends Controller
'consumables_count',
'components_count',
'licenses_count',
'tag_color',
'notes',
];
@@ -56,6 +64,7 @@ class ManufacturersController extends Controller
'updated_at',
'image',
'deleted_at',
'tag_color',
'notes',
])
->with('adminuser')
@@ -97,6 +106,10 @@ class ManufacturersController extends Controller
$manufacturers->where('support_email', '=', $request->input('support_email'));
}
if ($request->filled('tag_color')) {
$manufacturers->where('tag_color', '=', $request->input('manufacturers.tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -184,19 +197,19 @@ class ManufacturersController extends Controller
* @since [v4.0]
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy(Manufacturer $manufacturer): JsonResponse
{
$this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
$this->authorize('delete', $manufacturer);
if ($manufacturer->isDeletable()) {
$manufacturer->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
try {
DeleteManufacturerAction::run($manufacturer);
} catch (ItemStillHasChildren $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')])));
} catch (\Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.assoc_users')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
}
/**
@@ -251,6 +264,7 @@ class ManufacturersController extends Controller
'id',
'name',
'image',
'tag_color',
]);
if ($request->filled('search')) {

View File

@@ -2,6 +2,13 @@
namespace App\Http\Controllers\Api;
use App\Actions\Suppliers\DestroySupplierAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\SelectlistTransformer;
@@ -43,12 +50,13 @@ class SuppliersController extends Controller
'accessories_count',
'components_count',
'consumables_count',
'tag_color',
'url',
'notes',
];
$suppliers = Supplier::select(
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip'])
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip', 'tag_color'])
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('accessories as accessories_count')
@@ -191,27 +199,40 @@ class SuppliersController extends Controller
* @since [v4.0]
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy(Supplier $supplier): JsonResponse
{
$this->authorize('delete', Supplier::class);
$supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
$this->authorize('delete', $supplier);
if ($supplier->assets_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
try {
DestroySupplierAction::run(supplier: $supplier);
} catch (ItemStillHasAssets $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_assets', [
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasMaintenances $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_maintenances', [
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasLicenses $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_licenses', [
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasAccessories $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_accessories', [
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasConsumables $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_consumables', [
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasComponents $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_components', [
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
])));
} catch (\Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
if ($supplier->maintenances_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count])));
}
if ($supplier->licenses_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count])));
}
$supplier->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/suppliers/message.delete.success')));
}
@@ -231,6 +252,7 @@ class SuppliersController extends Controller
'id',
'name',
'image',
'tag_color',
]);
if ($request->filled('search')) {

View File

@@ -31,6 +31,7 @@ use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
use App\Http\Requests\DeleteUserRequest;
use Illuminate\Http\JsonResponse;
use App\Http\Requests\FilterRequest;
class UsersController extends Controller
{
@@ -42,7 +43,7 @@ class UsersController extends Controller
*
* @return array
*/
public function index(Request $request) : array
public function index(FilterRequest $request) : array
{
$this->authorize('view', User::class);
@@ -103,9 +104,80 @@ class UsersController extends Controller
'managedLocations as manages_locations_count'
]);
$allowed_columns =
[
'last_name',
'first_name',
'display_name',
'email',
'jobtitle',
'username',
'employee_num',
'groups',
'activated',
'created_at',
'updated_at',
'two_factor_enrolled',
'two_factor_optin',
'last_login',
'assets_count',
'licenses_count',
'consumables_count',
'accessories_count',
'manages_users_count',
'manages_locations_count',
'phone',
'mobile',
'address',
'city',
'state',
'country',
'zip',
'id',
'ldap_import',
'two_factor_optin',
'two_factor_enrolled',
'remote',
'vip',
'start_date',
'end_date',
'autoassign_licenses',
'website',
'locale',
'notes',
'employee_num',
if ($request->filled('search') != '') {
$users = $users->TextSearch($request->input('search'));
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'department',
'manager',
'created_by',
];
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
if (is_null($filter)) {
$filter = [];
}
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$users->ByFilter($filter);
} elseif ($request->filled('search')) {
$users->TextSearch($request->input('search'));
}
if ($request->filled('activated')) {
@@ -286,49 +358,6 @@ class UsersController extends Controller
$users->orderBy('first_name', $order);
break;
default:
$allowed_columns =
[
'last_name',
'first_name',
'display_name',
'email',
'jobtitle',
'username',
'employee_num',
'groups',
'activated',
'created_at',
'updated_at',
'two_factor_enrolled',
'two_factor_optin',
'last_login',
'assets_count',
'licenses_count',
'consumables_count',
'accessories_count',
'manages_users_count',
'manages_locations_count',
'phone',
'mobile',
'address',
'city',
'state',
'country',
'zip',
'id',
'ldap_import',
'two_factor_optin',
'two_factor_enrolled',
'remote',
'vip',
'start_date',
'end_date',
'autoassign_licenses',
'website',
'locale',
'notes',
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
$users = $users->orderBy($sort, $order);
break;

View File

@@ -240,10 +240,6 @@ class BulkAssetsController extends Controller
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
}
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
@@ -274,6 +270,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('company_id'))
|| ($request->filled('status_id'))
|| ($request->filled('model_id'))
|| ($request->filled('notes'))
|| ($request->filled('next_audit_date'))
|| ($request->filled('asset_eol_date'))
|| ($request->filled('null_name'))

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Categories\DestroyCategoryAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Category;
use Illuminate\Http\Request;
class BulkCategoriesController extends Controller
{
public function destroy(Request $request)
{
$this->authorize('delete', Category::class);
$errors = [];
$success_count = 0;
foreach ($request->ids as $id) {
$category = Category::find($id);
if (is_null($category)) {
$errors[] = trans('admin/categories/message.does_not_exist');
continue;
}
try {
DestroyCategoryAction::run(category: $category);
$success_count++;
} catch (ItemStillHasAccessories $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasAssetModels) {
$errors[] = trans('general.bulk_delete_associations.assoc_asset_models_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasAssets) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasComponents) {
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasConsumables) {
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasLicenses) {
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);;
} catch (\Exception $e) {
report($e);
$errors[] = trans('general.something_went_wrong');
}
}
if (count($errors) > 0) {
if ($success_count > 0) {
return redirect()->route('categories.index')->with('success', trans_choice('admin/categories/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
}
return redirect()->route('categories.index')->with('multi_error_messages', $errors);
} else {
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.bulk_success'));
}
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Manufacturers\DeleteManufacturerAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
class BulkManufacturersController extends Controller
{
public function destroy(Request $request)
{
$this->authorize('delete', Manufacturer::class);
$errors = [];
$success_count = 0;
foreach ($request->ids as $id) {
$manufacturer = Manufacturer::find($id);
if (is_null($manufacturer)) {
$errors[] = trans('admin/manufacturers/message.does_not_exist');
continue;
}
try {
DeleteManufacturerAction::run(manufacturer: $manufacturer);
$success_count++;
} catch (ItemStillHasAssets $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasAccessories $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_accessories_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasConsumables $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasComponents $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasLicenses $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);;
} catch (\Exception $e) {
report($e);
$errors[] = trans('general.something_went_wrong');
}
}
if (count($errors) > 0) {
if ($success_count > 0) {
return redirect()->route('manufacturers.index')->with('success', trans_choice('admin/manufacturers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
}
return redirect()->route('manufacturers.index')->with('multi_error_messages', $errors);
} else {
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.bulk_success'));
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Suppliers\DestroySupplierAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Supplier;
use Illuminate\Http\Request;
class BulkSuppliersController extends Controller
{
public function destroy(Request $request)
{
$this->authorize('delete', Supplier::class);
$errors = [];
$success_count = 0;
foreach ($request->ids as $id) {
$supplier = Supplier::find($id);
if (is_null($supplier)) {
$errors[] = trans('admin/suppliers/message.delete.not_found');
continue;
}
try {
DestroySupplierAction::run(supplier: $supplier);
} catch (ItemStillHasAssets $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets', ['asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasMaintenances $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasLicenses $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasAccessories $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_accessories', ['accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasConsumables $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_consumables', ['consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasComponents $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_components', ['components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (\Exception $e) {
report($e);
$errors[] = trans('general.something_went_wrong');
}
}
if (count($errors) > 0) {
if ($success_count > 0) {
return redirect()->route('suppliers.index')->with('success', trans_choice('admin/suppliers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
}
return redirect()->route('suppliers.index')->with('multi_error_messages', $errors);
} else {
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.bulk_success'));
}
}
}

View File

@@ -2,6 +2,14 @@
namespace App\Http\Controllers;
use App\Actions\Categories\DestroyCategoryAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Category;
@@ -70,6 +78,7 @@ class CategoriesController extends Controller
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->tag_color = $request->input('tag_color');
$category->notes = $request->input('notes');
$category->created_by = auth()->id();
@@ -124,6 +133,7 @@ class CategoriesController extends Controller
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->tag_color = $request->input('tag_color');
$category->notes = $request->input('notes');
$category = $request->handleImages($category);
@@ -143,20 +153,18 @@ class CategoriesController extends Controller
* @since [v1.0]
* @param int $categoryId
*/
public function destroy($categoryId) : RedirectResponse
public function destroy(Category $category): RedirectResponse
{
$this->authorize('delete', Category::class);
// Check if the category exists
if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
try {
DestroyCategoryAction::run($category);
} catch (ItemStillHasChildren $e) {
return redirect()->route('categories.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.category')]));
} catch (\Exception $e) {
report($e);
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.delete.error'));
}
if (! $category->isDeletable()) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=> $category->category_type]));
}
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
}

View File

@@ -60,6 +60,7 @@ final class CompaniesController extends Controller
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
$company->tag_color = $request->input('tag_color');
$company->notes = $request->input('notes');
$company->created_by = auth()->id();
@@ -102,6 +103,7 @@ final class CompaniesController extends Controller
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
$company->tag_color = $request->input('tag_color');
$company->notes = $request->input('notes');
$company = $request->handleImages($company);

View File

@@ -30,6 +30,7 @@ use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\Maintenance;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@@ -45,12 +46,14 @@ abstract class Controller extends BaseController
'accessories' => Accessory::class,
'maintenances' => Maintenance::class,
'assets' => Asset::class,
'audits' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'hardware' => Asset::class,
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'suppliers' => Supplier::class,
'users' => User::class,
];
@@ -58,12 +61,14 @@ abstract class Controller extends BaseController
'accessories' => 'private_uploads/accessories/',
'maintenances' => 'private_uploads/maintenances/',
'assets' => 'private_uploads/assets/',
'audits' => 'private_uploads/audits/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'hardware' => 'private_uploads/assets/',
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/models/',
'suppliers' => 'private_uploads/suppliers/',
'users' => 'private_uploads/users/',
];
@@ -71,12 +76,14 @@ abstract class Controller extends BaseController
'accessories' => 'accessory',
'maintenances' => 'maintenance',
'assets' => 'asset',
'audits' => 'audits',
'components' => 'component',
'consumables' => 'consumable',
'hardware' => 'asset',
'licenses' => 'license',
'locations' => 'location',
'models' => 'model',
'suppliers' => 'supplier',
'users' => 'user',
];

View File

@@ -55,6 +55,7 @@ class DepartmentsController extends Controller
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
$department->tag_color = $request->input('tag_color');
$department->notes = $request->input('notes');
$department = $request->handleImages($department);
@@ -157,6 +158,7 @@ class DepartmentsController extends Controller
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
$department->phone = $request->input('phone');
$department->fax = $request->input('fax');
$department->tag_color = $request->input('tag_color');
$department->notes = $request->input('notes');
$department = $request->handleImages($department);

View File

@@ -7,6 +7,7 @@ use App\Models\Group;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use \App\Models\User;
/**
* This controller handles all actions related to User Groups for
@@ -43,9 +44,20 @@ class GroupsController extends Controller
$permissions = config('permissions');
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$selectedPermissions = $request->old('permissions', $groupPermissions);
$users_query = User::where('show_in_list', 1)->whereNull('deleted_at');
$users_count = $users_query->count();
$users = collect();
if ($users_count <= config('app.max_unpaginated_records')) {
$users = $users_query->orderBy('first_name', 'asc')->orderBy('last_name', 'asc')->get();
}
// Show the page
return view('groups/edit', compact('permissions', 'selectedPermissions', 'groupPermissions'))->with('group', $group);
return view('groups/edit', compact('permissions', 'selectedPermissions', 'groupPermissions'))
->with('group', $group)
->with('associated_users', [])
->with('unselected_users', $users)
->with('all_users_count', $users_count);
}
/**
@@ -60,11 +72,23 @@ class GroupsController extends Controller
// create a new group instance
$group = new Group();
$group->name = $request->input('name');
if ($request->filled('permission')) {
$group->permissions = json_encode($request->array('permission'));
} else {
$group->permissions = null;
}
$group->permissions = json_encode($request->input('permission'));
$group->created_by = auth()->id();
$group->notes = $request->input('notes');
if ($group->save()) {
if ($request->filled('users_to_sync')) {
$associated_users = explode(',',$request->input('users_to_sync'));
$group->users()->sync($associated_users);
}
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
}
@@ -87,8 +111,26 @@ class GroupsController extends Controller
if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
$groupPermissions = [];
}
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
$users_query = User::where('show_in_list', 1)->whereNull('deleted_at');
$users_count = $users_query->count();
$associated_users = collect();
$unselected_users = collect();
if ($users_count <= config('app.max_unpaginated_records')) {
$associated_users = $group->users()->where('show_in_list', 1)->orderBy('first_name', 'asc')->orderBy('last_name', 'asc')->get();
// Get the unselected users
$unselected_users = User::where('show_in_list', 1)->whereNotIn('id', $associated_users->pluck('id')->toArray())->orderBy('first_name', 'asc')->orderBy('last_name', 'asc')->get();
}
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'))
->with('associated_users', $associated_users)
->with('unselected_users', $unselected_users)
->with('all_users_count', $users_count);
}
/**
@@ -102,11 +144,24 @@ class GroupsController extends Controller
public function update(Request $request, Group $group) : RedirectResponse
{
$group->name = $request->input('name');
$group->permissions = json_encode($request->input('permission'));
if ($request->filled('permission')) {
$group->permissions = json_encode($request->array('permission'));
} else {
$group->permissions = null;
}
$group->notes = $request->input('notes');
if (! config('app.lock_passwords')) {
if ($group->save()) {
if ($request->has('users_to_sync')) {
$associated_users = explode(',',$request->input('users_to_sync'));
$group->users()->sync($associated_users);
}
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.update'));
}

View File

@@ -82,6 +82,7 @@ class LocationsController extends Controller
$location->created_by = auth()->id();
$location->phone = request('phone');
$location->fax = request('fax');
$location->tag_color = $request->input('tag_color');
$location->notes = $request->input('notes');
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
@@ -156,6 +157,7 @@ class LocationsController extends Controller
$location->fax = request('fax');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$location->tag_color = $request->input('tag_color');
$location->notes = $request->input('notes');
// Only scope the location if the setting is enabled

View File

@@ -78,6 +78,7 @@ class MaintenancesController extends Controller
$maintenance->is_warranty = $request->input('is_warranty');
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
$maintenance->url = $request->input('url');
// Save the asset maintenance data
$maintenance->asset_id = $asset->id;
@@ -152,6 +153,7 @@ class MaintenancesController extends Controller
$maintenance->name = $request->input('name');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
$maintenance->url = $request->input('url');
// Todo - put this in a getter/setter?

View File

@@ -2,6 +2,14 @@
namespace App\Http\Controllers;
use App\Actions\Manufacturers\DeleteManufacturerAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Manufacturer;
@@ -93,6 +101,7 @@ class ManufacturersController extends Controller
$manufacturer->support_email = $request->input('support_email');
$manufacturer->notes = $request->input('notes');
$manufacturer = $request->handleImages($manufacturer);
$manufacturer->tag_color = $request->input('tag_color');
if ($manufacturer->save()) {
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.create.success'));
@@ -134,6 +143,7 @@ class ManufacturersController extends Controller
$manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
$manufacturer->tag_color = $request->input('tag_color');
$manufacturer->notes = $request->input('notes');
// Set the model's image property to null if the image is being deleted
@@ -157,32 +167,18 @@ class ManufacturersController extends Controller
* @param int $manufacturerId
* @since [v1.0]
*/
public function destroy($manufacturerId) : RedirectResponse
public function destroy(Manufacturer $manufacturer): RedirectResponse
{
$this->authorize('delete', Manufacturer::class);
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.not_found'));
$this->authorize('delete', $manufacturer);
try {
DeleteManufacturerAction::run($manufacturer);
} catch (ItemStillHasChildren $e) {
return redirect()->route('manufacturers.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')]));
} catch (\Exception $e) {
report($e);
return redirect()->route('manufacturers.index')->with('error', trans('general.something_went_wrong'));
}
if (! $manufacturer->isDeletable()) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.assoc_users'));
}
if ($manufacturer->image) {
try {
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
Log::info($e);
}
}
// Soft delete the manufacturer if active, permanent delete if is already deleted
if ($manufacturer->deleted_at === null) {
$manufacturer->delete();
} else {
$manufacturer->forceDelete();
}
// Redirect to the manufacturers management page
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.success'));
}

View File

@@ -3,12 +3,21 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Mail\CheckoutAccessoryMail;
use App\Mail\CheckoutAssetMail;
use App\Mail\CheckoutComponentMail;
use App\Mail\CheckoutConsumableMail;
use App\Mail\CheckoutLicenseMail;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Checkoutable;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\Maintenance;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
@@ -18,9 +27,11 @@ use App\Models\License;
use App\Models\ReportTemplate;
use App\Models\Setting;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\Mail;
use \Illuminate\Contracts\View\View;
use League\Csv\Reader;
@@ -436,10 +447,8 @@ class ReportsController extends Controller
// Open output stream
$handle = fopen('php://output', 'w');
stream_set_timeout($handle, 2000);
if ($request->filled('use_bom')) {
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
}
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
$header = [];
@@ -1118,34 +1127,32 @@ class ReportsController extends Controller
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
$query = CheckoutAcceptance::pending()
->where('checkoutable_type', 'App\Models\Asset')
$query = CheckoutAcceptance::Pending()
->with([
'checkoutable' => function (MorphTo $query) {
$query->morphWith([
AssetModel::class => ['model'],
Company::class => ['company'],
Asset::class => ['assignedTo'],
])->with('model.category');
$query->withTrashed()->morphWith([
Asset::class => ['model.category', 'assignedTo', 'company'],
Accessory::class => ['category','checkouts', 'company'],
LicenseSeat::class => ['user', 'license'],
Component::class => ['assignedTo', 'company'],
Consumable::class => ['company'],
]);
},
'assignedTo' => function($query){
$query->withTrashed();
}
]);
])->orderByDesc('checkout_acceptances.created_at');
if ($showDeleted) {
$query->withTrashed();
}
$assetsForReport = $query->get()
->map(function ($acceptance) {
return [
'assetItem' => $acceptance->checkoutable,
'acceptance' => $acceptance,
];
});
$itemsForReport = $query->get()
->filter(fn ($unaccepted) => $unaccepted->checkoutable)
->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
return view('reports/unaccepted_assets', compact('assetsForReport','showDeleted' ));
return view('reports/unaccepted_assets', compact('itemsForReport','showDeleted' ));
}
/**
@@ -1157,41 +1164,77 @@ class ReportsController extends Controller
public function sentAssetAcceptanceReminder(Request $request) : RedirectResponse
{
$this->authorize('reports.view');
if (!$acceptance = CheckoutAcceptance::pending()->find($request->input('acceptance_id'))) {
$id = $request->input('acceptance_id');
$query = CheckoutAcceptance::query()
->with([
'checkoutable' => function (MorphTo $query) {
$query->withTrashed()->morphWith([
Asset::class => ['model.category', 'assignedTo', 'company', 'checkouts'],
Accessory::class => ['category', 'company', 'checkouts'],
LicenseSeat::class => ['user', 'license', 'checkouts'],
Component::class => ['assignedTo', 'company', 'checkouts'],
Consumable::class => ['company', 'checkouts'],
]);
},
'assignedTo' => fn ($q) => $q->withTrashed(),
])
->pending();
$acceptance = $query->find($id);
if (!$acceptance) {
Log::debug('No pending acceptances');
// Redirect to the unaccepted assets report page with error
// Redirect to the unaccepted items report page with error
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$item = $acceptance->checkoutable;
$assignee = $acceptance->assignedTo ?? $item->assignedTo ?? null;
$email = $assignee?->email;
$locale = $assignee?->locale;
$assetItem = $acceptance->checkoutable;
Log::debug(print_r($assetItem, true));
Log::debug(print_r($acceptance, true));
if (is_null($acceptance->created_at)){
Log::debug('No acceptance created_at');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
} else {
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
if($item instanceof LicenseSeat){
$logItem_res = $item->license->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
}
else{
$logItem_res = $item->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
}
if ($logItem_res->isEmpty()){
Log::debug('Acceptance date mismatch');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$logItem = $logItem_res[0];
}
$email = $assetItem->assignedTo?->email;
$locale = $assetItem->assignedTo?->locale;
if (is_null($email) || $email === '') {
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
}
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale));
$mailable = $this->getCheckoutMailType($acceptance, $logItem);
Mail::to($email)->send($mailable->locale($locale));
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
}
private function getCheckoutMailType(CheckoutAcceptance $acceptance, $logItem) : Mailable
{
$lookup = [
Accessory::class => CheckoutAccessoryMail::class,
Asset::class => CheckoutAssetMail::class,
LicenseSeat::class => CheckoutLicenseMail::class,
Consumable::class => CheckoutConsumableMail::class,
Component::class => CheckoutComponentMail::class,
];
$mailable= $lookup[get_class($acceptance->checkoutable)];
return new $mailable($acceptance->checkoutable,
$acceptance->checkedOutTo ?? $acceptance->assignedTo,
$logItem->adminuser,
$acceptance,
$acceptance->note);
}
/**
* sentAssetAcceptanceReminder
*
@@ -1223,31 +1266,43 @@ class ReportsController extends Controller
public function postAssetAcceptanceReport($deleted = false) : Response
{
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
$showDeleted = request('deleted') === 'deleted';;
/**
* Get all assets with pending checkout acceptances
*/
if($showDeleted) {
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
} else {
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
}
$assetsForReport = $acceptances
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
});
$acceptances = CheckoutAcceptance::pending()
->with([
'checkoutable' => function (MorphTo $acceptance) {
$acceptance->withTrashed()->morphWith([
Asset::class => ['model.category', 'assignedTo', 'company'],
Accessory::class => ['category','checkouts', 'company'],
LicenseSeat::class => ['user', 'license'],
Component::class => ['assignedTo', 'company'],
Consumable::class => ['company'],
]);
},
'assignedTo',
])->orderByDesc('checkout_acceptances.created_at');
if ($showDeleted) {
$acceptances->withTrashed();
}
$itemsForReport = $acceptances->get()
->filter(fn ($unaccepted) => $unaccepted->checkoutable)
->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
$rows = [];
$header = [
trans('general.date'),
trans('general.type'),
trans('admin/companies/table.title'),
trans('general.category'),
trans('admin/hardware/form.model'),
trans('admin/hardware/form.name'),
trans('general.name'),
trans('admin/hardware/table.asset_tag'),
trans('admin/hardware/table.checkoutto'),
];
@@ -1255,16 +1310,19 @@ class ReportsController extends Controller
$header = array_map('trim', $header);
$rows[] = implode(',', $header);
foreach ($assetsForReport as $item) {
foreach ($itemsForReport as $item) {
if ($item['assetItem'] != null){
if ($item != null){
$row = [ ];
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
$row[] = str_replace(',', '', e($item['assetItem']->name));
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->display_name : trans('admin/reports/general.deleted_user')));
$row[] = str_replace(',', '', $item->acceptance->created_at);
$row[] = str_replace(',', '', $item->type);
$row[] = str_replace(',', '', $item->plain_text_company);
$row[] = str_replace(',', '', $item->plain_text_category);
$row[] = str_replace(',', '', $item->plain_text_model);
$row[] = str_replace(',', '', $item->plain_text_name);
$row[] = str_replace(',', '', $item->asset_tag);
$row[] = str_replace(',', '', ($item->acceptance->assignedto) ? $item->acceptance->assignedto->display_name : trans('admin/reports/general.deleted_user'));
$rows[] = implode(',', $row);
}
}

View File

@@ -589,6 +589,7 @@ class SettingsController extends Controller
$setting->time_display_format = $request->input('time_display_format');
$setting->digit_separator = $request->input('digit_separator');
$setting->name_display_format = $request->input('name_display_format');
$setting->week_start = $request->input('week_start', 0);
if ($setting->save()) {
return redirect()->route('settings.index')
@@ -772,6 +773,7 @@ class SettingsController extends Controller
$setting->label2_asset_logo = $request->input('label2_asset_logo');
$setting->label2_1d_type = $request->input('label2_1d_type');
$setting->label2_2d_type = $request->input('label2_2d_type');
$setting->label2_2d_prefix = $request->input('label2_2d_prefix');
$setting->label2_2d_target = $request->input('label2_2d_target');
$setting->label2_fields = $request->input('label2_fields');
$setting->label2_empty_row_count = $request->input('label2_empty_row_count');

View File

@@ -2,10 +2,18 @@
namespace App\Http\Controllers;
use App\Actions\Suppliers\DestroySupplierAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasLicenses;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Supplier;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\MessageBag;
/**
* This controller handles all actions related to Suppliers for
@@ -59,6 +67,7 @@ class SuppliersController extends Controller
$supplier->phone = request('phone');
$supplier->fax = request('fax');
$supplier->email = request('email');
$supplier->tag_color = $request->input('tag_color');
$supplier->notes = request('notes');
$supplier->url = $supplier->addhttp(request('url'));
$supplier->created_by = auth()->id();
@@ -103,6 +112,7 @@ class SuppliersController extends Controller
$supplier->fax = request('fax');
$supplier->email = request('email');
$supplier->url = $supplier->addhttp(request('url'));
$supplier->tag_color = $request->input('tag_color');
$supplier->notes = request('notes');
$supplier = $request->handleImages($supplier);
@@ -118,30 +128,41 @@ class SuppliersController extends Controller
*
* @param int $supplierId
*/
public function destroy($supplierId) : RedirectResponse
public function destroy(Supplier $supplier): RedirectResponse
{
$this->authorize('delete', Supplier::class);
if (is_null($supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.not_found'));
try {
DestroySupplierAction::run(supplier: $supplier);
} catch (ItemStillHasAssets $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_assets', [
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasMaintenances $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_maintenances', [
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasLicenses $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_licenses', [
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasAccessories $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_accessories', [
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasConsumables $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_consumables', [
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasComponents $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_components', [
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
]));
} catch (\Exception $e) {
report($e);
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.error'));
}
if ($supplier->assets_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count]));
}
if ($supplier->maintenances_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count]));
}
if ($supplier->licenses_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count]));
}
$supplier->delete();
return redirect()->route('suppliers.index')->with('success',
trans('admin/suppliers/message.delete.success')
);
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.success'));
}
/**
@@ -154,6 +175,5 @@ class SuppliersController extends Controller
{
$this->authorize('view', Supplier::class);
return view('suppliers/view', compact('supplier'));
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
use App\Enums\ActionType;
use App\Exceptions\AssetNotRequestable;
use App\Models\Actionlog;
use App\Models\Asset;
@@ -155,7 +156,19 @@ class ViewAssetsController extends Controller
public function getRequestableIndex() : View
{
$assets = Asset::with('model', 'defaultLoc', 'location', 'assignedTo', 'requests')->Hardware()->RequestableAssets();
$models = AssetModel::with('category', 'requests', 'assets')->RequestableModels()->get();
$models = AssetModel::with([
'category',
'requests',
'assets' => function ($q) {
$q->where('requestable', 1)
->whereHas('assetstatus', fn ($s) =>
$s->where('archived', 0)
->where(fn ($s) =>
$s->where('deployable', 1)->orWhere('pending', 1)
)
);
},
])->RequestableModels()->get();
return view('account/requestable-assets', compact('assets', 'models'));
}
@@ -201,7 +214,7 @@ class ViewAssetsController extends Controller
if (($item_request = $item->isRequestedBy($user)) || $cancel_by_admin) {
$item->cancelRequest($requestingUser);
$data['item_quantity'] = ($item_request) ? $item_request->qty : 1;
$logaction->logaction('request_canceled');
$logaction->logaction(ActionType::RequestCanceled);
if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) {
$settings->notify(new RequestAssetCancelation($data));

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use App\Rules\ValidJson;
use Illuminate\Foundation\Http\FormRequest;
class FilterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'filter' => ['nullable', new ValidJson()],
];
}
}

View File

@@ -109,7 +109,7 @@ class SettingsSamlRequest extends FormRequest
];
$pkey = openssl_pkey_new([
'private_key_bits' => config('app.saml_key_size'),
'private_key_bits' => (int) config('app.saml_key_size'),
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);

View File

@@ -26,6 +26,7 @@ class StoreAssetRequest extends ImageUploadRequest
public function prepareForValidation(): void
{
parent::prepareForValidation(); // call ImageUploadRequest thing
// Guard against users passing in an array for company_id instead of an integer.
// If the company_id is not an integer then we simply use what was
// provided to be caught by model level validation later.

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use App\Models\Department;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreDepartmentRequest extends ImageUploadRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Gate::allows('create', new Department);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$modelRules = (new Department)->getRules();
return array_merge(
$modelRules,
);
}
}

View File

@@ -49,6 +49,7 @@ class StoreLabelSettings extends FormRequest
'labels_pagewidth' => 'numeric|nullable',
'labels_pageheight' => 'numeric|nullable',
'qr_text' => 'max:31|nullable',
'label2_2d_prefix' => 'nullable|max:191',
'label2_template' => [
'required',
Rule::in($names),

View File

@@ -32,7 +32,7 @@ class StoreNotificationSettings extends FormRequest
],
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',
'audit_warning_days' => 'numeric|nullable|gte:0',
'due_checkin_days' => 'numeric|nullable|gt:0',
'audit_interval' => 'numeric|nullable|gt:0',
];

View File

@@ -28,23 +28,31 @@ class UpdateAssetRequest extends ImageUploadRequest
*/
public function rules()
{
$setting = Setting::getSettings();
$rules = array_merge(
parent::rules(),
(new Asset)->getRules(),
// this is to overwrite rulesets that include required, and rewrite unique_undeleted
// This overwrites the rulesets that are set at the model level (via Watson) but are not necessarily required at the request level when doing a PATCH update.
// Confusingly, this skips the unique_undeleted validator at the model level (and therefore the UniqueUndeletedTrait), so we have to re-add those
// rules here without the requiredness, since those values will already exist if you're updating an existing asset.
[
'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()
Rule::unique('assets', 'asset_tag')->ignore($this->asset)->withoutTrashed(),
],
'serial' => [
'string', 'max:255', 'not_array',
$setting->unique_serial=='1' ? Rule::unique('assets', 'serial')->ignore($this->asset)->withoutTrashed() : 'nullable',
],
],
);
// 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'))) {
if ($setting->digit_separator === '1.234,56' && is_string($this->input('purchase_cost'))) {
$rules['purchase_cost'] = ['nullable', 'string'];
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Traits;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
@@ -38,20 +39,13 @@ trait ConvertsBase64ToFiles
if (!$base64Contents) {
return;
}
// autogenerate filenames
if ($filename == 'auto'){
$header = explode(';', $base64Contents, 2)[0];
// Grab the image type from the header while we're at it.
$filename = $key . '.' . substr($header, strpos($header, '/')+1);
}
// Generate a temporary path to store the Base64 contents
$tempFilePath = tempnam(sys_get_temp_dir(), $filename);
// Store the contents using a stream, or by decoding manually
// Store the contents using a stream, or throw an Error (which doesn't do anything?)
if (Str::startsWith($base64Contents, 'data:') && count(explode(',', $base64Contents)) > 1) {
$source = fopen($base64Contents, 'r');
$source = fopen($base64Contents, 'r'); // PHP has special processing for "data:" URL's
$destination = fopen($tempFilePath, 'w');
stream_copy_to_stream($source, $destination);
@@ -59,7 +53,8 @@ trait ConvertsBase64ToFiles
fclose($source);
fclose($destination);
} else {
file_put_contents($tempFilePath, base64_decode($base64Contents, true));
// TODO - to get a better error message here, can we maybe do something with modifying the errorBag?
throw new ValidationException("Need Base64 URL starting with 'data:'"); // This doesn't actually throw?
}
$uploadedFile = new UploadedFile($tempFilePath, $filename, null, null, true);

View File

@@ -26,12 +26,32 @@ class AccessoriesTransformer
'id' => $accessory->id,
'name' => e($accessory->name),
'image' => ($accessory->image) ? Storage::disk('public')->url('accessories/'.e($accessory->image)) : null,
'company' => ($accessory->company) ? ['id' => $accessory->company->id, 'name'=> e($accessory->company->name)] : null,
'manufacturer' => ($accessory->manufacturer) ? ['id' => $accessory->manufacturer->id, 'name'=> e($accessory->manufacturer->name)] : null,
'supplier' => ($accessory->supplier) ? ['id' => $accessory->supplier->id, 'name'=> e($accessory->supplier->name)] : null,
'company' => ($accessory->company) ? [
'id' => $accessory->company->id,
'name'=> e($accessory->company->name),
'tag_color'=> ($accessory->company->tag_color) ? e($accessory->company->tag_color) : null,
] : null,
'manufacturer' => ($accessory->manufacturer) ? [
'id' => $accessory->manufacturer->id,
'name'=> e($accessory->manufacturer->name),
'tag_color'=> ($accessory->manufacturer->tag_color) ? e($accessory->manufacturer->tag_color) : null,
] : null,
'supplier' => ($accessory->supplier) ? [
'id' => $accessory->supplier->id,
'name'=> e($accessory->supplier->name),
'tag_color'=> ($accessory->supplier->tag_color) ? e($accessory->supplier->tag_color) : null,
] : null,
'model_number' => ($accessory->model_number) ? e($accessory->model_number) : null,
'category' => ($accessory->category) ? ['id' => $accessory->category->id, 'name'=> e($accessory->category->name)] : null,
'location' => ($accessory->location) ? ['id' => $accessory->location->id, 'name'=> e($accessory->location->name)] : null,
'category' => ($accessory->category) ? [
'id' => $accessory->category->id,
'name'=> e($accessory->category->name),
'tag_color'=> ($accessory->category->tag_color) ? e($accessory->category->tag_color) : null,
] : null,
'location' => ($accessory->location) ? [
'id' => $accessory->location->id,
'name'=> e($accessory->location->name),
'tag_color'=> ($accessory->location->tag_color) ? e($accessory->location->tag_color) : null,
] : null,
'notes' => ($accessory->notes) ? Helper::parseEscapedMarkedownInline($accessory->notes) : null,
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,

View File

@@ -149,6 +149,7 @@ class ActionlogsTransformer
'filename' => $actionlog->filename,
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_path()),
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
'mediatype' => StorageHelper::getMediaType($actionlog->uploads_file_path()),
] : null,
'item' => ($actionlog->item) ? [
@@ -160,6 +161,7 @@ class ActionlogsTransformer
'location' => ($actionlog->location) ? [
'id' => (int) $actionlog->location->id,
'name' => e($actionlog->location->name),
'tag_color'=> ($actionlog->location->tag_color) ? e($actionlog->location->tag_color) : null,
] : null,
'created_at' => Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($actionlog->updated_at, 'datetime'),

View File

@@ -44,6 +44,7 @@ class AssetModelsTransformer
'manufacturer' => ($assetmodel->manufacturer) ? [
'id' => (int) $assetmodel->manufacturer->id,
'name'=> e($assetmodel->manufacturer->name),
'tag_color'=> ($assetmodel->manufacturer->tag_color) ? e($assetmodel->manufacturer->tag_color) : null,
] : null,
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
'model_number' => ($assetmodel->model_number ? e($assetmodel->model_number): null),
@@ -60,6 +61,7 @@ class AssetModelsTransformer
'category' => ($assetmodel->category) ? [
'id' => (int) $assetmodel->category->id,
'name'=> e($assetmodel->category->name),
'tag_color'=> ($assetmodel->category->tag_color) ? e($assetmodel->category->tag_color) : null,
] : null,
'fieldset' => ($assetmodel->fieldset) ? [
'id' => (int) $assetmodel->fieldset->id,
@@ -68,7 +70,7 @@ class AssetModelsTransformer
'default_fieldset_values' => $default_field_values,
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
'requestable' => ($assetmodel->requestable == '1') ? true : false,
'require_serial' => $assetmodel->require_serial,
'require_serial' => ($assetmodel->require_serial == '1') ? true : false,
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
'created_by' => ($assetmodel->adminuser) ? [
'id' => (int) $assetmodel->adminuser->id,

View File

@@ -40,7 +40,6 @@ class AssetsTransformer
] : null,
'byod' => ($asset->byod ? true : false),
'requestable' => ($asset->requestable ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true) . ' months' : null,
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
@@ -53,10 +52,12 @@ class AssetsTransformer
'category' => (($asset->model) && ($asset->model->category)) ? [
'id' => (int) $asset->model->category->id,
'name'=> e($asset->model->category->name),
'tag_color'=> ($asset->model->category->tag_color) ? e($asset->model->category->tag_color) : null,
] : null,
'manufacturer' => (($asset->model) && ($asset->model->manufacturer)) ? [
'id' => (int) $asset->model->manufacturer->id,
'name'=> e($asset->model->manufacturer->name),
'tag_color'=> ($asset->model->manufacturer->tag_color) ? e($asset->model->manufacturer->tag_color) : null,
] : null,
'depreciation' => (($asset->model) && ($asset->model->depreciation)) ? [
'id' => (int) $asset->model->depreciation->id,
@@ -68,20 +69,24 @@ class AssetsTransformer
'supplier' => ($asset->supplier) ? [
'id' => (int) $asset->supplier->id,
'name'=> e($asset->supplier->name),
'tag_color'=> ($asset->supplier->tag_color) ? e($asset->supplier->tag_color) : null,
] : null,
'notes' => ($asset->notes) ? Helper::parseEscapedMarkedownInline($asset->notes) : null,
'order_number' => ($asset->order_number) ? e($asset->order_number) : null,
'company' => ($asset->company) ? [
'id' => (int) $asset->company->id,
'name'=> e($asset->company->name),
'tag_color'=> ($asset->company->tag_color) ? e($asset->company->tag_color) : null,
] : null,
'location' => ($asset->location) ? [
'id' => (int) $asset->location->id,
'name'=> e($asset->location->name),
'tag_color'=> ($asset->location->tag_color) ? e($asset->location->tag_color) : null,
] : null,
'rtd_location' => ($asset->defaultLoc) ? [
'id' => (int) $asset->defaultLoc->id,
'name'=> e($asset->defaultLoc->name),
'tag_color'=> ($asset->defaultLoc->tag_color) ? e($asset->defaultLoc->tag_color) : null,
] : null,
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
'qr' => ($setting->qr_code=='1') ? config('app.url').'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png' : null,

View File

@@ -66,6 +66,7 @@ class CategoriesTransformer
'id' => (int) $category->adminuser->id,
'name'=> e($category->adminuser->display_name),
] : null,
'tag_color' => $category->tag_color ? e($category->tag_color) : null,
'notes' => Helper::parseEscapedMarkedownInline($category->notes),
'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($category->updated_at, 'datetime'),

View File

@@ -40,6 +40,7 @@ class CompaniesTransformer
'id' => (int) $company->adminuser->id,
'name'=> e($company->adminuser->display_name),
] : null,
'tag_color' => ($company->tag_color!='') ? e($company->tag_color): null,
'notes' => Helper::parseEscapedMarkedownInline($company->notes),
'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($company->updated_at, 'datetime'),

View File

@@ -30,15 +30,25 @@ class ComponentsTransformer
'location' => ($component->location) ? [
'id' => (int) $component->location->id,
'name' => e($component->location->name),
'tag_color' => $component->location->tag_color ? e($component->location->tag_color) : null,
] : null,
'qty' => ($component->qty != '') ? (int) $component->qty : null,
'min_amt' => ($component->min_amt != '') ? (int) $component->min_amt : null,
'category' => ($component->category) ? [
'id' => (int) $component->category->id,
'name' => e($component->category->name),
'tag_color' => $component->category->tag_color ? e($component->category->tag_color) : null,
] : null,
'supplier' => ($component->supplier) ? [
'id' => $component->supplier->id,
'name'=> e($component->supplier->name),
'tag_color' => $component->supplier->tag_color ? e($component->supplier->tag_color) : null,
] : null,
'manufacturer' => ($component->manufacturer) ? [
'id' => $component->manufacturer->id,
'name'=> e($component->manufacturer->name),
'tag_color' => $component->manufacturer->tag_color ? e($component->manufacturer->tag_color) : null,
] : null,
'supplier' => ($component->supplier) ? ['id' => $component->supplier->id, 'name'=> e($component->supplier->name)] : null,
'manufacturer' => ($component->manufacturer) ? ['id' => $component->manufacturer->id, 'name'=> e($component->manufacturer->name)] : null,
'model_number' => ($component->model_number) ? e($component->model_number) : null,
'order_number' => e($component->order_number),
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
@@ -48,6 +58,7 @@ class ComponentsTransformer
'company' => ($component->company) ? [
'id' => (int) $component->company->id,
'name' => e($component->company->name),
'tag_color' => $component->company->tag_color ? e($component->company->tag_color) : null,
] : null,
'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null,
'created_by' => ($component->adminuser) ? [

View File

@@ -26,12 +26,32 @@ class ConsumablesTransformer
'id' => (int) $consumable->id,
'name' => e($consumable->name),
'image' => ($consumable->getImageUrl()) ? ($consumable->getImageUrl()) : null,
'category' => ($consumable->category) ? ['id' => $consumable->category->id, 'name' => e($consumable->category->name)] : null,
'company' => ($consumable->company) ? ['id' => (int) $consumable->company->id, 'name' => e($consumable->company->name)] : null,
'category' => ($consumable->category) ? [
'id' => $consumable->category->id,
'name' => e($consumable->category->name),
'tag_color' => $consumable->category->tag_color ? e($consumable->category->tag_color) : null,
] : null,
'company' => ($consumable->company) ? [
'id' => (int) $consumable->company->id,
'name' => e($consumable->company->name),
'tag_color' => $consumable->company->tag_color ? e($consumable->company->tag_color) : null,
] : null,
'item_no' => e($consumable->item_no),
'location' => ($consumable->location) ? ['id' => (int) $consumable->location->id, 'name' => e($consumable->location->name)] : null,
'manufacturer' => ($consumable->manufacturer) ? ['id' => (int) $consumable->manufacturer->id, 'name' => e($consumable->manufacturer->name)] : null,
'supplier' => ($consumable->supplier) ? ['id' => $consumable->supplier->id, 'name'=> e($consumable->supplier->name)] : null,
'location' => ($consumable->location) ? [
'id' => (int) $consumable->location->id,
'name' => e($consumable->location->name),
'tag_color' => $consumable->location->tag_color ? e($consumable->location->tag_color) : null,
] : null,
'manufacturer' => ($consumable->manufacturer) ? [
'id' => (int) $consumable->manufacturer->id,
'name' => e($consumable->manufacturer->name),
'tag_color' => $consumable->manufacturer->tag_color ? e($consumable->manufacturer->tag_color) : null,
] : null,
'supplier' => ($consumable->supplier) ? [
'id' => $consumable->supplier->id,
'name'=> e($consumable->supplier->name),
'tag_color' => $consumable->supplier->tag_color ? e($consumable->supplier->tag_color) : null,
] : null,
'min_amt' => (int) $consumable->min_amt,
'model_number' => ($consumable->model_number != '') ? e($consumable->model_number) : null,
'remaining' => $consumable->numRemaining(),

View File

@@ -32,6 +32,7 @@ class DepartmentsTransformer
'company' => ($department->company) ? [
'id' => (int) $department->company->id,
'name'=> e($department->company->name),
'tag_color' => $department->company->tag_color ? e($department->company->tag_color) : null,
] : null,
'manager' => ($department->manager) ? [
'id' => (int) $department->manager->id,
@@ -42,8 +43,10 @@ class DepartmentsTransformer
'location' => ($department->location) ? [
'id' => (int) $department->location->id,
'name' => e($department->location->name),
'tag_color' => $department->location->tag_color ? e($department->location->tag_color) : null,
] : null,
'users_count' => e($department->users_count),
'users_count' => (int) ($department->users_count),
'tag_color' => $department->tag_color ? e($department->tag_color) : null,
'notes' => Helper::parseEscapedMarkedownInline($department->notes),
'created_at' => Helper::getFormattedDateObject($department->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($department->updated_at, 'datetime'),

View File

@@ -34,8 +34,16 @@ class LicenseSeatsTransformer
[
'id' => (int) $seat->user->department->id,
'name' => e($seat->user->department->name),
'tag_color' => $seat->user->department->tag_color ? e($seat->user->department->tag_color) : null,
] : null,
'company'=> ($seat->user->company) ?
[
'id' => (int) $seat->user->company->id,
'name' => e($seat->user->company->name),
'tag_color' => $seat->user->company->tag_color ? e($seat->user->company->tag_color) : null,
] : null,
'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'),
] : null,
'assigned_asset' => ($seat->asset) ? [
@@ -46,6 +54,7 @@ class LicenseSeatsTransformer
'location' => ($seat->location()) ? [
'id' => (int) $seat->location()->id,
'name'=> e($seat->location()->name),
'tag_color' => $seat->location()->tag_color ? e($seat->location()->tag_color) : null,
'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'),
] : null,
'reassignable' => (bool) $seat->license->reassignable,

View File

@@ -25,7 +25,11 @@ class LicensesTransformer
'id' => (int) $license->id,
'name' => e($license->name),
'company' => ($license->company) ? ['id' => (int) $license->company->id, 'name'=> e($license->company->name)] : null,
'manufacturer' => ($license->manufacturer) ? ['id' => (int) $license->manufacturer->id, 'name'=> e($license->manufacturer->name)] : null,
'manufacturer' => ($license->manufacturer) ? [
'id' => (int) $license->manufacturer->id,
'name'=> e($license->manufacturer->name),
'tag_color'=> ($license->manufacturer->tag_color) ? e($license->manufacturer->tag_color) : null,
] : null,
'product_key' => (Gate::allows('viewKeys', License::class)) ? e($license->serial) : '------------',
'order_number' => ($license->order_number) ? e($license->order_number) : null,
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
@@ -44,8 +48,16 @@ class LicensesTransformer
'license_email' => ($license->license_email) ? e($license->license_email) : null,
'reassignable' => ($license->reassignable == 1) ? true : false,
'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,
'supplier' => ($license->supplier) ? [
'id' => (int) $license->supplier->id,
'name'=> e($license->supplier->name),
'tag_color'=> ($license->supplier->tag_color) ? e($license->supplier->tag_color) : null,
] : null,
'category' => ($license->category) ? [
'id' => (int) $license->category->id,
'name'=> e($license->category->name),
'tag_color'=> ($license->category->tag_color) ? e($license->category->tag_color) : null,
] : null,
'created_by' => ($license->adminuser) ? [
'id' => (int) $license->adminuser->id,
'name'=> e($license->adminuser->display_name),

View File

@@ -58,6 +58,7 @@ class LocationsTransformer
'children_count' => (int) $location->children_count,
'currency' => ($location->currency) ? e($location->currency) : null,
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
'tag_color' => $location->tag_color ? e($location->tag_color) : null,
'notes' => Helper::parseEscapedMarkedownInline($location->notes),
'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'),
'created_by' => $location->adminuser ? [
@@ -68,11 +69,13 @@ class LocationsTransformer
'parent' => ($location->parent) ? [
'id' => (int) $location->parent->id,
'name'=> e($location->parent->name),
'tag_color' => $location->parent->tag_color ? e($location->parent->tag_color) : null,
] : null,
'manager' => ($location->manager) ? (new UsersTransformer)->transformUser($location->manager) : null,
'company' => ($location->company) ? [
'id' => (int) $location->company->id,
'name'=> e($location->company->name)
'name'=> e($location->company->name),
'tag_color' => $location->company->tag_color ? e($location->company->tag_color) : null,
] : null,
'children' => $children_arr,

View File

@@ -66,6 +66,7 @@ class MaintenancesTransformer
'id' => $assetmaintenance->supplier->id,
'name'=> e($assetmaintenance->supplier->name)
] : null,
'url' => ($assetmaintenance->url) ? e($assetmaintenance->url) : null,
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'),

View File

@@ -37,6 +37,7 @@ class ManufacturersTransformer
'consumables_count' => (int) $manufacturer->consumables_count,
'accessories_count' => (int) $manufacturer->accessories_count,
'components_count' => (int) $manufacturer->components_count,
'tag_color' => $manufacturer->tag_color ? e($manufacturer->tag_color) : null,
'notes' => Helper::parseEscapedMarkedownInline($manufacturer->notes),
'created_by' => ($manufacturer->adminuser) ? [
'id' => (int) $manufacturer->adminuser->id,

View File

@@ -26,6 +26,7 @@ class SelectlistTransformer
'id' => (int) $select_item->id,
'text' => ($select_item->use_text) ? $select_item->use_text : $select_item->name,
'image' => ($select_item->use_image) ? $select_item->use_image : null,
'tag_color' => ($select_item->tag_color) ? $select_item->tag_color : null,
];
}

View File

@@ -43,6 +43,7 @@ class SuppliersTransformer
'licenses_count' => (int) $supplier->licenses_count,
'consumables_count' => (int) $supplier->consumables_count,
'components_count' => (int) $supplier->components_count,
'tag_color' => $supplier->tag_color ? e($supplier->tag_color) : null,
'notes' => ($supplier->notes) ? Helper::parseEscapedMarkedownInline($supplier->notes) : null,
'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'),
'created_by' => $supplier->adminuser ? [

View File

@@ -57,6 +57,7 @@ class UsersTransformer
'department' => ($user->department) ? [
'id' => (int) $user->department->id,
'name'=> e($user->department->name),
'tag_color' => ($user->department->tag_color) ? e($user->department->tag_color) : null,
] : null,
'department_manager' => ($user->department?->manager) ? [
'id' => (int) $user->department->manager->id,
@@ -65,6 +66,7 @@ class UsersTransformer
'location' => ($user->userloc) ? [
'id' => (int) $user->userloc->id,
'name'=> e($user->userloc->name),
'tag_color'=> ($user->userloc->tag_color) ? e($user->userloc->tag_color) : null,
] : null,
'notes'=> Helper::parseEscapedMarkedownInline($user->notes),
'role' => $role,
@@ -80,7 +82,11 @@ class UsersTransformer
'consumables_count' => (int) $user->consumables_count,
'manages_users_count' => (int) $user->manages_users_count,
'manages_locations_count' => (int) $user->manages_locations_count,
'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null,
'company' => ($user->company) ? [
'id' => (int) $user->company->id,
'name'=> e($user->company->name),
'tag_color'=> ($user->company->tag_color) ? e($user->company->tag_color) : null,
] : null,
'created_by' => ($user->createdBy) ? [
'id' => (int) $user->createdBy->id,
'name'=> e($user->createdBy->display_name),

View File

@@ -44,7 +44,7 @@ class AssetImporter extends ItemImporter
foreach ($this->customFields as $customField) {
$customFieldValue = $this->array_smart_custom_field_fetch($row, $customField);
if ($customFieldValue) {
if (!is_null($customFieldValue)) {
if ($customField->field_encrypted == 1) {
$this->item['custom_fields'][$customField->db_column_name()] = Crypt::encrypt($customFieldValue);
$this->log('Custom Field '.$customField->name.': '.Crypt::encrypt($customFieldValue));

View File

@@ -2,6 +2,7 @@
namespace App\Livewire;
use Livewire\Attributes\Computed;
use Livewire\Component;
class CategoryEditForm extends Component
@@ -12,43 +13,25 @@ class CategoryEditForm extends Component
public $eulaText;
public $originalSendCheckInEmailValue;
public bool $requireAcceptance;
public bool $sendCheckInEmail;
public bool $useDefaultEula;
public function mount()
{
$this->originalSendCheckInEmailValue = $this->sendCheckInEmail;
if ($this->eulaText || $this->useDefaultEula) {
$this->sendCheckInEmail = 1;
}
}
public function render()
{
return view('livewire.category-edit-form');
}
public function updated($property, $value)
{
if (! in_array($property, ['eulaText', 'useDefaultEula'])) {
return;
}
$this->sendCheckInEmail = $this->eulaText || $this->useDefaultEula ? 1 : $this->originalSendCheckInEmailValue;
}
public function getShouldDisplayEmailMessageProperty(): bool
#[Computed]
public function emailWillBeSendDueToEula(): bool
{
return $this->eulaText || $this->useDefaultEula;
}
public function getEmailMessageProperty(): string
#[Computed]
public function emailMessage(): string
{
if ($this->useDefaultEula) {
return trans('admin/categories/general.email_will_be_sent_due_to_global_eula');
@@ -57,13 +40,9 @@ class CategoryEditForm extends Component
return trans('admin/categories/general.email_will_be_sent_due_to_category_eula');
}
public function getEulaTextDisabledProperty()
#[Computed]
public function eulaTextDisabled()
{
return (bool)$this->useDefaultEula;
}
public function getSendCheckInEmailDisabledProperty()
{
return $this->eulaText || $this->useDefaultEula;
}
}

View File

@@ -159,7 +159,7 @@ class SlackSettingsForm extends Component
]);
try {
$test = $webhook->post($this->webhook_endpoint, ['body' => $payload, ['headers' => ['Content-Type' => 'application/json']]]);
$test = $webhook->post($this->webhook_endpoint, ['body' => $payload, 'headers' => ['Content-Type' => 'application/json']]);
if(($test->getStatusCode() == 302)||($test->getStatusCode() == 301)){
return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));

18
app/Mail/BaseMailable.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace App\Mail;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Headers;
class BaseMailable extends Mailable
{
public function headers(): Headers
{
return new Headers(
text: [
'X-Auto-Response-Suppress' => 'OOF, DR, RN, NRN, AutoReply',
'X-System-Sender' => 'Snipe-IT',
]
);
}
}

View File

@@ -6,14 +6,12 @@ use App\Models\Accessory;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CheckinAccessoryMail extends Mailable
class CheckinAccessoryMail extends BaseMailable
{
use Queueable, SerializesModels;

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