Compare commits

...

223 Commits

Author SHA1 Message Date
snipe 750c376725 Set item to null so it doesn’t get merged automatically 2025-09-29 13:29:00 +01:00
snipe 08630d948f Remove radio buttons - they don’t work correctly yet 2025-09-29 12:43:19 +01:00
snipe dc6ee342c5 Revert custom field name text box for now 2025-09-29 10:42:40 +01:00
snipe eea1922841 Added checkbox and radios 2025-09-25 13:22:52 +01:00
snipe 57824848e9 Update resources/views/blade/form-row.blade.php
Co-authored-by: Marcus Moore <mmoore@grokability.com>
2025-09-24 17:08:00 +01:00
snipe 235dcbc7d9 Updated more views 2025-09-24 16:58:35 +01:00
snipe f0561475cc Update resources/views/blade/input/text.blade.php
Co-authored-by: Marcus Moore <mmoore@grokability.com>
2025-09-24 16:41:56 +01:00
snipe 3b4c51bab6 Use slot instead of label 2025-09-24 14:52:29 +01:00
snipe 150c205615 Added checkboxes to status labels page 2025-09-23 21:37:17 +01:00
snipe cde22977b0 More checkbox changes 2025-09-23 21:23:03 +01:00
snipe 1c09657631 Handle checkboxes 2025-09-23 20:57:33 +01:00
snipe 02a4268180 Switched more fields to blade compaonents 2025-09-23 19:52:58 +01:00
snipe f8362f4a45 handled text fields in status labels 2025-09-23 18:44:06 +01:00
snipe 682c1a8fa7 Make required false 2025-09-23 18:43:50 +01:00
snipe 3863e82dcc Handle text and date fields on license edit 2025-09-23 18:43:39 +01:00
snipe b766f6e2b5 Made edit screen narrower on wide screens 2025-09-23 18:43:14 +01:00
snipe a3bad98096 Added date and tooltip components 2025-09-23 18:43:02 +01:00
snipe 33e7425dee Account for tooltips 2025-09-23 18:42:46 +01:00
snipe 2554b50b38 Updated suppliers 2025-09-23 17:04:49 +01:00
snipe 433a3e11fd Fixed field type 2025-09-23 17:00:03 +01:00
snipe c3efdd0c8d Updated company 2025-09-23 16:59:09 +01:00
snipe d2e3a13043 Added ability to mark fields as disabled 2025-09-23 16:58:53 +01:00
snipe c4923fa971 Updated address 2025-09-23 16:53:52 +01:00
snipe 253026de5d Updated locations edit 2025-09-23 16:51:55 +01:00
snipe a59914e9f9 Added placeholder back in 2025-09-23 16:38:12 +01:00
snipe 671e79f01b Sorry, this shouldn’t have been in there :( 2025-09-23 16:35:04 +01:00
snipe 5c716c3f24 Updated notes maxlength 2025-09-23 16:31:20 +01:00
snipe e138c9307e Added maxlength default for text 2025-09-23 16:30:18 +01:00
snipe 7c0c3b2bb8 Use shorthand for errors 2025-09-23 16:26:28 +01:00
snipe 514711ddbb Label blade component 2025-09-23 16:18:00 +01:00
snipe 9023eda66f Changed class 2025-09-23 14:25:52 +01:00
snipe 8ce3001ef9 First stab 2025-09-23 14:25:40 +01:00
snipe 25ce63f00b Merge pull request #17904 from grokability/#17804-searchable-columns
Fixed #17804 - make columns searchable in column picker
2025-09-19 12:43:46 +01:00
snipe 2462bc05b3 Added column search to additional views 2025-09-19 12:41:30 +01:00
snipe c3748da0b1 Fixed #17804 - make columns searchable in column picker 2025-09-19 12:16:56 +01:00
snipe 90c242a441 Merge pull request #17897 from marcusmoore/fixes/17896-prevent-bulk-checkout-of-checked-out-assets
Fixed #17896 - Prevent assigned assets from being bulk checked out
2025-09-19 07:03:43 +01:00
Marcus Moore 52239a88b5 Improve test name 2025-09-18 17:27:17 -07:00
Marcus Moore 7a3596c86d Test against other types 2025-09-18 17:21:27 -07:00
Marcus Moore ac8a9e38f0 Implement fix 2025-09-18 17:18:27 -07:00
Marcus Moore 5c08f3a27e Add failing test 2025-09-18 17:14:33 -07:00
snipe 5216dd75bf Bumped version 2025-09-18 14:49:15 +01:00
snipe b8b45d2d81 Merge pull request #17892 from grokability/#17891-fixes-maintenance-file-route
Fixed #17891 - missing maintenance file deletion route
2025-09-18 13:59:10 +01:00
snipe 4b2b2cb68e Fixed #17891 - missing maintenance file deletion route 2025-09-18 13:58:30 +01:00
snipe be4ace293e Use trans_choice for user acceptance 2025-09-18 13:51:57 +01:00
snipe 764b363bbc A few small tweaks to acceptance screen design 2025-09-18 13:38:37 +01:00
snipe 9da9166442 Merge pull request #17886 from grokability/small-tweaks-to-acceptance-pdf
Small adjustments for acceptance PDF layout
2025-09-17 22:01:41 +01:00
snipe 8ea339f0ef More small tweaks 2025-09-17 22:00:49 +01:00
snipe 89b36ba63f Derp. Uncomment the acceptance. 2025-09-17 21:43:40 +01:00
snipe 1d3dfa1fa4 Pull the acceptance stuff into the model 2025-09-17 21:43:17 +01:00
snipe ca567eec8a Small adjustments for layout 2025-09-17 21:08:13 +01:00
snipe 41da31c379 Merge pull request #17885 from grokability/#8859-show-cost-footer-on-models
Fixed #8859 - adds purchase sums on model view
2025-09-17 14:04:38 +01:00
snipe e81f63f46b Fixed #8859 - adds purchase sums on model view 2025-09-17 14:03:48 +01:00
snipe ade03e4827 Merge pull request #17882 from Godmartinz/add-total-cost-columns
Adds total cost to Accessories, Consumables, Components
2025-09-17 13:56:08 +01:00
snipe 33a4c88c3a Added table to deleted_at clauses to resolve ambiguity 2025-09-17 11:44:28 +01:00
snipe 1f79776b8f Pull HTML tags out before converting markdown 2025-09-16 19:21:39 +01:00
Godfrey M 11e5f851f0 typo 2025-09-16 10:49:33 -07:00
Godfrey M 4ca1db8a1b remove footer formatter from consumable purchase cost 2025-09-16 10:43:02 -07:00
Godfrey M 14b829aa30 add total cost to components and consumables 2025-09-16 10:37:32 -07:00
Godfrey M 384652b3df add total cost to accessories 2025-09-16 10:10:49 -07:00
snipe 9db65c6ae9 Merge pull request #17881 from grokability/#17873-eula-tab-on-users
Fixed #17873 - Added EULA tab to user view
2025-09-16 14:28:50 +01:00
snipe 1346e33e99 Check that the person trying to download can see both the user and the target 2025-09-16 14:21:03 +01:00
snipe ab9cc447aa Use more specific filename 2025-09-16 14:20:20 +01:00
snipe fe9e0444b4 Added EULA tab to user view 2025-09-16 13:20:50 +01:00
snipe a18957dbe9 Include output even if there is nothing to send 2025-09-16 12:33:06 +01:00
snipe 13d5b724ee Fixed tests 2025-09-16 12:16:18 +01:00
snipe c7d8203da9 Merge pull request #17866 from grokability/_reworked_tcpdf
Fixed #14744 and #17808 - Added CJK and Arabic font support for asset acceptance
2025-09-16 12:04:15 +01:00
snipe 96b5c1d8e1 Merge pull request #17876 from uberbrady/add_users_location_index
Add new index to users over deleted_at and location_id
2025-09-16 12:03:45 +01:00
Brady Wetherington 882ee80424 Add new index to users over deleted_at and location_id 2025-09-16 11:54:24 +01:00
snipe e977771fe4 One more query tweak 2025-09-16 11:50:50 +01:00
snipe 4339e4552e Fixed nesting in orWhere 2025-09-16 11:50:50 +01:00
snipe b54d222943 One more query tweak 2025-09-16 11:39:02 +01:00
snipe e4e613550a Merge pull request #17875 from grokability/fixed-eol-query
Fixed nesting in orWhere
2025-09-16 11:27:07 +01:00
snipe d1207444db Fixed nesting in orWhere 2025-09-16 11:20:49 +01:00
snipe 0bad75b263 Fixed declined asset name 2025-09-15 20:56:57 +01:00
snipe 74b98083e2 Override the getEula() method at the SnipeModel level 2025-09-15 20:05:35 +01:00
snipe 9034b5ec11 Slightly nicer display 2025-09-15 20:04:17 +01:00
snipe 927f557672 Fixed sorting on updated/created at 2025-09-15 18:13:46 +01:00
snipe 86fb089901 Remove unused blades 2025-09-15 18:13:32 +01:00
snipe 630ea05e17 A little more cleanup 2025-09-15 17:16:05 +01:00
snipe 7df5196083 A little cleanup 2025-09-15 16:09:49 +01:00
snipe 01f7b5d709 Added CJK and Arabic font support 2025-09-15 14:46:11 +01:00
snipe ec47ee3573 Merge pull request #17850 from Godmartinz/change-purchase-cost-to-unit
Rewords Purchase cost to Unit Cost for Accessories, Components, Consumables
2025-09-15 14:20:04 +01:00
snipe 7062962cc8 Show warnings on the dates if expired or terminated 2025-09-15 13:43:08 +01:00
snipe fde447846a Merge pull request #17865 from grokability/show-inactive-licenses
Show inactive licenses
2025-09-15 13:42:22 +01:00
snipe 319cb1bd1e Removed logging 2025-09-15 13:23:44 +01:00
snipe 58cda5ae6d Added scopes 2025-09-15 13:22:25 +01:00
snipe 251a3db880 Fixed factory 2025-09-15 13:20:34 +01:00
snipe 30b6dcd767 Added status to breadcrumbs 2025-09-15 13:13:50 +01:00
snipe 05f6622912 Added status to URL 2025-09-15 13:13:32 +01:00
snipe 36183ac19d Fixed url and tooltip text 2025-09-15 13:13:18 +01:00
snipe f6a823e0a8 Escape text 2025-09-15 13:13:04 +01:00
snipe 312353551d Changed button color 2025-09-15 13:11:58 +01:00
snipe fd5c9cee38 Merge pull request #17863 from grokability/added-status-label-to-asset-view
Added status label to asset view
2025-09-15 10:31:54 +01:00
snipe 84bf71802c Added status label to asset view 2025-09-15 10:31:06 +01:00
snipe 35739c2eef Merge pull request #17835 from marcusmoore/feature/10052-assigned-assets-via-api
Fixed #10052 - Added api endpoint for retrieving assets checked out to asset
2025-09-15 08:38:35 +01:00
snipe 1914a71623 Merge pull request #17851 from marcusmoore/fixes/qty-in-component-email
Fixed potentially incorrect qty in component checkout email
2025-09-15 08:37:48 +01:00
snipe dcc53886d9 Merge pull request #17857 from uberbrady/fix_client_tls_ldap
Fixed #17414 - client-side TLS certificate didn't work in Google LDAP
2025-09-15 08:37:14 +01:00
Brady Wetherington 21ef87ef09 Merge branch 'develop' into fix_client_tls_ldap 2025-09-12 18:52:43 +01:00
snipe b67f808da9 Fixed fieldname 2025-09-12 18:07:40 +01:00
snipe ad69447b53 Merge pull request #17858 from grokability/ignore-expiring-licenses-with-past-termination-date
Ignore expiring licenses with past termination date
2025-09-12 17:55:14 +01:00
snipe b4614df88c Removed awkward period 2025-09-12 17:39:52 +01:00
snipe 7171247cdc Updated tests 2025-09-12 17:37:36 +01:00
snipe 6d0084f108 Nicer alert layout for expiring asset report 2025-09-12 17:37:27 +01:00
snipe 29359f42ae Added table printout 2025-09-12 17:37:12 +01:00
snipe cf875bf872 Changed verbiage 2025-09-12 17:37:03 +01:00
snipe 13c0d335d3 Refactor alert query 2025-09-12 17:36:51 +01:00
snipe ceb33409b5 Removed duplicate array key 2025-09-12 17:36:33 +01:00
Brady Wetherington 83597d4a8b Possible fix to client-side TLS certificate issue for google LDAP 2025-09-12 17:12:35 +01:00
Marcus Moore 81eefc5448 Merge branch 'develop' into fixes/qty-in-component-email 2025-09-11 14:14:42 -07:00
Marcus Moore 082bc3ece4 Use correct qty in component checkout email 2025-09-11 14:10:31 -07:00
snipe b2406b61fb Merge pull request #17826 from marcusmoore/fixes/17585-accessory-checkout-amount-in-subject
Fixed #17585 - Display accessory checkout qty in subject line and intro text
2025-09-11 21:55:20 +01:00
Marcus Moore 15698d7694 Update introduction lines with qty 2025-09-11 13:50:48 -07:00
Godfrey M 990cd82f97 change purchase cost to unit cost in several places 2025-09-11 10:46:20 -07:00
snipe c87829b3e8 Merge pull request #17849 from Godmartinz/rollbar-unreassignablecount-fix
Fixed typo in UnreassignableCount
2025-09-11 18:43:07 +01:00
Godfrey M 6799c41d65 fixed typo 2025-09-11 10:23:53 -07:00
snipe 80c059be58 Skip terminated licenses in alert 2025-09-11 13:14:53 +01:00
snipe aa3f896538 Merge pull request #17842 from Godmartinz/expiration-and-termination-license-enhancement
Adds #8799 Prevention of checkout of expired or terminated licenses
2025-09-11 11:46:15 +01:00
snipe d8c17a8a5e One more small tweak to language 2025-09-11 11:40:15 +01:00
Godfrey M 850939367c adds translation warning message 2025-09-10 12:59:35 -07:00
Marcus Moore bf4fef9bf7 Merge branch 'develop' into fixes/17585-accessory-checkout-amount-in-subject 2025-09-10 12:49:41 -07:00
Godfrey M d5175961a4 adds a seperate formatter for checking out on license index, fix interactions 2025-09-10 12:42:10 -07:00
snipe e7e1d6a232 Small tweaks to language 2025-09-10 20:17:33 +01:00
snipe 712345f3a0 Added link to discussions and discord 2025-09-10 20:14:19 +01:00
snipe 54c9bc3dcb Merge pull request #17840 from marcusmoore/issue-template
Made install method required in issue template
2025-09-10 20:14:10 +01:00
Godfrey M e796c0da4a disallows checkout of expired or terminated licenses 2025-09-10 12:08:14 -07:00
Marcus Moore cf8ff0f43e Add note on English 2025-09-10 10:41:20 -07:00
Marcus Moore e67ce23a7c Require installation method 2025-09-10 10:23:22 -07:00
snipe a66bb95a81 A few more tweaks 2025-09-10 15:48:21 +01:00
snipe c66fa33b2e Have I mentioned how much I hate YAML? 2025-09-10 15:43:09 +01:00
snipe 56eebb9db4 Small tweaks to templates 2025-09-10 15:40:12 +01:00
snipe d9773f107e Added feature request form
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:38:19 +01:00
snipe e09112f46a Require installation type
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:33:06 +01:00
snipe 50a17a82b6 Sigh
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:30:42 +01:00
snipe 7eb032d646 Rename issue template (I guess?)
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:25:51 +01:00
snipe e065f22f8e Merge pull request #17838 from grokability/issue-form
New GH issue form
2025-09-10 15:20:51 +01:00
snipe c1b4ba1f85 Last one for now
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:20:27 +01:00
snipe eeaec471f0 Dunno if this will work
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:19:29 +01:00
snipe 0d3c8678d8 Moved text
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:17:16 +01:00
snipe bbddf5f95b Still a few more tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:15:09 +01:00
snipe 3b8c8b3af9 Nicer markdown
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:12:38 +01:00
snipe 84753aa13f Added browser console
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:08:37 +01:00
snipe 90b84451d8 Fixed indenting
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:07:04 +01:00
snipe 54c8ae41cc Still hate YAML
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:06:03 +01:00
snipe 7d32b1a724 God I hate YAML
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:05:27 +01:00
snipe 69ffd63ca6 Removed backticks
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:02:50 +01:00
snipe 4857c19eb6 More tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 15:02:07 +01:00
snipe d535e23da0 More template tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 14:51:33 +01:00
snipe 30e02544ab Try renaming so I can preview
via https://github.com/orgs/community/discussions/7039#discussioncomment-5327083

Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 14:43:16 +01:00
snipe ee53925bd2 Starter for preview
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 14:40:38 +01:00
snipe 40495b8a17 Small demo tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-09-10 12:33:33 +01:00
Marcus Moore 6bc9a82a7a Formatting 2025-09-09 14:51:22 -07:00
Marcus Moore 6504ee37bd Remove dump 2025-09-09 13:09:52 -07:00
Marcus Moore 082bff2fa8 Add and use query scope helper 2025-09-09 13:04:14 -07:00
Marcus Moore ab7bd86336 Improve assertions 2025-09-09 13:01:59 -07:00
Marcus Moore eada0b0bb5 Implement test 2025-09-09 11:58:43 -07:00
Marcus Moore f221f9f22a Implement test 2025-09-09 11:50:41 -07:00
snipe 6731e44a0d Merge pull request #17828 from marcusmoore/fixes/17586-acceptance-banner-qty
Fixed #17586 - Display accurate quantity in banner
2025-09-09 19:38:34 +01:00
Marcus Moore acc37045e4 Implement test 2025-09-09 11:34:50 -07:00
Marcus Moore a7c5899c16 Implement test 2025-09-09 11:33:54 -07:00
Marcus Moore 80b02635a9 Add test stub 2025-09-09 10:10:53 -07:00
snipe f90de5ec67 Merge pull request #17833 from grokability/tighter-controls-on-licenses
Tighter controls on license deletion, also fixes #16227 adding more tables to location print
2025-09-09 15:35:12 +01:00
snipe 9a3e046530 Fixed HTML
Signed-off-by: snipe <snipe@snipe.net>
2025-09-09 15:30:34 +01:00
snipe 7f56e461fe Added child location icon
Signed-off-by: snipe <snipe@snipe.net>
2025-09-09 15:19:00 +01:00
snipe 1da37e0d38 Added child location count
Signed-off-by: snipe <snipe@snipe.net>
2025-09-09 15:18:47 +01:00
snipe 0004d4936c Cleaned up print view
Signed-off-by: snipe <snipe@snipe.net>
2025-09-09 15:18:28 +01:00
snipe 7a6fdc4e0a Added parent ID to location API
Signed-off-by: snipe <snipe@snipe.net>
2025-09-09 15:18:09 +01:00
snipe 2eb727bd0c Added tests
Signed-off-by: snipe <snipe@snipe.net>
2025-09-09 13:51:56 +01:00
snipe 57af507170 Added deleted button to locations, check for additional relations
Signed-off-by: snipe <snipe@snipe.net>
2025-09-09 12:20:34 +01:00
snipe e37f87465c Merge pull request #17827 from Godmartinz/duplicate-emails-fix
Fixed #17756 - Duplicate checkout emails
2025-09-09 09:33:03 +01:00
Marcus Moore 324070f345 Begin to build out test 2025-09-08 17:20:22 -07:00
Marcus Moore e1aa843b6d Scaffold test 2025-09-08 17:05:14 -07:00
Godfrey M e652a7fd61 fix tests 2025-09-08 14:56:15 -07:00
Marcus Moore 2397bfbad0 Improve variable name 2025-09-08 14:51:19 -07:00
Marcus Moore 7e2bc8e452 Display accurate quantity in banner 2025-09-08 14:48:11 -07:00
Godfrey M 00e8fd0483 cloned mailable 2025-09-08 14:44:58 -07:00
Marcus Moore 6d8bf2c665 Display accessory checkout qty in subject line 2025-09-08 14:25:56 -07:00
snipe 72466f1aab Revert "Merge pull request #17823 from grokability/#17822-fix-n+1-in-topmenu"
This reverts commit 6901deccbf, reversing
changes made to 6b87c90e02.

Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 15:04:28 +01:00
snipe 6901deccbf Merge pull request #17823 from grokability/#17822-fix-n+1-in-topmenu
Fixed #17822 - n+1 in top menu check
2025-09-08 14:33:47 +01:00
snipe 5a9c906eb9 Added filter to assets loading
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 14:32:58 +01:00
snipe b95b60b49e Use eager-loaded model assets
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 14:07:49 +01:00
snipe 14408ef18f Fixed n+1 in top menu check
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 13:55:22 +01:00
snipe 6b87c90e02 Use scope for assets for show in sidebar
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 13:33:19 +01:00
snipe 2b4d5222eb Made “add new” buttons clearer on table headers
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 12:17:29 +01:00
snipe 9604ecebad Fixed jobtitle in advanced search
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 12:01:59 +01:00
snipe 0d67970a45 Update @swift2512 as a contributor 2025-09-08 09:27:34 +01:00
snipe 913b9f0c40 Reworks PR #15490 - adds location to inventory email
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 09:26:36 +01:00
snipe 610a5745f0 Merge pull request #17818 from grokability/dependabot/github_actions/develop/actions/stale-10
Bump actions/stale from 9 to 10
2025-09-08 09:17:19 +01:00
dependabot[bot] dff12324c6 Bump actions/stale from 9 to 10
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 08:16:24 +00:00
snipe f340390fc8 Merge pull request #17703 from marcusmoore/17369-accessory-checkout-qty-scaffold
Fixed issues around accessory acceptance
2025-09-08 09:09:26 +01:00
snipe be81e74921 Shifted log meta position
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 08:24:50 +01:00
snipe 2bee8729e4 Better escaping for display_name in API controller
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 08:14:33 +01:00
Marcus Moore 039564e74c Wrap migration in check 2025-09-03 12:52:46 -07:00
Marcus Moore e164595a0f Fall back to displaying 1 2025-09-03 12:35:18 -07:00
Marcus Moore d29e09a3ff Merge branch 'develop' into 17369-accessory-checkout-qty-scaffold
# Conflicts:
#	resources/views/account/accept/create.blade.php
#	resources/views/account/accept/index.blade.php
2025-09-03 12:26:43 -07:00
Marcus Moore 4298aad008 Merge branch 'develop' into 17369-accessory-checkout-qty-scaffold
# Conflicts:
#	app/Http/Controllers/Account/AcceptanceController.php
#	resources/views/notifications/markdown/asset-acceptance.blade.php
2025-08-26 11:39:52 -07:00
Marcus Moore 823c67400d Fix indent 2025-08-26 11:34:10 -07:00
Marcus Moore 3160d1064d Remove unused import 2025-08-26 11:32:38 -07:00
Marcus Moore 918426a2fa Add qty to accept assets table 2025-08-21 15:49:36 -07:00
Marcus Moore c076b37c9b Add qty to accessory eula pdf 2025-08-21 15:05:24 -07:00
Marcus Moore 7c58bfa282 Add qty to AcceptanceAssetDeclinedNotification 2025-08-21 14:56:24 -07:00
Marcus Moore 0caaba156d Add qty to AcceptanceAssetAcceptedNotification 2025-08-21 14:54:43 -07:00
Marcus Moore c22575812d Add qty to AcceptanceAssetAcceptedToUserNotification 2025-08-21 14:53:00 -07:00
Marcus Moore 0ede4da816 Remove CleanDeclinedAccessoryCheckouts command 2025-08-21 14:34:31 -07:00
Marcus Moore 98e23ff92e Remove legacy testing from test 2025-08-21 14:34:16 -07:00
Marcus Moore c7bdad649a Build out command 2025-08-20 16:06:11 -07:00
Marcus Moore 18c2508d2f Scaffold command for cleaning accessory_checkout 2025-08-20 13:49:19 -07:00
Marcus Moore 4dcfd8b353 Improve method name 2025-08-20 13:19:00 -07:00
Marcus Moore 726116574d Improve readability? 2025-08-20 12:40:03 -07:00
Marcus Moore 27f02014ca Add failing test 2025-08-20 12:26:55 -07:00
Marcus Moore f80f1acaa7 Improve variable name 2025-08-20 12:15:58 -07:00
Marcus Moore 39d5ffeceb Implement fix 2025-08-20 12:05:58 -07:00
Marcus Moore 48ba7eed3e Remove legacy checks from test 2025-08-20 11:45:36 -07:00
Marcus Moore 9caa240fdb Revert "Remove total qty of accessory checkouts"
This reverts commit 3ffb73a516.
2025-08-19 17:17:46 -07:00
Marcus Moore 3ffb73a516 Remove total qty of accessory checkouts 2025-08-19 17:04:13 -07:00
Marcus Moore cc1132be87 Remove flakiness 2025-08-19 16:50:36 -07:00
Marcus Moore 1c31f126ef Clear some flakiness 2025-08-19 16:46:41 -07:00
Marcus Moore d8eaf2676f Add a couple of notes 2025-08-19 15:54:46 -07:00
Marcus Moore 3101212c49 Continue to scaffold test 2025-08-14 17:24:36 -07:00
Marcus Moore 3f0ac103a1 Scaffold test 2025-08-14 13:29:18 -07:00
Marcus Moore 59bd6ca360 Update button text 2025-08-14 13:21:11 -07:00
Marcus Moore 22fe9a786e Display number of items being accepted 2025-08-14 12:36:46 -07:00
Marcus Moore d2ee8de9ac Attach qty to CheckoutAcceptance 2025-08-13 12:14:10 -07:00
Marcus Moore 03d3fb6a5f Add qty to checkout_acceptances table 2025-08-13 12:09:56 -07:00
149 changed files with 3925 additions and 1930 deletions
+2 -1
View File
@@ -3206,7 +3206,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
"profile": "https://github.com/swift2512",
"contributions": [
"bug"
"bug",
"code"
]
},
{
+137
View File
@@ -0,0 +1,137 @@
name: Bug Report
description: File a bug report.
title: "[Bug]: "
projects: ["grokability/snipe-it"]
type: bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Most issues are documented in the [Snipe-IT repository's issues](https://github.com/grokability/snipe-it/issues) or in the official [Common Issues section of the Documentation](https://snipe-it.readme.io/docs/common-issues#/) and are due to the following:
- `.env` misconfiguration
- [Server Permissions](https://snipe-it.readme.io/docs/debugging-permissions#/)
- [Database Migrations](https://snipe-it.readme.io/docs/database-issues#run-migrations)
Please make sure you've checked these resources before submitting a new issue. If you find an existing issue, please add your context to it instead of opening a new issue. If your issue is more of a question, consider [opening a new discussion](https://github.com/grokability/snipe-it/discussions) or [pop by our Discord](https://discord.gg/yZFtShAcKk) instead of creating an issue.
**Please write your bug report in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
- type: input
id: version
attributes:
label: Snipe-IT Version
description: What version of Snipe-IT are you seeing this issue on? You can find the version number in the footer of any page in Snipe-IT.
placeholder: ex. v8.3.1 - build 19577 (master)
validations:
required: true
- type: input
id: db-version
attributes:
label: MySQL/MariaDB version
description: What database are you using, and what version?
placeholder: ex. MySQL 5.7
validations:
required: true
- type: dropdown
id: install-method
attributes:
label: How did you install Snipe-IT?
options:
- Git install
- Manual install (downloading zip/tar.gz)
- Docker
- install.sh
- Hosted by Grokability
- Other
- Not sure
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see! (Be nice!)
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
- Other
- type: dropdown
id: on-demo
attributes:
label: Can you reproduce this on the public demo?
description: You can check this at https://demo.snipeitapp.com.
options:
- 'Yes'
- 'No'
- N/A
validations:
required: true
- type: dropdown
id: fmcs
attributes:
label: Do you have full multiple company support enabled?
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
options:
- 'Yes'
- 'No'
validations:
required: true
- type: dropdown
id: fmcs-location
attributes:
label: If you have full multiple company support enabled, do you have location scoping to company enabled?
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
options:
- 'Yes'
- 'No'
- I do not have full multiple company support enabled
validations:
required: true
- type: textarea
id: server-logs
attributes:
label: Application log output
description: Please copy and paste any relevant log output from `storage/logs/laravel.log`. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
id: browser-logs
attributes:
label: Browser console output
description: Please copy and paste any relevant log output from your browser console. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: checkboxes
id: common-issues
attributes:
label: Common Issues
description: Please make sure you have done the following before submitting your issue.
options:
- label: I have searched this repo for existing issues related to my issue (including closed issues)
required: true
- label: My APP_URL is set correctly in my .env file (including http or https and no trailing slash)
required: true
- label: I have searched the official Snipe-IT documentation and have checked the Common Issues documentation (where applicable)
required: true
- label: I have run database migrations (where applicable).
required: true
- label: I have attached screenshots and/or videos of the issue (where applicable)
required: true
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow this project's Code of Conduct
required: true
@@ -0,0 +1,38 @@
name: Feature Request
description: Request a new feature.
title: "[Feature]: "
projects: ["grokability/snipe-it"]
type: feature
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request! Please make sure to search the existing issues in this repository to see if your feature has already been requested, and feel free to add your context to any existing requests.
**Please write your issue in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
- type: input
id: version
attributes:
label: Snipe-IT Version
description: What version of Snipe-IT are you currently running? You can find the version number in the footer of any page in Snipe-IT.
placeholder: ex. v8.3.1 - build 19577 (master)
validations:
required: true
- type: textarea
id: feature-description
attributes:
label: How can we help?
description: Let us know in detail what feature you'd like to see added. While we can't promise to implement every feature request, we do read every one and take them into consideration when planning future releases.
placeholder: Tell us what you'd like to see in Snipe-IT! (Be nice!)
validations:
required: true
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow this project's Code of Conduct
required: true
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
issues: write
# pull-requests: write
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
debug-only: true
ascending: true
+1 -1
View File
@@ -52,7 +52,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=swift2512 "Code") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
+20 -3
View File
@@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Helpers\Helper;
use App\Mail\ExpiringAssetsMail;
use App\Mail\ExpiringLicenseMail;
use App\Models\Asset;
@@ -52,19 +53,35 @@ class SendExpirationAlerts extends Command
->filter(fn($item) => !empty($item))
->all();
// Expiring Assets
$assets = Asset::getExpiringWarrantee($alert_interval);
$assets = Asset::getExpiringWarrantyOrEol($alert_interval);
if ($assets->count() > 0) {
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
$this->table(
['ID', 'Tag', 'Model', 'Model Number', 'EOL', 'EOL Months', 'Warranty Expires', 'Warranty Months'],
$assets->map(fn($item) => ['ID' => $item->id, 'Tag' => $item->asset_tag, 'Model' => $item->model->name, 'Model Number' => $item->model->model_number, 'EOL' => $item->asset_eol_date, 'EOL Months' => $item->model->eol, 'Warranty Expires' => $item->warranty_expires, 'Warranty Months' => $item->warranty_months])
);
}
// Expiring licenses
$licenses = License::getExpiringLicenses($alert_interval);
if ($licenses->count() > 0) {
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
$this->table(
['ID', 'Name', 'Expires', 'Termination Date'],
$licenses->map(fn($item) => ['ID' => $item->id, 'Name' => $item->name, 'Expires' => $item->expiration_date, 'Termination Date' => $item->termination_date])
);
}
// Send a message even if the count is 0
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
} else {
if ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
+30 -2
View File
@@ -95,7 +95,7 @@ class Helper
$Parsedown->setSafeMode(true);
if ($str) {
return $Parsedown->text($str);
return $Parsedown->text(strip_tags($str));
}
}
@@ -105,7 +105,7 @@ class Helper
$Parsedown->setSafeMode(true);
if ($str) {
return $Parsedown->line($str);
return $Parsedown->line(strip_tags($str));
}
}
@@ -435,6 +435,34 @@ class Helper
return $colors[$index];
}
/**
* Check if a string has any RTL characters
* @param $value
* @return bool
*/
public static function hasRtl($string) {
$rtlChar = '/[\x{0590}-\x{083F}]|[\x{08A0}-\x{08FF}]|[\x{FB1D}-\x{FDFF}]|[\x{FE70}-\x{FEFF}]/u';
return preg_match($rtlChar, $string) != 0;
}
// is chinese, japanese or korean language
public static function isCjk($string) {
return Helper::isChinese($string) || Helper::isJapanese($string) || Helper::isKorean($string);
}
public static function isChinese($string) {
return preg_match("/\p{Han}+/u", $string);
}
public static function isJapanese($string) {
return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $string);
}
public static function isKorean($string) {
return preg_match('/[\x{3130}-\x{318F}\x{AC00}-\x{D7AF}]/u', $string);
}
/**
* Increases or decreases the brightness of a color by a percentage of the current brightness.
*
@@ -8,33 +8,23 @@ use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Http\Controllers\Controller;
use App\Mail\CheckoutAcceptanceResponseMail;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
use App\Models\Contracts\Acceptable;
use App\Models\Setting;
use App\Models\User;
use App\Models\AssetModel;
use App\Models\Accessory;
use App\Models\License;
use App\Models\Component;
use App\Models\Consumable;
use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use App\Http\Controllers\SettingsController;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use App\Helpers\Helper;
class AcceptanceController extends Controller
{
@@ -85,6 +75,11 @@ class AcceptanceController extends Controller
public function store(Request $request, $id) : RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
$assigned_user = User::find($acceptance->assigned_to_id);
$settings = Setting::getSettings();
$path_logo = '';
$sig_filename='';
if (is_null($acceptance)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -107,140 +102,75 @@ class AcceptanceController extends Controller
}
/**
* Get the signature and save it
* Check for the signature directory
*/
if (! Storage::exists('private_uploads/signatures')) {
Storage::makeDirectory('private_uploads/signatures', 775);
}
/**
* Check for the eula-pdfs directory
*/
if (! Storage::exists('private_uploads/eula-pdfs')) {
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
}
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
$display_model = '';
$pdf_view_route = '';
$pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf';
$sig_filename='';
// If signatures are required, make sure we have one
if (Setting::getSettings()->require_accept_signature == '1') {
// The item was accepted, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
} else {
return redirect()->back()->with('error', trans('general.shitty_browser'));
}
}
// Get the data array ready for the notifications and PDF generation
$data = [
'item_tag' => $item->asset_tag,
'item_name' => $item->name, // this handles licenses seats, which don't have a 'name' field
'item_model' => $item->model?->name,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(),
'note' => $request->input('note'),
'check_out_date' => Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false),
'accepted_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'declined_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'assigned_to' => $assigned_user->display_name,
'site_name' => $settings->site_name,
'company_name' => $item->company?->name?? $settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'logo' => ($settings->acceptance_pdf_logo) ? public_path() . '/uploads/' . $settings->acceptance_pdf_logo : null,
'date_settings' => $settings->date_display_format,
'admin' => auth()->user()->present()?->fullName,
'qty' => $acceptance->qty ?? 1,
];
if ($request->input('asset_acceptance') == 'accepted') {
/**
* Check for the eula-pdfs directory
*/
if (! Storage::exists('private_uploads/eula-pdfs')) {
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
}
if (Setting::getSettings()->require_accept_signature == '1') {
// Check if the signature directory exists, if not create it
if (!Storage::exists('private_uploads/signatures')) {
Storage::makeDirectory('private_uploads/signatures', 775);
}
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
// The item was accepted, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
} else {
return redirect()->back()->with('error', trans('general.shitty_browser'));
}
}
$assigned_user = User::find($acceptance->assigned_to_id);
// this is horrible
switch($acceptance->checkoutable_type){
case 'App\Models\Asset':
$pdf_view_route ='account.accept.accept-asset-eula';
$asset_model = AssetModel::find($item->model_id);
if (!$asset_model) {
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}
$display_model = $asset_model->name;
break;
case 'App\Models\Accessory':
$pdf_view_route ='account.accept.accept-accessory-eula';
$accessory = Accessory::find($item->id);
$display_model = $accessory->name;
break;
case 'App\Models\LicenseSeat':
$pdf_view_route ='account.accept.accept-license-eula';
$license = License::find($item->license_id);
$display_model = $license->name;
break;
case 'App\Models\Component':
$pdf_view_route ='account.accept.accept-component-eula';
$component = Component::find($item->id);
$display_model = $component->name;
break;
case 'App\Models\Consumable':
$pdf_view_route ='account.accept.accept-consumable-eula';
$consumable = Consumable::find($item->id);
$display_model = $consumable->name;
break;
}
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
// $pdf_view_route ='account.accept.accept-asset-eula';
// $asset_model = AssetModel::find($item->model_id);
// $display_model = $asset_model->name;
// $assigned_to = User::find($item->assigned_to)->present()->fullName;
//
// } elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
// $pdf_view_route ='account.accept.accept-accessory-eula';
// $accessory = Accessory::find($item->id);
// $display_model = $accessory->name;
// $assigned_to = User::find($item->assignedTo);
//
// }
/**
* Gather the data for the PDF. We fire this whether there is a signature required or not,
* since we want the moment-in-time proof of what the EULA was when they accepted it.
*/
$branding_settings = SettingsController::getPDFBranding();
$path_logo = "";
// Check for the PDF logo path and use that, otherwise use the regular logo path
if (!is_null($branding_settings->acceptance_pdf_logo)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
} elseif (!is_null($branding_settings->logo)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
}
$data = [
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(),
'note' => $request->input('note'),
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
'assigned_to' => $assigned_user->present()->fullName,
'company_name' => $branding_settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'logo' => $path_logo,
'date_settings' => $branding_settings->date_display_format,
'admin' => auth()->user()->present()?->fullName,
];
if ($pdf_view_route!='') {
Log::debug($pdf_filename.' is the filename, and the route was specified.');
$pdf = Pdf::loadView($pdf_view_route, $data);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
}
// Generate the PDF content
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
// Log the acceptance
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
// Send the PDF to the signing user
@@ -248,9 +178,8 @@ class AcceptanceController extends Controller
// Add the attachment for the signing user into the $data array
$data['file'] = $pdf_filename;
$locale = $assigned_user->locale;
try {
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($locale));
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($assigned_user->locale));
} catch (\Exception $e) {
Log::warning($e);
}
@@ -264,95 +193,21 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.accepted');
// Item was declined
} else {
/**
* Check for the eula-pdfs directory
*/
if (! Storage::exists('private_uploads/eula-pdfs')) {
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
for ($i = 0; $i < ($acceptance->qty ?? 1); $i++) {
$acceptance->decline($sig_filename, $request->input('note'));
}
if (Setting::getSettings()->require_accept_signature == '1') {
// Check if the signature directory exists, if not create it
if (!Storage::exists('private_uploads/signatures')) {
Storage::makeDirectory('private_uploads/signatures', 775);
}
// The item was accepted, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
} else {
return redirect()->back()->with('error', trans('general.shitty_browser'));
}
}
// Format the data to send the declined notification
$branding_settings = SettingsController::getPDFBranding();
// This is the most horriblest
switch($acceptance->checkoutable_type){
case 'App\Models\Asset':
$asset_model = AssetModel::find($item->model_id);
$display_model = $asset_model->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Accessory':
$accessory = Accessory::find($item->id);
$display_model = $accessory->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\LicenseSeat':
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Component':
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Consumable':
$consumable = Consumable::find($item->id);
$display_model = $consumable->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
}
$data = [
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'note' => $request->input('note'),
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name,
'date_settings' => $branding_settings->date_display_format,
];
if ($pdf_view_route!='') {
Log::debug($pdf_filename.' is the filename, and the route was specified.');
$pdf = Pdf::loadView($pdf_view_route, $data);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
}
$acceptance->decline($sig_filename, $request->input('note'));
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
Log::debug('New event acceptance.');
event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined');
}
// Send an email notification if one is requested
if ($acceptance->alert_on_response_id) {
try {
$recipient = User::find($acceptance->alert_on_response_id);
@@ -371,9 +226,10 @@ class AcceptanceController extends Controller
Log::warning($e);
}
}
return redirect()->to('account/accept')->with('success', $return_msg);
}
}
+24 -10
View File
@@ -3,11 +3,13 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use \Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use \Illuminate\Http\Response;
class ActionlogController extends Controller
{
public function displaySig($filename) : RedirectResponse | Response | bool
@@ -39,17 +41,29 @@ class ActionlogController extends Controller
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
{
$this->authorize('view', \App\Models\Asset::class);
if (config('filesystems.default') == 's3_private') {
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
if ($actionlog = Actionlog::where('filename', $filename)->with('user')->with('target')->firstOrFail()) {
$this->authorize('view', $actionlog->target);
$this->authorize('view', $actionlog->user);
if (config('filesystems.default') == 's3_private') {
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/' . $filename, now()->addMinutes(5)));
}
if (Storage::exists('private_uploads/eula-pdfs/' . $filename)) {
if (request()->input('inline') == 'true') {
return response()->file(config('app.private_uploads') . '/eula-pdfs/' . $filename);
}
return response()->download(config('app.private_uploads') . '/eula-pdfs/' . $filename);
}
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
}
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
}
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
return redirect()->back()->with('error', trans('general.record_not_found'));
}
}
+12 -2
View File
@@ -1290,9 +1290,19 @@ class AssetsController extends Controller
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
{
$this->authorize('view', Asset::class);
$this->authorize('view', $asset);
return [];
// to do
$query = Asset::where([
'assigned_to' => $asset->id,
'assigned_type' => Asset::class,
]);
$total = $query->count();
$assets = $query->applyOffsetAndLimit($total)->get();
return (new AssetsTransformer)->transformAssets($assets, $total);
}
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
@@ -38,6 +38,8 @@ class CategoriesController extends Controller
'consumables_count',
'components_count',
'licenses_count',
'created_at',
'updated_at',
'image',
'notes',
];
@@ -26,6 +26,12 @@ class LicensesController extends Controller
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
if ($request->input('status')=='inactive') {
$licenses->ExpiredLicenses();
} else {
$licenses->ActiveLicenses();
}
if ($request->filled('company_id')) {
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
}
@@ -94,6 +100,8 @@ class LicensesController extends Controller
$licenses->onlyTrashed();
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -37,10 +37,14 @@ class LocationsController extends Controller
'address',
'address2',
'assets_count',
'assets_count',
'assigned_assets_count',
'rtd_assets_count',
'accessories_count',
'assigned_accessories_count',
'assigned_assets_count',
'assigned_assets_count',
'components_count',
'consumables_count',
'users_count',
'children_count',
'city',
'country',
'created_at',
@@ -54,7 +58,6 @@ class LocationsController extends Controller
'rtd_assets_count',
'state',
'updated_at',
'users_count',
'zip',
'notes',
];
@@ -79,8 +82,9 @@ class LocationsController extends Controller
'locations.currency',
'locations.company_id',
'locations.notes',
'locations.created_by',
'locations.deleted_at',
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
@@ -88,6 +92,8 @@ class LocationsController extends Controller
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withCount('consumables as consumables_count')
->withCount('components as components_count')
->with('adminuser');
// Only scope locations if the setting is enabled
@@ -131,6 +137,14 @@ class LocationsController extends Controller
$locations->where('locations.company_id', '=', $request->input('company_id'));
}
if ($request->filled('parent_id')) {
$locations->where('locations.parent_id', '=', $request->input('parent_id'));
}
if ($request->input('status') == 'deleted') {
$locations->onlyTrashed();
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -224,8 +238,13 @@ class LocationsController extends Controller
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
->withCount('accessories as accessories_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withCount('consumables as consumables_count')
->withCount('components as components_count')
->findOrFail($id);
return (new LocationsTransformer)->transformLocation($location);
@@ -320,11 +339,15 @@ class LocationsController extends Controller
{
$this->authorize('delete', Location::class);
$location = Location::withCount('assignedAssets as assigned_assets_count')
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
->withCount('accessories as accessories_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withCount('accessories as accessories_count')
->withCount('consumables as consumables_count')
->withCount('components as components_count')
->findOrFail($id);
if (! $location->isDeletable()) {
@@ -647,6 +647,15 @@ class BulkAssetsController extends Controller
$assets = Asset::findOrFail($asset_ids);
// Prevent checking out assets that are already checked out
if ($assets->pluck('assigned_to')->unique()->filter()->isNotEmpty()) {
// re-add the asset ids so the assets select is re-populated
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect(route('hardware.bulkcheckout.show'))
->with('error', trans('general.error_assets_already_checked_out'));
}
if (request('checkout_to_type') == 'asset') {
foreach ($asset_ids as $asset_id) {
if ($target->id == $asset_id) {
@@ -102,13 +102,15 @@ class ComponentCheckoutController extends Controller
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
}
$component->checkout_qty = $request->input('assigned_qty');
// Update the component data
$component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'created_by' => auth()->user()->id,
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => $request->input('assigned_qty'),
'assigned_qty' => $component->checkout_qty,
'asset_id' => $request->input('asset_id'),
'note' => $request->input('note'),
]);
@@ -25,7 +25,7 @@ class LicenseCheckoutController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param $id
* @return \Illuminate\Contracts\View\View
* @return \Illuminate\Contracts\View\View |\Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(License $license)
@@ -39,6 +39,11 @@ class LicenseCheckoutController extends Controller
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
}
// Make sure the license is expired or terminated
if ($license->isInactive()) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
}
// We don't currently allow checking out licenses to locations, so we'll reset that to user if needed
if (session()->get('checkout_to_type') == 'location') {
session()->put(['checkout_to_type' => 'user']);
@@ -70,8 +75,19 @@ class LicenseCheckoutController extends Controller
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$this->authorize('checkout', $license);
// Make sure there is at least one available to checkout
if ($license->availCount()->count() < 1) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
}
// Make sure the license is expired or terminated
if ($license->isInactive()) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
}
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
$licenseSeat->created_by = auth()->id();
$licenseSeat->notes = $request->input('notes');
@@ -114,6 +130,7 @@ class LicenseCheckoutController extends Controller
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
}
if (! $licenseSeat->license->is($license)) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
}
@@ -256,6 +256,9 @@ class LicensesController extends Controller
else {
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
}
if($license->isInactive()){
session()->flash('warning', (trans('admin/licenses/message.checkout.license_is_inactive')));
}
$this->authorize('view', $license);
return view('licenses.view', compact('license'))
+60 -49
View File
@@ -189,30 +189,36 @@ class LocationsController extends Controller
{
$this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) {
$location = Location::withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
->withCount('accessories as accessories_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withCount('consumables as consumables_count')
->withCount('components as components_count')
->find($locationId);
if (!$location) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
}
if ($location->users()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
} elseif ($location->children()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_child_loc'));
} elseif ($location->assets()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
} elseif ($location->assignedassets()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
}
if ($location->isDeletable()) {
if ($location->image) {
try {
Storage::disk('public')->delete('locations/'.$location->image);
} catch (\Exception $e) {
Log::error($e);
if ($location->image) {
try {
Storage::disk('public')->delete('locations/'.$location->image);
} catch (\Exception $e) {
Log::error($e);
}
}
$location->delete();
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
} else {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
}
$location->delete();
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
}
/**
@@ -247,23 +253,41 @@ class LocationsController extends Controller
$this->authorize('view', Location::class);
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('assigned', false)
->with('assets', $location->assets)
->with('assignedAssets', $location->assignedAssets)
->with('accessories', $location->accessories)
->with('assignedAccessories', $location->assignedAccessories)
->with('users',$location->users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
->with('consumables', $location->consumables)
->with('components', $location->components)
->with('children', $location->children);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
public function print_all_assigned($id) : View | RedirectResponse
{
$this->authorize('view', Location::class);
if ($location = Location::where('id', $id)->first()) {
return view('locations/print')
->with('assigned', true)
->with('assets', $location->assets)
->with('assignedAssets', $location->assignedAssets)
->with('accessories', $location->accessories)
->with('assignedAccessories', $location->assignedAccessories)
->with('users',$location->users)
->with('location', $location)
->with('consumables', $location->consumables)
->with('components', $location->components)
->with('children', $location->children);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
/**
* Returns a view that presents a form to clone a location.
@@ -321,33 +345,12 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
}
// Check validation
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
}
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}
public function print_all_assigned($id) : View | RedirectResponse
{
$this->authorize('view', Location::class);
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
/**
* Returns a view that allows the user to bulk delete locations
@@ -366,8 +369,12 @@ class LocationsController extends Controller
$locations = Location::whereIn('id', $locations_raw_array)
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
->withCount('accessories as accessories_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('consumables as consumables_count')
->withCount('components as components_count')
->withCount('users as users_count')->get();
$valid_count = 0;
@@ -400,9 +407,13 @@ class LocationsController extends Controller
$locations = Location::whereIn('id', $locations_raw_array)
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
->withCount('accessories as accessories_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')->get();
->withCount('users as users_count')
->withCount('consumables as consumables_count')
->withCount('components as components_count')->get();
$success_count = 0;
$error_count = 0;
+5 -9
View File
@@ -274,22 +274,18 @@ class ReportsController extends Controller
$target_name = '';
if ($actionlog->target) {
if ($actionlog->targetType() == 'user') {
$target_name = $actionlog->target->display_name;
} else {
$target_name = $actionlog->target->getDisplayNameAttribute();
}
$target_name = $actionlog->target->display_name;
}
if($actionlog->item){
$item_name = e($actionlog->item->getDisplayNameAttribute());
if ($actionlog->item){
$item_name = e($actionlog->item->display_name);
} else {
$item_name = '';
}
$row = [
$actionlog->created_at,
($actionlog->adminuser) ? e($actionlog->adminuser->display_name) : '',
($actionlog->adminuser) ? $actionlog->adminuser->display_name : '',
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
@@ -298,10 +294,10 @@ class ReportsController extends Controller
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
$target_name,
($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta,
$actionlog->remote_ip,
$actionlog->user_agent,
$actionlog->action_source,
$actionlog->log_meta,
];
fputcsv($handle, $row);
}
+1 -4
View File
@@ -34,10 +34,7 @@ class AssetCountForSidebar
}
try {
$total_assets = Asset::count();
if ($settings->show_archived_in_list != '1') {
$total_assets -= Asset::Archived()->count();
}
$total_assets = Asset::AssetsForShow()->count();
view()->share('total_assets', $total_assets);
} catch (\Exception $e) {
Log::debug($e);
@@ -36,6 +36,7 @@ class AccessoriesTransformer
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
'total_cost' => Helper::formatCurrencyOutput($accessory->totalCostSum()),
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter
'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
@@ -147,7 +147,7 @@ class ActionlogsTransformer
[
'url' => $actionlog->uploads_file_url(),
'filename' => $actionlog->filename,
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_url()),
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_path()),
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
] : null,
@@ -155,7 +155,7 @@ class ActionlogsTransformer
'id' => (int) $actionlog->item->id,
'name' => e($actionlog->item->display_name) ?? null,
'type' => e($actionlog->itemType()),
'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null
'serial' => e($actionlog->item->serial) ? e($actionlog->item->serial) : null
] : null,
'location' => ($actionlog->location) ? [
'id' => (int) $actionlog->location->id,
@@ -168,7 +168,7 @@ class ActionlogsTransformer
'action_type' => $actionlog->present()->actionType(),
'admin' => ($actionlog->adminuser) ? [
'id' => (int) $actionlog->adminuser->id,
'name' => e($actionlog->adminuser->display_name),
'name' => e($actionlog->adminuser->display_name) ?? null,
'first_name'=> e($actionlog->adminuser->first_name),
'last_name'=> e($actionlog->adminuser->last_name)
] : null,
@@ -180,7 +180,7 @@ class ActionlogsTransformer
] : null,
'target' => ($actionlog->target) ? [
'id' => (int) $actionlog->target->id,
'name' => ($actionlog->target->display_name) ?? null,
'name' => e($actionlog->target->display_name) ?? null,
'type' => e($actionlog->targetType()),
] : null,
@@ -43,6 +43,7 @@ class ComponentsTransformer
'order_number' => e($component->order_number),
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
'total_cost' => Helper::formatCurrencyOutput($component->totalCostSum()),
'remaining' => (int) $component->numRemaining(),
'company' => ($component->company) ? [
'id' => (int) $component->company->id,
@@ -37,6 +37,7 @@ class ConsumablesTransformer
'remaining' => $consumable->numRemaining(),
'order_number' => e($consumable->order_number),
'purchase_cost' => Helper::formatCurrencyOutput($consumable->purchase_cost),
'total_cost' => Helper::formatCurrencyOutput($consumable->totalCostSum()),
'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'),
'qty' => (int) $consumable->qty,
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
@@ -51,7 +51,7 @@ class LicenseSeatsTransformer
'reassignable' => (bool) $seat->license->reassignable,
'notes' => e($seat->notes),
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
'disabled' => $seat->unreassignable_seat,
'disabled' => $seat->unreassignable_seat || $seat->license->isInactive(),
];
$permissions_array['available_actions'] = [
@@ -31,11 +31,11 @@ class LicensesTransformer
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost),
'purchase_cost_numeric' => $license->purchase_cost,
'notes' => Helper::parseEscapedMarkedownInline($license->notes),
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
'seats' => (int) $license->seats,
'free_seats_count' => (int) $license->free_seats_count - License::unReassignableCount($license),
'remaining' => (int) $license->free_seats_count,
@@ -54,7 +54,7 @@ class LicensesTransformer
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
'disabled' => $license->isInactive(),
];
$permissions_array['available_actions'] = [
@@ -53,6 +53,9 @@ class LocationsTransformer
'assets_count' => (int) $location->assets_count,
'rtd_assets_count' => (int) $location->rtd_assets_count,
'users_count' => (int) $location->users_count,
'consumables_count' => (int) $location->consumables_count,
'components_count' => (int) $location->components_count,
'children_count' => (int) $location->children_count,
'currency' => ($location->currency) ? e($location->currency) : null,
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
'notes' => Helper::parseEscapedMarkedownInline($location->notes),
@@ -76,12 +79,13 @@ class LocationsTransformer
];
$permissions_array['available_actions'] = [
'update' => Gate::allows('update', Location::class) ? true : false,
'update' => (Gate::allows('update', Location::class) && ($location->deleted_at == '')),
'delete' => $location->isDeletable(),
'bulk_selectable' => [
'delete' => $location->isDeletable()
],
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
'restore' => (Gate::allows('create', Location::class) && ($location->deleted_at != '')),
];
$array += $permissions_array;
+37 -11
View File
@@ -96,8 +96,8 @@ class CheckoutableListener
if (!empty($to)) {
try {
Mail::to(array_flatten($to))->send($mailable->locale($notifiable->locale));
Mail::to(array_flatten($cc))->send($mailable->locale(Setting::getSettings()->locale));
$toMail = (clone $mailable)->locale($notifiable->locale);
Mail::to(array_flatten($to))->send($toMail);
Log::info('Checkout Mail sent to checkout target');
} catch (ClientException $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
@@ -105,6 +105,16 @@ class CheckoutableListener
Log::debug("Exception caught during checkout email: " . $e->getMessage());
}
}
if (!empty($cc)) {
try {
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
Mail::to(array_flatten($cc))->send($ccMail);
} catch (ClientException $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
}
}
}
if ($shouldSendWebhookNotification) {
@@ -179,16 +189,26 @@ class CheckoutableListener
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
try {
if (!empty($to)) {
Mail::to(array_flatten($to))->send($mailable->locale($notifiable->locale));
Mail::to(array_flatten($cc))->send($mailable->locale(Setting::getSettings()->locale));
Log::info('Checkin Mail sent to CC addresses');
if (!empty($to)) {
try {
$toMail = (clone $mailable)->locale($notifiable->locale);
Mail::to(array_flatten($to))->send($toMail);
Log::info('Checkin Mail sent to checkin target');
} catch (ClientException $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
}
}
if (!empty($cc)) {
try {
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
Mail::to(array_flatten($cc))->send($ccMail);
} catch (ClientException $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
}
}
@@ -242,6 +262,12 @@ class CheckoutableListener
$acceptance->checkoutable()->associate($event->checkoutable);
$acceptance->assignedTo()->associate($event->checkedOutTo);
$acceptance->qty = 1;
if (isset($event->checkoutable->checkout_qty)) {
$acceptance->qty = $event->checkoutable->checkout_qty;
}
$category = $this->getCategoryFromCheckoutable($event->checkoutable);
if ($category?->alert_on_response) {
+6 -4
View File
@@ -43,7 +43,7 @@ class CheckoutAccessoryMail extends Mailable
return new Envelope(
from: $from,
subject: trans('mail.Accessory_Checkout_Notification'),
subject: trans_choice('mail.Accessory_Checkout_Notification', $this->checkout_qty),
);
}
@@ -83,17 +83,19 @@ class CheckoutAccessoryMail extends Mailable
],
);
}
private function introductionLine(): string
{
if ($this->target instanceof Location) {
return trans('mail.new_item_checked_location', ['location' => $this->target->name ]);
return trans_choice('mail.new_item_checked_location', $this->checkout_qty, ['location' => $this->target->name]);
}
if ($this->requiresAcceptance()) {
return trans('mail.new_item_checked_with_acceptance');
return trans_choice('mail.new_item_checked_with_acceptance', $this->checkout_qty);
}
if (!$this->requiresAcceptance()) {
return trans('mail.new_item_checked');
return trans_choice('mail.new_item_checked', $this->checkout_qty);
}
// we shouldn't get here but let's send a default message just in case
+4 -3
View File
@@ -138,14 +138,15 @@ class CheckoutAssetMail extends Mailable
private function introductionLine(): string
{
if ($this->firstTimeSending && $this->target instanceof Location) {
return trans('mail.new_item_checked_location', ['location' => $this->target->name ]);
return trans_choice('mail.new_item_checked_location', 1, ['location' => $this->target->name]);
}
if ($this->firstTimeSending && $this->requiresAcceptance()) {
return trans('mail.new_item_checked_with_acceptance');
return trans_choice('mail.new_item_checked_with_acceptance', 1);
}
if ($this->firstTimeSending && !$this->requiresAcceptance()) {
return trans('mail.new_item_checked');
return trans_choice('mail.new_item_checked', 1);
}
if (!$this->firstTimeSending && $this->requiresAcceptance()) {
+1 -1
View File
@@ -26,7 +26,7 @@ class CheckoutComponentMail extends Mailable
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$this->qty = $component->assets->first()?->pivot?->assigned_qty;
$this->qty = $component->checkout_qty;
$this->settings = Setting::getSettings();
}
+4 -21
View File
@@ -309,27 +309,6 @@ class Accessory extends SnipeModel
return $this->category->require_acceptance ?? false;
}
/**
* Checks for a category-specific EULA, and if that doesn't exist,
* checks for a settings level EULA
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return string
*/
public function getEula()
{
if ($this->category->eula_text) {
return Helper::parseEscapedMarkedown($this->category->eula_text);
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
}
return null;
}
/**
* Check how many items within an accessory are checked out
*
@@ -378,6 +357,10 @@ class Accessory extends SnipeModel
$accessory_checkout->limit(1)->delete();
}
public function totalCostSum() {
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
}
/**
* -----------------------------------------------
+43 -40
View File
@@ -161,7 +161,6 @@ class Asset extends Depreciable
'eol_explicit',
'last_audit_date',
'next_audit_date',
'asset_eol_date',
'last_checkin',
'last_checkout',
];
@@ -824,21 +823,26 @@ class Asset extends Depreciable
* @since [v2.0]
* @return mixed
*/
public static function getExpiringWarrantee($days = 30)
public static function getExpiringWarrantyOrEol($days = 30)
{
$days = (is_null($days)) ? 30 : $days;
return self::where('archived', '=', '0') // this can stay for right now, as `archived` defaults to 0 at the db level, but should probably be replaced with assetstatus->archived?
->whereNotNull('warranty_months')
->whereNotNull('purchase_date')
->whereNull('deleted_at')
return self::where('archived', '=', '0')
->NotArchived()
->whereRaw(
'DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) <= DATE_ADD(NOW(), INTERVAL '
. $days
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()'
)
->orderByRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH)')
->whereNull('deleted_at')
->where(function ($query) use ($days) {
// Check for manual asset EOL first
$query->where(function ($query) use ($days) {
$query->whereNotNull('asset_eol_date')
->whereBetween('asset_eol_date', [Carbon::now(), Carbon::now()->addDays($days)]);
// Otherwise use the warranty months + purchase date + threshold
})->orWhere(function ($query) use ($days) {
$query->whereNotNull('purchase_date')
->whereNotNull('warranty_months')
->whereBetween('purchase_date', [Carbon::now(), Carbon::now()->addMonths('assets.warranty_months')->addDays($days)]);
});
})
->orderBy('asset_eol_date', 'ASC')
->orderBy('purchase_date', 'ASC')
->get();
}
@@ -1019,31 +1023,6 @@ class Asset extends Depreciable
return false;
}
/**
* Checks for a category-specific EULA, and if that doesn't exist,
* checks for a settings level EULA
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return string | false
*/
public function getEula()
{
if (($this->model) && ($this->model->category)) {
if (($this->model->category->eula_text) && ($this->model->category->use_default_eula == 0)) {
return Helper::parseEscapedMarkedown($this->model->category->eula_text);
} elseif ($this->model->category->use_default_eula == 1) {
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
} else {
return false;
}
}
return false;
}
public function getComponentCost()
{
$cost = 0;
@@ -1893,6 +1872,30 @@ class Asset extends Depreciable
);
}
if ($fieldname == 'jobtitle') {
$query->where(function ($query) use ($search_val) {
if (is_array($search_val)) {
$query->whereHasMorph(
'assignedTo',
[User::class],
function ($query) use ($search_val) {
$query->whereIn('users.jobtitle', $search_val);
}
);
} else {
$query->whereHasMorph(
'assignedTo',
[User::class],
function ($query) use ($search_val) {
$query->where(function ($query) use ($search_val) {
$query->where('users.jobtitle', 'LIKE', '%' . $search_val . '%');
});
}
);
}
});
}
/**
* THIS CLUNKY BIT IS VERY IMPORTANT
@@ -1917,7 +1920,7 @@ class Asset extends Depreciable
*/
if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier')
&& ($fieldname!='status_label') && ($fieldname!='assigned_to') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')
&& ($fieldname!='status_label') && ($fieldname!='assigned_to') && ($fieldname!='model') && ($fieldname!='jobtitle') && ($fieldname!='company') && ($fieldname!='manufacturer')
) {
$query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
}
+105 -3
View File
@@ -2,11 +2,14 @@
namespace App\Models;
use App\Helpers\Helper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Notifications\Notifiable;
use TCPDF;
class CheckoutAcceptance extends Model
{
@@ -129,8 +132,7 @@ class CheckoutAcceptance extends Model
/**
* Filter checkout acceptences by the user
*
* @param Illuminate\Database\Eloquent\Builder $query
* @param User $user
* @param User $user
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForUser(Builder $query, User $user)
@@ -141,11 +143,111 @@ class CheckoutAcceptance extends Model
/**
* Filter to only get pending acceptances
*
* @param Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePending(Builder $query)
{
return $query->whereNull('accepted_at')->whereNull('declined_at');
}
public function scopeDeclined(Builder $query)
{
return $query->whereNull('accepted_at')->whereNotNull('declined_at');
}
protected function displayCheckoutableType(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => strtolower(str_replace('App\Models\\', '', $this->checkoutable_type)),
);
}
public function generateAcceptancePdf($data, $pdf_filename) {
// set some language dependent data:
$lg = Array();
$lg['a_meta_charset'] = 'UTF-8';
$lg['w_page'] = 'page';
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->setRTL(false);
$pdf->setLanguageArray($lg);
$pdf->SetFontSubsetting(true);
$pdf->SetCreator('Snipe-IT Asset Management System');
$pdf->SetAuthor($data['assigned_to']);
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
$pdf->SetSubject('Asset Acceptance: '.$data['item_tag']);
$pdf->SetKeywords('Snipe-IT, assets, acceptance, eula, tos');
$pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->SetPrintHeader(false);
$pdf->SetPrintFooter(false);
$pdf->AddPage();
if ($data['logo'] != null) {
$pdf->writeHTML('<img src="'.$data['logo'].'">', true, 0, true, 0, '');
} else {
$pdf->writeHTML('<h3>'.$data['site_name'].'</h3><br /><br />', true, 0, true, 0, 'C');
}
$pdf->Ln();
$pdf->writeHTML(trans('general.date') . ': ' . Helper::getFormattedDateObject(now(), 'datetime', false), true, 0, true, 0, '');
if ($data['company_name'] != null) {
$pdf->writeHTML(trans('general.company') . ': ' . e($data['company_name']), true, 0, true, 0, '');
}
if ($data['item_tag'] != null) {
$pdf->writeHTML(trans('general.asset_tag') . ': ' . e($data['item_tag']), true, 0, true, 0, '');
}
if ($data['item_name'] != null) {
$pdf->writeHTML(trans('general.name') . ': ' . e($data['item_name']), true, 0, true, 0, '');
}
if ($data['item_model'] != null) {
$pdf->writeHTML(trans('general.asset_model') . ': ' . e($data['item_model']), true, 0, true, 0, '');
}
if ($data['item_serial'] != null) {
$pdf->writeHTML(trans('admin/hardware/form.serial').': '.e($data['item_serial']), true, 0, true, 0, '');
}
if (($data['qty'] != null) && ($data['qty'] > 1)) {
$pdf->writeHTML(trans('general.qty').': '.e($data['qty']), true, 0, true, 0, '');
}
$pdf->writeHTML(trans('general.assignee').': '.e($data['assigned_to']), true, 0, true, 0, '');
$pdf->Ln();
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
// Break the EULA into lines based on newlines, and check each line for RTL or CJK characters
$eula_lines = preg_split("/\r\n|\n|\r/", $data['eula']);
foreach ($eula_lines as $eula_line) {
Helper::hasRtl($eula_line) ? $pdf->setRTL(true) : $pdf->setRTL(false);
Helper::isCjk($eula_line) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(Helper::parseEscapedMarkedown($eula_line), true, 0, true, 0, '');
}
$pdf->Ln();
$pdf->Ln();
$pdf->setRTL(false);
$pdf->Ln();
if ($data['signature'] != null) {
$pdf->writeHTML('<img src="'.$data['signature'].'">', true, 0, true, 0, '');
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
$pdf->writeHTML(e($data['assigned_to']), true, 0, true, 0, 'C');
$pdf->Ln();
}
if ($data['note'] != null) {
Helper::isCjk($data['note']) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(trans('general.notes') . ': ' . e($data['note']), true, 0, true, 0, '');
$pdf->Ln();
}
$pdf->writeHTML(trans('general.assigned_date').': '.e($data['check_out_date']), true, 0, true, 0, '');
$pdf->writeHTML(trans('general.accepted_date').': '.e($data['accepted_date']), true, 0, true, 0, '');
return $pdf->Output($pdf_filename, 'S');
}
}
+3 -18
View File
@@ -217,24 +217,6 @@ class Component extends SnipeModel
return $this->category->require_acceptance;
}
/**
* Checks for a category-specific EULA, and if that doesn't exist,
* checks for a settings level EULA
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return string | false
*/
public function getEula()
{
if ($this->category->eula_text) {
return Helper::parseEscapedMarkedown($this->category->eula_text);
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
} else {
return null;
}
}
/**
* Establishes the component -> action logs relationship
@@ -306,7 +288,10 @@ class Component extends SnipeModel
return $this->qty - $this->numCheckedOut();
}
public function totalCostSum() {
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
}
/**
* -----------------------------------------------
* BEGIN MUTATORS
+3 -19
View File
@@ -285,25 +285,6 @@ class Consumable extends SnipeModel
return $this->category->require_acceptance;
}
/**
* Checks for a category-specific EULA, and if that doesn't exist,
* checks for a settings level EULA
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return string | false
*/
public function getEula()
{
if ($this->category->eula_text) {
return Helper::parseEscapedMarkedown($this->category->eula_text);
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
} else {
return null;
}
}
/**
* Check how many items within a consumable are checked out
*
@@ -331,7 +312,10 @@ class Consumable extends SnipeModel
return $remaining;
}
public function totalCostSum() {
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
}
/**
* -----------------------------------------------
* BEGIN MUTATORS
+7 -5
View File
@@ -78,6 +78,13 @@ class Ldap extends Model
if (env('LDAPTLS_CACERT')) {
putenv('LDAPTLS_CACERT='.env('LDAPTLS_CACERT'));
}
// You _were_ allowed to do this *after* the ldap_connect() in some versions of PHP, but it's not how they want
// you to anymore, and it seems to not work at all in later PHP versions.
if (Setting::getSettings()->ldap_client_tls_cert && Setting::getSettings()->ldap_client_tls_key) {
ldap_set_option(null, LDAP_OPT_X_TLS_CERTFILE, Setting::get_client_side_cert_path());
ldap_set_option(null, LDAP_OPT_X_TLS_KEYFILE, Setting::get_client_side_key_path());
}
$connection = @ldap_connect($ldap_host);
if (! $connection) {
@@ -89,11 +96,6 @@ class Ldap extends Model
ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, $ldap_version);
ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, 20);
if (Setting::getSettings()->ldap_client_tls_cert && Setting::getSettings()->ldap_client_tls_key) {
ldap_set_option(null, LDAP_OPT_X_TLS_CERTFILE, Setting::get_client_side_cert_path());
ldap_set_option(null, LDAP_OPT_X_TLS_KEYFILE, Setting::get_client_side_key_path());
}
if ($ldap_use_tls=='1') {
ldap_start_tls($connection);
}
+95 -29
View File
@@ -14,6 +14,7 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Watson\Validating\ValidatingTrait;
class License extends Depreciable
{
use HasFactory;
@@ -296,6 +297,38 @@ class License extends Depreciable
}
$this->attributes['termination_date'] = $value;
}
public function isInactive(): bool
{
$day = now()->startOfDay();
$expired = $this->expiration_date && $this->asDateTime($this->expiration_date)->startofDay()->lessThanOrEqualTo($day);
$terminated = $this->termination_date && $this->asDateTime($this->termination_date)->startofDay()->lessThanOrEqualTo($day);
return $this->isExpired() || $this->isTerminated();
}
public function isExpired(): bool
{
$day = now()->startOfDay();
$expired = $this->expiration_date && $this->asDateTime($this->expiration_date)->startofDay()->lessThanOrEqualTo($day);
return $expired;
}
public function isTerminated(): bool
{
$day = now()->startOfDay();
$terminated = $this->termination_date && $this->asDateTime($this->termination_date)->startofDay()->lessThanOrEqualTo($day);
return $terminated;
}
/**
* Sets free_seat_count attribute
*
@@ -375,27 +408,6 @@ class License extends Depreciable
return false;
}
/**
* Checks for a category-specific EULA, and if that doesn't exist,
* checks for a settings level EULA
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return string | false
*/
public function getEula()
{
if ($this->category) {
if ($this->category->eula_text) {
return Helper::parseEscapedMarkedown($this->category->eula_text);
} elseif ($this->category->use_default_eula == '1') {
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
}
}
return false;
}
/**
* Establishes the license -> assigned user relationship
@@ -596,7 +608,7 @@ class License extends Depreciable
{
$count = 0;
if (!$license->reassignable) {
$count = licenseSeat::query()->where('unreassignable_seat', '=', true)
$count = LicenseSeat::query()->where('unreassignable_seat', '=', true)
->where('license_id', '=', $license->id)
->count();
}
@@ -695,26 +707,80 @@ class License extends Depreciable
}
/**
* Returns expiring licenses
* Returns expiring licenses.
*
* @todo should refactor. I don't like get() in model methods
* This checks if:
*
* 1) The license has not been deleted
* 2) The expiration date is between now and the number of days specified
* 3) There is an expiration date set and the termination date has not passed
* 4) The license termination date is null or has not passed
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v1.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
* @see \App\Console\Commands\SendExpiringLicenseNotifications
*/
public static function getExpiringLicenses($days = 60)
{
$days = (is_null($days)) ? 60 : $days;
return self::whereNotNull('expiration_date')
->whereNull('deleted_at')
->whereRaw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) ')
->where('expiration_date', '>', date('Y-m-d'))
return self::whereNull('licenses.deleted_at')
// The termination date is null or within range
->where(function ($query) use ($days) {
$query->whereNull('termination_date')
->orWhereBetween('termination_date', [Carbon::now(), Carbon::now()->addDays($days)]);
})
->where(function ($query) use ($days) {
$query->whereNotNull('expiration_date')
// Handle expired licenses without termination dates
->where(function ($query) use ($days) {
$query->whereNull('termination_date')
->whereBetween('expiration_date', [Carbon::now(), Carbon::now()->addDays($days)]);
})
// Handle expired licenses with termination dates in the future
->orWhere(function ($query) use ($days) {
$query->whereBetween('termination_date', [Carbon::now(), Carbon::now()->addDays($days)]);
});
})
->orderBy('expiration_date', 'ASC')
->orderBy('termination_date', 'ASC')
->get();
}
public function scopeActiveLicenses($query)
{
return $query->whereNull('licenses.deleted_at')
// The termination date is null or within range
->where(function ($query) {
$query->whereNull('termination_date')
->orWhereDate('termination_date', '>', [Carbon::now()]);
})
->where(function ($query) {
$query->whereNull('expiration_date')
->orWhereDate('expiration_date', '>', [Carbon::now()]);
});
}
public function scopeExpiredLicenses($query)
{
return $query->whereNull('licenses.deleted_at')
// The termination date is null or within range
->where(function ($query) {
$query->whereNull('termination_date')
->orWhereDate('termination_date', '<=', [Carbon::now()]);
})
->orWhere(function ($query) {
$query->whereNull('expiration_date')
->orWhereDate('expiration_date', '<=', [Carbon::now()]);
});
}
/**
* Query builder scope to order on manufacturer
*
+16
View File
@@ -7,6 +7,7 @@ use App\Models\Traits\CompanyableChildTrait;
use App\Notifications\CheckinLicenseNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -64,6 +65,21 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
return $this->license->getEula();
}
protected function name(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => $this->license->name,
);
}
protected function displayName(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => $this->license->name,
);
}
/**
* Establishes the seat -> license relationship
*
+12 -5
View File
@@ -43,6 +43,7 @@ class Location extends SnipeModel
'company_id' => 'integer',
];
/**
* Whether the model should inject its identifier to the unique
* validation rules before attempting validation. If this property
@@ -113,13 +114,19 @@ class Location extends SnipeModel
{
return Gate::allows('delete', $this)
&& ($this->assets_count == 0)
&& ($this->assigned_assets_count == 0)
&& ($this->children_count == 0)
&& ($this->accessories_count == 0)
&& ($this->users_count == 0);
&& ($this->deleted_at == '')
&& (($this->assets_count ?? $this->assets()->count()) === 0)
&& (($this->assigned_assets_count ?? $this->assignedAssets()->count()) === 0)
&& (($this->accessories_count ?? $this->accessories()->count()) === 0)
&& (($this->assigned_accessories_count ?? $this->assignedAccessories()->count()) === 0)
&& (($this->children_count ?? $this->children()->count()) === 0)
&& (($this->components_count ?? $this->components()->count()) === 0)
&& (($this->consumables_count ?? $this->consumables()->count()) === 0)
&& (($this->rtd_assets_count ?? $this->rtd_assets()->count()) === 0)
&& (($this->users_count ?? $this->users()->count()) === 0);
}
/**
* Establishes the user -> location relationship
*
+38
View File
@@ -3,8 +3,10 @@
namespace App\Models;
use App\Helpers\Helper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Request;
class SnipeModel extends Model
{
@@ -156,6 +158,20 @@ class SnipeModel extends Model
$this->attributes['status_id'] = $value;
}
/**
* Applies offset (from request) and limit to query.
*
* @param Builder $query
* @param int $total
* @return void
*/
public function scopeApplyOffsetAndLimit(Builder $query, int $total)
{
$offset = (Request::input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$query->skip($offset)->take($limit);
}
protected function displayName(): Attribute
{
@@ -164,5 +180,27 @@ class SnipeModel extends Model
);
}
public function getEula()
{
// This is - for now - only for assets, where the asset model is the thing tied to the category
if (($this->model) && ($this->model->category)) {
if (($this->model->category->eula_text) && ($this->model->category->use_default_eula == 0)) {
return $this->model->category->eula_text;
} elseif ($this->model->category->use_default_eula == 1) {
return Setting::getSettings()->default_eula_text;
} else {
return false;
}
// For everything else, just check the category for EULA info
} elseif (($this->category) && ($this->category->eula_text)) {
return $this->category->eula_text;
} elseif ((Setting::getSettings()->default_eula_text) && (($this->category) && ($this->category->use_default_eula == '1'))) {
return Setting::getSettings()->default_eula_text;
}
return null;
}
}
@@ -2,6 +2,7 @@
namespace App\Notifications;
use AllowDynamicProperties;
use App\Helpers\Helper;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
@@ -10,7 +11,7 @@ use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class AcceptanceAssetAcceptedNotification extends Notification
#[AllowDynamicProperties] class AcceptanceAssetAcceptedNotification extends Notification
{
use Queueable;
@@ -22,15 +23,18 @@ class AcceptanceAssetAcceptedNotification extends Notification
public function __construct($params)
{
$this->item_tag = $params['item_tag'];
$this->item_name = $params['item_name'];
$this->item_model = $params['item_model'];
$this->item_serial = $params['item_serial'];
$this->item_status = $params['item_status'];
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false);
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'datetime', false);
$this->assigned_to = $params['assigned_to'];
$this->note = $params['note'];
$this->company_name = $params['company_name'];
$this->admin = $params['admin'] ?? null;
$this->settings = Setting::getSettings();
$this->file = $params['file'] ?? null;
$this->qty = $params['qty'] ?? null;
$this->note = $params['note'] ?? null;
$this->admin = $params['admin'] ?? null;
}
@@ -65,6 +69,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
$message = (new MailMessage)->markdown('notifications.markdown.asset-acceptance',
[
'item_tag' => $this->item_tag,
'item_name' => $this->item_name,
'item_model' => $this->item_model,
'item_serial' => $this->item_serial,
'item_status' => $this->item_status,
@@ -72,8 +77,9 @@ class AcceptanceAssetAcceptedNotification extends Notification
'accepted_date' => $this->accepted_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'intro_text' => trans('mail.acceptance_asset_accepted'),
'admin' => $this->admin,
'qty' => $this->qty,
'intro_text' => trans('mail.acceptance_asset_accepted'),
])
->subject(trans('mail.acceptance_asset_accepted'));
@@ -2,13 +2,14 @@
namespace App\Notifications;
use AllowDynamicProperties;
use App\Helpers\Helper;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class AcceptanceAssetAcceptedToUserNotification extends Notification
#[AllowDynamicProperties] class AcceptanceAssetAcceptedToUserNotification extends Notification
{
use Queueable;
@@ -20,16 +21,18 @@ class AcceptanceAssetAcceptedToUserNotification extends Notification
public function __construct($params)
{
$this->item_tag = $params['item_tag'];
$this->item_name = $params['item_name'];
$this->item_model = $params['item_model'];
$this->item_serial = $params['item_serial'];
$this->item_status = $params['item_status'];
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false);
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'datetime', false);
$this->assigned_to = $params['assigned_to'];
$this->note = $params['note'];
$this->note = $params['note'] ?? null;
$this->company_name = $params['company_name'];
$this->settings = Setting::getSettings();
$this->file = $params['file'] ?? null;
$this->qty = $params['qty'] ?? null;
$this->admin = $params['admin'] ?? null;
}
/**
@@ -59,6 +62,7 @@ class AcceptanceAssetAcceptedToUserNotification extends Notification
$message = (new MailMessage)->markdown('notifications.markdown.asset-acceptance',
[
'item_tag' => $this->item_tag,
'item_name' => $this->item_name,
'item_model' => $this->item_model,
'item_serial' => $this->item_serial,
'item_status' => $this->item_status,
@@ -66,10 +70,12 @@ class AcceptanceAssetAcceptedToUserNotification extends Notification
'accepted_date' => $this->accepted_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'intro_text' => trans('mail.acceptance_asset_accepted_to_user', ['site_name' => $this->company_name ?? $this->settings->site_name]),
'admin' => $this->admin,
'qty' => $this->qty,
'intro_text' => trans_choice('mail.acceptance_asset_accepted_to_user', $this->qty, ['qty' => $this->qty, 'site_name' => $this->settings->site_name]),
])
->attach($pdf_path)
->subject(trans('mail.acceptance_asset_accepted_to_user', ['site_name' => $this->settings->site_name]));
->subject(trans_choice('mail.acceptance_asset_accepted_to_user', $this->qty, ['qty' => $this->qty, 'site_name' => $this->settings->site_name]));
return $message;
}
@@ -30,6 +30,7 @@ class AcceptanceAssetDeclinedNotification extends Notification
$this->assigned_to = $params['assigned_to'];
$this->company_name = $params['company_name'];
$this->settings = Setting::getSettings();
$this->qty = $params['qty'] ?? null;
}
/**
@@ -69,6 +70,7 @@ class AcceptanceAssetDeclinedNotification extends Notification
'declined_date' => $this->declined_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'qty' => $this->qty,
'intro_text' => trans('mail.acceptance_asset_declined'),
])
->subject(trans('mail.acceptance_asset_declined'));
+1 -1
View File
@@ -61,7 +61,7 @@ use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
->from(($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot')
->to($channel)
->attachment(function ($attachment) {
$item = $this->params['item'];
$item = $this->params['item'] ?? null;
$admin_user = $this->params['admin'];
$fields = [
'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->display_name.'>',
+7 -1
View File
@@ -120,7 +120,13 @@ class AccessoryPresenter extends Presenter
'field' => 'purchase_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.purchase_cost'),
'title' => trans('general.unit_cost'),
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'total_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.total_cost'),
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right text-padding-number-cell',
], [
+27 -21
View File
@@ -79,6 +79,25 @@ class ComponentPresenter extends Presenter
'title' => trans('general.manufacturer'),
'visible' => false,
'formatter' => 'manufacturersLinkObjFormatter',
], [
'field' => 'location',
'searchable' => true,
'sortable' => true,
'title' => trans('general.location'),
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'order_number',
'searchable' => true,
'sortable' => true,
'title' => trans('general.order_number'),
'visible' => true,
], [
'field' => 'purchase_date',
'searchable' => true,
'sortable' => true,
'title' => trans('general.purchase_date'),
'visible' => true,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'min_amt',
'searchable' => false,
@@ -103,33 +122,20 @@ class ComponentPresenter extends Presenter
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'location',
'searchable' => true,
'sortable' => true,
'title' => trans('general.location'),
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'order_number',
'searchable' => true,
'sortable' => true,
'title' => trans('general.order_number'),
'visible' => true,
], [
'field' => 'purchase_date',
'searchable' => true,
'sortable' => true,
'title' => trans('general.purchase_date'),
'visible' => true,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'purchase_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.purchase_cost'),
'title' => trans('general.unit_cost'),
'visible' => true,
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right',
], [
'field' => 'total_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.total_cost'),
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'notes',
'searchable' => true,
+37 -30
View File
@@ -67,35 +67,6 @@ class ConsumablePresenter extends Presenter
'searchable' => true,
'sortable' => true,
'title' => trans('general.model_no'),
], [
'field' => 'item_no',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/consumables/general.item_no'),
], [
'field' => 'qty',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.total'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'remaining',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.remaining'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'min_amt',
'searchable' => false,
'sortable' => true,
'title' => trans('general.min_amt'),
'visible' => true,
'formatter' => 'minAmtFormatter',
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'location',
'searchable' => true,
@@ -103,6 +74,12 @@ class ConsumablePresenter extends Presenter
'title' => trans('general.location'),
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'item_no',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/consumables/general.item_no'),
], [
'field' => 'manufacturer',
'searchable' => true,
'sortable' => true,
@@ -122,12 +99,42 @@ class ConsumablePresenter extends Presenter
'title' => trans('general.purchase_date'),
'visible' => true,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'min_amt',
'searchable' => false,
'sortable' => true,
'title' => trans('general.min_amt'),
'visible' => true,
'formatter' => 'minAmtFormatter',
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'qty',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.total'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'remaining',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.remaining'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'purchase_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.purchase_cost'),
'title' => trans('general.unit_cost'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'total_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.total_cost'),
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right text-padding-number-cell',
], [
+1 -1
View File
@@ -202,7 +202,7 @@ class LicensePresenter extends Presenter
'switchable' => false,
'title' => trans('general.checkin').'/'.trans('general.checkout'),
'visible' => true,
'formatter' => 'licensesInOutFormatter',
'formatter' => 'licenseInOutFormatter',
'printIgnore' => true,
];
+34 -5
View File
@@ -61,6 +61,15 @@ class LocationPresenter extends Presenter
'title' => trans('admin/locations/table.parent'),
'visible' => true,
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'users_count',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.people'),
'titleTooltip' => trans('general.people'),
'visible' => true,
'class' => 'css-house-user',
], [
'field' => 'assets_count',
'searchable' => false,
@@ -98,7 +107,7 @@ class LocationPresenter extends Presenter
'titleTooltip' => trans('general.accessories'),
'visible' => true,
'class' => 'css-accessory',
], [
],[
'field' => 'assigned_accessories_count',
'searchable' => false,
'sortable' => true,
@@ -108,14 +117,34 @@ class LocationPresenter extends Presenter
'visible' => true,
'class' => 'css-accessory-alt',
], [
'field' => 'users_count',
'field' => 'components_count',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.people'),
'titleTooltip' => trans('general.people'),
'title' => trans('general.components'),
'titleTooltip' => trans('general.components'),
'visible' => true,
'class' => 'css-house-user',
'class' => 'css-component',
],
[
'field' => 'consumables_count',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.consumables'),
'titleTooltip' => trans('general.consumables'),
'visible' => true,
'class' => 'css-consumable',
],
[
'field' => 'children_count',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.child_locations'),
'titleTooltip' => trans('general.child_locations'),
'visible' => true,
'class' => 'css-child-locations',
], [
'field' => 'currency',
'searchable' => true,
+21 -26
View File
@@ -60,18 +60,14 @@ class UserPresenter extends Presenter
'title' => trans('admin/users/table.name'),
'visible' => true,
'formatter' => 'usersLinkFormatter',
],
[
], [
'field' => 'first_name',
'searchable' => true,
'sortable' => true,
'title' => trans('general.first_name'),
'visible' => false,
'formatter' => 'usersLinkFormatter',
],
[
], [
'field' => 'last_name',
'searchable' => true,
'sortable' => true,
@@ -85,7 +81,23 @@ class UserPresenter extends Presenter
'sortable' => true,
'switchable' => false,
'title' => trans('admin/users/table.display_name'),
'visible' => false,
], [
'field' => 'username',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/users/table.username'),
'visible' => true,
'formatter' => 'usernameRoleLinkFormatter',
],
[
'field' => 'employee_num',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.employee_number'),
'visible' => false,
],
[
'field' => 'jobtitle',
@@ -129,7 +141,7 @@ class UserPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'title' => trans('admin/users/table.phone'),
'visible' => true,
'visible' => false,
'formatter' => 'phoneFormatter',
],
[
@@ -190,24 +202,7 @@ class UserPresenter extends Presenter
'title' => trans('general.zip'),
'visible' => false,
],
[
'field' => 'username',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/users/table.username'),
'visible' => true,
'formatter' => 'usernameRoleLinkFormatter',
],
[
'field' => 'employee_num',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.employee_number'),
'visible' => false,
],
[
'field' => 'locale',
'searchable' => true,
@@ -231,7 +226,7 @@ class UserPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'title' => trans('admin/users/general.department_manager'),
'visible' => true,
'visible' => false,
'formatter' => 'usersLinkObjFormatter',
],
[
@@ -248,7 +243,7 @@ class UserPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'title' => trans('admin/users/table.manager'),
'visible' => true,
'visible' => false,
'formatter' => 'usersLinkObjFormatter',
],
[
+13 -4
View File
@@ -349,10 +349,19 @@ class BreadcrumbsServiceProvider extends ServiceProvider
/**
* Licenses Breadcrumbs
*/
Breadcrumbs::for('licenses.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.licenses'), route('licenses.index'))
);
if ((request()->is('licenses*')) && (request()->status=='inactive')) {
Breadcrumbs::for('licenses.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.licenses'), route('licenses.index'))
->push(trans('general.show_inactive'), route('licenses.index'))
);
} else {
Breadcrumbs::for('licenses.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.licenses'), route('licenses.index'))
);
}
Breadcrumbs::for('licenses.create', fn (Trail $trail) =>
$trail->parent('licenses.index', route('licenses.index'))
+2 -1
View File
@@ -32,11 +32,11 @@
"arietimmerman/laravel-scim-server": "dev-laravel_11_compatibility",
"bacon/bacon-qr-code": "^2.0",
"barryvdh/laravel-debugbar": "^3.13",
"barryvdh/laravel-dompdf": "^2.0",
"doctrine/cache": "^1.10",
"doctrine/dbal": "^3.1",
"doctrine/instantiator": "^1.3",
"eduardokum/laravel-mail-auto-embed": "^2.0",
"elibyy/tcpdf-laravel": "^11.5",
"enshrined/svg-sanitize": "^0.22.0",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.24",
@@ -73,6 +73,7 @@
"spatie/laravel-ignition": "^2.0",
"tabuna/breadcrumbs": "^4.2",
"tecnickcom/tc-lib-barcode": "^1.15",
"tecnickcom/tc-lib-pdf-font": "^2.6",
"tecnickcom/tcpdf": "^6.5",
"unicodeveloper/laravel-password": "^1.0",
"watson/validating": "^8.1"
Generated
+430 -392
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -300,7 +300,6 @@ return [
App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
Barryvdh\DomPDF\ServiceProvider::class,
/*
* Package Service Providers...
@@ -315,6 +314,8 @@ return [
Unicodeveloper\DumbPassword\DumbPasswordServiceProvider::class,
Eduardokum\LaravelMailAutoEmbed\ServiceProvider::class,
Laravel\Socialite\SocialiteServiceProvider::class,
Elibyy\TCPDF\ServiceProvider::class,
/*
* Application Service Providers...
@@ -371,7 +372,7 @@ return [
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'PDF' => Barryvdh\DomPDF\Facade::class,
'PDF' => Elibyy\TCPDF\Facades\TCPDF::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
+17
View File
@@ -0,0 +1,17 @@
<?php
return [
'mode' => 'utf-8',
'format' => 'A4',
'author' => '',
'subject' => '',
'keywords' => '',
'creator' => 'Snipe-IT',
'display_mode' => 'fullpage',
'tempDir' => base_path('../temp/'),
'pdf_a' => false,
'pdf_a_auto' => false,
'icc_profile_path' => '',
'defaultCssFile' => false,
'pdfWrapper' => 'misterspelik\LaravelPdf\Wrapper\PdfWrapper',
];
+5 -5
View File
@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v8.3.1',
'full_app_version' => 'v8.3.1 - build 19577-g7dd493da3',
'build_version' => '19577',
'app_version' => 'v8.3.2',
'full_app_version' => 'v8.3.2 - build 19905-g028b4e7b7',
'build_version' => '19905',
'prerelease_version' => '',
'hash_version' => 'g7dd493da3',
'full_hash' => 'v8.3.1-15-g7dd493da3',
'hash_version' => 'g028b4e7b7',
'full_hash' => 'v8.3.2-319-g028b4e7b7',
'branch' => 'develop',
);
+2 -2
View File
@@ -33,9 +33,9 @@ class LicenseFactory extends Factory
'seats' => $this->faker->numberBetween(1, 10),
'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'),
'order_number' => $this->faker->numberBetween(1000000, 50000000),
'expiration_date' => $this->faker->dateTimeBetween('now', '+3 years', date_default_timezone_get())->format('Y-m-d H:i:s'),
'expiration_date' => null,
'reassignable' => $this->faker->boolean(),
'termination_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d H:i:s'),
'termination_date' => null,
'supplier_id' => Supplier::factory(),
'category_id' => Category::factory(),
];
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::whenTableDoesntHaveColumn('checkout_acceptances', 'qty', function () {
Schema::table('checkout_acceptances', function (Blueprint $table) {
$table->unsignedInteger('qty')->nullable()->after('assigned_to_id')->default(null);
});
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::whenTableHasColumn('checkout_acceptances', 'qty', function () {
Schema::table('checkout_acceptances', function (Blueprint $table) {
$table->dropColumn('qty');
});
});
}
};
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
// We are doing 'deleted_at' *first* here because that way this index can do double-duty -
// handling queries for 'all undeleted users' as well as 'users who are deleted in this location'
// and 'users who are not-deleted in this location'
$table->index(['deleted_at','location_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropIndex(['deleted_at','location_id']);
});
}
};
+4 -2
View File
@@ -21,11 +21,11 @@ class SettingsSeeder extends Seeder
$settings->header_color = null;
$settings->label2_2d_type = 'QRCODE';
$settings->default_currency = 'USD';
$settings->brand = 3;
$settings->brand = 2;
$settings->ldap_enabled = 0;
$settings->full_multiple_companies_support = 0;
$settings->label2_1d_type = 'C128';
$settings->skin = '';
$settings->skin = 'blue';
$settings->email_domain = 'example.org';
$settings->email_format = 'filastname';
$settings->username_format = 'filastname';
@@ -41,6 +41,8 @@ class SettingsSeeder extends Seeder
if ($user = User::where('username', '=', 'admin')->first()) {
$user->locale = 'en-US';
$user->enable_sound = 1;
$user->enable_confetti = 1;
$user->save();
}
+11
View File
@@ -1049,6 +1049,7 @@ th.css-license > .th-inner,
th.css-location > .th-inner,
th.css-users > .th-inner,
th.css-currency > .th-inner,
th.css-child-locations > .th-inner,
th.css-history > .th-inner {
font-size: 0px;
line-height: 0.75 !important;
@@ -1071,6 +1072,7 @@ th.css-license > .th-inner::before,
th.css-location > .th-inner::before,
th.css-users > .th-inner::before,
th.css-currency > .th-inner::before,
th.css-child-locations > .th-inner::before,
th.css-history > .th-inner::before {
display: inline-block;
font-size: 20px;
@@ -1152,6 +1154,12 @@ th.css-accessory-alt > .th-inner::before {
font-size: 19px;
margin-bottom: 0px;
}
th.css-child-locations > .th-inner::before {
content: "\f64f";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-currency > .th-inner::before {
content: "\24";
font-family: "Font Awesome 5 Free";
@@ -1493,6 +1501,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=app.css.map*/
File diff suppressed because one or more lines are too long
+11
View File
@@ -673,6 +673,7 @@ th.css-license > .th-inner,
th.css-location > .th-inner,
th.css-users > .th-inner,
th.css-currency > .th-inner,
th.css-child-locations > .th-inner,
th.css-history > .th-inner {
font-size: 0px;
line-height: 0.75 !important;
@@ -695,6 +696,7 @@ th.css-license > .th-inner::before,
th.css-location > .th-inner::before,
th.css-users > .th-inner::before,
th.css-currency > .th-inner::before,
th.css-child-locations > .th-inner::before,
th.css-history > .th-inner::before {
display: inline-block;
font-size: 20px;
@@ -776,6 +778,12 @@ th.css-accessory-alt > .th-inner::before {
font-size: 19px;
margin-bottom: 0px;
}
th.css-child-locations > .th-inner::before {
content: "\f64f";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-currency > .th-inner::before {
content: "\24";
font-family: "Font Awesome 5 Free";
@@ -1117,6 +1125,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=overrides.css.map*/
File diff suppressed because one or more lines are too long
+22
View File
@@ -22385,6 +22385,7 @@ th.css-license > .th-inner,
th.css-location > .th-inner,
th.css-users > .th-inner,
th.css-currency > .th-inner,
th.css-child-locations > .th-inner,
th.css-history > .th-inner {
font-size: 0px;
line-height: 0.75 !important;
@@ -22407,6 +22408,7 @@ th.css-license > .th-inner::before,
th.css-location > .th-inner::before,
th.css-users > .th-inner::before,
th.css-currency > .th-inner::before,
th.css-child-locations > .th-inner::before,
th.css-history > .th-inner::before {
display: inline-block;
font-size: 20px;
@@ -22488,6 +22490,12 @@ th.css-accessory-alt > .th-inner::before {
font-size: 19px;
margin-bottom: 0px;
}
th.css-child-locations > .th-inner::before {
content: "\f64f";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-currency > .th-inner::before {
content: "\24";
font-family: "Font Awesome 5 Free";
@@ -22829,6 +22837,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=app.css.map*/
@@ -23989,6 +24000,7 @@ th.css-license > .th-inner,
th.css-location > .th-inner,
th.css-users > .th-inner,
th.css-currency > .th-inner,
th.css-child-locations > .th-inner,
th.css-history > .th-inner {
font-size: 0px;
line-height: 0.75 !important;
@@ -24011,6 +24023,7 @@ th.css-license > .th-inner::before,
th.css-location > .th-inner::before,
th.css-users > .th-inner::before,
th.css-currency > .th-inner::before,
th.css-child-locations > .th-inner::before,
th.css-history > .th-inner::before {
display: inline-block;
font-size: 20px;
@@ -24092,6 +24105,12 @@ th.css-accessory-alt > .th-inner::before {
font-size: 19px;
margin-bottom: 0px;
}
th.css-child-locations > .th-inner::before {
content: "\f64f";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-currency > .th-inner::before {
content: "\24";
font-family: "Font Awesome 5 Free";
@@ -24433,6 +24452,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=overrides.css.map*/
+3 -3
View File
@@ -2,8 +2,8 @@
"/js/dist/all.js": "/js/dist/all.js?id=76d88f0f91b852f7eecbce357ab5858b",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=42f97cd5b9ee7521b04a448e7fc16ac9",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=d81a7ed323f68a7c5e3e9115f7fb5404",
"/css/build/overrides.css": "/css/build/overrides.css?id=257e65d85ce9cf5a413065df1b131c03",
"/css/build/app.css": "/css/build/app.css?id=3dcb1500ec5991cc4058fd5e4d9b8b49",
"/css/build/overrides.css": "/css/build/overrides.css?id=d6ec1f1e36c57f8cd96218d2a59a2580",
"/css/build/app.css": "/css/build/app.css?id=d591faf82795dc8151a6d3f84c26f5a4",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=ee0ed88465dd878588ed044eefb67723",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=3d8a3d2035ea28aaad4a703c2646f515",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=3979929a3423ff35b96b1fc84299fdf3",
@@ -19,7 +19,7 @@
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=b2cd9f59d7e8587939ce27b2d3363d82",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=7277edd636cf46aa7786a4449ce0ead7",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=cbd06cc1d58197ccc81d4376bbaf0d28",
"/css/dist/all.css": "/css/dist/all.css?id=a6b8969f0f70c07cf740f30bb7139b45",
"/css/dist/all.css": "/css/dist/all.css?id=ff957e6cef08b72d32cf28fe50da645a",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
+13
View File
@@ -746,6 +746,7 @@ th.css-license > .th-inner,
th.css-location > .th-inner,
th.css-users > .th-inner,
th.css-currency > .th-inner,
th.css-child-locations > .th-inner,
th.css-history > .th-inner
{
font-size: 0px;
@@ -771,6 +772,7 @@ th.css-license > .th-inner::before,
th.css-location > .th-inner::before,
th.css-users > .th-inner::before,
th.css-currency > .th-inner::before,
th.css-child-locations > .th-inner::before,
th.css-history > .th-inner::before
{
display: inline-block;
@@ -853,6 +855,13 @@ th.css-accessory-alt > .th-inner::before {
margin-bottom: 0px;
}
th.css-child-locations > .th-inner::before {
content: "\f64f"; // change this to f51e for coins
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-currency > .th-inner::before {
content: "\24"; // change this to f51e for coins
font-family: "Font Awesome 5 Free";
@@ -1251,4 +1260,8 @@ caption.tableCaption {
clip: rect(0,0,0,0);
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
@@ -46,6 +46,7 @@ return array(
'not_enough_seats' => 'Not enough license seats available for checkout',
'mismatch' => 'The license seat provided does not match the license',
'unavailable' => 'This seat is not available for checkout.',
'license_is_inactive' => 'This license is expired or terminated.',
),
'checkin' => array(
@@ -3,12 +3,13 @@
return array(
'does_not_exist' => 'Location does not exist.',
'assoc_users' => 'This location is not currently deletable because it is the location of record for at least one asset or user, has assets assigned to it, or is the parent location of another location. Please update your records to no longer reference this location and try again ',
'assoc_users' => 'This location is not currently deletable because it is the location of record for at least one item or user, has assets assigned to it, or is the parent location of another location. Please update your records to no longer reference this location and try again ',
'assoc_assets' => 'This location is currently associated with at least one asset and cannot be deleted. Please update your assets to no longer reference this location and try again. ',
'assoc_child_loc' => 'This location is currently the parent of at least one child location and cannot be deleted. Please update your locations to no longer reference this location and try again. ',
'assigned_assets' => 'Assigned Assets',
'current_location' => 'Current Location',
'open_map' => 'Open in :map_provider_icon Maps',
'deleted_warning' => 'This location has been deleted. Please restore it before attempting to make any changes.',
'create' => array(
@@ -12,7 +12,8 @@ return [
'create' => 'Create Location',
'update' => 'Update Location',
'print_assigned' => 'Print Assigned',
'print_all_assigned' => 'Print All Assigned',
'print_inventory' => 'Print Inventory',
'print_all_assigned' => 'Print Inventory and Assigned',
'name' => 'Location Name',
'address' => 'Address',
'address2' => 'Address Line 2',
+2 -3
View File
@@ -11,7 +11,6 @@ return array(
'title' => 'Asset Models',
'update' => 'Update Asset Model',
'view' => 'View Asset Model',
'update' => 'Update Asset Model',
'clone' => 'Clone Model',
'edit' => 'Edit Model',
'clone' => 'Clone Model',
'edit' => 'Edit Model',
);
+9 -2
View File
@@ -309,10 +309,12 @@ return [
'total_licenses' => 'total licenses',
'total_accessories' => 'total accessories',
'total_consumables' => 'total consumables',
'total_cost' => 'Total Cost',
'type' => 'Type',
'undeployable' => 'Un-deployable',
'unknown_admin' => 'Unknown Admin',
'unknown_user' => 'Unknown User',
'unit_cost' => 'Unit Cost',
'username' => 'Username',
'update' => 'Update',
'updating_item' => 'Updating :item',
@@ -353,9 +355,11 @@ return [
'audit_overdue' => 'Overdue for Audit',
'accept' => 'Accept :asset',
'i_accept' => 'I accept',
'i_decline_item' => 'Decline this item',
'i_accept_item' => 'Accept this item',
'i_accept_with_count' => 'I accept :count item|I accept :count items',
'i_decline_item' => 'Decline this item|Decline these items',
'i_accept_item' => 'Accept this item|Accept these items',
'i_decline' => 'I decline',
'i_decline_with_count' => 'I decline :count item|I decline :count items',
'accept_decline' => 'Accept/Decline',
'sign_tos' => 'Sign below to indicate that you agree to the terms of service:',
'clear_signature' => 'Clear Signature',
@@ -516,6 +520,7 @@ return [
'item_name_var' => ':item Name',
'error_user_company' => 'Checkout target company and asset company do not match',
'error_user_company_accept_view' => 'An Asset assigned to you belongs to a different company so you can\'t accept nor deny it, please check with your manager',
'error_assets_already_checked_out' => 'One or more of the assets are already checked out',
'importer' => [
'checked_out_to_fullname' => 'Checked Out to: Full Name',
'checked_out_to_first_name' => 'Checked Out to: First Name',
@@ -587,6 +592,7 @@ return [
'components' => ':count Component|:count Components',
],
'show_inactive' => 'Expired or Terminated',
'more_info' => 'More Info',
'quickscan_bulk_help' => 'Checking this box will edit the asset record to reflect this new location. Leaving it unchecked will simply note the location in the audit log. Note that if this asset is checked out, it will not change the location of the person, asset or location it is checked out to.',
'whoops' => 'Whoops!',
@@ -612,6 +618,7 @@ return [
'footer_credit' => '<a target="_blank" href="https://snipeitapp.com" rel="noopener">Snipe-IT</a> is open source software, made with <i class="fa fa-heart" aria-hidden="true" style="color: #a94442; font-size: 10px" /></i><span class="sr-only">love</span> by <a href="https://bsky.app/profile/snipeitapp.com" rel="noopener">@snipeitapp.com</a>.',
'set_password' => 'Set a Password',
'upload_deleted' => 'Upload Deleted',
'child_locations' => 'Child Locations',
// Add form placeholders here
'placeholders' => [
+10 -9
View File
@@ -3,7 +3,7 @@
return [
'Accessory_Checkin_Notification' => 'Accessory checked in',
'Accessory_Checkout_Notification' => 'Accessory checked out',
'Accessory_Checkout_Notification' => 'Accessory checked out|:count Accessories checked out',
'Asset_Checkin_Notification' => 'Asset checked in: :tag',
'Asset_Checkout_Notification' => 'Asset checked out: :tag',
'Confirm_Accessory_Checkin' => 'Accessory checkin confirmation',
@@ -21,8 +21,8 @@ return [
'Expected_Checkin_Date' => 'An asset checked out to you is due to be checked back in on :date',
'Expected_Checkin_Notification' => 'Reminder: :name checkin deadline approaching',
'Expected_Checkin_Report' => 'Expected asset checkin report',
'Expiring_Assets_Report' => 'Expiring Assets Report.',
'Expiring_Licenses_Report' => 'Expiring Licenses Report.',
'Expiring_Assets_Report' => 'Expiring Assets Report',
'Expiring_Licenses_Report' => 'Expiring Licenses Report',
'Item_Request_Canceled' => 'Item Request Canceled',
'Item_Requested' => 'Item Requested',
'License_Checkin_Notification' => 'License checked in',
@@ -31,7 +31,7 @@ return [
'Low_Inventory_Report' => 'Low Inventory Report',
'a_user_canceled' => 'A user has canceled an item request on the website',
'a_user_requested' => 'A user has requested an item on the website',
'acceptance_asset_accepted_to_user' => 'You have accepted an item assigned to you by :site_name',
'acceptance_asset_accepted_to_user' => 'You have accepted an item assigned to you by :site_name|You have accepted :qty items assigned to you by :site_name',
'acceptance_asset_accepted' => 'A user has accepted an item',
'acceptance_asset_declined' => 'A user has declined an item',
'send_pdf_copy' => 'Send a copy of this acceptance to my email address',
@@ -42,8 +42,9 @@ return [
'asset_name' => 'Asset Name',
'asset_requested' => 'Asset requested',
'asset_tag' => 'Asset Tag',
'assets_warrantee_alert' => 'There is :count asset with a warranty expiring in the next :threshold days.|There are :count assets with warranties expiring in the next :threshold days.',
'assets_warrantee_alert' => 'There is :count asset with an expiring warranty or that are reaching their end of life in the next :threshold days.|There are :count assets with expiring warranties or that are reaching their end of life in the next :threshold days.',
'assigned_to' => 'Assigned To',
'eol' => 'EOL',
'best_regards' => 'Best regards,',
'canceled' => 'Canceled',
'checkin_date' => 'Checkin Date',
@@ -68,16 +69,16 @@ return [
'inventory_report' => 'Inventory Report',
'item' => 'Item',
'item_checked_reminder' => 'This is a reminder that you currently have :count items checked out to you that you have not accepted or declined. Please click the link below to confirm your decision.',
'license_expiring_alert' => 'There is :count license expiring in the next :threshold days.|There are :count licenses expiring in the next :threshold days.',
'license_expiring_alert' => 'There is :count license expiring or terminating in the next :threshold days.|There are :count licenses expiring or terminating in the next :threshold days.',
'link_to_update_password' => 'Please click on the following link to update your :web password:',
'login' => 'Login',
'login_first_admin' => 'Login to your new Snipe-IT installation using the credentials below:',
'low_inventory_alert' => 'There is :count item that is below minimum inventory or will soon be low.|There are :count items that are below minimum inventory or will soon be low.',
'min_QTY' => 'Min QTY',
'name' => 'Name',
'new_item_checked' => 'A new item has been checked out under your name, details are below.',
'new_item_checked_with_acceptance' => 'A new item has been checked out under your name that requires acceptance, details are below.',
'new_item_checked_location' => 'A new item has been checked out to :location, details are below.',
'new_item_checked' => 'A new item has been checked out under your name, details are below.|:count new items have been checked out under your name, details are below.',
'new_item_checked_with_acceptance' => 'A new item has been checked out under your name that requires acceptance, details are below.|:count new items have been checked out under your name that requires acceptance, details are below.',
'new_item_checked_location' => 'A new item has been checked out to :location, details are below.|:count new items have been checked out to :location, details are below.',
'recent_item_checked' => 'An item was recently checked out under your name that requires acceptance, details are below.',
'notes' => 'Notes',
'password' => 'Password',
+76 -10
View File
@@ -16,19 +16,85 @@
@section('inputFields')
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/accessories/general.accessory_name')])
<!-- Name -->
<x-form-row
:label="trans('general.name')"
:$item
:$errors
name="name"
/>
@include ('partials.forms.edit.category-select', ['translated_name' => trans('general.category'), 'fieldname' => 'category_id', 'required' => 'true','category_type' => 'accessory'])
@include ('partials.forms.edit.supplier-select', ['translated_name' => trans('general.supplier'), 'fieldname' => 'supplier_id'])
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
@include ('partials.forms.edit.model_number')
@include ('partials.forms.edit.order_number')
@include ('partials.forms.edit.datepicker', ['translated_name' => trans('general.purchase_date'),'fieldname' => 'purchase_date'])
@include ('partials.forms.edit.purchase_cost', ['currency_type' => $item->location->currency ?? null])
@include ('partials.forms.edit.quantity')
@include ('partials.forms.edit.minimum_quantity')
@include ('partials.forms.edit.notes')
<!-- Model Number -->
<x-form-row
:label="trans('general.model_no')"
:$item
name="model_number"
/>
<!-- Order number -->
<x-form-row
:label="trans('general.order_number')"
:$item
name="order_number"
input_div_class="col-md-5 col-sm-12"
/>
<!-- Purchase date -->
<x-form-row
:label="trans('general.purchase_date')"
:$item
name="purchase_date"
type="date"
input_div_class="col-md-4 col-sm-12"
:value="old('purchase_date', (($item->purchase_date && $item->purchase_date->format('Y-m-d')) ?? ''))"
/>
<!-- Purchase cost -->
<x-form-row
:label="trans('general.unit_cost')"
:$item
name="purchase_cost"
type="number"
maxlength="25"
min="0.00"
max="99999999999999999.000"
step="0.001"
input_div_class="col-md-4 col-sm-12"
/>
<!-- QTY -->
<x-form-row
:label="trans('general.quantity')"
:$item
input_div_class="col-md-2"
name="qty"
/>
<!-- Min Amount -->
<x-form-row
:label="trans('general.min_amt')"
:$item
name="min_amt"
input_div_class="col-md-2"
minlength="1"
maxlength="5"
type="number"
:info_tooltip_text="trans('general.min_amt_help')"
/>
<!-- Notes -->
<x-form-row
:label="trans('general.notes')"
:$item
name="notes"
type="textarea"
maxlength="65000"
placeholder="{{ trans('general.placeholders.notes') }}"
/>
@include ('partials.forms.edit.image-upload', ['image_path' => app('accessories_upload_path')])
@stop
+13 -1
View File
@@ -236,7 +236,7 @@
<div class="row">
<div class="col-md-3" style="padding-bottom: 10px;">
<strong>
{{ trans('general.purchase_cost') }}
{{ trans('general.unit_cost') }}
</strong>
</div>
<div class="col-md-9" style="word-wrap: break-word;">
@@ -244,6 +244,18 @@
</div>
</div>
@endif
@if ($accessory->purchase_cost)
<div class="row">
<div class="col-md-3" style="padding-bottom: 10px;">
<strong>
{{ trans('general.total_cost') }}
</strong>
</div>
<div class="col-md-9" style="word-wrap: break-word;">
{{ Helper::formatCurrencyOutput($accessory->totalCostSum()) }}
</div>
</div>
@endif
<div class="row">
<div class="col-md-3" style="padding-bottom: 10px;">
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
body {
font-family:'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
@if ($logo)
<center>
<img src="{{ $logo }}">
<p>{{$company_name}}</p>
</center>
@endif
<br>
<p>
{{ trans('general.date') }}: {{ date($date_settings) }} <br>
{{ trans('general.asset_model') }}: {{ $item_model }}<br>
</p>
@if ($eula)
<hr>
{!! $eula !!}
<hr>
@endif
<p>
{{ trans('general.assigned_date') }}: {{$check_out_date}}<br>
{{ trans('general.assignee') }}: {{$assigned_to}}<br>
{{ trans('general.accepted_date') }}: {{$accepted_date}}
</p>
@if ($signature!='')
<img src="{{ $signature }}" style="max-width: 600px; border-bottom: black solid 1px;">
@endif
</body>
</html>
@@ -1,50 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
body {
font-family:'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
@if ($logo)
<center>
<img src="{{ $logo }}">
<p>{{$company_name}}</p>
</center>
@endif
<br>
<p>
{{ trans('general.date') }}: {{ date($date_settings) }} <br>
{{ trans('general.asset_tag') }}: {{ $item_tag }}<br>
{{ trans('general.asset_model') }}: {{ $item_model }}<br>
{{ trans('admin/hardware/form.serial') }}: {{ $item_serial }}
</p>
@if ($eula)
<hr>
{!! $eula !!}
<hr>
@endif
<p>
{{ trans('general.assigned_date') }}: {{$check_out_date}}<br>
{{ trans('general.assignee') }}: {{$assigned_to}}<br>
{{ trans('general.accepted_date') }}: {{$accepted_date}}
</p>
@if ($signature!='')
<img src="{{ $signature }}" style="max-width: 600px; border-bottom: black solid 1px;">
@endif
</body>
</html>
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
body {
font-family:'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
@if ($logo)
<center>
<img src="{{ $logo }}">
<p>{{$company_name}}</p>
</center>
@endif
<br>
<p>
{{ trans('general.date') }}: {{ date($date_settings) }} <br>
{{ trans('general.component') }}: {{ $item_model }}<br>
</p>
@if ($eula)
<hr>
{!! $eula !!}
<hr>
@endif
<p>
{{ trans('general.assigned_date') }}: {{$check_out_date}}<br>
{{ trans('general.assignee') }}: {{$assigned_to}}<br>
{{ trans('general.accepted_date') }}: {{$accepted_date}}
</p>
@if ($signature!='')
<img src="{{ $signature }}" style="max-width: 600px; border-bottom: black solid 1px;">
@endif
</body>
</html>
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
body {
font-family:'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
@if ($logo)
<center>
<img src="{{ $logo }}">
<p>{{$company_name}}</p>
</center>
@endif
<br>
<p>
{{ trans('general.date') }}: {{ date($date_settings) }} <br>
{{ trans('general.consumable') }}: {{ $item_model }}<br>
</p>
@if ($eula)
<hr>
{!! $eula !!}
<hr>
@endif
<p>
{{ trans('general.assigned_date') }}: {{$check_out_date}}<br>
{{ trans('general.assignee') }}: {{$assigned_to}}<br>
{{ trans('general.accepted_date') }}: {{$accepted_date}}
</p>
@if ($signature!='')
<img src="{{ $signature }}" style="max-width: 600px; border-bottom: black solid 1px;">
@endif
</body>
</html>
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
body {
font-family:'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
@if ($logo)
<center>
<img src="{{ $logo }}">
<p>{{$company_name}}</p>
</center>
@endif
<br>
<p>
{{ trans('general.date') }}: {{ date($date_settings) }} <br>
{{ trans('general.license') }}: {{ $item_model }}<br>
</p>
@if ($eula)
<hr>
{!! $eula !!}
<hr>
@endif
<p>
{{ trans('general.assigned_date') }}: {{$check_out_date}}<br>
{{ trans('general.assignee') }}: {{$assigned_to}}<br>
{{ trans('general.accepted_date') }}: {{$accepted_date}}
</p>
@if ($signature!='')
<img src="{{ $signature }}" style="max-width: 600px; border-bottom: black solid 1px;">
@endif
</body>
</html>
+55 -32
View File
@@ -20,11 +20,21 @@
}
.m-signature-pad--body {
border-style: solid;
border-style: dashed;
border-color: grey;
border-width: thin;
border-width: thick;
padding-top: 0px;
}
.m-signature-pad {
box-shadow: none;
background-color: inherit;
border: none;
}
</style>
@@ -39,25 +49,38 @@
<div class="box-header with-border">
<h2 class="box-title">
{{ $acceptance->checkoutable->display_name }}
{{ (($acceptance->checkoutable) && ($acceptance->checkoutable->serial)) ? ' - '.trans('general.serial_number').': '.$acceptance->checkoutable->serial : '' }}
@if ($acceptance->qty > 1)
<strong>×{{ $acceptance->qty }}</strong>
@endif
{!! (($acceptance->checkoutable) && ($acceptance->checkoutable->serial)) ? '<br>'.trans('general.serial_number').': '.e($acceptance->checkoutable->serial) : '' !!}
</h2>
</div>
<div class="box-body">
@if ($acceptance->checkoutable->getEula())
<div class="col-md-12" style="padding-top: 15px; padding-bottom: 15px;">
<div style="background-color: rgba(211,211,211,0.25); padding: 10px; border: lightgrey 1px solid;">
{!! str_replace('<p>', '<p dir="auto">', $acceptance->checkoutable->getEula()) !!}
<div class="col-md-12" style="padding-top: 5px; padding-bottom: 15px;">
<div style="background-color: rgba(211,211,211,0.25); padding: 0px 10px 10px 10px; border: lightgrey 1px solid;">
{!! str_replace('<p>', '<p dir="auto">', Helper::parseEscapedMarkedown($acceptance->checkoutable->getEula())) !!}
</div>
</div>
@endif
<div class="col-md-12">
<label class="form-control">
<input type="radio" name="asset_acceptance" id="accepted" value="accepted">
{{trans('general.i_accept')}}
@if ($acceptance->qty)
{{trans_choice('general.i_accept_with_count', $acceptance->qty)}}
@else
{{trans('general.i_accept')}}
@endif
</label>
<label class="form-control">
<input type="radio" name="asset_acceptance" id="declined" value="declined">
{{trans('general.i_decline')}}
@if ($acceptance->qty)
{{trans_choice('general.i_decline_with_count', $acceptance->qty)}}
@else
{{trans('general.i_decline')}}
@endif
</label>
</div>
@@ -77,30 +100,37 @@
<canvas style="width:100%;"></canvas>
<input type="hidden" name="signature_output" id="signature_output">
</div>
<div class="col-md-12 col-sm-12 col-lg-12 col-xs-12 text-center">
<div class="col-md-12 col-sm-12 col-lg-12 col-xs-12 text-left">
<button type="button" class="btn btn-sm btn-default clear" data-action="clear" id="clear_button">{{trans('general.clear_signature')}}</button>
</div>
</div>
</div>
@endif
@if (auth()->user()->email!='')
<div class="col-md-12" style="padding-top: 20px; display: none;" id="showEmailBox">
<label class="form-control">
<input type="checkbox" value="1" name="send_copy" id="send_copy" checked="checked" aria-label="send_copy">
{{ trans('mail.send_pdf_copy') }} ({{ auth()->user()->email }})
</label>
</div>
@endif
</div> <!-- / box-body -->
<div class="box-footer text-right" style="display: none;" id="showSubmit">
<button type="submit" class="btn btn-success" id="submit-button">
<i class="fa fa-check icon-white" aria-hidden="true" id="submitIcon"></i>
<span id="buttonText">
{{ trans('general.i_accept_item') }}
<div class="box-footer" style="display: none;" id="showSubmit">
<div class="row">
<div class="col-md-7">
@if (auth()->user()->email!='')
<div class="col-md-12" style="display: none;" id="showEmailBox">
<label class="form-control">
<input type="checkbox" value="1" name="send_copy" id="send_copy" checked="checked" aria-label="send_copy">
{{ trans('mail.send_pdf_copy') }} ({{ auth()->user()->email }})
</label>
</div>
@endif
</div>
<div class="col-md-5 text-right">
<button type="submit" class="btn btn-success" id="submit-button">
<i class="fa fa-check icon-white" aria-hidden="true" id="submitIcon"></i>
<span id="buttonText">
{{ trans_choice('general.i_accept_item', $acceptance->qty ?? null) }}
</span>
</button>
</button>
</div>
</div>
</div><!-- /.box-footer -->
</div> <!-- / box-default -->
</div> <!-- / col -->
@@ -161,7 +191,7 @@
$("#showSubmit").show();
$("#submit-button").removeClass("btn-success").addClass("btn-danger").show();
$("#submitIcon").removeClass("fa-check").addClass("fa-times");
$("#buttonText").text('{{ trans('general.i_decline_item') }}');
$("#buttonText").text('{{ trans_choice('general.i_decline_item', $acceptance->qty ?? 1) }}');
$("#note").prop('required', true);
} else if ($(this).is(':checked') && $(this).attr('id') === 'accepted') {
@@ -169,16 +199,9 @@
$("#showSubmit").show();
$("#submit-button").removeClass("btn-danger").addClass("btn-success").show();
$("#submitIcon").removeClass("fa-check").addClass("fa-check");
$("#buttonText").text('{{ trans('general.i_accept_item') }}');
$("#buttonText").text('{{ trans_choice('general.i_accept_item', $acceptance->qty ?? 1) }}');
$("#note").prop('required', false);
}
});
</script>
@stop
@@ -32,6 +32,7 @@
<tr>
<th>{{ trans('general.name')}}</th>
<th>{{ trans('general.type')}}</th>
<th>{{ trans('general.qty') }}</th>
<th>{{ trans('general.serial_number')}}</th>
<th>{{ trans('table.actions')}}</th>
</tr>
@@ -41,8 +42,9 @@
<tr>
@if ($acceptance->checkoutable)
<td>{{ ($acceptance->checkoutable) ? $acceptance->checkoutable->present()->name : '' }}</td>
<td>{{ $acceptance->checkoutable_item_type }}</td>
<td>{{ ($acceptance->checkoutable) ? $acceptance->checkoutable->serial : '' }}</td>
<td>{{ $acceptance->checkoutable_item_type }}</td>
<td>{{ $acceptance->qty ?? '1' }}</td>
<td>{{ ($acceptance->checkoutable) ? $acceptance->checkoutable->serial : '' }}</td>
<td><a href="{{ route('account.accept.item', $acceptance) }}" class="btn btn-default btn-sm">{{ trans('general.accept_decline') }}</a></td>
@else
<td> ----- </td>
+10 -3
View File
@@ -9,7 +9,7 @@
{{-- Account page content --}}
@section('content')
@if ($acceptances = \App\Models\CheckoutAcceptance::forUser(Auth::user())->pending()->count())
@if ($acceptanceQuantity = \App\Models\CheckoutAcceptance::forUser(Auth::user())->pending()->sum('qty'))
<div class="row">
<div class="col-md-12">
<div class="alert alert alert-warning fade in">
@@ -17,7 +17,7 @@
<strong>
<a href="{{ route('account.accept') }}" style="color: white;">
{{ trans_choice('general.unaccepted_profile_warning', $acceptances, ['count' => $acceptances]) }}
{{ trans_choice('general.unaccepted_profile_warning', $acceptanceQuantity, ['count' => $acceptanceQuantity]) }}
</a>
</strong>
</div>
@@ -446,6 +446,9 @@
<th class="col-md-2" data-switchable="true" data-visible="false">
{{ trans('general.name') }}
</th>
<th class="col-md-2" data-switchable="true" data-visible="false">
{{ trans('general.status') }}
</th>
<th class="col-md-2" data-switchable="true" data-visible="true">
{{ trans('admin/hardware/table.asset_model') }}
</th>
@@ -461,7 +464,6 @@
<th class="col-md-2" data-switchable="true" data-visible="false">
{{ trans('general.location') }}
</th>
@can('self.view_purchase_cost')
<th class="col-md-6" data-footer-formatter="sumFormatter" data-fieldname="purchase_cost">
{{ trans('general.purchase_cost') }}
@@ -508,6 +510,11 @@
<td>
{{ $asset->name }}
</td>
<td>
<x-icon type="circle-solid" class="text-blue" />
{{ $asset->assetstatus->name }}
<label class="label label-default">{{ trans('general.deployed') }}</label>
</td>
<td>
{{ $asset->model->name }}
</td>
-3
View File
@@ -1,3 +0,0 @@
<p>
Hi.
</p>
+2 -4
View File
@@ -4,7 +4,7 @@
'object_type' => '',
])
<!-- begin non-ajaxed file listing table -->
<!-- begin ajaxed file listing table -->
<div class="table-responsive">
<table
data-columns="{{ \App\Presenters\UploadedFilesPresenter::dataTableLayout() }}"
@@ -30,6 +30,4 @@
</div>
<!-- end non-ajaxed file listing table -->
<!-- end ajaxed file listing table -->
@@ -0,0 +1,5 @@
<!-- form-label blade component -->
<label {{ $attributes->merge(['class' => 'control-label']) }}>
{{ $slot }}
</label>
+107
View File
@@ -0,0 +1,107 @@
<!-- form-row blade component -->
@props([
'checkbox_value' => null,
'disabled' => false,
'div_style' => null,
'error_offset_class' => 'col-md-7 col-md-offset-3',
'errors',
'help_text' => null,
'info_tooltip_text' => null,
'input_class' => null,
'input_div_class' => 'col-md-8 col-sm-12',
'input_group_addon' => null,
'input_style' => null,
'item' => null,
'label' => null,
'label_class' => 'col-md-3 col-sm-12 col-xs-12',
'label_style' => null,
'max' => null,
'maxlength' => 191,
'min' => null,
'minlength' => null,
'name' => null,
'placeholder' => null,
'step' => null,
'type' => 'text',
'value' => null,
'value_text' => null,
])
<div {{ $attributes->merge(['class' => 'form-group']) }}>
@if (isset($label))
<x-form-label
:for="$name"
:style="$label_style ?? null"
class="{{ $label_class }}"
>
{{ $label }}
</x-form-label>
@else
@php
$input_div_class = $input_div_class . ' ' . $error_offset_class;
@endphp
@endif
<div {{ $attributes->merge(['class' => $input_div_class, 'style' => $div_style]) }}>
@php
$blade_type = in_array($type, ['text', 'email', 'url', 'tel', 'number', 'password']) ? 'text' : $type;
@endphp
<x-dynamic-component
:$checkbox_value
:$disabled
:$input_group_addon
:$input_style
:$item
:$max
:$maxlength
:$min
:$minlength
:$name
:$placeholder
:$step
:$type
:$value_text
:aria-label="$name"
:class="$input_class"
:component="'input.'.$blade_type"
:id="$name"
:required="Helper::checkIfRequired($item, $name)"
:value="old($name, $item->{$name})"
/>
</div>
@if ($info_tooltip_text)
<!-- Info Tooltip -->
<div class="col-md-1 text-left" style="padding-left:0; margin-top: 5px;">
<x-input.info-tooltip>
{{ $info_tooltip_text }}
</x-input.info-tooltip>
</div>
@endif
@error($name)
<!-- Form Error -->
<div {{ $attributes->merge(['class' => $error_offset_class]) }}>
<span class="alert-msg" role="alert">
<i class="fas fa-times" aria-hidden="true"></i>
{{ $message }}
</span>
</div>
@enderror
@if ($help_text)
<!-- Help Text -->
<div {{ $attributes->merge(['class' => $error_offset_class]) }}>
<p class="help-block">
{!! $help_text !!}
</p>
</div>
@endif
</div>
@@ -0,0 +1,16 @@
@props([
'item' => null,
'field_name' => null,
'input_style' => null,
'required' => false,
'disabled' => false,
'checkbox_value' => null,
'name' => null,
'label' => null,
'value_text' => null,
])
<label class="form-control{{ $disabled ? ' form-control--disabled' : '' }}">
<input type="checkbox" name="{{ $name }}" aria-label="{{ $name }}" value="{{ $checkbox_value }}" @checked(old($name, $item->$name)) {!! $disabled ? 'class="disabled" disabled' : '' !!}>
{{ $value_text ?? $label }}
</label>
@@ -0,0 +1,12 @@
@props([
'item' => null,
'input_style' => null,
'end_date' => null,
])
<!-- Datepicker -->
<div {{ $attributes->merge(['class' => 'input-group date']) }} data-provide="datepicker" data-date-today-highlight="true" data-date-language="{{ auth()->user()->locale }}" data-date-locale="{{ auth()->user()->locale }}" data-date-format="yyyy-mm-dd" data-date-autoclose="true" data-date-clear-btn="true"{{ $end_date ? ' data-date-end-date=' . $end_date : '' }}>
<input type="text" {{ $attributes->merge(['class' => 'form-control']) }}>
<span class="input-group-addon"><x-icon type="calendar" /></span>
</div>
@@ -0,0 +1,4 @@
<a href="#" data-tooltip="true" title="{{ $slot }}" style="padding-left: 0px;">
<x-icon type="more-info" style="font-size: 20px;" />
<span class="sr-only">{{ $slot }}</span>
</a>
@@ -0,0 +1,15 @@
@props([
'item' => null,
'field_name' => null,
'input_style' => null,
'required' => false,
'disabled' => false,
'name' => null,
'label' => null,
'value_text' => null,
])
<label class="form-control{{ $disabled ? ' form-control--disabled' : '' }}">
<input type="radio" name="{{ $name }}" aria-label="{{ $name }}" @checked(old($name)) {!! $disabled ? 'class="disabled" disabled' : '' !!}>
{{ $value_text ?? $label }}
</label>
@@ -0,0 +1,15 @@
@props([
'input_style' => null,
'input_group_addon' => null,
'required' => false,
'item' => null,
])
<!-- input-text blade component -->
<input
{{ $attributes->merge(['class' => 'form-control', 'style' => $input_style]) }}
@required($required)
/>
@if ($input_group_addon)
<span class="input-group-addon">{{ $input_group_addon }}</span>
@endif
+6 -1
View File
@@ -9,7 +9,12 @@
@section('inputFields')
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/categories/general.name')])
<!-- Name -->
<x-form-row
:label="trans('general.name')"
:$item
name="name"
/>
<!-- Type -->
<div class="form-group {{ $errors->has('category_type') ? ' has-error' : '' }}">
@@ -61,6 +61,7 @@
@if ($category->category_type=='asset')
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="categoryAssetsTable"
id="categoryAssetsTable"
data-buttons="assetButtons"
+41 -19
View File
@@ -8,26 +8,48 @@
{{-- Page content --}}
@section('inputFields')
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/companies/table.name')])
@include ('partials.forms.edit.phone')
@include ('partials.forms.edit.fax')
@include ('partials.forms.edit.email')
<!-- Name -->
<x-form-row
:label="trans('general.name')"
:$item
name="name"
/>
<!-- Phone -->
<x-form-row
:label="trans('general.phone')"
:$item
name="phone"
type="tel"
/>
<!-- Fax -->
<x-form-row
:label="trans('general.fax')"
:$item
name="fax"
type="tel"
/>
<!-- Email -->
<x-form-row
:label="trans('general.email')"
:$item
name="fax"
type="email"
/>
@include ('partials.forms.edit.image-upload', ['image_path' => app('companies_upload_path')])
<div class="form-group{!! $errors->has('notes') ? ' has-error' : '' !!}">
<label for="notes" class="col-md-3 control-label">{{ trans('general.notes') }}</label>
<div class="col-md-8">
<x-input.textarea
name="notes"
id="notes"
:value="old('notes', $item->notes)"
placeholder="{{ trans('general.placeholders.notes') }}"
aria-label="notes"
rows="5"
/>
</div>
</div>
<!-- Notes -->
<x-form-row
:label="trans('general.notes')"
:$item
name="notes"
type="textarea"
maxlength="65000"
placeholder="{{ trans('general.placeholders.notes') }}"
/>
@stop
+1
View File
@@ -94,6 +94,7 @@
data-cookie-id-table="assetsListingTable"
data-id-table="assetsListingTable"
data-side-pagination="server"
data-show-columns-search="true"
data-sort-order="asc"
data-toolbar="#assetsBulkEditToolbar"
data-bulk-button-id="#bulkAssetEditButton"
+90 -10
View File
@@ -16,20 +16,100 @@
{{-- Page content --}}
@section('inputFields')
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/components/table.title')])
<!-- Name -->
<x-form-row
:label="trans('general.name')"
:$item
name="name"
/>
@include ('partials.forms.edit.category-select', ['translated_name' => trans('general.category'), 'fieldname' => 'category_id','category_type' => 'component'])
@include ('partials.forms.edit.quantity')
@include ('partials.forms.edit.minimum_quantity')
@include ('partials.forms.edit.serial', ['fieldname' => 'serial'])
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
@include ('partials.forms.edit.model_number')
<!-- QTY -->
<x-form-row
:label="trans('general.quantity')"
:$item
name="qty"
input_div_class="col-md-2"
minlength="1"
maxlength="5"
type="number"
/>
<!-- Min Amount -->
<x-form-row
:label="trans('general.min_amt')"
:$item
name="min_amt"
input_div_class="col-md-2"
minlength="1"
maxlength="5"
type="number"
:info_tooltip_text="trans('general.min_amt_help')"
/>
<x-form-row
:label="trans('admin/hardware/form.serial')"
:$item
name="serial"
type="text"
/>
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
<!-- Model Number -->
<x-form-row
:label="trans('general.model_no')"
:$item
name="model_number"
input_div_class="col-md-5 col-sm-12"
/>
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
@include ('partials.forms.edit.supplier-select', ['translated_name' => trans('general.supplier'), 'fieldname' => 'supplier_id'])
@include ('partials.forms.edit.order_number')
@include ('partials.forms.edit.datepicker', ['translated_name' => trans('general.purchase_date'),'fieldname' => 'purchase_date'])
@include ('partials.forms.edit.purchase_cost')
@include ('partials.forms.edit.notes')
<!-- Order number -->
<x-form-row
:label="trans('general.order_number')"
:$item
name="order_number"
input_div_class="col-md-5 col-sm-12"
/>
<!-- Purchase date -->
<x-form-row
:label="trans('general.purchase_date')"
:$item
name="purchase_date"
type="date"
input_div_class="col-md-4 col-sm-12"
:value="old('purchase_date', (($item->purchase_date && $item->purchase_date->format('Y-m-d')) ?? ''))"
/>
<!-- Purchase cost -->
<x-form-row
:label="trans('general.unit_cost')"
:$item
name="purchase_cost"
type="number"
maxlength="25"
min="0.00"
max="99999999999999999.000"
step="0.001"
input_div_class="col-md-4 col-sm-12"
/>
<!-- Notes -->
<x-form-row
:label="trans('general.notes')"
:$item
name="notes"
type="textarea"
maxlength="65000"
placeholder="{{ trans('general.placeholders.notes') }}"
/>
@include ('partials.forms.edit.image-upload', ['image_path' => app('components_upload_path')])
+8 -1
View File
@@ -201,12 +201,19 @@
@endif
@if ($component->purchase_cost)
<div class="col-md-12" style="padding-bottom: 5px;"><strong>{{ trans('admin/components/general.cost') }}:</strong>
<div class="col-md-12" style="padding-bottom: 5px;"><strong>{{ trans('general.unit_cost') }}:</strong>
{{ $snipeSettings->default_currency }}
{{ Helper::formatCurrencyOutput($component->purchase_cost) }} </div>
@endif
@if ($component->purchase_cost)
<div class="col-md-12" style="padding-bottom: 5px;"><strong>{{ trans('general.total_cost') }}:</strong>
{{ $snipeSettings->default_currency }}
{{ Helper::formatCurrencyOutput($component->totalCostSum()) }} </div>
@endif
@if ($component->order_number)
<div class="col-md-12" style="padding-bottom: 5px;"><strong>{{ trans('general.order_number') }}:</strong>
{{ $component->order_number }} </div>
+88 -9
View File
@@ -15,19 +15,98 @@
@section('inputFields')
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/consumables/table.title')])
<!-- Name -->
<x-form-row
:label="trans('general.name')"
:$item
name="name"
/>
@include ('partials.forms.edit.category-select', ['translated_name' => trans('general.category'), 'fieldname' => 'category_id', 'required' => 'true', 'category_type' => 'consumable'])
@include ('partials.forms.edit.supplier-select', ['translated_name' => trans('general.supplier'), 'fieldname' => 'supplier_id'])
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
@include ('partials.forms.edit.model_number')
@include ('partials.forms.edit.item_number')
@include ('partials.forms.edit.order_number')
@include ('partials.forms.edit.datepicker', ['translated_name' => trans('general.purchase_date'),'fieldname' => 'purchase_date'])
@include ('partials.forms.edit.purchase_cost')
@include ('partials.forms.edit.quantity')
@include ('partials.forms.edit.minimum_quantity')
@include ('partials.forms.edit.notes')
<!-- Model Number -->
<x-form-row
:label="trans('general.model_no')"
:$item
name="model_number"
input_div_class="col-md-5 col-sm-12"
/>
<!-- Model Number -->
<x-form-row
:label="trans('admin/consumables/general.item_no')"
:$item
name="item_no"
input_div_class="col-md-5 col-sm-12"
/>
<!-- Order number -->
<x-form-row
:label="trans('general.order_number')"
:$item
name="order_number"
input_div_class="col-md-5 col-sm-12"
/>
<!-- Purchase date -->
<x-form-row
:label="trans('general.purchase_date')"
:$item
name="purchase_date"
type="date"
input_div_class="col-md-4 col-sm-12"
:value="old('purchase_date', (($item->purchase_date && $item->purchase_date->format('Y-m-d')) ?? ''))"
/>
<!-- Purchase cost -->
<x-form-row
:label="trans('general.unit_cost')"
:$item
name="purchase_cost"
type="number"
maxlength="25"
min="0.00"
max="99999999999999999.000"
step="0.001"
input_div_class="col-md-4 col-sm-12"
/>
<!-- QTY -->
<x-form-row
:label="trans('general.quantity')"
:$item
name="qty"
input_div_class="col-md-2"
/>
<!-- Min Amount -->
<x-form-row
:label="trans('general.min_amt')"
:$item
name="min_amt"
input_div_class="col-md-2"
minlength="1"
maxlength="5"
type="number"
:info_tooltip_text="trans('general.min_amt_help')"
/>
<!-- Notes -->
<x-form-row
:label="trans('general.notes')"
:$item
name="notes"
type="textarea"
maxlength="65000"
placeholder="{{ trans('general.placeholders.notes') }}"
/>
@include ('partials.forms.edit.image-upload', ['image_path' => app('consumables_upload_path')])
@stop

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