Compare commits

..

192 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
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
Brady Wetherington
b96e2fb52c Added migration to fix incorrect action_type in action_log 2025-10-23 15:40:54 +01: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
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
1184 changed files with 189244 additions and 5177 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
# --------------------------------------------
@@ -197,7 +201,7 @@ 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,7 +26,7 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

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

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

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

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

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

@@ -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

@@ -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 == '') {

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

@@ -142,7 +142,7 @@ class IconHelper
case 'more-files':
return 'fa-solid fa-laptop-file';
case 'maintenances':
return 'fa-solid fa-screwdriver-wrench';
return 'fas fa-wrench';
case 'seats':
return 'far fa-list-alt';
case 'globe-us':

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;
@@ -138,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,
@@ -183,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);
}
@@ -204,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

@@ -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
{

View File

@@ -43,6 +43,7 @@ class CategoriesController extends Controller
'created_at',
'updated_at',
'image',
'tag_color',
'notes',
];
@@ -57,6 +58,7 @@ class CategoriesController extends Controller
'require_acceptance',
'checkin_email',
'image',
'tag_color',
'notes',
])
->with('adminuser')

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

@@ -58,8 +58,8 @@ class ComponentsController extends Controller
];
$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 = [];
@@ -112,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';
@@ -143,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

@@ -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

@@ -47,6 +47,7 @@ class ManufacturersController extends Controller
'consumables_count',
'components_count',
'licenses_count',
'tag_color',
'notes',
];
@@ -63,6 +64,7 @@ class ManufacturersController extends Controller
'updated_at',
'image',
'deleted_at',
'tag_color',
'notes',
])
->with('adminuser')
@@ -104,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');
@@ -258,6 +264,7 @@ class ManufacturersController extends Controller
'id',
'name',
'image',
'tag_color',
]);
if ($request->filled('search')) {

View File

@@ -50,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')
@@ -251,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);
@@ -162,6 +163,11 @@ class UsersController extends Controller
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);

View File

@@ -78,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();
@@ -132,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);

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;
@@ -52,6 +53,7 @@ abstract class Controller extends BaseController
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'suppliers' => Supplier::class,
'users' => User::class,
];
@@ -66,6 +68,7 @@ abstract class Controller extends BaseController
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/models/',
'suppliers' => 'private_uploads/suppliers/',
'users' => 'private_uploads/users/',
];
@@ -80,6 +83,7 @@ abstract class Controller extends BaseController
'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,12 +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()) {
$group->users()->sync($request->input('associated_users'));
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'));
}
@@ -88,11 +111,26 @@ class GroupsController extends Controller
if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
$groupPermissions = [];
}
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
$associated_users = $group->users()->get();
//dd($associated_users->toArray());
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'))->with('associated_users', $associated_users);
$selected_array = Helper::selectedPermissionsArray($permissions, $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);
}
/**
@@ -106,13 +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()) {
$group->users()->sync($request->input('associated_users'));
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

@@ -68,12 +68,6 @@ class MaintenancesController extends Controller
{
$this->authorize('update', Asset::class);
\Log::error(print_r($request->input('selected_assets[]'), true));
if (!$request->filled('selected_assets')) {
return redirect()->back()->withInput()->with('error', 'No assets were selected.');
}
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
// Loop through the selected assets

View File

@@ -101,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'));
@@ -142,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

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;
@@ -1116,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' ));
}
/**
@@ -1155,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
*
@@ -1221,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'),
];
@@ -1253,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

@@ -67,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();
@@ -111,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);

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

@@ -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

@@ -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

@@ -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

@@ -161,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,

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

@@ -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

@@ -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;

View File

@@ -7,15 +7,13 @@ use App\Models\Asset;
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\Notifications\Messages\MailMessage;
use Illuminate\Queue\SerializesModels;
class CheckinAssetMail extends Mailable
class CheckinAssetMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -7,14 +7,12 @@ use App\Models\Component;
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 CheckinComponentMail extends Mailable
class CheckinComponentMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -6,14 +6,12 @@ use App\Models\LicenseSeat;
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 CheckinLicenseMail extends Mailable
class CheckinLicenseMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -5,13 +5,11 @@ namespace App\Mail;
use App\Models\CheckoutAcceptance;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CheckoutAcceptanceResponseMail extends Mailable
class CheckoutAcceptanceResponseMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -8,15 +8,13 @@ use App\Models\Location;
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;
use Illuminate\Support\Facades\Log;
class CheckoutAccessoryMail extends Mailable
class CheckoutAccessoryMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -8,8 +8,6 @@ use App\Models\Location;
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\Attachment;
use Illuminate\Mail\Mailables\Content;
@@ -17,7 +15,7 @@ use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\SerializesModels;
class CheckoutAssetMail extends Mailable
class CheckoutAssetMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -6,13 +6,12 @@ use App\Models\Component;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CheckoutComponentMail extends Mailable
class CheckoutComponentMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -6,15 +6,12 @@ use App\Models\Consumable;
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;
use Illuminate\Support\Facades\Log;
class CheckoutConsumableMail extends Mailable
class CheckoutConsumableMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -7,14 +7,12 @@ use App\Models\LicenseSeat;
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 CheckoutLicenseMail extends Mailable
class CheckoutLicenseMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -3,14 +3,12 @@
namespace App\Mail;
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 ExpiringAssetsMail extends Mailable
class ExpiringAssetsMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -3,14 +3,12 @@
namespace App\Mail;
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 ExpiringLicenseMail extends Mailable
class ExpiringLicenseMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -3,14 +3,12 @@
namespace App\Mail;
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 SendUpcomingAuditMail extends Mailable
class SendUpcomingAuditMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -3,14 +3,12 @@
namespace App\Mail;
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 UnacceptedAssetReminderMail extends Mailable
class UnacceptedAssetReminderMail extends BaseMailable
{
use Queueable, SerializesModels;

View File

@@ -9,6 +9,7 @@ use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
use App\Enums\ActionType;
/**
* Model for the Actionlog (the table that keeps a historical log of
@@ -335,9 +336,12 @@ class Actionlog extends SnipeModel
* @since [v3.0]
* @return bool
*/
public function logaction($actiontype)
public function logaction(string|ActionType $actiontype)
{
$this->action_type = $actiontype;
if (is_string($actiontype)) {
$actiontype = ActionType::from($actiontype);
}
$this->action_type = $actiontype->value;
$this->remote_ip = request()->ip();
$this->user_agent = request()->header('User-Agent');
$this->action_source = $this->determineActionSource();
@@ -360,7 +364,7 @@ class Actionlog extends SnipeModel
{
$now = Carbon::now();
$last_audit_date = $this->created_at; // this is the action log's created at, not the asset itself
$next_audit = $last_audit_date->addMonth($monthInterval); // this actually *modifies* the $last_audit_date
$next_audit = $last_audit_date->addMonth((int) $monthInterval); // this actually *modifies* the $last_audit_date
$next_audit_days = (int) round($now->diffInDays($next_audit, true));
$override_default_next = $next_audit;
@@ -518,6 +522,8 @@ class Actionlog extends SnipeModel
return 'private_uploads/locations/'.$this->filename;
case Maintenance::class:
return 'private_uploads/maintenances/'.$this->filename;
case Supplier::class:
return 'private_uploads/suppliers/'.$this->filename;
case User::class:
return 'private_uploads/users/'.$this->filename;
default:

View File

@@ -282,7 +282,7 @@ class Asset extends Depreciable
protected function warrantyExpires(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => ($attributes['warranty_months'] && $attributes['purchase_date']) ? Carbon::parse($attributes['purchase_date'])->addMonths($attributes['warranty_months']) : null,
get: fn(mixed $value, array $attributes) => ($attributes['warranty_months'] && $attributes['purchase_date']) ? Carbon::parse($attributes['purchase_date'])->addMonths((int)$attributes['warranty_months']) : null,
);
}
@@ -408,6 +408,13 @@ class Asset extends Depreciable
);
}
protected function expectedCheckinDiffForHumans(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => array_key_exists('expected_checkin', $attributes) ? Carbon::parse($this->expected_checkin)->diffForHumans() : null,
);
}
/**
* Establishes the asset -> company relationship
*

View File

@@ -73,6 +73,7 @@ class Category extends SnipeModel
'alert_on_response',
'use_default_eula',
'created_by',
'tag_color',
'notes',
];

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Models;
class Checkoutable
{
public function __construct(
public int $acceptance_id,
public string $company,
public string $category,
public string $model,
public string $asset_tag,
public string $name,
public string $type,
public object $acceptance,
public object $assignee,
public readonly string $plain_text_category,
public readonly string $plain_text_model,
public readonly string $plain_text_name,
public readonly string $plain_text_company,
){}
public static function fromAcceptance(CheckoutAcceptance $unaccepted): self
{
$unaccepted_row = $unaccepted->checkoutable;
$acceptance = $unaccepted;
$assignee = $acceptance->assignedTo;
$company = $unaccepted_row?->company?->present()?->nameUrl() ?? '';
$category = $model = $name = $tag = '';
$type = $acceptance->checkoutable_item_type ?? '';
if($unaccepted_row instanceof Asset){
$category = optional($unaccepted_row->model?->category?->present())->nameUrl() ?? '';
$model = optional($unaccepted_row->present())->modelUrl() ?? '';
$name = optional($unaccepted_row->present())->nameUrl() ?? '';
$tag = (string) ($unaccepted_row->asset_tag ?? '');
}
if($unaccepted_row instanceof Accessory){
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
$model = $unaccepted_row->model_number ?? '';
$name = optional($unaccepted_row->present())->nameUrl() ?? '';
}
if($unaccepted_row instanceof LicenseSeat){
$category = optional($unaccepted_row->license->category?->present())->nameUrl() ?? '';
$company = optional($unaccepted_row->license->company?->present())?->nameUrl() ?? '';
$model = '';
$name = $unaccepted_row->license->present()->nameUrl() ?? '';
}
if($unaccepted_row instanceof Consumable){
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
$model = $unaccepted_row->model_number ?? '';
$name = $unaccepted_row?->present()?->nameUrl() ?? '';
}
if($unaccepted_row instanceof Component){
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
$model = $unaccepted_row->model_number ?? '';
$name = $unaccepted_row?->present()?->nameUrl() ?? '';
}
return new self(
acceptance_id: $acceptance->id,
company: $company,
category: $category,
model: $model,
asset_tag: $tag,
name: $name,
type: $type,
acceptance: $acceptance,
assignee: $assignee,
//plain text for CSVs
plain_text_category: $unaccepted_row?->model?->category?->name ?? $unaccepted_row?->license?->category?->name ?? $unaccepted_row?->category?->name ?? '',
plain_text_model: $unaccepted_row?->model?->name ?? $unaccepted_row?->model_number ?? '',
plain_text_name: $unaccepted_row?->name ?? $unaccepted_row?->license?->name ?? '',
plain_text_company: $unaccepted_row?->company->name ?? $unaccepted_row?->license?->company?->name ?? '',
);
}
}

View File

@@ -72,6 +72,7 @@ final class Company extends SnipeModel
'fax',
'email',
'created_by',
'tag_color',
'notes',
];

View File

@@ -238,14 +238,26 @@ class Component extends SnipeModel
* @since [v5.0]
* @return int
*/
public function numCheckedOut()
public function numCheckedOut(bool $recalculate = false)
{
$checkedout = 0;
/**
*
* WARNING: This method caches the result, so if you're doing something
* that is going to change the number of checked-out items, make sure to pass
* 'true' as the first parameter to force this to recalculate the number of checked-out
* items!!!!!
*
*/
// In case there are elements checked out to assets that belong to a different company
// than this asset and full multiple company support is on we'll remove the global scope,
// so they are included in the count.
return $this->uncontrainedAssets->sum('pivot.assigned_qty');
if (is_null($this->sum_unconstrained_assets) || $recalculate) {
// This, in a components-listing context, is mostly important for when it sets a 'zero' which
// is *not* null - so we don't have to keep recalculating for un-checked-out components
$this->sum_unconstrained_assets = $this->uncontrainedAssets()->sum('assigned_qty') ?? 0;
}
return $this->sum_unconstrained_assets;
}
@@ -275,7 +287,19 @@ class Component extends SnipeModel
{
return $this->category?->checkin_email;
}
/**
* Get the list of checkouts for this License
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function checkouts()
{
return $this->assetlog()->where('action_type', '=', 'checkout')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* Check how many items within a component are remaining

View File

@@ -317,6 +317,20 @@ class Consumable extends SnipeModel
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
}
/**
* Get the list of checkouts for this consumable
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function checkouts()
{
return $this->assetlog()->where('action_type', '=', 'checkout')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* -----------------------------------------------
* BEGIN MUTATORS

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Watson\Validating\ValidatingTrait;
@@ -22,7 +23,10 @@ class Department extends SnipeModel
*/
protected $injectUniqueIdentifier = true;
use ValidatingTrait, UniqueUndeletedTrait;
protected $presenter = \App\Presenters\DepartmentPresenter::class;
use ValidatingTrait, UniqueUndeletedTrait, Presentable;
protected $casts = [
'manager_id' => 'integer',
@@ -31,10 +35,13 @@ class Department extends SnipeModel
];
protected $rules = [
'name' => 'required|max:255|is_unique_department',
'location_id' => 'numeric|nullable',
'company_id' => 'numeric|nullable',
'manager_id' => 'numeric|nullable',
'name' => 'required|max:255|is_unique_across_company_and_location:departments,name',
'location_id' => 'numeric|nullable|exists:locations,id',
'company_id' => 'numeric|nullable|exists:companies,id',
'manager_id' => 'numeric|nullable|exists:users,id',
'phone' => 'string|max:255|nullable',
'fax' => 'string|max:255|nullable',
'notes' => 'string|max:255|nullable',
];
/**
@@ -50,6 +57,7 @@ class Department extends SnipeModel
'location_id',
'company_id',
'manager_id',
'tag_color',
'notes',
];

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Models\Labels\Sheets\Avery;
use App\Helpers\Helper;
use App\Models\Labels\RectangleSheet;
abstract class L4736 extends RectangleSheet
{
private const PAPER_FORMAT = 'A4';
private const PAPER_ORIENTATION = 'P';
/* Data in pt from Word Template */
private const COLUMN1_X = 31.70;
private const COLUMN2_X = 167.92;
private const ROW1_Y = 53.00;
private const ROW2_Y = 112.8;
private const LABEL_W = 122.24;
private const LABEL_H = 66.5;
private float $pageWidth;
private float $pageHeight;
private float $pageMarginLeft;
private float $pageMarginTop;
private float $columnSpacing;
private float $rowSpacing;
private float $labelWidth;
private float $labelHeight;
public function __construct()
{
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0);
$this->pageWidth = $paperSize->width;
$this->pageHeight = $paperSize->height;
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
$columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
$this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
$rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
$this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
}
public function getPageWidth()
{
return $this->pageWidth;
}
public function getPageHeight()
{
return $this->pageHeight;
}
public function getPageMarginTop()
{
return $this->pageMarginTop;
}
public function getPageMarginBottom()
{
return $this->pageMarginTop;
}
public function getPageMarginLeft()
{
return $this->pageMarginLeft;
}
public function getPageMarginRight()
{
return $this->pageMarginLeft;
}
public function getColumns()
{
return 4;
}
public function getRows()
{
return 12;
}
public function getLabelColumnSpacing()
{
return $this->columnSpacing;
}
public function getLabelRowSpacing()
{
return $this->rowSpacing;
}
public function getLabelWidth()
{
return $this->labelWidth;
}
public function getLabelHeight()
{
return $this->labelHeight;
}
public function getLabelBorder()
{
return 0;
}
}
?>

View File

@@ -0,0 +1,137 @@
<?php
namespace App\Models\Labels\Sheets\Avery;
class L4736_A extends L4736
{
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 4.80;
private const TITLE_SIZE = 3.00;
private const TITLE_MARGIN = 1.80;
private const LABEL_SIZE = 2.8;
private const LABEL_MARGIN = - 0.45;
private const FIELD_SIZE = 3.80;
private const FIELD_MARGIN = 0.20;
public function getUnit()
{
return 'mm';
}
public function getLabelMarginTop()
{
return 0.06;
}
public function getLabelMarginBottom()
{
return 0.06;
}
public function getLabelMarginLeft()
{
return 0.06;
}
public function getLabelMarginRight()
{
return 0.06;
}
public function getSupportAssetTag()
{
return true;
}
public function getSupport1DBarcode()
{
return false;
}
public function getSupport2DBarcode()
{
return true;
}
public function getSupportFields()
{
return 4;
}
public function getSupportLogo()
{
return false;
}
public function getSupportTitle()
{
return true;
}
public function preparePDF($pdf)
{
}
public function write($pdf, $record)
{
$pa = $this->getLabelPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$pa->x1, $pa->y1,
'freesans', '', self::TITLE_SIZE, 'C',
$pa->w, self::TITLE_SIZE, true, 0
);
}
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
$usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN;
$barcodeSize = $usableHeight;
if ($record->has('barcode2d')) {
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
}
$fields = $record->get('fields');
$fieldCount = count($fields);
$perFieldHeight = (self::LABEL_SIZE + self::LABEL_MARGIN)
+ (self::FIELD_SIZE + self::FIELD_MARGIN);
$baseHeight = $fieldCount * $perFieldHeight;
$scale = 1.0;
if ($baseHeight > $usableHeight && $baseHeight > 0) {
$scale = $usableHeight / $baseHeight;
}
$labelSize = self::LABEL_SIZE * $scale;
$labelMargin = self::LABEL_MARGIN * $scale;
$fieldSize = self::FIELD_SIZE * $scale;
$fieldMargin = self::FIELD_MARGIN * $scale;
foreach ($fields as $field) {
static::writeText(
$pdf, $field['label'],
$currentX, $currentY,
'freesans', '', $labelSize, 'L',
$usableWidth, $labelSize, true, 0
);
$currentY += $labelSize + $labelMargin;
static::writeText(
$pdf, $field['value'],
$currentX, $currentY,
'freemono', 'B', $fieldSize, 'L',
$usableWidth, $fieldSize, true, 0, 0.01
);
$currentY += $fieldSize + $fieldMargin;
}
}
}
?>

View File

@@ -1,110 +1,66 @@
<?php
namespace App\Models\Labels\Sheets\Avery;
use App\Helpers\Helper;
use App\Models\Labels\RectangleSheet;
abstract class L6009 extends RectangleSheet
{
private const PAPER_FORMAT = 'A4';
private const PAPER_ORIENTATION = 'P';
/* Data in pt from Word Template */
private const COLUMN1_X = 31.70;
private const COLUMN2_X = 167.92;
private const ROW1_Y = 53.00;
private const ROW2_Y = 112.8;
private const LABEL_W = 122.24;
private const LABEL_H = 66.5;
private float $pageWidth;
private float $pageHeight;
private float $pageMarginLeft;
private float $pageMarginTop;
private float $columnSpacing;
private float $rowSpacing;
private float $labelWidth;
private float $labelHeight;
public function __construct()
{
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0);
$this->pageWidth = $paperSize->width;
$this->pageHeight = $paperSize->height;
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
$columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
$this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
$rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
$this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
}
public function getPageWidth()
{
return $this->pageWidth;
}
public function getPageHeight()
{
return $this->pageHeight;
}
public function getPageMarginTop()
{
return $this->pageMarginTop;
}
public function getPageMarginBottom()
{
return $this->pageMarginTop;
}
public function getPageMarginLeft()
{
return $this->pageMarginLeft;
}
public function getPageMarginRight()
{
return $this->pageMarginLeft;
}
public function getColumns()
{
return 4;
}
public function getRows()
{
return 12;
}
public function getLabelColumnSpacing()
{
return $this->columnSpacing;
}
public function getLabelRowSpacing()
{
return $this->rowSpacing;
}
public function getLabelWidth()
{
return $this->labelWidth;
}
public function getLabelHeight()
{
return $this->labelHeight;
}
public function getLabelBorder()
{
return 0;
}
}
?>
<?php
namespace App\Models\Labels\Sheets\Avery;
use App\Helpers\Helper;
use App\Models\Labels\RectangleSheet;
abstract class L6009 extends RectangleSheet
{
private const PAPER_FORMAT = 'A4';
private const PAPER_ORIENTATION = 'P';
// Convert mm to pt (1 mm = 2.83465 pt)
private const LABEL_W = 129.54;
private const LABEL_H = 60.09;
private const COLUMN1_X = 22.67;
private const ROW1_Y = 37.41;
private const COLUMN_SPACING = 14.17;
private const ROW_SPACING = 2.26;
private float $pageWidth;
private float $pageHeight;
private float $pageMarginLeft;
private float $pageMarginTop;
private float $columnSpacing;
private float $rowSpacing;
private float $labelWidth;
private float $labelHeight;
public function __construct()
{
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0);
$this->pageWidth = $paperSize->width;
$this->pageHeight = $paperSize->height;
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
$this->columnSpacing = Helper::convertUnit(self::COLUMN_SPACING, 'pt', $this->getUnit());
$this->rowSpacing = Helper::convertUnit(self::ROW_SPACING, 'pt', $this->getUnit());
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
}
public function getPageWidth() { return $this->pageWidth; }
public function getPageHeight() { return $this->pageHeight; }
public function getPageMarginTop() { return $this->pageMarginTop; }
public function getPageMarginBottom() { return $this->pageMarginTop; }
public function getPageMarginLeft() { return $this->pageMarginLeft; }
public function getPageMarginRight() { return $this->pageMarginLeft; }
public function getColumns() { return 4; }
public function getRows() { return 12; }
public function getLabelColumnSpacing(){ return $this->columnSpacing; }
public function getLabelRowSpacing() { return $this->rowSpacing; }
public function getLabelWidth() { return $this->labelWidth; }
public function getLabelHeight() { return $this->labelHeight; }
public function getLabelBorder() { return 0; }
}
?>

View File

@@ -1,121 +1,104 @@
<?php
namespace App\Models\Labels\Sheets\Avery;
class L6009_A extends L6009
{
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 4.80;
private const TITLE_SIZE = 3.00;
private const TITLE_MARGIN = 1.80;
private const LABEL_SIZE = 2.8;
private const LABEL_MARGIN = - 0.45;
private const FIELD_SIZE = 3.80;
private const FIELD_MARGIN = 0.20;
public function getUnit()
{
return 'mm';
}
public function getLabelMarginTop()
{
return 0.06;
}
public function getLabelMarginBottom()
{
return 0.06;
}
public function getLabelMarginLeft()
{
return 0.06;
}
public function getLabelMarginRight()
{
return 0.06;
}
public function getSupportAssetTag()
{
return true;
}
public function getSupport1DBarcode()
{
return false;
}
public function getSupport2DBarcode()
{
return true;
}
public function getSupportFields()
{
return 4;
}
public function getSupportLogo()
{
return false;
}
public function getSupportTitle()
{
return true;
}
public function preparePDF($pdf)
{
}
public function write($pdf, $record)
{
$pa = $this->getLabelPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$pa->x1, $pa->y1,
'freesans', '', self::TITLE_SIZE, 'C',
$pa->w, self::TITLE_SIZE, true, 0
);
}
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
$usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN;
$barcodeSize = $usableHeight;
if ($record->has('barcode2d')) {
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
}
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, $field['label'],
$currentX, $currentY,
'freesans', '', self::LABEL_SIZE, 'L',
$usableWidth, self::LABEL_SIZE, true, 0
);
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
static::writeText(
$pdf, $field['value'],
$currentX, $currentY,
'freemono', 'B', self::FIELD_SIZE, 'L',
$usableWidth, self::FIELD_SIZE, true, 0, 0.01
);
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
}
}
}
?>
<?php
namespace App\Models\Labels\Sheets\Avery;
class L6009_A extends L6009
{
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 4.80;
private const TITLE_SIZE = 3.00;
private const TITLE_MARGIN = 1.80;
private const LABEL_SIZE = 2.8;
private const LABEL_MARGIN = -0.45;
private const FIELD_SIZE = 3.80;
private const FIELD_MARGIN = 0.20;
public function getUnit() { return 'mm'; }
public function getLabelMarginTop() { return 0.06; }
public function getLabelMarginBottom() { return 0.06; }
public function getLabelMarginLeft() { return 0.06; }
public function getLabelMarginRight() { return 0.06; }
public function getSupportAssetTag() { return true; }
public function getSupport1DBarcode() { return false; }
public function getSupport2DBarcode() { return true; }
public function getSupportFields() { return 4; }
public function getSupportLogo() { return false; }
public function getSupportTitle() { return false; }
public function preparePDF($pdf) {}
public function write($pdf, $record)
{
$pa = $this->getLabelPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$pa->x1, $pa->y1,
'freesans', '', self::TITLE_SIZE, 'C',
$pa->w, self::TITLE_SIZE, true, 0
);
}
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
$usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN;
$barcodeSize = $usableHeight;
if ($record->has('barcode2d')) {
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
}
$fields = $record->get('fields');
// Below rescales the size of the field box to fit, it feels like it could/should be abstracted one class above
// to be usable on other labels but im unsure of how to implement that, since it uses a lot of private
// constants.
// Figure out how tall the label fields wants to be
$fieldCount = count($fields);
$perFieldHeight = (self::LABEL_SIZE + self::LABEL_MARGIN)
+ (self::FIELD_SIZE + self::FIELD_MARGIN);
$baseHeight = $fieldCount * $perFieldHeight;
// If it doesn't fit in the available height, scale everything down
$scale = 1.0;
if ($baseHeight > $usableHeight && $baseHeight > 0) {
$scale = $usableHeight / $baseHeight;
}
$labelSize = self::LABEL_SIZE * $scale;
$labelMargin = self::LABEL_MARGIN * $scale;
$fieldSize = self::FIELD_SIZE * $scale;
$fieldMargin = self::FIELD_MARGIN * $scale;
foreach ($fields as $field) {
static::writeText(
$pdf, $field['label'],
$currentX, $currentY,
'freesans', '', $labelSize, 'L',
$usableWidth, $labelSize, true, 0
);
$currentY += $labelSize + $labelMargin;
static::writeText(
$pdf, $field['value'],
$currentX, $currentY,
'freemono', 'B', $fieldSize, 'L',
$usableWidth, $fieldSize, true, 0, 0.01
);
$currentY += $fieldSize + $fieldMargin;
}
}
}
?>

View File

@@ -4,15 +4,15 @@ namespace App\Models\Labels\Tapes\Brother;
class TZe_24mm_E extends TZe_24mm
{
private const BARCODE_MARGIN = 1.50;
private const BARCODE_MARGIN = 1.75;
private const TAG_SIZE = 2.00;
private const TITLE_SIZE = 2.80;
private const TITLE_MARGIN = 0.50;
private const LABEL_SIZE = 2.00;
private const LABEL_MARGIN = - 0.35;
private const LABEL_MARGIN = - 0.75;
private const FIELD_SIZE = 2.80;
private const FIELD_MARGIN = 0.15;
private const BARCODE1D_SIZE = - 1.00;
private const BARCODE1D_SIZE = - 2.25;
public function getUnit() { return 'mm'; }
public function getWidth() { return 45.0; }

View File

@@ -416,7 +416,12 @@ class License extends Depreciable
}
return false;
}
public function checkouts()
{
return $this->assetlog()->where('action_type', '=', 'checkout')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* Determine whether the user should be required to accept the license
*

View File

@@ -135,7 +135,31 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
return false;
}
/**
* Get the list of checkouts for this License
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function checkouts()
{
return $this->assetlog()->where('action_type', '=', 'checkout')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* Establishes the license -> action logs relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assetlog()
{
return $this->hasMany(Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed();
}
/**
* Query builder scope to order on department
*
@@ -153,17 +177,24 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
}
public function scopeOrderCompany($query, $order)
{
return $query->leftJoin('users as license_seat_users', 'license_seats.assigned_to', '=', 'license_seat_users.id')
->leftJoin('companies as license_user_company', 'license_user_company.id', '=', 'license_seat_users.company_id')
->whereNotNull('license_seats.assigned_to')
->orderBy('license_user_company.name', $order);
}
public function scopeByAssigned($query)
{
return $query->where(
function ($query) {
$query->whereNotNull('assigned_to')
->orWhere(
function ($query) {
$query->whereNotNull('asset_id');
}
);
->orWhereNotNull('asset_id');
}
);

View File

@@ -77,6 +77,7 @@ class Location extends SnipeModel
'manager_id',
'image',
'company_id',
'tag_color',
'notes',
];
protected $hidden = ['user_id'];

View File

@@ -53,6 +53,7 @@ class Manufacturer extends SnipeModel
'support_url',
'url',
'warranty_lookup_url',
'tag_color',
'notes',
];

View File

@@ -20,13 +20,6 @@ class SnipeModel extends Model
$this->attributes['purchase_date'] = $value;
}
protected function purchaseDateForDatepicker(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => array_key_exists('purchase_date', $attributes) ? Carbon::parse($attributes['purchase_date'])->format('Y-m-d') : null,
);
}
protected function purchaseDateFormatted(): Attribute
{

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