Merge branch 'develop' into feature/sc-24347
This commit is contained in:
commit
0103f20193
979 changed files with 8919 additions and 270875 deletions
|
@ -3217,6 +3217,24 @@
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "DarrenRainey",
|
||||||
|
"name": "Darren Rainey",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6136439?v=4",
|
||||||
|
"profile": "https://darrenraineys.co.uk",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "maciej-poleszczyk",
|
||||||
|
"name": "maciej-poleszczyk",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/133033121?v=4",
|
||||||
|
"profile": "https://github.com/maciej-poleszczyk",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
2
.github/workflows/tests-mysql.yml
vendored
2
.github/workflows/tests-mysql.yml
vendored
|
@ -76,4 +76,4 @@ jobs:
|
||||||
DB_DATABASE: snipeit
|
DB_DATABASE: snipeit
|
||||||
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
run: php artisan test --parallel
|
run: php artisan test
|
||||||
|
|
2
.github/workflows/tests-postgres.yml
vendored
2
.github/workflows/tests-postgres.yml
vendored
|
@ -74,4 +74,4 @@ jobs:
|
||||||
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
||||||
DB_USERNAME: snipeit
|
DB_USERNAME: snipeit
|
||||||
DB_PASSWORD: password
|
DB_PASSWORD: password
|
||||||
run: php artisan test --parallel
|
run: php artisan test
|
||||||
|
|
7
.github/workflows/tests-sqlite.yml
vendored
7
.github/workflows/tests-sqlite.yml
vendored
|
@ -43,6 +43,9 @@ jobs:
|
||||||
cp -v .env.testing.example .env
|
cp -v .env.testing.example .env
|
||||||
cp -v .env.testing.example .env.testing
|
cp -v .env.testing.example .env.testing
|
||||||
|
|
||||||
|
- name: Create database file
|
||||||
|
run: touch database/database.sqlite
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
|
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
|
||||||
|
|
||||||
|
@ -57,5 +60,5 @@ jobs:
|
||||||
|
|
||||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||||
env:
|
env:
|
||||||
DB_CONNECTION: sqlite_testing
|
DB_CONNECTION: sqlite
|
||||||
run: php artisan test --parallel
|
run: php artisan test
|
||||||
|
|
|
@ -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/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
|
| [<img src="https://avatars.githubusercontent.com/u/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/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/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") |
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
|
74
README.md
74
README.md
|
@ -1,6 +1,6 @@
|
||||||

|

|
||||||
|
|
||||||
[](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
|
[](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
|
||||||
[](#contributing) [](https://discord.gg/yZFtShAcKk)
|
[](#contributing) [](https://discord.gg/yZFtShAcKk)
|
||||||
|
|
||||||
## Snipe-IT - Open Source Asset Management System
|
## Snipe-IT - Open Source Asset Management System
|
||||||
|
@ -14,6 +14,21 @@ Snipe-IT is actively developed and we [release quite frequently](https://github.
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
|
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### Table of Contents
|
||||||
|
* [Installation](#installation)
|
||||||
|
* [User's Manual](#users-manual)
|
||||||
|
* [Bug Reports & Feature Requests](#bug-reports--feature-requests)
|
||||||
|
* [Security](#security)
|
||||||
|
* [Upgrading](#upgrading)
|
||||||
|
* [Translations!](#translations-)
|
||||||
|
* [Libraries, Modules & Related Projects](#libraries-modules--related-projects)
|
||||||
|
* [Join the Community!](#join-the-community)
|
||||||
|
* [Contributing](#contributing)
|
||||||
|
* [Announcement List](#announcement-list)
|
||||||
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
@ -22,8 +37,6 @@ For instructions on installing and configuring Snipe-IT on your server, check ou
|
||||||
|
|
||||||
If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help.
|
If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help.
|
||||||
|
|
||||||
<!-- [](https://heroku.com/deploy) -->
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
### User's Manual
|
### User's Manual
|
||||||
For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.io/docs/overview).
|
For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.io/docs/overview).
|
||||||
|
@ -35,20 +48,21 @@ Feel free to check out the [GitHub Issues for this project](https://github.com/s
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
|
> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
|
||||||
>
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.**
|
||||||
|
-----
|
||||||
|
|
||||||
|
|
||||||
### Upgrading
|
### Upgrading
|
||||||
|
|
||||||
Please see the [upgrading documentation](https://snipe-it.readme.io/docs/upgrading) for instructions on upgrading Snipe-IT.
|
Please see the [upgrading documentation](https://snipe-it.readme.io/docs/upgrading) for instructions on upgrading Snipe-IT.
|
||||||
|
|
||||||
------
|
------
|
||||||
### Announcement List
|
|
||||||
|
|
||||||
To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important.
|
|
||||||
|
|
||||||
------
|
|
||||||
|
|
||||||
### Translations!
|
### Translations!
|
||||||
|
|
||||||
Please see the [translations documentation](https://snipe-it.readme.io/docs/translations) for information about available languages and how to add translations to Snipe-IT.
|
Please see the [translations documentation](https://snipe-it.readme.io/docs/translations) for information about available languages and how to add translations to Snipe-IT.
|
||||||
|
@ -82,23 +96,33 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
### Contributing
|
### Join the Community!
|
||||||
|
|
||||||
Please refrain from submitting issues or pull requests generated by fully-automated tools. Maintainers reserve the right, at their sole discretion, to close such submissions and to block any account responsible for them.
|
- **[Join our Discord](https://discord.gg/yZFtShAcKk)!** It’s full of great people. We even wrote about it [here](https://grokstar.dev/culture/2024/06/the-unlikely-rise-of-discord-as-a-support-channel/)!
|
||||||
|
- **Follow us on Bluesky** at [@snipeitapp.com](https://bsky.app/profile/snipeitapp.com)
|
||||||
Ideally, contributions should follow from a human-to-human discussion in the form of an issue.
|
- **Follow us on Mastodon** at [hachyderm.io/@grokability](https://hachyderm.io/@grokability)
|
||||||
|
- **Follow our blog** at [Grokstar.Dev](https://grokstar.dev)
|
||||||
Please see the complete documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
|
- **Subscribe here** on Github for notifications about new releases. (We recommend selecting "Releases" only for most users - this repo can get noisy.)
|
||||||
|
|
||||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
|
||||||
|
|
||||||
The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
|
||||||
|
|
||||||
[Here is a list](CONTRIBUTORS.md) of the wonderful people that have contributed to the Snipe-IT.
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
### Security
|
### Contributing
|
||||||
|
|
||||||
|
**Please refrain from submitting issues or pull requests generated by fully-automated tools. Maintainers reserve the right, at their sole discretion, to close such submissions and to block any account responsible for them.**
|
||||||
|
|
||||||
|
Contributions should follow from a human-to-human discussion in the form of an issue for the best chances of being merged into the core project. (Sometimes we might already be working on that feature, sometimes we've decided against )
|
||||||
|
|
||||||
|
Please see the complete documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
|
||||||
|
|
||||||
|
This project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||||
|
|
||||||
|
The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
||||||
|
|
||||||
|
Be sure to check out all of the [amazing people](CONTRIBUTORS.md) that have contributed to Snipe-IT over the years!
|
||||||
|
|
||||||
|
------
|
||||||
|
### Announcement List
|
||||||
|
|
||||||
|
To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important.
|
||||||
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.**
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Asset;
|
||||||
use App\Models\Department;
|
use App\Models\Department;
|
||||||
use App\Models\Group;
|
use App\Models\Group;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
@ -322,22 +323,29 @@ class LdapSync extends Command
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$add_manager_to_cache = true;
|
||||||
if ($ldap_manager["count"] > 0) {
|
if ($ldap_manager["count"] > 0) {
|
||||||
|
try {
|
||||||
|
// Get the Manager's username
|
||||||
|
// PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array.
|
||||||
|
$ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0];
|
||||||
|
|
||||||
// Get the Manager's username
|
// Get User from Manager username.
|
||||||
// PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array.
|
$ldap_manager = User::where('username', $ldapManagerUsername)->first();
|
||||||
$ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0];
|
|
||||||
|
|
||||||
// Get User from Manager username.
|
if ($ldap_manager && isset($ldap_manager->id)) {
|
||||||
$ldap_manager = User::where('username', $ldapManagerUsername)->first();
|
// Link user to manager id.
|
||||||
|
$user->manager_id = $ldap_manager->id;
|
||||||
if ($ldap_manager && isset($ldap_manager->id)) {
|
}
|
||||||
// Link user to manager id.
|
} catch (\Exception $e) {
|
||||||
$user->manager_id = $ldap_manager->id;
|
$add_manager_to_cache = false;
|
||||||
|
\Log::warning('Handling ldap manager ' . $item['manager'] . ' caused an exception: ' . $e->getMessage() . '. Continuing synchronization.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed'
|
if ($add_manager_to_cache) {
|
||||||
|
$manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed'
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,6 +426,8 @@ class LdapSync extends Command
|
||||||
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
|
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
|
||||||
$user->groups()->attach($ldap_default_group);
|
$user->groups()->attach($ldap_default_group);
|
||||||
}
|
}
|
||||||
|
//updates assets location based on user's location
|
||||||
|
Asset::where('assigned_to', '=', $user->id)->update(['location_id' => $user->location_id]);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
foreach ($user->getErrors()->getMessages() as $key => $err) {
|
foreach ($user->getErrors()->getMessages() as $key => $err) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use Illuminate\Console\Command;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Console\Helper\ProgressIndicator;
|
||||||
|
|
||||||
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
|
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
|
||||||
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
|
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
|
||||||
|
@ -29,6 +30,11 @@ class ObjectImportCommand extends Command
|
||||||
*/
|
*/
|
||||||
protected $description = 'Import Items from CSV';
|
protected $description = 'Import Items from CSV';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The progress indicator instance.
|
||||||
|
*/
|
||||||
|
protected ProgressIndicator $progressIndicator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new command instance.
|
* Create a new command instance.
|
||||||
*
|
*
|
||||||
|
@ -39,8 +45,6 @@ class ObjectImportCommand extends Command
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
private $bar;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
*
|
*
|
||||||
|
@ -48,6 +52,8 @@ class ObjectImportCommand extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->progressIndicator = new ProgressIndicator($this->output);
|
||||||
|
|
||||||
$filename = $this->argument('filename');
|
$filename = $this->argument('filename');
|
||||||
$class = title_case($this->option('item-type'));
|
$class = title_case($this->option('item-type'));
|
||||||
$classString = "App\\Importer\\{$class}Importer";
|
$classString = "App\\Importer\\{$class}Importer";
|
||||||
|
@ -61,46 +67,25 @@ class ObjectImportCommand extends Command
|
||||||
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
|
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
|
||||||
// $logFile = $this->option('logfile');
|
// $logFile = $this->option('logfile');
|
||||||
// Log::useFiles($logFile);
|
// Log::useFiles($logFile);
|
||||||
$this->comment('======= Importing Items from '.$filename.' =========');
|
$this->progressIndicator->start('======= Importing Items from '.$filename.' =========');
|
||||||
|
|
||||||
$importer->import();
|
$importer->import();
|
||||||
|
|
||||||
$this->bar = null;
|
$this->progressIndicator->finish('Import finished.');
|
||||||
|
|
||||||
if (! empty($this->errors)) {
|
|
||||||
$this->comment('The following Errors were encountered.');
|
|
||||||
foreach ($this->errors as $asset => $error) {
|
|
||||||
$this->comment('Error: Item: '.$asset.' failed validation: '.json_encode($error));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->comment('All Items imported successfully!');
|
|
||||||
}
|
|
||||||
$this->comment('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function errorCallback($item, $field, $errorString)
|
public function errorCallback($item, $field, $error)
|
||||||
{
|
{
|
||||||
$this->errors[$item->name][$field] = $errorString;
|
$this->output->write("\x0D\x1B[2K");
|
||||||
|
|
||||||
|
$this->warn('Error: Item: '.$item->name.' failed validation: '.json_encode($error));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function progress($count)
|
public function progress($importedItemsCount)
|
||||||
{
|
{
|
||||||
if (! $this->bar) {
|
$this->progressIndicator->advance();
|
||||||
$this->bar = $this->output->createProgressBar($count);
|
|
||||||
}
|
|
||||||
static $index = 0;
|
|
||||||
$index++;
|
|
||||||
if ($index < $count) {
|
|
||||||
$this->bar->advance();
|
|
||||||
} else {
|
|
||||||
$this->bar->finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tracks the current item for error messages
|
|
||||||
private $updating;
|
|
||||||
// An array of errors encountered while parsing
|
|
||||||
private $errors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log a message to file, configurable by the --log-file parameter.
|
* Log a message to file, configurable by the --log-file parameter.
|
||||||
* If a warning message is passed, we'll spit it to the console as well.
|
* If a warning message is passed, we'll spit it to the console as well.
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Contracts\Encryption\DecryptException;
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Intervention\Image\ImageManagerStatic as Image;
|
use Intervention\Image\ImageManagerStatic as Image;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
|
|
||||||
|
@ -708,6 +709,28 @@ class Helper
|
||||||
|
|
||||||
return $randomString;
|
return $randomString;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* A method to be used to handle deprecations notifications, currently handling MS Teams. more can be added when needed.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author [Godfrey Martinez]
|
||||||
|
* @since [v7.0.14]
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function deprecationCheck() : array {
|
||||||
|
// The check and message that the user is still using the deprecated version
|
||||||
|
$deprecations = [
|
||||||
|
'ms_teams_deprecated' => array(
|
||||||
|
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'),
|
||||||
|
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Jan 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// if item of concern is being used and its being used with the deprecated values return the notification array.
|
||||||
|
if(Setting::getSettings()->webhook_selected === 'microsoft' && $deprecations['ms_teams_deprecated']['check']) {
|
||||||
|
return $deprecations;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This nasty little method gets the low inventory info for the
|
* This nasty little method gets the low inventory info for the
|
||||||
|
@ -1123,6 +1146,7 @@ class Helper
|
||||||
'png' => 'far fa-image',
|
'png' => 'far fa-image',
|
||||||
'webp' => 'far fa-image',
|
'webp' => 'far fa-image',
|
||||||
'avif' => 'far fa-image',
|
'avif' => 'far fa-image',
|
||||||
|
'svg' => 'fas fa-vector-square',
|
||||||
// word
|
// word
|
||||||
'doc' => 'far fa-file-word',
|
'doc' => 'far fa-file-word',
|
||||||
'docx' => 'far fa-file-word',
|
'docx' => 'far fa-file-word',
|
||||||
|
@ -1135,7 +1159,7 @@ class Helper
|
||||||
//Text
|
//Text
|
||||||
'txt' => 'far fa-file-alt',
|
'txt' => 'far fa-file-alt',
|
||||||
'rtf' => 'far fa-file-alt',
|
'rtf' => 'far fa-file-alt',
|
||||||
'xml' => 'far fa-file-alt',
|
'xml' => 'fas fa-code',
|
||||||
// Misc
|
// Misc
|
||||||
'pdf' => 'far fa-file-pdf',
|
'pdf' => 'far fa-file-pdf',
|
||||||
'lic' => 'far fa-save',
|
'lic' => 'far fa-save',
|
||||||
|
@ -1148,41 +1172,7 @@ class Helper
|
||||||
return 'far fa-file';
|
return 'far fa-file';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function show_file_inline($filename)
|
|
||||||
{
|
|
||||||
$extension = substr(strrchr($filename, '.'), 1);
|
|
||||||
|
|
||||||
if ($extension) {
|
|
||||||
switch ($extension) {
|
|
||||||
case 'jpg':
|
|
||||||
case 'jpeg':
|
|
||||||
case 'gif':
|
|
||||||
case 'png':
|
|
||||||
case 'webp':
|
|
||||||
case 'avif':
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random encrypted password.
|
|
||||||
*
|
|
||||||
* @author Wes Hulette <jwhulette@gmail.com>
|
|
||||||
*
|
|
||||||
* @since 5.0.0
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function generateEncyrptedPassword(): string
|
|
||||||
{
|
|
||||||
return bcrypt(self::generateUnencryptedPassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a random unencrypted password.
|
* Get a random unencrypted password.
|
||||||
|
|
|
@ -7,6 +7,7 @@ use Illuminate\Http\Response;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||||
class StorageHelper
|
class StorageHelper
|
||||||
{
|
{
|
||||||
public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse
|
public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse
|
||||||
|
@ -25,4 +26,64 @@ class StorageHelper
|
||||||
return Storage::disk($disk)->download($filename);
|
return Storage::disk($disk)->download($filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This determines the file types that should be allowed inline and checks their fileinfo extension
|
||||||
|
* to determine that they are safe to display inline.
|
||||||
|
*
|
||||||
|
* @author <A. Gianotto> [<snipe@snipe.net]>
|
||||||
|
* @since v7.0.14
|
||||||
|
* @param $file_with_path
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function allowSafeInline($file_with_path) {
|
||||||
|
|
||||||
|
$allowed_inline = [
|
||||||
|
'pdf',
|
||||||
|
'svg',
|
||||||
|
'jpg',
|
||||||
|
'gif',
|
||||||
|
'svg',
|
||||||
|
'avif',
|
||||||
|
'webp',
|
||||||
|
'png',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// The file exists and is allowed to be displayed inline
|
||||||
|
if (Storage::exists($file_with_path) && (in_array(pathinfo($file_with_path, PATHINFO_EXTENSION), $allowed_inline))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide whether to show the file inline or download it.
|
||||||
|
*/
|
||||||
|
public static function showOrDownloadFile($file, $filename) {
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
|
||||||
|
if (request('inline') == 'true') {
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
|
||||||
|
// This is NOT allowed as inline - force it to be displayed as text in the browser
|
||||||
|
if (self::allowSafeInline($file) != true) {
|
||||||
|
$headers = array_merge($headers, ['Content-Type' => 'text/plain']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else seems okay, but the file doesn't exist on the server.
|
||||||
|
if (Storage::missing($file)) {
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Storage::download($file, $filename, $headers);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,50 +106,29 @@ class AccessoriesFilesController extends Controller
|
||||||
* @param int $accessoryId
|
* @param int $accessoryId
|
||||||
* @param int $fileId
|
* @param int $fileId
|
||||||
*/
|
*/
|
||||||
public function show($accessoryId = null, $fileId = null, $download = true) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
|
public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
|
||||||
{
|
{
|
||||||
|
|
||||||
Log::debug('Private filesystem is: '.config('filesystems.default'));
|
|
||||||
$accessory = Accessory::find($accessoryId);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// the accessory is valid
|
// the accessory is valid
|
||||||
if (isset($accessory->id)) {
|
if ($accessory = Accessory::find($accessoryId)) {
|
||||||
$this->authorize('view', $accessory);
|
$this->authorize('view', $accessory);
|
||||||
$this->authorize('accessories.files', $accessory);
|
$this->authorize('accessories.files', $accessory);
|
||||||
|
|
||||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
|
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
|
||||||
return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found'));
|
$file = 'private_uploads/accessories/'.$log->filename;
|
||||||
}
|
|
||||||
|
|
||||||
$file = 'private_uploads/accessories/'.$log->filename;
|
try {
|
||||||
|
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||||
if (Storage::missing($file)) {
|
} catch (\Exception $e) {
|
||||||
Log::debug('FILE DOES NOT EXISTS for '.$file);
|
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found'));
|
||||||
Log::debug('URL should be '.Storage::url($file));
|
|
||||||
|
|
||||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Display the file inline
|
|
||||||
if (request('inline') == 'true') {
|
|
||||||
$headers = [
|
|
||||||
'Content-Disposition' => 'inline',
|
|
||||||
];
|
|
||||||
return Storage::download($file, $log->filename, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
|
||||||
// won't work, as they're not accessible via the web
|
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.log_record_not_found'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
return redirect()->route('accessories.index')->with('error', trans('general.file_not_found'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,4 +338,5 @@ class AcceptanceController extends Controller
|
||||||
return redirect()->to('account/accept')->with('success', $return_msg);
|
return redirect()->to('account/accept')->with('success', $return_msg);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,16 @@ class ActionlogController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStoredEula($filename) : Response | BinaryFileResponse
|
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('view', \App\Models\Asset::class);
|
$this->authorize('view', \App\Models\Asset::class);
|
||||||
$file = config('app.private_uploads').'/eula-pdfs/'.$filename;
|
$file = config('app.private_uploads').'/eula-pdfs/'.$filename;
|
||||||
return response()->download($file);
|
|
||||||
|
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
|
||||||
|
return response()->download($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,20 +56,21 @@ class AssetModelsController extends Controller
|
||||||
'models.id',
|
'models.id',
|
||||||
'models.image',
|
'models.image',
|
||||||
'models.name',
|
'models.name',
|
||||||
'model_number',
|
'models.model_number',
|
||||||
'min_amt',
|
'models.min_amt',
|
||||||
'eol',
|
'models.eol',
|
||||||
'requestable',
|
'models.created_by',
|
||||||
|
'models.requestable',
|
||||||
'models.notes',
|
'models.notes',
|
||||||
'models.created_at',
|
'models.created_at',
|
||||||
'category_id',
|
'models.category_id',
|
||||||
'manufacturer_id',
|
'models.manufacturer_id',
|
||||||
'depreciation_id',
|
'models.depreciation_id',
|
||||||
'fieldset_id',
|
'models.fieldset_id',
|
||||||
'models.deleted_at',
|
'models.deleted_at',
|
||||||
'models.updated_at',
|
'models.updated_at',
|
||||||
])
|
])
|
||||||
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues','adminuser')
|
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
||||||
->withCount('assets as assets_count');
|
->withCount('assets as assets_count');
|
||||||
|
|
||||||
if ($request->input('status')=='deleted') {
|
if ($request->input('status')=='deleted') {
|
||||||
|
@ -95,7 +96,7 @@ class AssetModelsController extends Controller
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';
|
||||||
|
|
||||||
switch ($sort) {
|
switch ($request->input('sort')) {
|
||||||
case 'manufacturer':
|
case 'manufacturer':
|
||||||
$assetmodels->OrderManufacturer($order);
|
$assetmodels->OrderManufacturer($order);
|
||||||
break;
|
break;
|
||||||
|
@ -105,6 +106,9 @@ class AssetModelsController extends Controller
|
||||||
case 'fieldset':
|
case 'fieldset':
|
||||||
$assetmodels->OrderFieldset($order);
|
$assetmodels->OrderFieldset($order);
|
||||||
break;
|
break;
|
||||||
|
case 'created_by':
|
||||||
|
$assetmodels->OrderByCreatedByName($order);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$assetmodels->orderBy($sort, $order);
|
$assetmodels->orderBy($sort, $order);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -33,6 +33,8 @@ use Illuminate\Http\Request;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\View\Label;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,10 +82,10 @@ class AssetsController extends Controller
|
||||||
$this->authorize('reports.view');
|
$this->authorize('reports.view');
|
||||||
} else {
|
} else {
|
||||||
$transformer = 'App\Http\Transformers\AssetsTransformer';
|
$transformer = 'App\Http\Transformers\AssetsTransformer';
|
||||||
$this->authorize('index', Asset::class);
|
$this->authorize('index', Asset::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$settings = Setting::getSettings();
|
$settings = Setting::getSettings();
|
||||||
|
|
||||||
$allowed_columns = [
|
$allowed_columns = [
|
||||||
|
@ -126,8 +128,19 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$assets = Asset::select('assets.*')
|
$assets = Asset::select('assets.*')
|
||||||
->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo', 'adminuser','model.depreciation',
|
->with(
|
||||||
'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
'location',
|
||||||
|
'assetstatus',
|
||||||
|
'company',
|
||||||
|
'defaultLoc',
|
||||||
|
'assignedTo',
|
||||||
|
'adminuser',
|
||||||
|
'model.depreciation',
|
||||||
|
'model.category',
|
||||||
|
'model.manufacturer',
|
||||||
|
'model.fieldset',
|
||||||
|
'supplier'
|
||||||
|
); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
||||||
|
|
||||||
|
|
||||||
if ($filter_non_deprecable_assets) {
|
if ($filter_non_deprecable_assets) {
|
||||||
|
@ -159,8 +172,8 @@ class AssetsController extends Controller
|
||||||
* Handle due and overdue audits and checkin dates
|
* Handle due and overdue audits and checkin dates
|
||||||
*/
|
*/
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
// Audit (singular) is left over from earlier legacy APIs
|
// Audit (singular) is left over from earlier legacy APIs
|
||||||
case 'audits' :
|
case 'audits':
|
||||||
switch ($upcoming_status) {
|
switch ($upcoming_status) {
|
||||||
case 'due':
|
case 'due':
|
||||||
$assets->DueForAudit($settings);
|
$assets->DueForAudit($settings);
|
||||||
|
@ -187,7 +200,7 @@ class AssetsController extends Controller
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* End handling due and overdue audits and checkin dates
|
* End handling due and overdue audits and checkin dates
|
||||||
|
@ -265,7 +278,6 @@ class AssetsController extends Controller
|
||||||
$join->on('status_alias.id', '=', 'assets.status_id');
|
$join->on('status_alias.id', '=', 'assets.status_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -345,7 +357,7 @@ class AssetsController extends Controller
|
||||||
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
|
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
||||||
switch ($sort_override) {
|
switch ($sort_override) {
|
||||||
case 'model':
|
case 'model':
|
||||||
$assets->OrderModels($order);
|
$assets->OrderModels($order);
|
||||||
|
@ -399,7 +411,6 @@ class AssetsController extends Controller
|
||||||
} else {
|
} else {
|
||||||
$assets->orderBy($sort_override, $order);
|
$assets->orderBy($sort_override, $order);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$assets->orderBy($column_sort, $order);
|
$assets->orderBy($column_sort, $order);
|
||||||
}
|
}
|
||||||
|
@ -413,11 +424,11 @@ class AssetsController extends Controller
|
||||||
|
|
||||||
$total = $assets->count();
|
$total = $assets->count();
|
||||||
$assets = $assets->skip($offset)->take($limit)->get();
|
$assets = $assets->skip($offset)->take($limit)->get();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Include additional associated relationships
|
* Include additional associated relationships
|
||||||
*/
|
*/
|
||||||
if ($request->input('components')) {
|
if ($request->input('components')) {
|
||||||
$assets->loadMissing(['components' => function ($query) {
|
$assets->loadMissing(['components' => function ($query) {
|
||||||
$query->orderBy('created_at', 'desc');
|
$query->orderBy('created_at', 'desc');
|
||||||
|
@ -441,7 +452,7 @@ class AssetsController extends Controller
|
||||||
* @since [v4.2.1]
|
* @since [v4.2.1]
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
*/
|
*/
|
||||||
public function showByTag(Request $request, $tag) : JsonResponse | array
|
public function showByTag(Request $request, $tag): JsonResponse | array
|
||||||
{
|
{
|
||||||
$this->authorize('index', Asset::class);
|
$this->authorize('index', Asset::class);
|
||||||
$assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo');
|
$assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo');
|
||||||
|
@ -463,12 +474,10 @@ class AssetsController extends Controller
|
||||||
} else {
|
} else {
|
||||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are 0 results, return the "no such asset" response
|
// If there are 0 results, return the "no such asset" response
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -479,7 +488,7 @@ class AssetsController extends Controller
|
||||||
* @since [v4.2.1]
|
* @since [v4.2.1]
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function showBySerial(Request $request, $serial) : JsonResponse | array
|
public function showBySerial(Request $request, $serial): JsonResponse | array
|
||||||
{
|
{
|
||||||
$this->authorize('index', Asset::class);
|
$this->authorize('index', Asset::class);
|
||||||
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
|
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
|
||||||
|
@ -488,14 +497,13 @@ class AssetsController extends Controller
|
||||||
if ($request->input('deleted', 'false') == 'true') {
|
if ($request->input('deleted', 'false') == 'true') {
|
||||||
$assets = $assets->withTrashed();
|
$assets = $assets->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($assets = $assets->get()) && ($assets->count()) > 0) {
|
if (($assets = $assets->get()) && ($assets->count()) > 0) {
|
||||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are 0 results, return the "no such asset" response
|
// If there are 0 results, return the "no such asset" response
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -506,20 +514,20 @@ class AssetsController extends Controller
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, $id) : JsonResponse | array
|
public function show(Request $request, $id): JsonResponse | array
|
||||||
{
|
{
|
||||||
if ($asset = Asset::with('assetstatus')
|
if ($asset = Asset::with('assetstatus')
|
||||||
->with('assignedTo')->withTrashed()
|
->with('assignedTo')->withTrashed()
|
||||||
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id)) {
|
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id)
|
||||||
|
) {
|
||||||
$this->authorize('view', $asset);
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
return (new AssetsTransformer)->transformAsset($asset, $request->input('components') );
|
return (new AssetsTransformer)->transformAsset($asset, $request->input('components'));
|
||||||
}
|
}
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function licenses(Request $request, $id) : array
|
public function licenses(Request $request, $id): array
|
||||||
{
|
{
|
||||||
$this->authorize('view', Asset::class);
|
$this->authorize('view', Asset::class);
|
||||||
$this->authorize('view', License::class);
|
$this->authorize('view', License::class);
|
||||||
|
@ -527,7 +535,7 @@ class AssetsController extends Controller
|
||||||
$licenses = $asset->licenses()->get();
|
$licenses = $asset->licenses()->get();
|
||||||
|
|
||||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -537,7 +545,7 @@ class AssetsController extends Controller
|
||||||
* @since [v4.0.16]
|
* @since [v4.0.16]
|
||||||
* @see \App\Http\Transformers\SelectlistTransformer
|
* @see \App\Http\Transformers\SelectlistTransformer
|
||||||
*/
|
*/
|
||||||
public function selectlist(Request $request) : array
|
public function selectlist(Request $request): array
|
||||||
{
|
{
|
||||||
|
|
||||||
$assets = Asset::select([
|
$assets = Asset::select([
|
||||||
|
@ -548,7 +556,7 @@ class AssetsController extends Controller
|
||||||
'assets.assigned_to',
|
'assets.assigned_to',
|
||||||
'assets.assigned_type',
|
'assets.assigned_type',
|
||||||
'assets.status_id',
|
'assets.status_id',
|
||||||
])->with('model', 'assetstatus', 'assignedTo')->NotArchived();
|
])->with('model', 'assetstatus', 'assignedTo')->NotArchived();
|
||||||
|
|
||||||
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
|
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
|
||||||
$assets = $assets->RTD();
|
$assets = $assets->RTD();
|
||||||
|
@ -570,12 +578,12 @@ class AssetsController extends Controller
|
||||||
$asset->use_text = $asset->present()->fullName;
|
$asset->use_text = $asset->present()->fullName;
|
||||||
|
|
||||||
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
||||||
$asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
|
$asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($asset->assetstatus->getStatuslabelType() == 'pending') {
|
if ($asset->assetstatus->getStatuslabelType() == 'pending') {
|
||||||
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
|
$asset->use_text .= '(' . $asset->assetstatus->getStatuslabelType() . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null;
|
$asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null;
|
||||||
|
@ -601,12 +609,12 @@ class AssetsController extends Controller
|
||||||
$asset->created_by = auth()->id();
|
$asset->created_by = auth()->id();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is here just legacy reasons. Api\AssetController
|
* this is here just legacy reasons. Api\AssetController
|
||||||
* used image_source once to allow encoded image uploads.
|
* used image_source once to allow encoded image uploads.
|
||||||
*/
|
*/
|
||||||
if ($request->has('image_source')) {
|
if ($request->has('image_source')) {
|
||||||
$request->offsetSet('image', $request->offsetGet('image_source'));
|
$request->offsetSet('image', $request->offsetGet('image_source'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset = $request->handleImages($asset);
|
$asset = $request->handleImages($asset);
|
||||||
|
|
||||||
|
@ -623,9 +631,9 @@ class AssetsController extends Controller
|
||||||
|
|
||||||
// If input value is null, use custom field's default value
|
// If input value is null, use custom field's default value
|
||||||
if ($field_val == null) {
|
if ($field_val == null) {
|
||||||
Log::debug('Field value for '.$field->db_column.' is null');
|
Log::debug('Field value for ' . $field->db_column . ' is null');
|
||||||
$field_val = $field->defaultValue($request->get('model_id'));
|
$field_val = $field->defaultValue($request->get('model_id'));
|
||||||
Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
|
Log::debug('Use the default fieldset value of ' . $field->defaultValue($request->get('model_id')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the field is set to encrypted, make sure we encrypt the value
|
// if the field is set to encrypted, make sure we encrypt the value
|
||||||
|
@ -643,7 +651,7 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($field->element == 'checkbox') {
|
if ($field->element == 'checkbox') {
|
||||||
if(is_array($field_val)) {
|
if (is_array($field_val)) {
|
||||||
$field_val = implode(',', $field_val);
|
$field_val = implode(',', $field_val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -702,64 +710,64 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is here just legacy reasons. Api\AssetController
|
* this is here just legacy reasons. Api\AssetController
|
||||||
* used image_source once to allow encoded image uploads.
|
* used image_source once to allow encoded image uploads.
|
||||||
*/
|
*/
|
||||||
if ($request->has('image_source')) {
|
if ($request->has('image_source')) {
|
||||||
$request->offsetSet('image', $request->offsetGet('image_source'));
|
$request->offsetSet('image', $request->offsetGet('image_source'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset = $request->handleImages($asset);
|
$asset = $request->handleImages($asset);
|
||||||
$model = $asset->model;
|
$model = $asset->model;
|
||||||
|
|
||||||
// Update custom fields
|
|
||||||
$problems_updating_encrypted_custom_fields = false;
|
|
||||||
if (($model) && (isset($model->fieldset))) {
|
|
||||||
foreach ($model->fieldset->fields as $field) {
|
|
||||||
$field_val = $request->input($field->db_column, null);
|
|
||||||
|
|
||||||
if ($request->has($field->db_column)) {
|
// Update custom fields
|
||||||
if ($field->element == 'checkbox') {
|
$problems_updating_encrypted_custom_fields = false;
|
||||||
if(is_array($field_val)) {
|
if (($model) && (isset($model->fieldset))) {
|
||||||
$field_val = implode(',', $field_val);
|
foreach ($model->fieldset->fields as $field) {
|
||||||
}
|
$field_val = $request->input($field->db_column, null);
|
||||||
|
|
||||||
|
if ($request->has($field->db_column)) {
|
||||||
|
if ($field->element == 'checkbox') {
|
||||||
|
if (is_array($field_val)) {
|
||||||
|
$field_val = implode(',', $field_val);
|
||||||
}
|
}
|
||||||
if ($field->field_encrypted == '1') {
|
|
||||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
|
||||||
$field_val = Crypt::encrypt($field_val);
|
|
||||||
} else {
|
|
||||||
$problems_updating_encrypted_custom_fields = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$asset->{$field->db_column} = $field_val;
|
|
||||||
}
|
}
|
||||||
|
if ($field->field_encrypted == '1') {
|
||||||
|
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||||
|
$field_val = Crypt::encrypt($field_val);
|
||||||
|
} else {
|
||||||
|
$problems_updating_encrypted_custom_fields = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$asset->{$field->db_column} = $field_val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($asset->save()) {
|
}
|
||||||
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
|
if ($asset->save()) {
|
||||||
$location = $target->location_id;
|
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
|
||||||
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
|
$location = $target->location_id;
|
||||||
$location = $target->location_id;
|
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
|
||||||
|
$location = $target->location_id;
|
||||||
|
|
||||||
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
|
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
|
||||||
->update(['location_id' => $target->location_id]);
|
->update(['location_id' => $target->location_id]);
|
||||||
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
|
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
|
||||||
$location = $target->id;
|
$location = $target->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($target)) {
|
if (isset($target)) {
|
||||||
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
|
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($asset->image) {
|
if ($asset->image) {
|
||||||
$asset->image = $asset->getImageUrl();
|
$asset->image = $asset->getImageUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($problems_updating_encrypted_custom_fields) {
|
if ($problems_updating_encrypted_custom_fields) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
|
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.encrypted_warning')));
|
||||||
} else {
|
} else {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||||
|
@ -773,7 +781,7 @@ class AssetsController extends Controller
|
||||||
* @param int $assetId
|
* @param int $assetId
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
*/
|
*/
|
||||||
public function destroy($id) : JsonResponse
|
public function destroy($id): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Asset::class);
|
$this->authorize('delete', Asset::class);
|
||||||
|
|
||||||
|
@ -799,7 +807,7 @@ class AssetsController extends Controller
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore a soft-deleted asset.
|
* Restore a soft-deleted asset.
|
||||||
|
@ -808,7 +816,7 @@ class AssetsController extends Controller
|
||||||
* @param int $assetId
|
* @param int $assetId
|
||||||
* @since [v5.1.18]
|
* @since [v5.1.18]
|
||||||
*/
|
*/
|
||||||
public function restore(Request $request, $assetId = null) : JsonResponse
|
public function restore(Request $request, $assetId = null): JsonResponse
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($asset = Asset::withTrashed()->find($assetId)) {
|
if ($asset = Asset::withTrashed()->find($assetId)) {
|
||||||
|
@ -827,7 +835,6 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -837,7 +844,7 @@ class AssetsController extends Controller
|
||||||
* @param string $tag
|
* @param string $tag
|
||||||
* @since [v6.0.5]
|
* @since [v6.0.5]
|
||||||
*/
|
*/
|
||||||
public function checkoutByTag(AssetCheckoutRequest $request, $tag) : JsonResponse
|
public function checkoutByTag(AssetCheckoutRequest $request, $tag): JsonResponse
|
||||||
{
|
{
|
||||||
if ($asset = Asset::where('asset_tag', $tag)->first()) {
|
if ($asset = Asset::where('asset_tag', $tag)->first()) {
|
||||||
return $this->checkout($request, $asset->id);
|
return $this->checkout($request, $asset->id);
|
||||||
|
@ -852,13 +859,13 @@ class AssetsController extends Controller
|
||||||
* @param int $assetId
|
* @param int $assetId
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
*/
|
*/
|
||||||
public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonResponse
|
public function checkout(AssetCheckoutRequest $request, $asset_id): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('checkout', Asset::class);
|
$this->authorize('checkout', Asset::class);
|
||||||
$asset = Asset::findOrFail($asset_id);
|
$asset = Asset::findOrFail($asset_id);
|
||||||
|
|
||||||
if (! $asset->availableForCheckout()) {
|
if (! $asset->availableForCheckout()) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available')));
|
return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available')));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->authorize('checkout', $asset);
|
$this->authorize('checkout', $asset);
|
||||||
|
@ -875,14 +882,12 @@ class AssetsController extends Controller
|
||||||
$asset->location_id = ($target) ? $target->id : '';
|
$asset->location_id = ($target) ? $target->id : '';
|
||||||
$error_payload['target_id'] = $request->input('assigned_location');
|
$error_payload['target_id'] = $request->input('assigned_location');
|
||||||
$error_payload['target_type'] = 'location';
|
$error_payload['target_type'] = 'location';
|
||||||
|
|
||||||
} elseif (request('checkout_to_type') == 'asset') {
|
} elseif (request('checkout_to_type') == 'asset') {
|
||||||
$target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset'));
|
$target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset'));
|
||||||
// Override with the asset's location_id if it has one
|
// Override with the asset's location_id if it has one
|
||||||
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
|
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
|
||||||
$error_payload['target_id'] = $request->input('assigned_asset');
|
$error_payload['target_id'] = $request->input('assigned_asset');
|
||||||
$error_payload['target_type'] = 'asset';
|
$error_payload['target_type'] = 'asset';
|
||||||
|
|
||||||
} elseif (request('checkout_to_type') == 'user') {
|
} elseif (request('checkout_to_type') == 'user') {
|
||||||
// Fetch the target and set the asset's new location_id
|
// Fetch the target and set the asset's new location_id
|
||||||
$target = User::find(request('assigned_user'));
|
$target = User::find(request('assigned_user'));
|
||||||
|
@ -896,7 +901,7 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! isset($target)) {
|
if (! isset($target)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
|
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset ' . e($asset->asset_tag) . ' is invalid - ' . $error_payload['target_type'] . ' does not exist.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
|
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
|
||||||
|
@ -910,15 +915,15 @@ class AssetsController extends Controller
|
||||||
// TODO: Follow up here. WTF. Commented out for now.
|
// TODO: Follow up here. WTF. Commented out for now.
|
||||||
|
|
||||||
|
|
||||||
// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
|
// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
|
||||||
// $asset->location_id = $target->rtd_location_id;
|
// $asset->location_id = $target->rtd_location_id;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if ($asset->checkOut($target, auth()->user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
|
if ($asset->checkOut($target, auth()->user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
|
return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -929,7 +934,7 @@ class AssetsController extends Controller
|
||||||
* @param int $assetId
|
* @param int $assetId
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
*/
|
*/
|
||||||
public function checkin(Request $request, $asset_id) : JsonResponse
|
public function checkin(Request $request, $asset_id): JsonResponse
|
||||||
{
|
{
|
||||||
$asset = Asset::with('model')->findOrFail($asset_id);
|
$asset = Asset::with('model')->findOrFail($asset_id);
|
||||||
$this->authorize('checkin', $asset);
|
$this->authorize('checkin', $asset);
|
||||||
|
@ -937,7 +942,7 @@ class AssetsController extends Controller
|
||||||
$target = $asset->assignedTo;
|
$target = $asset->assignedTo;
|
||||||
if (is_null($target)) {
|
if (is_null($target)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
'asset_tag'=> e($asset->asset_tag),
|
'asset_tag' => e($asset->asset_tag),
|
||||||
'model' => e($asset->model->name),
|
'model' => e($asset->model->name),
|
||||||
'model_number' => e($asset->model->model_number)
|
'model_number' => e($asset->model->model_number)
|
||||||
], trans('admin/hardware/message.checkin.already_checked_in')));
|
], trans('admin/hardware/message.checkin.already_checked_in')));
|
||||||
|
@ -960,7 +965,7 @@ class AssetsController extends Controller
|
||||||
if ($request->filled('location_id')) {
|
if ($request->filled('location_id')) {
|
||||||
$asset->location_id = $request->input('location_id');
|
$asset->location_id = $request->input('location_id');
|
||||||
|
|
||||||
if ($request->input('update_default_location')){
|
if ($request->input('update_default_location')) {
|
||||||
$asset->rtd_location_id = $request->input('location_id');
|
$asset->rtd_location_id = $request->input('location_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -968,8 +973,8 @@ class AssetsController extends Controller
|
||||||
if ($request->filled('status_id')) {
|
if ($request->filled('status_id')) {
|
||||||
$asset->status_id = $request->input('status_id');
|
$asset->status_id = $request->input('status_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
|
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at') . ' ' . date('H:i:s') : date('Y-m-d H:i:s');
|
||||||
$originalValues = $asset->getRawOriginal();
|
$originalValues = $asset->getRawOriginal();
|
||||||
|
|
||||||
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
||||||
|
@ -987,7 +992,8 @@ class AssetsController extends Controller
|
||||||
[Asset::class],
|
[Asset::class],
|
||||||
function (Builder $query) use ($asset) {
|
function (Builder $query) use ($asset) {
|
||||||
$query->where('id', $asset->id);
|
$query->where('id', $asset->id);
|
||||||
})
|
}
|
||||||
|
)
|
||||||
->get()
|
->get()
|
||||||
->map(function ($acceptance) {
|
->map(function ($acceptance) {
|
||||||
$acceptance->delete();
|
$acceptance->delete();
|
||||||
|
@ -997,13 +1003,13 @@ class AssetsController extends Controller
|
||||||
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
|
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', [
|
return response()->json(Helper::formatStandardApiResponse('success', [
|
||||||
'asset_tag'=> e($asset->asset_tag),
|
'asset_tag' => e($asset->asset_tag),
|
||||||
'model' => e($asset->model->name),
|
'model' => e($asset->model->name),
|
||||||
'model_number' => e($asset->model->model_number)
|
'model_number' => e($asset->model->model_number)
|
||||||
], trans('admin/hardware/message.checkin.success')));
|
], trans('admin/hardware/message.checkin.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
|
return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1012,7 +1018,7 @@ class AssetsController extends Controller
|
||||||
* @author [A. Janes] [<ajanes@adagiohealth.org>]
|
* @author [A. Janes] [<ajanes@adagiohealth.org>]
|
||||||
* @since [v6.0]
|
* @since [v6.0]
|
||||||
*/
|
*/
|
||||||
public function checkinByTag(Request $request, $tag = null) : JsonResponse
|
public function checkinByTag(Request $request, $tag = null): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('checkin', Asset::class);
|
$this->authorize('checkin', Asset::class);
|
||||||
if (null == $tag && null !== ($request->input('asset_tag'))) {
|
if (null == $tag && null !== ($request->input('asset_tag'))) {
|
||||||
|
@ -1025,8 +1031,8 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
'asset'=> e($tag)
|
'asset' => e($tag)
|
||||||
], 'Asset with tag '.e($tag).' not found'));
|
], 'Asset with tag ' . e($tag) . ' not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1037,7 +1043,7 @@ class AssetsController extends Controller
|
||||||
* @param int $id
|
* @param int $id
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
*/
|
*/
|
||||||
public function audit(Request $request) : JsonResponse
|
public function audit(Request $request): JsonResponse
|
||||||
|
|
||||||
{
|
{
|
||||||
$this->authorize('audit', Asset::class);
|
$this->authorize('audit', Asset::class);
|
||||||
|
@ -1048,8 +1054,8 @@ class AssetsController extends Controller
|
||||||
// No tag passed - return an error
|
// No tag passed - return an error
|
||||||
if (!$request->filled('asset_tag')) {
|
if (!$request->filled('asset_tag')) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
'asset_tag'=> '',
|
'asset_tag' => '',
|
||||||
'error'=> trans('admin/hardware/message.no_tag'),
|
'error' => trans('admin/hardware/message.no_tag'),
|
||||||
], trans('admin/hardware/message.no_tag')), 200);
|
], trans('admin/hardware/message.no_tag')), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1097,28 +1103,25 @@ class AssetsController extends Controller
|
||||||
$asset->logAudit(request('note'), request('location_id'));
|
$asset->logAudit(request('note'), request('location_id'));
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', [
|
return response()->json(Helper::formatStandardApiResponse('success', [
|
||||||
'asset_tag'=> e($asset->asset_tag),
|
'asset_tag' => e($asset->asset_tag),
|
||||||
'note'=> e($request->input('note')),
|
'note' => e($request->input('note')),
|
||||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
||||||
], trans('admin/hardware/message.audit.success')));
|
], trans('admin/hardware/message.audit.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asset failed validation or was not able to be saved
|
// Asset failed validation or was not able to be saved
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
'asset_tag'=> e($asset->asset_tag),
|
'asset_tag' => e($asset->asset_tag),
|
||||||
'error'=> $asset->getErrors()->first(),
|
'error' => $asset->getErrors()->first(),
|
||||||
], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200);
|
], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// No matching asset for the asset tag that was passed.
|
// No matching asset for the asset tag that was passed.
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
'asset_tag'=> e($request->input('asset_tag')),
|
'asset_tag' => e($request->input('asset_tag')),
|
||||||
'error'=> trans('admin/hardware/message.audit.error'),
|
'error' => trans('admin/hardware/message.audit.error'),
|
||||||
], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200);
|
], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1129,7 +1132,7 @@ class AssetsController extends Controller
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
*/
|
*/
|
||||||
public function requestable(Request $request) : JsonResponse | array
|
public function requestable(Request $request): JsonResponse | array
|
||||||
{
|
{
|
||||||
$this->authorize('viewRequestable', Asset::class);
|
$this->authorize('viewRequestable', Asset::class);
|
||||||
|
|
||||||
|
@ -1150,8 +1153,18 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$assets = Asset::select('assets.*')
|
$assets = Asset::select('assets.*')
|
||||||
->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
|
->with(
|
||||||
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests');
|
'location',
|
||||||
|
'assetstatus',
|
||||||
|
'assetlog',
|
||||||
|
'company',
|
||||||
|
'assignedTo',
|
||||||
|
'model.category',
|
||||||
|
'model.manufacturer',
|
||||||
|
'model.fieldset',
|
||||||
|
'supplier',
|
||||||
|
'requests'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1159,7 +1172,7 @@ class AssetsController extends Controller
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$assets->TextSearch($request->input('search'));
|
$assets->TextSearch($request->input('search'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search custom fields by column name
|
// Search custom fields by column name
|
||||||
foreach ($all_custom_fields as $field) {
|
foreach ($all_custom_fields as $field) {
|
||||||
if ($request->filled($field->db_column_name())) {
|
if ($request->filled($field->db_column_name())) {
|
||||||
|
@ -1200,4 +1213,89 @@ class AssetsController extends Controller
|
||||||
|
|
||||||
return (new AssetsTransformer)->transformRequestedAssets($assets, $total);
|
return (new AssetsTransformer)->transformRequestedAssets($assets, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate asset labels by tag
|
||||||
|
*
|
||||||
|
* @author [Nebelkreis] [https://github.com/NebelKreis]
|
||||||
|
*
|
||||||
|
* @param Request $request Contains asset_tags array of asset tags to generate labels for
|
||||||
|
* @return JsonResponse Returns base64 encoded PDF on success, error message on failure
|
||||||
|
*/
|
||||||
|
public function getLabels(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('view', Asset::class);
|
||||||
|
|
||||||
|
// Validate that asset tags were provided in the request
|
||||||
|
if (!$request->filled('asset_tags')) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null,
|
||||||
|
trans('admin/hardware/message.no_assets_selected')), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert asset tags from request into collection and fetch matching assets
|
||||||
|
$asset_tags = collect($request->input('asset_tags'));
|
||||||
|
$assets = Asset::whereIn('asset_tag', $asset_tags)->get();
|
||||||
|
|
||||||
|
// Return error if no assets were found for the provided tags
|
||||||
|
if ($assets->isEmpty()) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null,
|
||||||
|
trans('admin/hardware/message.does_not_exist')), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$settings = Setting::getSettings();
|
||||||
|
|
||||||
|
// Check if logo file exists in storage and disable logo if not found
|
||||||
|
// This prevents errors when trying to include a non-existent logo in the PDF
|
||||||
|
$settings->label_logo = ($original_logo = $settings->label_logo) && !Storage::disk('public')->exists('/' . $original_logo) ? null : $settings->label_logo;
|
||||||
|
|
||||||
|
|
||||||
|
$label = new Label();
|
||||||
|
|
||||||
|
if (!$label) {
|
||||||
|
throw new \Exception('Label object could not be created');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure label with assets and settings
|
||||||
|
// bulkedit=false and count=0 are default values for label generation
|
||||||
|
$label = $label->with('assets', $assets)
|
||||||
|
->with('settings', $settings)
|
||||||
|
->with('bulkedit', false)
|
||||||
|
->with('count', 0);
|
||||||
|
|
||||||
|
// Generate PDF using callback function
|
||||||
|
// The callback captures the PDF content in $pdf_content variable
|
||||||
|
$pdf_content = '';
|
||||||
|
$label->render(function($pdf) use (&$pdf_content) {
|
||||||
|
$pdf_content = $pdf->Output('', 'S');
|
||||||
|
return $pdf;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify PDF was generated successfully
|
||||||
|
if (empty($pdf_content)) {
|
||||||
|
throw new \Exception('PDF content is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$encoded_content = base64_encode($pdf_content);
|
||||||
|
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', [
|
||||||
|
'pdf' => $encoded_content
|
||||||
|
], trans('admin/hardware/message.labels_generated')));
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
|
'error_message' => $e->getMessage(),
|
||||||
|
'error_line' => $e->getLine(),
|
||||||
|
'error_file' => $e->getFile()
|
||||||
|
], trans('admin/hardware/message.error_generating_labels')), 500);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
|
'error_message' => $e->getMessage(),
|
||||||
|
'error_line' => $e->getLine(),
|
||||||
|
'error_file' => $e->getFile()
|
||||||
|
], $e->getMessage()), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ class ComponentsController extends Controller
|
||||||
'name',
|
'name',
|
||||||
'min_amt',
|
'min_amt',
|
||||||
'order_number',
|
'order_number',
|
||||||
|
'model_number',
|
||||||
'serial',
|
'serial',
|
||||||
'purchase_date',
|
'purchase_date',
|
||||||
'purchase_cost',
|
'purchase_cost',
|
||||||
|
@ -47,7 +48,7 @@ class ComponentsController extends Controller
|
||||||
];
|
];
|
||||||
|
|
||||||
$components = Component::select('components.*')
|
$components = Component::select('components.*')
|
||||||
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser');
|
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$components = $components->TextSearch($request->input('search'));
|
$components = $components->TextSearch($request->input('search'));
|
||||||
|
@ -69,6 +70,14 @@ class ComponentsController extends Controller
|
||||||
$components->where('supplier_id', '=', $request->input('supplier_id'));
|
$components->where('supplier_id', '=', $request->input('supplier_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('manufacturer_id')) {
|
||||||
|
$components->where('manufacturer_id', '=', $request->input('manufacturer_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('model_number')) {
|
||||||
|
$components->where('model_number', '=', $request->input('model_number'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('location_id')) {
|
if ($request->filled('location_id')) {
|
||||||
$components->where('location_id', '=', $request->input('location_id'));
|
$components->where('location_id', '=', $request->input('location_id'));
|
||||||
}
|
}
|
||||||
|
@ -98,6 +107,9 @@ class ComponentsController extends Controller
|
||||||
case 'supplier':
|
case 'supplier':
|
||||||
$components = $components->OrderSupplier($order);
|
$components = $components->OrderSupplier($order);
|
||||||
break;
|
break;
|
||||||
|
case 'manufacturer':
|
||||||
|
$components = $components->OrderManufacturer($order);
|
||||||
|
break;
|
||||||
case 'created_by':
|
case 'created_by':
|
||||||
$components = $components->OrderByCreatedBy($order);
|
$components = $components->OrderByCreatedBy($order);
|
||||||
break;
|
break;
|
||||||
|
@ -297,9 +309,7 @@ class ComponentsController extends Controller
|
||||||
public function checkin(Request $request, $component_asset_id) : JsonResponse
|
public function checkin(Request $request, $component_asset_id) : JsonResponse
|
||||||
{
|
{
|
||||||
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
|
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
|
||||||
|
|
||||||
if (is_null($component = Component::find($component_assets->component_id))) {
|
if (is_null($component = Component::find($component_assets->component_id))) {
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.not_found')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.not_found')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,17 +317,13 @@ class ComponentsController extends Controller
|
||||||
|
|
||||||
$max_to_checkin = $component_assets->assigned_qty;
|
$max_to_checkin = $component_assets->assigned_qty;
|
||||||
|
|
||||||
if ($max_to_checkin > 1) {
|
$validator = Validator::make($request->all(), [
|
||||||
|
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
|
||||||
$validator = Validator::make($request->all(), [
|
]);
|
||||||
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
|
|
||||||
]);
|
if ($validator->fails()) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and ' . $max_to_checkin));
|
||||||
if ($validator->fails()) {
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and '.$max_to_checkin));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Validation passed, so let's figure out what we have to do here.
|
// Validation passed, so let's figure out what we have to do here.
|
||||||
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty', 1));
|
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty', 1));
|
||||||
|
@ -327,28 +333,23 @@ class ComponentsController extends Controller
|
||||||
$component_assets->assigned_qty = $qty_remaining_in_checkout;
|
$component_assets->assigned_qty = $qty_remaining_in_checkout;
|
||||||
|
|
||||||
Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
|
Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
|
||||||
|
|
||||||
DB::table('components_assets')->where('id',
|
DB::table('components_assets')->where('id', $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
|
||||||
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
|
|
||||||
|
|
||||||
// If the checked-in qty is exactly the same as the assigned_qty,
|
// If the checked-in qty is exactly the same as the assigned_qty,
|
||||||
// we can simply delete the associated components_assets record
|
// we can simply delete the associated components_assets record
|
||||||
if ($qty_remaining_in_checkout == 0) {
|
if ($qty_remaining_in_checkout === 0) {
|
||||||
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
|
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$asset = Asset::find($component_assets->asset_id);
|
$asset = Asset::find($component_assets->asset_id);
|
||||||
|
|
||||||
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
|
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success')));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record'));
|
return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record'));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,6 +258,8 @@ class ConsumablesController extends Controller
|
||||||
|
|
||||||
$this->authorize('checkout', $consumable);
|
$this->authorize('checkout', $consumable);
|
||||||
|
|
||||||
|
$consumable->checkout_qty = $request->input('checkout_qty', 1);
|
||||||
|
|
||||||
// Make sure there is at least one available to checkout
|
// Make sure there is at least one available to checkout
|
||||||
if ($consumable->numRemaining() <= 0) {
|
if ($consumable->numRemaining() <= 0) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
|
||||||
|
@ -268,6 +270,12 @@ class ConsumablesController extends Controller
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure there is at least one available to checkout
|
||||||
|
if ($consumable->numRemaining() <= 0 || $consumable->checkout_qty > $consumable->numRemaining()) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable', ['requested' => $consumable->checkout_qty, 'remaining' => $consumable->numRemaining() ])));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
|
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
|
||||||
if (!$user = User::find($request->input('assigned_to'))) {
|
if (!$user = User::find($request->input('assigned_to'))) {
|
||||||
|
@ -278,7 +286,8 @@ class ConsumablesController extends Controller
|
||||||
// Update the consumable data
|
// Update the consumable data
|
||||||
$consumable->assigned_to = $request->input('assigned_to');
|
$consumable->assigned_to = $request->input('assigned_to');
|
||||||
|
|
||||||
$consumable->users()->attach($consumable->id,
|
for ($i = 0; $i < $consumable->checkout_qty; $i++) {
|
||||||
|
$consumable->users()->attach($consumable->id,
|
||||||
[
|
[
|
||||||
'consumable_id' => $consumable->id,
|
'consumable_id' => $consumable->id,
|
||||||
'created_by' => $user->id,
|
'created_by' => $user->id,
|
||||||
|
@ -286,6 +295,8 @@ class ConsumablesController extends Controller
|
||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
|
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,7 @@ class ImportController extends Controller
|
||||||
public function index() : JsonResponse | array
|
public function index() : JsonResponse | array
|
||||||
{
|
{
|
||||||
$this->authorize('import');
|
$this->authorize('import');
|
||||||
$imports = Import::latest()->get();
|
$imports = Import::with('adminuser')->latest()->get();
|
||||||
|
|
||||||
return (new ImportsTransformer)->transformImports($imports);
|
return (new ImportsTransformer)->transformImports($imports);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +132,7 @@ class ImportController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$import->filesize = filesize($path.'/'.$file_name);
|
$import->filesize = filesize($path.'/'.$file_name);
|
||||||
|
$import->created_by = auth()->id();
|
||||||
$import->save();
|
$import->save();
|
||||||
$results[] = $import;
|
$results[] = $import;
|
||||||
}
|
}
|
||||||
|
@ -177,6 +176,9 @@ class ImportController extends Controller
|
||||||
case 'asset':
|
case 'asset':
|
||||||
$redirectTo = 'hardware.index';
|
$redirectTo = 'hardware.index';
|
||||||
break;
|
break;
|
||||||
|
case 'assetModel':
|
||||||
|
$redirectTo = 'models.index';
|
||||||
|
break;
|
||||||
case 'accessory':
|
case 'accessory':
|
||||||
$redirectTo = 'accessories.index';
|
$redirectTo = 'accessories.index';
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -60,7 +60,8 @@ class ManufacturersController extends Controller
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
->withCount('licenses as licenses_count')
|
->withCount('licenses as licenses_count')
|
||||||
->withCount('consumables as consumables_count')
|
->withCount('consumables as consumables_count')
|
||||||
->withCount('accessories as accessories_count');
|
->withCount('accessories as accessories_count')
|
||||||
|
->withCount('components as components_count');
|
||||||
|
|
||||||
if ($request->input('deleted') == 'true') {
|
if ($request->input('deleted') == 'true') {
|
||||||
$manufacturers->onlyTrashed();
|
$manufacturers->onlyTrashed();
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ReportsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('action_type')) {
|
if ($request->filled('action_type')) {
|
||||||
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
|
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('created_by')) {
|
if ($request->filled('created_by')) {
|
||||||
|
@ -53,15 +53,16 @@ class ReportsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('action_source')) {
|
if ($request->filled('action_source')) {
|
||||||
$actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc');
|
$actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('remote_ip')) {
|
||||||
|
$actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('remote_ip')) {
|
|
||||||
$actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'))->orderBy('created_at', 'desc');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->filled('uploads')) {
|
if ($request->filled('uploads')) {
|
||||||
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
|
$actionlogs = $actionlogs->whereNotNull('filename');
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowed_columns = [
|
$allowed_columns = [
|
||||||
|
@ -74,6 +75,8 @@ class ReportsController extends Controller
|
||||||
'note',
|
'note',
|
||||||
'remote_ip',
|
'remote_ip',
|
||||||
'user_agent',
|
'user_agent',
|
||||||
|
'target_type',
|
||||||
|
'item_type',
|
||||||
'action_source',
|
'action_source',
|
||||||
'action_date',
|
'action_date',
|
||||||
];
|
];
|
||||||
|
@ -91,7 +94,7 @@ class ReportsController extends Controller
|
||||||
$actionlogs->OrderByCreatedBy($order);
|
$actionlogs->OrderByCreatedBy($order);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'action_logs.created_at';
|
||||||
$actionlogs = $actionlogs->orderBy($sort, $order);
|
$actionlogs = $actionlogs->orderBy($sort, $order);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,8 @@ class StatuslabelsController extends Controller
|
||||||
$request->except('deployable', 'pending', 'archived');
|
$request->except('deployable', 'pending', 'archived');
|
||||||
|
|
||||||
if (! $request->filled('type')) {
|
if (! $request->filled('type')) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]), 500);
|
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]));
|
||||||
}
|
}
|
||||||
|
|
||||||
$statuslabel = new Statuslabel;
|
$statuslabel = new Statuslabel;
|
||||||
|
|
|
@ -14,6 +14,7 @@ use App\Http\Transformers\UsersTransformer;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\Accessory;
|
use App\Models\Accessory;
|
||||||
|
use App\Models\Company;
|
||||||
use App\Models\Consumable;
|
use App\Models\Consumable;
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
@ -282,6 +283,7 @@ class UsersController extends Controller
|
||||||
'autoassign_licenses',
|
'autoassign_licenses',
|
||||||
'website',
|
'website',
|
||||||
'locale',
|
'locale',
|
||||||
|
'notes',
|
||||||
];
|
];
|
||||||
|
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
|
||||||
|
@ -371,6 +373,7 @@ class UsersController extends Controller
|
||||||
|
|
||||||
$user = new User;
|
$user = new User;
|
||||||
$user->fill($request->all());
|
$user->fill($request->all());
|
||||||
|
$user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||||
$user->created_by = auth()->id();
|
$user->created_by = auth()->id();
|
||||||
|
|
||||||
if ($request->has('permissions')) {
|
if ($request->has('permissions')) {
|
||||||
|
@ -452,6 +455,10 @@ class UsersController extends Controller
|
||||||
|
|
||||||
$user->fill($request->all());
|
$user->fill($request->all());
|
||||||
|
|
||||||
|
if ($request->filled('company_id')) {
|
||||||
|
$user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($user->id == $request->input('manager_id')) {
|
if ($user->id == $request->input('manager_id')) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
|
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
|
||||||
}
|
}
|
||||||
|
@ -474,10 +481,11 @@ class UsersController extends Controller
|
||||||
$user->permissions = $permissions_array;
|
$user->permissions = $permissions_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the location of any assets checked out to this user
|
if($request->has('location_id')) {
|
||||||
Asset::where('assigned_type', User::class)
|
// Update the location of any assets checked out to this user
|
||||||
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
Asset::where('assigned_type', User::class)
|
||||||
|
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
||||||
|
}
|
||||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
|
|
|
@ -61,43 +61,30 @@ class AssetFilesController extends Controller
|
||||||
*/
|
*/
|
||||||
public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
|
public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
|
||||||
{
|
{
|
||||||
$asset = Asset::find($assetId);
|
if ($asset = Asset::find($assetId)) {
|
||||||
// the asset is valid
|
|
||||||
if (isset($asset->id)) {
|
|
||||||
$this->authorize('view', $asset);
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
|
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
$file = 'private_uploads/assets/'.$log->filename;
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
|
if ($log->action_type == 'audit') {
|
||||||
|
$file = 'private_uploads/audits/'.$log->filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.file_not_found'));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = 'private_uploads/assets/'.$log->filename;
|
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found'));
|
||||||
|
|
||||||
if ($log->action_type == 'audit') {
|
|
||||||
$file = 'private_uploads/audits/'.$log->filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! Storage::exists($file)) {
|
|
||||||
return response('File '.$file.' not found on server', 404)
|
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request('inline') == 'true') {
|
|
||||||
|
|
||||||
$headers = [
|
|
||||||
'Content-Disposition' => 'inline',
|
|
||||||
];
|
|
||||||
|
|
||||||
return Storage::download($file, $log->filename, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
}
|
}
|
||||||
// Prepare the error message
|
|
||||||
$error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
|
|
||||||
|
|
||||||
// Redirect to the hardware management page
|
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||||
return redirect()->route('hardware.index')->with('error', $error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,7 +17,6 @@ use App\Models\Location;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\Statuslabel;
|
use App\Models\Statuslabel;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use App\View\Label;
|
use App\View\Label;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
@ -112,8 +111,10 @@ class AssetsController extends Controller
|
||||||
|
|
||||||
$settings = Setting::getSettings();
|
$settings = Setting::getSettings();
|
||||||
|
|
||||||
$success = false;
|
$successes = [];
|
||||||
|
$failures = [];
|
||||||
$serials = $request->input('serials');
|
$serials = $request->input('serials');
|
||||||
|
$asset = null;
|
||||||
|
|
||||||
for ($a = 1; $a <= count($asset_tags); $a++) {
|
for ($a = 1; $a <= count($asset_tags); $a++) {
|
||||||
$asset = new Asset();
|
$asset = new Asset();
|
||||||
|
@ -200,20 +201,35 @@ class AssetsController extends Controller
|
||||||
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
|
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
|
||||||
}
|
}
|
||||||
|
|
||||||
$success = true;
|
$successes[] = "<a href='" . route('hardware.show', ['hardware' => $asset->id]) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$failures[] = join(",", $asset->getErrors()->all());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||||
|
|
||||||
|
|
||||||
if ($success) {
|
if ($successes) {
|
||||||
|
if ($failures) {
|
||||||
|
//some succeeded, some failed
|
||||||
|
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) //FIXME - not tested
|
||||||
|
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]))
|
||||||
|
->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => join("; ", $failures)]));
|
||||||
|
} else {
|
||||||
|
if (count($successes) == 1) {
|
||||||
|
//the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed
|
||||||
|
//and re-translated
|
||||||
|
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||||
|
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
|
||||||
|
} else {
|
||||||
|
//multi-success
|
||||||
|
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||||
|
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
|
||||||
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||||
|
|
|
@ -52,6 +52,10 @@ class BulkAssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset_ids = $request->input('ids');
|
$asset_ids = $request->input('ids');
|
||||||
|
if ($request->input('bulk_actions') === 'checkout') {
|
||||||
|
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
||||||
|
return redirect()->route('hardware.bulkcheckout.show');
|
||||||
|
}
|
||||||
|
|
||||||
// Figure out where we need to send the user after the update is complete, and store that in the session
|
// Figure out where we need to send the user after the update is complete, and store that in the session
|
||||||
$bulk_back_url = request()->headers->get('referer');
|
$bulk_back_url = request()->headers->get('referer');
|
||||||
|
@ -241,10 +245,12 @@ class BulkAssetsController extends Controller
|
||||||
|| ($request->filled('status_id'))
|
|| ($request->filled('status_id'))
|
||||||
|| ($request->filled('model_id'))
|
|| ($request->filled('model_id'))
|
||||||
|| ($request->filled('next_audit_date'))
|
|| ($request->filled('next_audit_date'))
|
||||||
|
|| ($request->filled('asset_eol_date'))
|
||||||
|| ($request->filled('null_name'))
|
|| ($request->filled('null_name'))
|
||||||
|| ($request->filled('null_purchase_date'))
|
|| ($request->filled('null_purchase_date'))
|
||||||
|| ($request->filled('null_expected_checkin_date'))
|
|| ($request->filled('null_expected_checkin_date'))
|
||||||
|| ($request->filled('null_next_audit_date'))
|
|| ($request->filled('null_next_audit_date'))
|
||||||
|
|| ($request->filled('null_asset_eol_date'))
|
||||||
|| ($request->anyFilled($custom_field_columns))
|
|| ($request->anyFilled($custom_field_columns))
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
@ -267,7 +273,8 @@ class BulkAssetsController extends Controller
|
||||||
->conditionallyAddItem('requestable')
|
->conditionallyAddItem('requestable')
|
||||||
->conditionallyAddItem('supplier_id')
|
->conditionallyAddItem('supplier_id')
|
||||||
->conditionallyAddItem('warranty_months')
|
->conditionallyAddItem('warranty_months')
|
||||||
->conditionallyAddItem('next_audit_date');
|
->conditionallyAddItem('next_audit_date')
|
||||||
|
->conditionallyAddItem('asset_eol_date');
|
||||||
foreach ($custom_field_columns as $key => $custom_field_column) {
|
foreach ($custom_field_columns as $key => $custom_field_column) {
|
||||||
$this->conditionallyAddItem($custom_field_column);
|
$this->conditionallyAddItem($custom_field_column);
|
||||||
}
|
}
|
||||||
|
@ -312,6 +319,17 @@ class BulkAssetsController extends Controller
|
||||||
$this->update_array['next_audit_date'] = null;
|
$this->update_array['next_audit_date'] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->input('null_asset_eol_date')=='1') {
|
||||||
|
$this->update_array['asset_eol_date'] = null;
|
||||||
|
|
||||||
|
// If they are nulling the EOL date to allow it to calculate, set eol explicit to 0
|
||||||
|
if ($request->input('calc_eol')=='1') {
|
||||||
|
$this->update_array['eol_explicit'] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('purchase_cost')) {
|
if ($request->filled('purchase_cost')) {
|
||||||
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
|
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
|
||||||
}
|
}
|
||||||
|
@ -571,31 +589,34 @@ class BulkAssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids, $request) {
|
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
|
||||||
foreach ($asset_ids as $asset_id) {
|
foreach ($asset_ids as $asset_id) {
|
||||||
$asset = Asset::findOrFail($asset_id);
|
$asset = Asset::findOrFail($asset_id);
|
||||||
$this->authorize('checkout', $asset);
|
$this->authorize('checkout', $asset);
|
||||||
|
|
||||||
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
|
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
|
||||||
|
|
||||||
|
//TODO - I think this logic is duplicated in the checkOut method?
|
||||||
if ($target->location_id != '') {
|
if ($target->location_id != '') {
|
||||||
$asset->location_id = $target->location_id;
|
$asset->location_id = $target->location_id;
|
||||||
$asset->unsetEventDispatcher();
|
// TODO - I don't know why this is being saved without events
|
||||||
$asset->save();
|
$asset::withoutEvents(function () use ($asset) {
|
||||||
|
$asset->save();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($error) {
|
if (!$checkout_success) {
|
||||||
array_merge_recursive($errors, $asset->getErrors()->toArray());
|
$errors = array_merge_recursive($errors, $asset->getErrors()->toArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (! $errors) {
|
if (! $errors) {
|
||||||
// Redirect to the new asset page
|
// Redirect to the new asset page
|
||||||
return redirect()->to('hardware')->with('success', trans('admin/hardware/message.checkout.success'));
|
return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids));
|
||||||
}
|
}
|
||||||
// Redirect to the asset management page with error
|
// Redirect to the asset management page with error
|
||||||
return redirect()->route('hardware.bulkcheckout.show')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
|
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
|
||||||
} catch (ModelNotFoundException $e) {
|
} catch (ModelNotFoundException $e) {
|
||||||
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
|
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,8 @@ class ComponentsController extends Controller
|
||||||
$component->name = $request->input('name');
|
$component->name = $request->input('name');
|
||||||
$component->category_id = $request->input('category_id');
|
$component->category_id = $request->input('category_id');
|
||||||
$component->supplier_id = $request->input('supplier_id');
|
$component->supplier_id = $request->input('supplier_id');
|
||||||
|
$component->manufacturer_id = $request->input('manufacturer_id');
|
||||||
|
$component->model_number = $request->input('model_number');
|
||||||
$component->location_id = $request->input('location_id');
|
$component->location_id = $request->input('location_id');
|
||||||
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||||
$component->order_number = $request->input('order_number', null);
|
$component->order_number = $request->input('order_number', null);
|
||||||
|
@ -150,6 +152,8 @@ class ComponentsController extends Controller
|
||||||
$component->name = $request->input('name');
|
$component->name = $request->input('name');
|
||||||
$component->category_id = $request->input('category_id');
|
$component->category_id = $request->input('category_id');
|
||||||
$component->supplier_id = $request->input('supplier_id');
|
$component->supplier_id = $request->input('supplier_id');
|
||||||
|
$component->manufacturer_id = $request->input('manufacturer_id');
|
||||||
|
$component->model_number = $request->input('model_number');
|
||||||
$component->location_id = $request->input('location_id');
|
$component->location_id = $request->input('location_id');
|
||||||
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||||
$component->order_number = $request->input('order_number');
|
$component->order_number = $request->input('order_number');
|
||||||
|
@ -189,7 +193,7 @@ class ComponentsController extends Controller
|
||||||
$this->authorize('delete', $component);
|
$this->authorize('delete', $component);
|
||||||
|
|
||||||
// Remove the image if one exists
|
// Remove the image if one exists
|
||||||
if (Storage::disk('public')->exists('components/'.$component->image)) {
|
if ($component->image && Storage::disk('public')->exists('components/' . $component->image)) {
|
||||||
try {
|
try {
|
||||||
Storage::disk('public')->delete('components/'.$component->image);
|
Storage::disk('public')->delete('components/'.$component->image);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
|
@ -112,40 +112,25 @@ class ComponentsFilesController extends Controller
|
||||||
public function show($componentId = null, $fileId = null)
|
public function show($componentId = null, $fileId = null)
|
||||||
{
|
{
|
||||||
Log::debug('Private filesystem is: '.config('filesystems.default'));
|
Log::debug('Private filesystem is: '.config('filesystems.default'));
|
||||||
$component = Component::find($componentId);
|
|
||||||
|
|
||||||
// the component is valid
|
// the component is valid
|
||||||
if (isset($component->id)) {
|
if ($component = Component::find($componentId)) {
|
||||||
$this->authorize('view', $component);
|
$this->authorize('view', $component);
|
||||||
$this->authorize('components.files', $component);
|
$this->authorize('components.files', $component);
|
||||||
|
|
||||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
|
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = 'private_uploads/components/'.$log->filename;
|
$file = 'private_uploads/components/'.$log->filename;
|
||||||
|
|
||||||
if (Storage::missing($file)) {
|
try {
|
||||||
Log::debug('FILE DOES NOT EXISTS for '.$file);
|
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||||
Log::debug('URL should be '.Storage::url($file));
|
} catch (\Exception $e) {
|
||||||
|
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found'));
|
||||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Display the file inline
|
|
||||||
if (request('inline') == 'true') {
|
|
||||||
$headers = [
|
|
||||||
'Content-Disposition' => 'inline',
|
|
||||||
];
|
|
||||||
return Storage::download($file, $log->filename, $headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||||
|
|
|
@ -70,7 +70,7 @@ class ConsumableCheckoutController extends Controller
|
||||||
$this->authorize('checkout', $consumable);
|
$this->authorize('checkout', $consumable);
|
||||||
|
|
||||||
// If the quantity is not present in the request or is not a positive integer, set it to 1
|
// If the quantity is not present in the request or is not a positive integer, set it to 1
|
||||||
$quantity = $request->input('qty');
|
$quantity = $request->input('checkout_qty');
|
||||||
if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) {
|
if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) {
|
||||||
$quantity = 1;
|
$quantity = 1;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ class ConsumableCheckoutController extends Controller
|
||||||
// Update the consumable data
|
// Update the consumable data
|
||||||
$consumable->assigned_to = e($request->input('assigned_to'));
|
$consumable->assigned_to = e($request->input('assigned_to'));
|
||||||
|
|
||||||
for($i = 0; $i < $quantity; $i++){
|
for ($i = 0; $i < $quantity; $i++){
|
||||||
$consumable->users()->attach($consumable->id, [
|
$consumable->users()->attach($consumable->id, [
|
||||||
'consumable_id' => $consumable->id,
|
'consumable_id' => $consumable->id,
|
||||||
'created_by' => $admin_user->id,
|
'created_by' => $admin_user->id,
|
||||||
|
@ -100,6 +100,8 @@ class ConsumableCheckoutController extends Controller
|
||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$consumable->checkout_qty = $quantity;
|
||||||
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
|
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
|
||||||
|
|
||||||
$request->request->add(['checkout_to_type' => 'user']);
|
$request->request->add(['checkout_to_type' => 'user']);
|
||||||
|
|
|
@ -104,7 +104,6 @@ class ConsumablesFilesController extends Controller
|
||||||
* @since [v1.4]
|
* @since [v1.4]
|
||||||
* @param int $consumableId
|
* @param int $consumableId
|
||||||
* @param int $fileId
|
* @param int $fileId
|
||||||
* @return \Symfony\Consumable\HttpFoundation\Response
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function show($consumableId = null, $fileId = null)
|
public function show($consumableId = null, $fileId = null)
|
||||||
|
@ -116,36 +115,18 @@ class ConsumablesFilesController extends Controller
|
||||||
$this->authorize('view', $consumable);
|
$this->authorize('view', $consumable);
|
||||||
$this->authorize('consumables.files', $consumable);
|
$this->authorize('consumables.files', $consumable);
|
||||||
|
|
||||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
|
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
$file = 'private_uploads/consumables/'.$log->filename;
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = 'private_uploads/consumables/'.$log->filename;
|
try {
|
||||||
|
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||||
if (Storage::missing($file)) {
|
} catch (\Exception $e) {
|
||||||
Log::debug('FILE DOES NOT EXISTS for '.$file);
|
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found'));
|
||||||
Log::debug('URL should be '.Storage::url($file));
|
|
||||||
|
|
||||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Display the file inline
|
|
||||||
if (request('inline') == 'true') {
|
|
||||||
$headers = [
|
|
||||||
'Content-Disposition' => 'inline',
|
|
||||||
];
|
|
||||||
return Storage::download($file, $log->filename, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
|
||||||
// won't work, as they're not accessible via the web
|
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The log record doesn't exist somehow
|
||||||
|
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||||
|
|
|
@ -112,37 +112,19 @@ class LicenseFilesController extends Controller
|
||||||
$this->authorize('view', $license);
|
$this->authorize('view', $license);
|
||||||
$this->authorize('licenses.files', $license);
|
$this->authorize('licenses.files', $license);
|
||||||
|
|
||||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
|
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
$file = 'private_uploads/licenses/'.$log->filename;
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = 'private_uploads/licenses/'.$log->filename;
|
|
||||||
|
|
||||||
if (Storage::missing($file)) {
|
|
||||||
Log::debug('NOT EXISTS for '.$file);
|
|
||||||
Log::debug('NOT EXISTS URL should be '.Storage::url($file));
|
|
||||||
|
|
||||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (request('inline') == 'true') {
|
|
||||||
|
|
||||||
$headers = [
|
|
||||||
'Content-Disposition' => 'inline',
|
|
||||||
];
|
|
||||||
|
|
||||||
return Storage::download($file, $log->filename, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
|
||||||
// won't work, as they're not accessible via the web
|
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The log record doesn't exist somehow
|
||||||
|
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
|
||||||
|
|
|
@ -259,7 +259,7 @@ class ReportsController extends Controller
|
||||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||||
Log::debug('Added headers: '.$executionTime);
|
Log::debug('Added headers: '.$executionTime);
|
||||||
|
|
||||||
$actionlogs = Actionlog::with('item', 'user', 'target', 'location')
|
$actionlogs = Actionlog::with('item', 'user', 'target', 'location', 'adminuser')
|
||||||
->orderBy('created_at', 'DESC')
|
->orderBy('created_at', 'DESC')
|
||||||
->chunk(20, function ($actionlogs) use ($handle) {
|
->chunk(20, function ($actionlogs) use ($handle) {
|
||||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||||
|
@ -286,7 +286,7 @@ class ReportsController extends Controller
|
||||||
|
|
||||||
$row = [
|
$row = [
|
||||||
$actionlog->created_at,
|
$actionlog->created_at,
|
||||||
($actionlog->admin) ? e($actionlog->admin->getFullNameAttribute()) : '',
|
($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '',
|
||||||
$actionlog->present()->actionType(),
|
$actionlog->present()->actionType(),
|
||||||
e($actionlog->itemType()),
|
e($actionlog->itemType()),
|
||||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||||
|
|
|
@ -334,6 +334,8 @@ class SettingsController extends Controller
|
||||||
$setting->depreciation_method = $request->input('depreciation_method');
|
$setting->depreciation_method = $request->input('depreciation_method');
|
||||||
$setting->dash_chart_type = $request->input('dash_chart_type');
|
$setting->dash_chart_type = $request->input('dash_chart_type');
|
||||||
$setting->profile_edit = $request->input('profile_edit', 0);
|
$setting->profile_edit = $request->input('profile_edit', 0);
|
||||||
|
$setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0);
|
||||||
|
|
||||||
|
|
||||||
if ($request->input('per_page') != '') {
|
if ($request->input('per_page') != '') {
|
||||||
$setting->per_page = $request->input('per_page');
|
$setting->per_page = $request->input('per_page');
|
||||||
|
|
|
@ -323,7 +323,7 @@ class BulkUsersController extends Controller
|
||||||
$logAction->item_type = $itemType;
|
$logAction->item_type = $itemType;
|
||||||
$logAction->target_id = $item->assigned_to;
|
$logAction->target_id = $item->assigned_to;
|
||||||
$logAction->target_type = User::class;
|
$logAction->target_type = User::class;
|
||||||
$logAction->created_at = auth()->id();
|
$logAction->created_by = auth()->id();
|
||||||
$logAction->note = 'Bulk checkin items';
|
$logAction->note = 'Bulk checkin items';
|
||||||
$logAction->logaction('checkin from');
|
$logAction->logaction('checkin from');
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\UploadFileRequest;
|
use App\Http\Requests\UploadFileRequest;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Input;
|
|
||||||
use Illuminate\Support\Facades\Response;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
@ -116,31 +113,30 @@ class UserFilesController extends Controller
|
||||||
public function show($userId = null, $fileId = null)
|
public function show($userId = null, $fileId = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
if (empty($fileId)) {
|
if (empty($fileId)) {
|
||||||
return redirect()->route('users.show')->with('error', 'Invalid file request');
|
return redirect()->route('users.show')->with('error', 'Invalid file request');
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = User::find($userId);
|
if ($user = User::find($userId)) {
|
||||||
|
|
||||||
// the license is valid
|
|
||||||
if (isset($user->id)) {
|
|
||||||
|
|
||||||
$this->authorize('view', $user);
|
$this->authorize('view', $user);
|
||||||
|
|
||||||
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
|
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
|
||||||
|
$file = 'private_uploads/users/'.$log->filename;
|
||||||
|
|
||||||
// Display the file inline
|
try {
|
||||||
if (request('inline') == 'true') {
|
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||||
$headers = [
|
} catch (\Exception $e) {
|
||||||
'Content-Disposition' => 'inline',
|
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
|
||||||
];
|
|
||||||
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Storage::download('private_uploads/users/'.$log->filename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found'));
|
// The log record doesn't exist somehow
|
||||||
|
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
|
||||||
|
|
||||||
|
|
||||||
|
return redirect()->back()->with('error', trans('general.file_not_found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to the user management page if the user doesn't exist
|
// Redirect to the user management page if the user doesn't exist
|
||||||
|
|
|
@ -288,33 +288,31 @@ class UsersController extends Controller
|
||||||
$user->password = bcrypt($request->input('password'));
|
$user->password = bcrypt($request->input('password'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Update the location of any assets checked out to this user
|
// Update the location of any assets checked out to this user
|
||||||
Asset::where('assigned_type', User::class)
|
Asset::where('assigned_type', User::class)
|
||||||
->where('assigned_to', $user->id)
|
->where('assigned_to', $user->id)
|
||||||
->update(['location_id' => $user->location_id]);
|
->update(['location_id' => $user->location_id]);
|
||||||
|
|
||||||
$permissions_array = $request->input('permission');
|
$permissions_array = $request->input('permission');
|
||||||
|
|
||||||
|
// Strip out the superuser permission if the user isn't a superadmin
|
||||||
|
if (! auth()->user()->isSuperUser()) {
|
||||||
|
unset($permissions_array['superuser']);
|
||||||
|
$permissions_array['superuser'] = $orig_superuser;
|
||||||
|
}
|
||||||
|
|
||||||
// Strip out the superuser permission if the user isn't a superadmin
|
$user->permissions = json_encode($permissions_array);
|
||||||
if (! auth()->user()->isSuperUser()) {
|
|
||||||
unset($permissions_array['superuser']);
|
|
||||||
$permissions_array['superuser'] = $orig_superuser;
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->permissions = json_encode($permissions_array);
|
// Handle uploaded avatar
|
||||||
|
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
|
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||||
|
|
||||||
// Handle uploaded avatar
|
if ($user->save()) {
|
||||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
// Redirect to the user page
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))
|
||||||
|
->with('success', trans('admin/users/message.success.update'));
|
||||||
if ($user->save()) {
|
}
|
||||||
// Redirect to the user page
|
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||||
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))
|
|
||||||
->with('success', trans('admin/users/message.success.update'));
|
|
||||||
}
|
|
||||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,9 +21,14 @@ class AssetCheckinRequest extends Request
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
$settings = \App\Models\Setting::getSettings();
|
||||||
|
|
||||||
];
|
$rules = [];
|
||||||
|
|
||||||
|
if($settings->require_checkinout_notes) {
|
||||||
|
$rules['note'] = 'string|required';
|
||||||
|
}
|
||||||
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function response(array $errors)
|
public function response(array $errors)
|
||||||
|
|
|
@ -21,6 +21,8 @@ class AssetCheckoutRequest extends Request
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
|
$settings = \App\Models\Setting::getSettings();
|
||||||
|
|
||||||
$rules = [
|
$rules = [
|
||||||
'assigned_user' => 'required_without_all:assigned_asset,assigned_location',
|
'assigned_user' => 'required_without_all:assigned_asset,assigned_location',
|
||||||
'assigned_asset' => 'required_without_all:assigned_user,assigned_location',
|
'assigned_asset' => 'required_without_all:assigned_user,assigned_location',
|
||||||
|
@ -35,7 +37,11 @@ class AssetCheckoutRequest extends Request
|
||||||
'nullable',
|
'nullable',
|
||||||
'date'
|
'date'
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if($settings->require_checkinout_notes) {
|
||||||
|
$rules['note'] = 'required|string';
|
||||||
|
}
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,11 @@ class ItemImportRequest extends FormRequest
|
||||||
|
|
||||||
$filename = config('app.private_uploads').'/imports/'.$import->file_path;
|
$filename = config('app.private_uploads').'/imports/'.$import->file_path;
|
||||||
$import->import_type = $this->input('import-type');
|
$import->import_type = $this->input('import-type');
|
||||||
$class = title_case($import->import_type);
|
$class = ucfirst($import->import_type);
|
||||||
$classString = "App\\Importer\\{$class}Importer";
|
$classString = "App\\Importer\\{$class}Importer";
|
||||||
$importer = new $classString($filename);
|
$importer = new $classString($filename);
|
||||||
$import->field_map = request('column-mappings');
|
$import->field_map = request('column-mappings');
|
||||||
|
$import->created_by = auth()->id();
|
||||||
$import->save();
|
$import->save();
|
||||||
$fieldMappings = [];
|
$fieldMappings = [];
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ class ItemImportRequest extends FormRequest
|
||||||
$fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER);
|
$fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER);
|
||||||
}
|
}
|
||||||
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
|
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
|
||||||
->setUserId(auth()->id())
|
->setCreatedBy(auth()->id())
|
||||||
->setUpdating($this->get('import-update'))
|
->setUpdating($this->get('import-update'))
|
||||||
->setShouldNotify($this->get('send-welcome'))
|
->setShouldNotify($this->get('send-welcome'))
|
||||||
->setUsernameFormat('firstname.lastname')
|
->setUsernameFormat('firstname.lastname')
|
||||||
|
|
|
@ -26,11 +26,19 @@ class StoreAssetRequest extends ImageUploadRequest
|
||||||
|
|
||||||
public function prepareForValidation(): void
|
public function prepareForValidation(): void
|
||||||
{
|
{
|
||||||
|
// Guard against users passing in an array for company_id instead of an integer.
|
||||||
|
// If the company_id is not an integer then we simply use what was
|
||||||
|
// provided to be caught by model level validation later.
|
||||||
|
// The use of is_numeric accounts for 1 and '1'.
|
||||||
|
$idForCurrentUser = is_numeric($this->company_id)
|
||||||
|
? Company::getIdForCurrentUser($this->company_id)
|
||||||
|
: $this->company_id;
|
||||||
|
|
||||||
$this->parseLastAuditDate();
|
$this->parseLastAuditDate();
|
||||||
|
|
||||||
$this->merge([
|
$this->merge([
|
||||||
'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
|
'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
|
||||||
'company_id' => Company::getIdForCurrentUser($this->company_id),
|
'company_id' => $idForCurrentUser,
|
||||||
'assigned_to' => $assigned_to ?? null,
|
'assigned_to' => $assigned_to ?? null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ class StoreNotificationSettings extends FormRequest
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'alert_email' => 'email_array|nullable',
|
'alert_email' => 'email_array|nullable',
|
||||||
'admin_cc_email' => 'email|nullable',
|
'admin_cc_email' => 'email_array|nullable',
|
||||||
'alert_threshold' => 'numeric|nullable|gt:0',
|
'alert_threshold' => 'numeric|nullable|gt:0',
|
||||||
'alert_interval' => 'numeric|nullable|gt:0',
|
'alert_interval' => 'numeric|nullable|gt:0',
|
||||||
'audit_warning_days' => 'numeric|nullable|gt:0',
|
'audit_warning_days' => 'numeric|nullable|gt:0',
|
||||||
|
|
|
@ -23,7 +23,7 @@ trait MayContainCustomFields
|
||||||
return str_starts_with($attributes, '_snipeit_');
|
return str_starts_with($attributes, '_snipeit_');
|
||||||
});
|
});
|
||||||
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
|
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
|
||||||
if (count($request_fields) > 0) {
|
if (count($request_fields) > 0 && $validator->errors()->isEmpty()) {
|
||||||
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
|
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
|
||||||
->each(function ($request_field_name) use ($request_fields, $validator) {
|
->each(function ($request_field_name) use ($request_fields, $validator) {
|
||||||
if (CustomField::where('db_column', $request_field_name)->exists()) {
|
if (CustomField::where('db_column', $request_field_name)->exists()) {
|
||||||
|
|
|
@ -141,6 +141,8 @@ class ActionlogsTransformer
|
||||||
if ($actionlog->item) {
|
if ($actionlog->item) {
|
||||||
if ($actionlog->itemType() == 'asset') {
|
if ($actionlog->itemType() == 'asset') {
|
||||||
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||||
|
} elseif ($actionlog->itemType() == 'accessory') {
|
||||||
|
$file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||||
} elseif ($actionlog->itemType() == 'license') {
|
} elseif ($actionlog->itemType() == 'license') {
|
||||||
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||||
} elseif ($actionlog->itemType() == 'user') {
|
} elseif ($actionlog->itemType() == 'user') {
|
||||||
|
@ -158,7 +160,6 @@ class ActionlogsTransformer
|
||||||
[
|
[
|
||||||
'url' => $file_url,
|
'url' => $file_url,
|
||||||
'filename' => $actionlog->filename,
|
'filename' => $actionlog->filename,
|
||||||
'inlineable' => (bool) Helper::show_file_inline($actionlog->filename),
|
|
||||||
] : null,
|
] : null,
|
||||||
|
|
||||||
'item' => ($actionlog->item) ? [
|
'item' => ($actionlog->item) ? [
|
||||||
|
@ -346,4 +347,4 @@ class ActionlogsTransformer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,10 @@ class AssetModelsTransformer
|
||||||
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
||||||
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
||||||
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
|
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
|
||||||
|
'created_by' => ($assetmodel->adminuser) ? [
|
||||||
|
'id' => (int) $assetmodel->adminuser->id,
|
||||||
|
'name'=> e($assetmodel->adminuser->present()->fullName()),
|
||||||
|
] : null,
|
||||||
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
|
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
|
||||||
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
|
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
|
||||||
'deleted_at' => Helper::getFormattedDateObject($assetmodel->deleted_at, 'datetime'),
|
'deleted_at' => Helper::getFormattedDateObject($assetmodel->deleted_at, 'datetime'),
|
||||||
|
|
|
@ -38,6 +38,8 @@ class ComponentsTransformer
|
||||||
'name' => e($component->category->name),
|
'name' => e($component->category->name),
|
||||||
] : null,
|
] : null,
|
||||||
'supplier' => ($component->supplier) ? ['id' => $component->supplier->id, 'name'=> e($component->supplier->name)] : null,
|
'supplier' => ($component->supplier) ? ['id' => $component->supplier->id, 'name'=> e($component->supplier->name)] : null,
|
||||||
|
'manufacturer' => ($component->manufacturer) ? ['id' => $component->manufacturer->id, 'name'=> e($component->manufacturer->name)] : null,
|
||||||
|
'model_number' => ($component->model_number) ? e($component->model_number) : null,
|
||||||
'order_number' => e($component->order_number),
|
'order_number' => e($component->order_number),
|
||||||
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
|
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
|
||||||
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
|
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
|
||||||
|
|
|
@ -36,6 +36,7 @@ class ManufacturersTransformer
|
||||||
'licenses_count' => (int) $manufacturer->licenses_count,
|
'licenses_count' => (int) $manufacturer->licenses_count,
|
||||||
'consumables_count' => (int) $manufacturer->consumables_count,
|
'consumables_count' => (int) $manufacturer->consumables_count,
|
||||||
'accessories_count' => (int) $manufacturer->accessories_count,
|
'accessories_count' => (int) $manufacturer->accessories_count,
|
||||||
|
'components_count' => (int) $manufacturer->components_count,
|
||||||
'created_by' => ($manufacturer->adminuser) ? [
|
'created_by' => ($manufacturer->adminuser) ? [
|
||||||
'id' => (int) $manufacturer->adminuser->id,
|
'id' => (int) $manufacturer->adminuser->id,
|
||||||
'name'=> e($manufacturer->adminuser->present()->fullName()),
|
'name'=> e($manufacturer->adminuser->present()->fullName()),
|
||||||
|
|
|
@ -42,6 +42,7 @@ class AccessoryImporter extends ItemImporter
|
||||||
}
|
}
|
||||||
$this->log('No Matching Accessory, Creating a new one');
|
$this->log('No Matching Accessory, Creating a new one');
|
||||||
$accessory = new Accessory();
|
$accessory = new Accessory();
|
||||||
|
$accessory->created_by = auth()->id();
|
||||||
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
|
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
|
||||||
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
|
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
|
||||||
$accessory->fill($this->sanitizeItemForStoring($accessory));
|
$accessory->fill($this->sanitizeItemForStoring($accessory));
|
||||||
|
|
174
app/Importer/AssetModelImporter.php
Normal file
174
app/Importer/AssetModelImporter.php
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Importer;
|
||||||
|
|
||||||
|
use App\Models\AssetModel;
|
||||||
|
use App\Models\Depreciation;
|
||||||
|
use App\Models\CustomFieldset;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we are importing users via an Asset/etc import, we use createOrFetchUser() in
|
||||||
|
* Importer\Importer.php. [ALG]
|
||||||
|
*
|
||||||
|
* Class LocationImporter
|
||||||
|
*/
|
||||||
|
class AssetModelImporter extends ItemImporter
|
||||||
|
{
|
||||||
|
protected $models;
|
||||||
|
|
||||||
|
public function __construct($filename)
|
||||||
|
{
|
||||||
|
parent::__construct($filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handle($row)
|
||||||
|
{
|
||||||
|
parent::handle($row);
|
||||||
|
$this->createAssetModelIfNotExists($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a model if a duplicate does not exist.
|
||||||
|
* @todo Investigate how this should interact with Importer::createModelIfNotExists
|
||||||
|
*
|
||||||
|
* @author A. Gianotto
|
||||||
|
* @since 6.1.0
|
||||||
|
* @param array $row
|
||||||
|
*/
|
||||||
|
public function createAssetModelIfNotExists(array $row)
|
||||||
|
{
|
||||||
|
|
||||||
|
$editingAssetModel = false;
|
||||||
|
$assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
|
||||||
|
|
||||||
|
if ($assetModel) {
|
||||||
|
if (! $this->updating) {
|
||||||
|
$this->log('A matching Model '.$this->item['name'].' already exists');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log('Updating Model');
|
||||||
|
$editingAssetModel = true;
|
||||||
|
} else {
|
||||||
|
$this->log('No Matching Model, Create a new one');
|
||||||
|
$assetModel = new AssetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull the records from the CSV to determine their values
|
||||||
|
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
|
||||||
|
$this->item['category'] = trim($this->findCsvMatch($row, 'category'));
|
||||||
|
$this->item['manufacturer'] = trim($this->findCsvMatch($row, 'manufacturer'));
|
||||||
|
$this->item['min_amt'] = trim($this->findCsvMatch($row, 'min_amt'));
|
||||||
|
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
|
||||||
|
$this->item['eol'] = trim($this->findCsvMatch($row, 'eol'));
|
||||||
|
$this->item['notes'] = trim($this->findCsvMatch($row, 'notes'));
|
||||||
|
$this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset'));
|
||||||
|
$this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation'));
|
||||||
|
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0;
|
||||||
|
|
||||||
|
if (!empty($this->item['category'])) {
|
||||||
|
if ($category = $this->createOrFetchCategory($this->item['category'])) {
|
||||||
|
$this->item['category_id'] = $category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($this->item['manufacturer'])) {
|
||||||
|
if ($manufacturer = $this->createOrFetchManufacturer($this->item['manufacturer'])) {
|
||||||
|
$this->item['manufacturer_id'] = $manufacturer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->item['depreciation'])) {
|
||||||
|
if ($depreciation = $this->fetchDepreciation($this->item['depreciation'])) {
|
||||||
|
$this->item['depreciation_id'] = $depreciation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->item['fieldset'])) {
|
||||||
|
if ($fieldset = $this->createOrFetchCustomFieldset($this->item['fieldset'])) {
|
||||||
|
$this->item['fieldset_id'] = $fieldset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug('Item array is: ');
|
||||||
|
Log::debug(print_r($this->item, true));
|
||||||
|
|
||||||
|
|
||||||
|
if ($editingAssetModel) {
|
||||||
|
Log::debug('Updating existing model');
|
||||||
|
$assetModel->update($this->sanitizeItemForUpdating($assetModel));
|
||||||
|
} else {
|
||||||
|
Log::debug('Creating model');
|
||||||
|
$assetModel->fill($this->sanitizeItemForStoring($assetModel));
|
||||||
|
$assetModel->created_by = auth()->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($assetModel->save()) {
|
||||||
|
$this->log('AssetModel '.$assetModel->name.' created or updated from CSV import');
|
||||||
|
return $assetModel;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->log($assetModel->getErrors()->first());
|
||||||
|
$this->addErrorToBag($assetModel, $assetModel->getErrors()->keys()[0], $assetModel->getErrors()->first());
|
||||||
|
return $assetModel->getErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch an existing depreciation, or create new if it doesn't exist.
|
||||||
|
*
|
||||||
|
* We only do a fetch vs create here since Depreciations have additional fields required
|
||||||
|
* and cannot be created without them (months, for example.))
|
||||||
|
*
|
||||||
|
* @author A. Gianotto
|
||||||
|
* @since 7.1.3
|
||||||
|
* @param $depreciation_name string
|
||||||
|
* @return int id of depreciation created/found
|
||||||
|
*/
|
||||||
|
public function fetchDepreciation($depreciation_name) : ?int
|
||||||
|
{
|
||||||
|
if ($depreciation_name != '') {
|
||||||
|
|
||||||
|
if ($depreciation = Depreciation::where('name', '=', $depreciation_name)->first()) {
|
||||||
|
$this->log('A matching Depreciation '.$depreciation_name.' already exists');
|
||||||
|
return $depreciation->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch an existing fieldset, or create new if it doesn't exist
|
||||||
|
*
|
||||||
|
* @author A. Gianotto
|
||||||
|
* @since 7.1.3
|
||||||
|
* @param $fieldset_name string
|
||||||
|
* @return int id of fieldset created/found
|
||||||
|
*/
|
||||||
|
public function createOrFetchCustomFieldset($fieldset_name) : ?int
|
||||||
|
{
|
||||||
|
if ($fieldset_name != '') {
|
||||||
|
$fieldset = CustomFieldset::where('name', '=', $fieldset_name)->first();
|
||||||
|
|
||||||
|
if ($fieldset) {
|
||||||
|
$this->log('A matching fieldset '.$fieldset_name.' already exists');
|
||||||
|
return $fieldset->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldset = new CustomFieldset();
|
||||||
|
$fieldset->name = $fieldset_name;
|
||||||
|
|
||||||
|
if ($fieldset->save()) {
|
||||||
|
$this->log('Fieldset '.$fieldset_name.' was created');
|
||||||
|
|
||||||
|
return $fieldset->id;
|
||||||
|
}
|
||||||
|
$this->logError($fieldset, 'Fieldset');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ class ComponentImporter extends ItemImporter
|
||||||
}
|
}
|
||||||
$this->log('No matching component, creating one');
|
$this->log('No matching component, creating one');
|
||||||
$component = new Component;
|
$component = new Component;
|
||||||
|
$component->created_by = auth()->id();
|
||||||
$component->fill($this->sanitizeItemForStoring($component));
|
$component->fill($this->sanitizeItemForStoring($component));
|
||||||
|
|
||||||
// This sets an attribute on the Loggable trait for the action log
|
// This sets an attribute on the Loggable trait for the action log
|
||||||
|
@ -58,7 +59,7 @@ class ComponentImporter extends ItemImporter
|
||||||
if (isset($this->item['asset_tag']) && ($asset = Asset::where('asset_tag', $this->item['asset_tag'])->first())) {
|
if (isset($this->item['asset_tag']) && ($asset = Asset::where('asset_tag', $this->item['asset_tag'])->first())) {
|
||||||
$component->assets()->attach($component->id, [
|
$component->assets()->attach($component->id, [
|
||||||
'component_id' => $component->id,
|
'component_id' => $component->id,
|
||||||
'created_by' => $this->created_by,
|
'created_by' => auth()->id(),
|
||||||
'created_at' => date('Y-m-d H:i:s'),
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
'assigned_qty' => 1, // Only assign the first one to the asset
|
'assigned_qty' => 1, // Only assign the first one to the asset
|
||||||
'asset_id' => $asset->id,
|
'asset_id' => $asset->id,
|
||||||
|
|
|
@ -41,6 +41,7 @@ class ConsumableImporter extends ItemImporter
|
||||||
}
|
}
|
||||||
$this->log('No matching consumable, creating one');
|
$this->log('No matching consumable, creating one');
|
||||||
$consumable = new Consumable();
|
$consumable = new Consumable();
|
||||||
|
$consumable->created_by = auth()->id();
|
||||||
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
|
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
|
||||||
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
|
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
|
||||||
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
|
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
|
||||||
|
|
|
@ -21,7 +21,6 @@ abstract class Importer
|
||||||
* Id of User performing import
|
* Id of User performing import
|
||||||
* @var
|
* @var
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected $created_by;
|
protected $created_by;
|
||||||
/**
|
/**
|
||||||
* Are we updating items in the import
|
* Are we updating items in the import
|
||||||
|
@ -149,17 +148,23 @@ abstract class Importer
|
||||||
{
|
{
|
||||||
$headerRow = $this->csv->fetchOne();
|
$headerRow = $this->csv->fetchOne();
|
||||||
$this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record
|
$this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record
|
||||||
$results = $this->normalizeInputArray($this->csv->getRecords($headerRow));
|
|
||||||
|
|
||||||
$this->populateCustomFields($headerRow);
|
$this->populateCustomFields($headerRow);
|
||||||
|
|
||||||
DB::transaction(function () use (&$results) {
|
DB::transaction(function () use ($headerRow) {
|
||||||
|
$importedItemsCount = 0;
|
||||||
Model::unguard();
|
Model::unguard();
|
||||||
$resultsCount = count($results);
|
|
||||||
foreach ($results as $row) {
|
foreach ($this->csv->getRecords($headerRow) as $row) {
|
||||||
|
//Lowercase header values to ensure we're comparing values properly.
|
||||||
|
$row = array_change_key_case($row, CASE_LOWER);
|
||||||
|
|
||||||
$this->handle($row);
|
$this->handle($row);
|
||||||
|
|
||||||
|
$importedItemsCount++;
|
||||||
|
|
||||||
if ($this->progressCallback) {
|
if ($this->progressCallback) {
|
||||||
call_user_func($this->progressCallback, $resultsCount);
|
call_user_func($this->progressCallback, $importedItemsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->log('------------- Action Summary ----------------');
|
$this->log('------------- Action Summary ----------------');
|
||||||
|
@ -237,22 +242,6 @@ abstract class Importer
|
||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to lowercase header values to ensure we're comparing values properly.
|
|
||||||
*
|
|
||||||
* @param $results
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function normalizeInputArray($results)
|
|
||||||
{
|
|
||||||
$newArray = [];
|
|
||||||
foreach ($results as $index => $arrayToNormalize) {
|
|
||||||
$newArray[$index] = array_change_key_case($arrayToNormalize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $newArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Figure out the fieldname of the custom field
|
* Figure out the fieldname of the custom field
|
||||||
*
|
*
|
||||||
|
@ -374,6 +363,7 @@ abstract class Importer
|
||||||
|
|
||||||
// No luck finding a user on username or first name, let's create one.
|
// No luck finding a user on username or first name, let's create one.
|
||||||
$user = new User;
|
$user = new User;
|
||||||
|
|
||||||
$user->first_name = $user_array['first_name'];
|
$user->first_name = $user_array['first_name'];
|
||||||
$user->last_name = $user_array['last_name'];
|
$user->last_name = $user_array['last_name'];
|
||||||
$user->username = $user_array['username'];
|
$user->username = $user_array['username'];
|
||||||
|
@ -417,7 +407,7 @@ abstract class Importer
|
||||||
*
|
*
|
||||||
* @return self
|
* @return self
|
||||||
*/
|
*/
|
||||||
public function setUserId($created_by)
|
public function setCreatedBy($created_by)
|
||||||
{
|
{
|
||||||
$this->created_by = $created_by;
|
$this->created_by = $created_by;
|
||||||
|
|
||||||
|
@ -503,6 +493,16 @@ abstract class Importer
|
||||||
|
|
||||||
public function fetchHumanBoolean($value)
|
public function fetchHumanBoolean($value)
|
||||||
{
|
{
|
||||||
|
$true = [
|
||||||
|
'yes',
|
||||||
|
'y',
|
||||||
|
'true',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (in_array(strtolower($value), $true)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
return (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,6 +539,7 @@ abstract class Importer
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch an existing manager
|
* Fetch an existing manager
|
||||||
*
|
*
|
||||||
|
|
|
@ -94,7 +94,7 @@ class ItemImporter extends Importer
|
||||||
|
|
||||||
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
|
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
|
||||||
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
|
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
|
||||||
$this->item['created_by'] = $this->created_by;
|
$this->item['created_by'] = auth()->id();
|
||||||
$this->item['serial'] = $this->findCsvMatch($row, 'serial');
|
$this->item['serial'] = $this->findCsvMatch($row, 'serial');
|
||||||
// NO need to call this method if we're running the user import.
|
// NO need to call this method if we're running the user import.
|
||||||
// TODO: Merge these methods.
|
// TODO: Merge these methods.
|
||||||
|
@ -113,7 +113,7 @@ class ItemImporter extends Importer
|
||||||
protected function determineCheckout($row)
|
protected function determineCheckout($row)
|
||||||
{
|
{
|
||||||
// Locations don't get checked out to anyone/anything
|
// Locations don't get checked out to anyone/anything
|
||||||
if (get_class($this) == LocationImporter::class) {
|
if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +249,7 @@ class ItemImporter extends Importer
|
||||||
|
|
||||||
$this->log('No Matching Model, Creating a new one');
|
$this->log('No Matching Model, Creating a new one');
|
||||||
$asset_model = new AssetModel();
|
$asset_model = new AssetModel();
|
||||||
|
$asset_model->created_by = auth()->id();
|
||||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||||
$item['name'] = $asset_model_name;
|
$item['name'] = $asset_model_name;
|
||||||
$item['model_number'] = $asset_modelNumber;
|
$item['model_number'] = $asset_modelNumber;
|
||||||
|
@ -256,11 +257,8 @@ class ItemImporter extends Importer
|
||||||
$item['category_id'] = $this->createOrFetchCategory($asset_model_category);
|
$item['category_id'] = $this->createOrFetchCategory($asset_model_category);
|
||||||
|
|
||||||
$asset_model->fill($item);
|
$asset_model->fill($item);
|
||||||
//$asset_model = AssetModel::firstOrNew($item);
|
|
||||||
$item = null;
|
$item = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($asset_model->save()) {
|
if ($asset_model->save()) {
|
||||||
$this->log('Asset Model '.$asset_model_name.' with model number '.$asset_modelNumber.' was created');
|
$this->log('Asset Model '.$asset_model_name.' with model number '.$asset_modelNumber.' was created');
|
||||||
|
|
||||||
|
@ -287,21 +285,28 @@ class ItemImporter extends Importer
|
||||||
$classname = class_basename(get_class($this));
|
$classname = class_basename(get_class($this));
|
||||||
$item_type = strtolower(substr($classname, 0, strpos($classname, 'Importer')));
|
$item_type = strtolower(substr($classname, 0, strpos($classname, 'Importer')));
|
||||||
|
|
||||||
|
// If we're importing asset models only (without attached assets), override the category type to asset
|
||||||
|
if ($item_type == 'assetmodel') {
|
||||||
|
$item_type = 'asset';
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($asset_category)) {
|
if (empty($asset_category)) {
|
||||||
$asset_category = 'Unnamed Category';
|
$asset_category = 'Unnamed Category';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$category = Category::where(['name' => $asset_category, 'category_type' => $item_type])->first();
|
$category = Category::where(['name' => $asset_category, 'category_type' => $item_type])->first();
|
||||||
|
|
||||||
if ($category) {
|
|
||||||
$this->log('A matching category: '.$asset_category.' already exists');
|
|
||||||
|
|
||||||
|
if ($category) {
|
||||||
|
$this->log('A matching category: '.$category->name.' already exists');
|
||||||
return $category->id;
|
return $category->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$category = new Category();
|
$category = new Category();
|
||||||
|
$category->created_by = auth()->id();
|
||||||
$category->name = $asset_category;
|
$category->name = $asset_category;
|
||||||
$category->category_type = $item_type;
|
$category->category_type = $item_type;
|
||||||
$category->created_by = $this->created_by;
|
|
||||||
|
|
||||||
if ($category->save()) {
|
if ($category->save()) {
|
||||||
$this->log('Category '.$asset_category.' was created');
|
$this->log('Category '.$asset_category.' was created');
|
||||||
|
@ -330,6 +335,7 @@ class ItemImporter extends Importer
|
||||||
return $company->id;
|
return $company->id;
|
||||||
}
|
}
|
||||||
$company = new Company();
|
$company = new Company();
|
||||||
|
$company->created_by = auth()->id();
|
||||||
$company->name = $asset_company_name;
|
$company->name = $asset_company_name;
|
||||||
|
|
||||||
if ($company->save()) {
|
if ($company->save()) {
|
||||||
|
@ -386,6 +392,7 @@ class ItemImporter extends Importer
|
||||||
}
|
}
|
||||||
$this->log('Creating a new status');
|
$this->log('Creating a new status');
|
||||||
$status = new Statuslabel();
|
$status = new Statuslabel();
|
||||||
|
$status->created_by = auth()->id();
|
||||||
$status->name = trim($asset_statuslabel_name);
|
$status->name = trim($asset_statuslabel_name);
|
||||||
|
|
||||||
$status->deployable = 1;
|
$status->deployable = 1;
|
||||||
|
@ -425,7 +432,7 @@ class ItemImporter extends Importer
|
||||||
//Otherwise create a manufacturer.
|
//Otherwise create a manufacturer.
|
||||||
$manufacturer = new Manufacturer();
|
$manufacturer = new Manufacturer();
|
||||||
$manufacturer->name = trim($item_manufacturer);
|
$manufacturer->name = trim($item_manufacturer);
|
||||||
$manufacturer->created_by = $this->created_by;
|
$manufacturer->created_by = auth()->id();
|
||||||
|
|
||||||
if ($manufacturer->save()) {
|
if ($manufacturer->save()) {
|
||||||
$this->log('Manufacturer '.$manufacturer->name.' was created');
|
$this->log('Manufacturer '.$manufacturer->name.' was created');
|
||||||
|
@ -466,7 +473,7 @@ class ItemImporter extends Importer
|
||||||
$location->city = '';
|
$location->city = '';
|
||||||
$location->state = '';
|
$location->state = '';
|
||||||
$location->country = '';
|
$location->country = '';
|
||||||
$location->created_by = $this->created_by;
|
$location->created_by = auth()->id();
|
||||||
|
|
||||||
if ($location->save()) {
|
if ($location->save()) {
|
||||||
$this->log('Location '.$asset_location.' was created');
|
$this->log('Location '.$asset_location.' was created');
|
||||||
|
@ -502,7 +509,7 @@ class ItemImporter extends Importer
|
||||||
|
|
||||||
$supplier = new Supplier();
|
$supplier = new Supplier();
|
||||||
$supplier->name = $item_supplier;
|
$supplier->name = $item_supplier;
|
||||||
$supplier->created_by = $this->created_by;
|
$supplier->created_by = auth()->id();
|
||||||
|
|
||||||
if ($supplier->save()) {
|
if ($supplier->save()) {
|
||||||
$this->log('Supplier '.$item_supplier.' was created');
|
$this->log('Supplier '.$item_supplier.' was created');
|
||||||
|
|
|
@ -84,6 +84,7 @@ class LicenseImporter extends ItemImporter
|
||||||
$license->update($this->sanitizeItemForUpdating($license));
|
$license->update($this->sanitizeItemForUpdating($license));
|
||||||
} else {
|
} else {
|
||||||
$license->fill($this->sanitizeItemForStoring($license));
|
$license->fill($this->sanitizeItemForStoring($license));
|
||||||
|
$license->created_by = auth()->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This sets an attribute on the Loggable trait for the action log
|
// This sets an attribute on the Loggable trait for the action log
|
||||||
|
|
|
@ -51,6 +51,7 @@ class LocationImporter extends ItemImporter
|
||||||
} else {
|
} else {
|
||||||
$this->log('No Matching Location, Create a new one');
|
$this->log('No Matching Location, Create a new one');
|
||||||
$location = new Location;
|
$location = new Location;
|
||||||
|
$location->created_by = auth()->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the records from the CSV to determine their values
|
// Pull the records from the CSV to determine their values
|
||||||
|
@ -65,7 +66,6 @@ class LocationImporter extends ItemImporter
|
||||||
$this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou'));
|
$this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou'));
|
||||||
$this->item['manager'] = trim($this->findCsvMatch($row, 'manager'));
|
$this->item['manager'] = trim($this->findCsvMatch($row, 'manager'));
|
||||||
$this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username'));
|
$this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username'));
|
||||||
$this->item['created_by'] = auth()->id();
|
|
||||||
|
|
||||||
if ($this->findCsvMatch($row, 'parent_location')) {
|
if ($this->findCsvMatch($row, 'parent_location')) {
|
||||||
$this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location')));
|
$this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location')));
|
||||||
|
|
|
@ -114,6 +114,7 @@ class UserImporter extends ItemImporter
|
||||||
|
|
||||||
$this->log('No matching user, creating one');
|
$this->log('No matching user, creating one');
|
||||||
$user = new User();
|
$user = new User();
|
||||||
|
$user->created_by = auth()->id();
|
||||||
$user->fill($this->sanitizeItemForStoring($user));
|
$user->fill($this->sanitizeItemForStoring($user));
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
|
|
|
@ -3,13 +3,20 @@
|
||||||
namespace App\Listeners;
|
namespace App\Listeners;
|
||||||
|
|
||||||
use App\Events\CheckoutableCheckedOut;
|
use App\Events\CheckoutableCheckedOut;
|
||||||
|
use App\Mail\CheckinAccessoryMail;
|
||||||
|
use App\Mail\CheckinLicenseMail;
|
||||||
|
use App\Mail\CheckoutAccessoryMail;
|
||||||
|
use App\Mail\CheckoutAssetMail;
|
||||||
|
use App\Mail\CheckinAssetMail;
|
||||||
|
use App\Mail\CheckoutConsumableMail;
|
||||||
|
use App\Mail\CheckoutLicenseMail;
|
||||||
use App\Models\Accessory;
|
use App\Models\Accessory;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\Component;
|
use App\Models\Component;
|
||||||
use App\Models\Consumable;
|
use App\Models\Consumable;
|
||||||
use App\Models\LicenseSeat;
|
use App\Models\LicenseSeat;
|
||||||
use App\Models\Recipients\AdminRecipient;
|
use App\Models\Location;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\CheckinAccessoryNotification;
|
use App\Notifications\CheckinAccessoryNotification;
|
||||||
|
@ -20,9 +27,12 @@ use App\Notifications\CheckoutAssetNotification;
|
||||||
use App\Notifications\CheckoutConsumableNotification;
|
use App\Notifications\CheckoutConsumableNotification;
|
||||||
use App\Notifications\CheckoutLicenseSeatNotification;
|
use App\Notifications\CheckoutLicenseSeatNotification;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Notification;
|
use Illuminate\Support\Facades\Notification;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Osama\LaravelTeamsNotification\TeamsNotification;
|
||||||
|
|
||||||
class CheckoutableListener
|
class CheckoutableListener
|
||||||
{
|
{
|
||||||
|
@ -43,33 +53,57 @@ class CheckoutableListener
|
||||||
/**
|
/**
|
||||||
* Make a checkout acceptance and attach it in the notification
|
* Make a checkout acceptance and attach it in the notification
|
||||||
*/
|
*/
|
||||||
|
$settings = Setting::getSettings();
|
||||||
$acceptance = $this->getCheckoutAcceptance($event);
|
$acceptance = $this->getCheckoutAcceptance($event);
|
||||||
$notifiables = $this->getNotifiables($event);
|
$adminCcEmailsArray = [];
|
||||||
|
|
||||||
|
if($settings->admin_cc_email !== '') {
|
||||||
|
$adminCcEmail = $settings->admin_cc_email;
|
||||||
|
$adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail));
|
||||||
|
}
|
||||||
|
$ccEmails = array_filter($adminCcEmailsArray);
|
||||||
|
$mailable = $this->getCheckoutMailType($event, $acceptance);
|
||||||
|
$notifiable = $this->getNotifiables($event);
|
||||||
|
|
||||||
|
if (!$event->checkedOutTo->locale){
|
||||||
|
$mailable->locale($event->checkedOutTo->locale);
|
||||||
|
}
|
||||||
// Send email notifications
|
// Send email notifications
|
||||||
try {
|
try {
|
||||||
foreach ($notifiables as $notifiable) {
|
/**
|
||||||
if ($notifiable instanceof User && $notifiable->email != '') {
|
* Send an email if any of the following conditions are met:
|
||||||
if (! $event->checkedOutTo->locale){
|
* 1. The asset requires acceptance
|
||||||
Notification::locale(Setting::getSettings()->locale)->send($notifiable, $this->getCheckoutNotification($event, $acceptance));
|
* 2. The item has a EULA
|
||||||
}
|
* 3. The item should send an email at check-in/check-out
|
||||||
else {
|
*/
|
||||||
Notification::send($notifiable, $this->getCheckoutNotification($event, $acceptance));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send Webhook notification
|
if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() ||
|
||||||
if ($this->shouldSendWebhookNotification()) {
|
$this->checkoutableShouldSendEmail($event)) {
|
||||||
// Slack doesn't include the URL in its messaging format, so this is needed to hit the endpoint
|
Log::info('Sending checkout email, Locale: ' . ($event->checkedOutTo->locale ?? 'default'));
|
||||||
if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general') {
|
if (!empty($notifiable)) {
|
||||||
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
|
Mail::to($notifiable)->cc($ccEmails)->send($mailable);
|
||||||
->notify($this->getCheckoutNotification($event, $acceptance));
|
} elseif (!empty($ccEmails)) {
|
||||||
} else {
|
Mail::cc($ccEmails)->send($mailable);
|
||||||
Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint)
|
}
|
||||||
->notify($this->getCheckoutNotification($event, $acceptance));
|
Log::info('Checkout Mail sent.');
|
||||||
|
}
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkout email: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
// Send Webhook notification
|
||||||
|
try{
|
||||||
|
if ($this->shouldSendWebhookNotification()) {
|
||||||
|
if ($this->newMicrosoftTeamsWebhookEnabled()) {
|
||||||
|
$message = $this->getCheckoutNotification($event)->toMicrosoftTeams();
|
||||||
|
$notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint);
|
||||||
|
$notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams
|
||||||
|
} else {
|
||||||
|
Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint)
|
||||||
|
->notify($this->getCheckoutNotification($event, $acceptance));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (ClientException $e) {
|
} catch (ClientException $e) {
|
||||||
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
|
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
@ -103,34 +137,57 @@ class CheckoutableListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$settings = Setting::getSettings();
|
||||||
|
$adminCcEmailsArray = [];
|
||||||
|
|
||||||
$notifiables = $this->getNotifiables($event);
|
if($settings->admin_cc_email !== '') {
|
||||||
|
$adminCcEmail = $settings->admin_cc_email;
|
||||||
|
$adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail));
|
||||||
|
}
|
||||||
|
$ccEmails = array_filter($adminCcEmailsArray);
|
||||||
|
$mailable = $this->getCheckinMailType($event);
|
||||||
|
$notifiable = $this->getNotifiables($event);
|
||||||
|
if (!$event->checkedOutTo->locale){
|
||||||
|
$mailable->locale($event->checkedOutTo->locale);
|
||||||
|
}
|
||||||
// Send email notifications
|
// Send email notifications
|
||||||
try {
|
try {
|
||||||
foreach ($notifiables as $notifiable) {
|
/**
|
||||||
if ($notifiable instanceof User && $notifiable->email != '') {
|
* Send an email if any of the following conditions are met:
|
||||||
if (! $event->checkedOutTo->locale){
|
* 1. The asset requires acceptance
|
||||||
Notification::locale(Setting::getSettings()->locale)->send($notifiable, $this->getCheckoutNotification($event, $acceptance));
|
* 2. The item has a EULA
|
||||||
}
|
* 3. The item should send an email at check-in/check-out
|
||||||
else {
|
*/
|
||||||
Notification::send($notifiable, $this->getCheckinNotification($event));
|
if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() ||
|
||||||
|
$this->checkoutableShouldSendEmail($event)) {
|
||||||
|
Log::info('Sending checkin email, Locale: ' . ($event->checkedOutTo->locale ?? 'default'));
|
||||||
|
if (!empty($notifiable)) {
|
||||||
|
Mail::to($notifiable)->cc($ccEmails)->send($mailable);
|
||||||
|
} elseif (!empty($ccEmails)){
|
||||||
|
Mail::cc($ccEmails)->send($mailable);
|
||||||
}
|
}
|
||||||
|
Log::info('Checkin Mail sent.');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Send Webhook notification
|
|
||||||
if ($this->shouldSendWebhookNotification()) {
|
|
||||||
// Slack doesn't include the URL in its messaging format, so this is needed to hit the endpoint
|
|
||||||
if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general') {
|
|
||||||
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
|
|
||||||
->notify($this->getCheckinNotification($event));
|
|
||||||
} else {
|
|
||||||
Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint)
|
|
||||||
->notify($this->getCheckinNotification($event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (ClientException $e) {
|
} catch (ClientException $e) {
|
||||||
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug("Exception caught during checkin email: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send Webhook notification
|
||||||
|
try {
|
||||||
|
if ($this->shouldSendWebhookNotification()) {
|
||||||
|
if ($this->newMicrosoftTeamsWebhookEnabled()) {
|
||||||
|
$message = $this->getCheckinNotification($event)->toMicrosoftTeams();
|
||||||
|
$notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint);
|
||||||
|
$notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams
|
||||||
|
} else {
|
||||||
|
Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint)
|
||||||
|
->notify($this->getCheckinNotification($event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
Log::warning("Exception caught during checkin notification: " . $e->getMessage());
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::warning("Exception caught during checkin notification: " . $e->getMessage());
|
Log::warning("Exception caught during checkin notification: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
@ -159,33 +216,6 @@ class CheckoutableListener
|
||||||
return $acceptance;
|
return $acceptance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the entities to be notified of the passed event
|
|
||||||
*
|
|
||||||
* @param Event $event
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
private function getNotifiables($event)
|
|
||||||
{
|
|
||||||
$notifiables = collect();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify who checked out the item as long as the model can route notifications
|
|
||||||
*/
|
|
||||||
if (method_exists($event->checkedOutTo, 'routeNotificationFor')) {
|
|
||||||
$notifiables->push($event->checkedOutTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify Admin users if the settings is activated
|
|
||||||
*/
|
|
||||||
if ((Setting::getSettings()) && (Setting::getSettings()->admin_cc_email != '')) {
|
|
||||||
$notifiables->push(new AdminRecipient());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $notifiables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the appropriate notification for the event
|
* Get the appropriate notification for the event
|
||||||
*
|
*
|
||||||
|
@ -234,7 +264,7 @@ class CheckoutableListener
|
||||||
break;
|
break;
|
||||||
case Consumable::class:
|
case Consumable::class:
|
||||||
$notificationClass = CheckoutConsumableNotification::class;
|
$notificationClass = CheckoutConsumableNotification::class;
|
||||||
break;
|
break;
|
||||||
case LicenseSeat::class:
|
case LicenseSeat::class:
|
||||||
$notificationClass = CheckoutLicenseSeatNotification::class;
|
$notificationClass = CheckoutLicenseSeatNotification::class;
|
||||||
break;
|
break;
|
||||||
|
@ -243,6 +273,50 @@ class CheckoutableListener
|
||||||
|
|
||||||
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note);
|
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note);
|
||||||
}
|
}
|
||||||
|
private function getCheckoutMailType($event, $acceptance){
|
||||||
|
$lookup = [
|
||||||
|
Accessory::class => CheckoutAccessoryMail::class,
|
||||||
|
Asset::class => CheckoutAssetMail::class,
|
||||||
|
LicenseSeat::class => CheckoutLicenseMail::class,
|
||||||
|
Consumable::class => CheckoutConsumableMail::class,
|
||||||
|
];
|
||||||
|
$mailable= $lookup[get_class($event->checkoutable)];
|
||||||
|
|
||||||
|
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $event->note, $acceptance);
|
||||||
|
|
||||||
|
}
|
||||||
|
private function getCheckinMailType($event){
|
||||||
|
$lookup = [
|
||||||
|
Accessory::class => CheckinAccessoryMail::class,
|
||||||
|
Asset::class => CheckinAssetMail::class,
|
||||||
|
LicenseSeat::class => CheckinLicenseMail::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
$mailable= $lookup[get_class($event->checkoutable)];
|
||||||
|
|
||||||
|
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
|
||||||
|
|
||||||
|
}
|
||||||
|
private function getNotifiables($event){
|
||||||
|
|
||||||
|
if($event->checkedOutTo instanceof Asset){
|
||||||
|
$event->checkedOutTo->load('assignedTo');
|
||||||
|
return $event->checkedOutTo->assignedto?->email ?? '';
|
||||||
|
}
|
||||||
|
else if($event->checkedOutTo instanceof Location) {
|
||||||
|
return $event->checkedOutTo->manager?->email ?? '';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return $event->checkedOutTo?->email ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function webhookSelected(){
|
||||||
|
if(Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general'){
|
||||||
|
return 'slack';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Setting::getSettings()->webhook_selected;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the listeners for the subscriber.
|
* Register the listeners for the subscriber.
|
||||||
|
@ -271,4 +345,17 @@ class CheckoutableListener
|
||||||
{
|
{
|
||||||
return Setting::getSettings() && Setting::getSettings()->webhook_endpoint;
|
return Setting::getSettings() && Setting::getSettings()->webhook_endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkoutableShouldSendEmail($event): bool
|
||||||
|
{
|
||||||
|
if($event->checkoutable instanceof LicenseSeat){
|
||||||
|
return $event->checkoutable->license->checkin_email();
|
||||||
|
}
|
||||||
|
return (method_exists($event->checkoutable, 'checkin_email') && $event->checkoutable->checkin_email());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newMicrosoftTeamsWebhookEnabled(): bool
|
||||||
|
{
|
||||||
|
return Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,12 @@ class CustomFieldSetDefaultValuesForModel extends Component
|
||||||
{
|
{
|
||||||
$this->fields->each(function ($field) {
|
$this->fields->each(function ($field) {
|
||||||
$this->selectedValues[$field->db_column] = $this->getSelectedValueForField($field);
|
$this->selectedValues[$field->db_column] = $this->getSelectedValueForField($field);
|
||||||
|
|
||||||
|
// if the element is a checkbox and the value was just sent to null, make it
|
||||||
|
// an array since Livewire can't bind to non-array values for checkboxes.
|
||||||
|
if ($field->element === 'checkbox' && is_null($this->selectedValues[$field->db_column])) {
|
||||||
|
$this->selectedValues[$field->db_column] = [];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,9 @@ class Importer extends Component
|
||||||
case 'asset':
|
case 'asset':
|
||||||
$results = $this->assets_fields;
|
$results = $this->assets_fields;
|
||||||
break;
|
break;
|
||||||
|
case 'assetModel':
|
||||||
|
$results = $this->assetmodels_fields;
|
||||||
|
break;
|
||||||
case 'accessory':
|
case 'accessory':
|
||||||
$results = $this->accessories_fields;
|
$results = $this->accessories_fields;
|
||||||
break;
|
break;
|
||||||
|
@ -82,6 +85,9 @@ class Importer extends Component
|
||||||
case 'component':
|
case 'component':
|
||||||
$results = $this->components_fields;
|
$results = $this->components_fields;
|
||||||
break;
|
break;
|
||||||
|
case 'consumable':
|
||||||
|
$results = $this->consumables_fields;
|
||||||
|
break;
|
||||||
case 'license':
|
case 'license':
|
||||||
$results = $this->licenses_fields;
|
$results = $this->licenses_fields;
|
||||||
break;
|
break;
|
||||||
|
@ -91,10 +97,14 @@ class Importer extends Component
|
||||||
case 'location':
|
case 'location':
|
||||||
$results = $this->locations_fields;
|
$results = $this->locations_fields;
|
||||||
break;
|
break;
|
||||||
|
case 'user':
|
||||||
|
$results = $this->users_fields;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$results = [];
|
$results = [];
|
||||||
}
|
}
|
||||||
asort($results, SORT_FLAG_CASE | SORT_STRING);
|
asort($results, SORT_FLAG_CASE | SORT_STRING);
|
||||||
|
|
||||||
if ($type == "asset") {
|
if ($type == "asset") {
|
||||||
// add Custom Fields after a horizontal line
|
// add Custom Fields after a horizontal line
|
||||||
$results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’";
|
$results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’";
|
||||||
|
@ -107,6 +117,7 @@ class Importer extends Component
|
||||||
|
|
||||||
public function updatingTypeOfImport($type)
|
public function updatingTypeOfImport($type)
|
||||||
{
|
{
|
||||||
|
|
||||||
// go through each header, find a matching field to try and map it to.
|
// go through each header, find a matching field to try and map it to.
|
||||||
foreach ($this->headerRow as $i => $header) {
|
foreach ($this->headerRow as $i => $header) {
|
||||||
// do we have something mapped already?
|
// do we have something mapped already?
|
||||||
|
@ -152,13 +163,14 @@ class Importer extends Component
|
||||||
{
|
{
|
||||||
$this->authorize('import');
|
$this->authorize('import');
|
||||||
$this->importTypes = [
|
$this->importTypes = [
|
||||||
'asset' => trans('general.assets'),
|
'accessory' => trans('general.accessories'),
|
||||||
'accessory' => trans('general.accessories'),
|
'asset' => trans('general.assets'),
|
||||||
'consumable' => trans('general.consumables'),
|
'assetModel' => trans('general.asset_models'),
|
||||||
'component' => trans('general.components'),
|
'component' => trans('general.components'),
|
||||||
'license' => trans('general.licenses'),
|
'consumable' => trans('general.consumables'),
|
||||||
'user' => trans('general.users'),
|
'license' => trans('general.licenses'),
|
||||||
'location' => trans('general.locations'),
|
'location' => trans('general.locations'),
|
||||||
|
'user' => trans('general.users'),
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,7 +208,6 @@ class Importer extends Component
|
||||||
'supplier' => trans('general.supplier'),
|
'supplier' => trans('general.supplier'),
|
||||||
'purchase_cost' => trans('general.purchase_cost'),
|
'purchase_cost' => trans('general.purchase_cost'),
|
||||||
'purchase_date' => trans('general.purchase_date'),
|
'purchase_date' => trans('general.purchase_date'),
|
||||||
'purchase_order' => trans('admin/licenses/form.purchase_order'),
|
|
||||||
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
|
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
|
||||||
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
|
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
|
||||||
'manufacturer' => trans('general.manufacturer'),
|
'manufacturer' => trans('general.manufacturer'),
|
||||||
|
@ -332,6 +343,19 @@ class Importer extends Component
|
||||||
'parent_location' => trans('admin/locations/table.parent'),
|
'parent_location' => trans('admin/locations/table.parent'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->assetmodels_fields = [
|
||||||
|
'item_name' => trans('general.item_name_var', ['item' => trans('general.asset_model')]),
|
||||||
|
'category' => trans('general.category'),
|
||||||
|
'manufacturer' => trans('general.manufacturer'),
|
||||||
|
'model_number' => trans('general.model_no'),
|
||||||
|
'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
|
||||||
|
'min_amt' => trans('mail.min_QTY'),
|
||||||
|
'fieldset' => trans('admin/models/general.fieldset'),
|
||||||
|
'eol' => trans('general.eol'),
|
||||||
|
'requestable' => trans('admin/models/general.requestable'),
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
// "real fieldnames" to a list of aliases for that field
|
// "real fieldnames" to a list of aliases for that field
|
||||||
$this->aliases_fields = [
|
$this->aliases_fields = [
|
||||||
'item_name' =>
|
'item_name' =>
|
||||||
|
@ -360,6 +384,23 @@ class Importer extends Component
|
||||||
'eol date',
|
'eol date',
|
||||||
'asset eol date',
|
'asset eol date',
|
||||||
],
|
],
|
||||||
|
'eol' =>
|
||||||
|
[
|
||||||
|
'eol',
|
||||||
|
'EOL',
|
||||||
|
'eol months',
|
||||||
|
],
|
||||||
|
'depreciation' =>
|
||||||
|
[
|
||||||
|
'Depreciation',
|
||||||
|
'depreciation',
|
||||||
|
],
|
||||||
|
'requestable' =>
|
||||||
|
[
|
||||||
|
'requestable',
|
||||||
|
'Requestable',
|
||||||
|
],
|
||||||
|
|
||||||
'gravatar' =>
|
'gravatar' =>
|
||||||
[
|
[
|
||||||
'gravatar',
|
'gravatar',
|
||||||
|
@ -504,7 +545,6 @@ class Importer extends Component
|
||||||
if (!$this->activeFile) {
|
if (!$this->activeFile) {
|
||||||
$this->message = trans('admin/hardware/message.import.file_missing');
|
$this->message = trans('admin/hardware/message.import.file_missing');
|
||||||
$this->message_type = 'danger';
|
$this->message_type = 'danger';
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,6 +559,8 @@ class Importer extends Component
|
||||||
$this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings
|
$this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->file_id = $id;
|
||||||
$this->import_errors = null;
|
$this->import_errors = null;
|
||||||
$this->statusText = null;
|
$this->statusText = null;
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,11 @@ namespace App\Livewire;
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
|
use Osama\LaravelTeamsNotification\TeamsNotification;
|
||||||
class SlackSettingsForm extends Component
|
class SlackSettingsForm extends Component
|
||||||
{
|
{
|
||||||
public $webhook_endpoint;
|
public $webhook_endpoint;
|
||||||
|
@ -19,6 +20,7 @@ class SlackSettingsForm extends Component
|
||||||
public $webhook_placeholder;
|
public $webhook_placeholder;
|
||||||
public $webhook_icon;
|
public $webhook_icon;
|
||||||
public $webhook_selected;
|
public $webhook_selected;
|
||||||
|
public $teams_webhook_deprecated;
|
||||||
public array $webhook_text;
|
public array $webhook_text;
|
||||||
|
|
||||||
public Setting $setting;
|
public Setting $setting;
|
||||||
|
@ -62,7 +64,7 @@ class SlackSettingsForm extends Component
|
||||||
"name" => trans('admin/settings/general.ms_teams'),
|
"name" => trans('admin/settings/general.ms_teams'),
|
||||||
"icon" => "fa-brands fa-microsoft",
|
"icon" => "fa-brands fa-microsoft",
|
||||||
"placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX",
|
"placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX",
|
||||||
"link" => "https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet#create-incoming-webhooks-1",
|
"link" => "https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498",
|
||||||
"test" => "msTeamTestWebhook"
|
"test" => "msTeamTestWebhook"
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -79,15 +81,17 @@ class SlackSettingsForm extends Component
|
||||||
$this->webhook_channel = $this->setting->webhook_channel;
|
$this->webhook_channel = $this->setting->webhook_channel;
|
||||||
$this->webhook_botname = $this->setting->webhook_botname;
|
$this->webhook_botname = $this->setting->webhook_botname;
|
||||||
$this->webhook_options = $this->setting->webhook_selected;
|
$this->webhook_options = $this->setting->webhook_selected;
|
||||||
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
|
$this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows');
|
||||||
|
if($this->webhook_selected === 'microsoft' || $this->webhook_selected === 'google'){
|
||||||
$this->webhook_channel = '#NA';
|
$this->webhook_channel = '#NA';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
|
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
|
||||||
$this->isDisabled= '';
|
$this->isDisabled= '';
|
||||||
}
|
}
|
||||||
|
if($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) {
|
||||||
|
session()->flash('warning', 'The selected Microsoft Teams webhook URL will be deprecated Jan 31st, 2025. Please use a workflow URL. Microsofts Documentation on creating a workflow can be found <a href="https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498" target="_blank"> here.</a>');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function updated($field) {
|
public function updated($field) {
|
||||||
|
|
||||||
|
@ -109,7 +113,11 @@ class SlackSettingsForm extends Component
|
||||||
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
|
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
|
||||||
$this->webhook_channel = '#NA';
|
$this->webhook_channel = '#NA';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedwebhookEndpoint()
|
||||||
|
{
|
||||||
|
$this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isButtonDisabled() {
|
private function isButtonDisabled() {
|
||||||
|
@ -126,7 +134,9 @@ class SlackSettingsForm extends Component
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$this->isButtonDisabled();
|
$this->isButtonDisabled();
|
||||||
|
|
||||||
return view('livewire.slack-settings-form');
|
return view('livewire.slack-settings-form');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWebhook(){
|
public function testWebhook(){
|
||||||
|
@ -236,20 +246,32 @@ class SlackSettingsForm extends Component
|
||||||
}
|
}
|
||||||
public function msTeamTestWebhook(){
|
public function msTeamTestWebhook(){
|
||||||
|
|
||||||
$payload =
|
try {
|
||||||
[
|
|
||||||
"@type" => "MessageCard",
|
|
||||||
"@context" => "http://schema.org/extensions",
|
|
||||||
"summary" => trans('mail.snipe_webhook_summary'),
|
|
||||||
"title" => trans('mail.snipe_webhook_test'),
|
|
||||||
"text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
if($this->teams_webhook_deprecated){
|
||||||
$response = Http::withHeaders([
|
//will use the deprecated webhook format
|
||||||
'content-type' => 'applications/json',
|
$payload =
|
||||||
])->post($this->webhook_endpoint,
|
[
|
||||||
$payload)->throw();
|
"@type" => "MessageCard",
|
||||||
|
"@context" => "http://schema.org/extensions",
|
||||||
|
"summary" => trans('mail.snipe_webhook_summary'),
|
||||||
|
"title" => trans('mail.snipe_webhook_test'),
|
||||||
|
"text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||||
|
];
|
||||||
|
$response = Http::withHeaders([
|
||||||
|
'content-type' => 'applications/json',
|
||||||
|
])->post($this->webhook_endpoint,
|
||||||
|
$payload)->throw();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$notification = new TeamsNotification($this->webhook_endpoint);
|
||||||
|
$message = trans('general.webhook_test_msg', ['app' => $this->webhook_name]);
|
||||||
|
$notification->success()->sendMessage($message);
|
||||||
|
|
||||||
|
$response = Http::withHeaders([
|
||||||
|
'content-type' => 'applications/json',
|
||||||
|
])->post($this->webhook_endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
if(($response->getStatusCode() == 302)||($response->getStatusCode() == 301)){
|
if(($response->getStatusCode() == 302)||($response->getStatusCode() == 301)){
|
||||||
return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||||
|
|
70
app/Mail/CheckinAccessoryMail.php
Normal file
70
app/Mail/CheckinAccessoryMail.php
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\Accessory;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CheckinAccessoryMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Accessory $accessory, $checkedOutTo, User $checkedInby, $note)
|
||||||
|
{
|
||||||
|
$this->item = $accessory;
|
||||||
|
$this->target = $checkedOutTo;
|
||||||
|
$this->admin = $checkedInby;
|
||||||
|
$this->note = $note;
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||||
|
|
||||||
|
return new Envelope(
|
||||||
|
from: $from,
|
||||||
|
subject: trans('mail.Accessory_Checkin_Notification'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
markdown: 'mail.markdown.checkin-accessory',
|
||||||
|
with: [
|
||||||
|
'item' => $this->item,
|
||||||
|
'admin' => $this->admin,
|
||||||
|
'note' => $this->note,
|
||||||
|
'target' => $this->target,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
93
app/Mail/CheckinAssetMail.php
Normal file
93
app/Mail/CheckinAssetMail.php
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CheckinAssetMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Asset $asset, $checkedOutTo, User $checkedInBy, $note)
|
||||||
|
{
|
||||||
|
$this->target = $checkedOutTo;
|
||||||
|
$this->item = $asset;
|
||||||
|
$this->admin = $checkedInBy;
|
||||||
|
$this->note = $note;
|
||||||
|
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
|
$this->expected_checkin = '';
|
||||||
|
|
||||||
|
if ($this->item->expected_checkin) {
|
||||||
|
$this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||||
|
|
||||||
|
return new Envelope(
|
||||||
|
from: $from,
|
||||||
|
subject: trans('mail.Asset_Checkin_Notification'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mail representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return Content
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
$this->item->load('assetstatus');
|
||||||
|
$fields = [];
|
||||||
|
|
||||||
|
// Check if the item has custom fields associated with it
|
||||||
|
if (($this->item->model) && ($this->item->model->fieldset)) {
|
||||||
|
$fields = $this->item->model->fieldset->fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Content(
|
||||||
|
markdown: 'mail.markdown.checkin-asset',
|
||||||
|
with: [
|
||||||
|
'item' => $this->item,
|
||||||
|
'status' => $this->item->assetstatus?->name,
|
||||||
|
'admin' => $this->admin,
|
||||||
|
'note' => $this->note,
|
||||||
|
'target' => $this->target,
|
||||||
|
'fields' => $fields,
|
||||||
|
'expected_checkin' => $this->expected_checkin,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
71
app/Mail/CheckinLicenseMail.php
Normal file
71
app/Mail/CheckinLicenseMail.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\LicenseSeat;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CheckinLicenseMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(LicenseSeat $licenseSeat, $checkedOutTo, User $checkedInBy, $note)
|
||||||
|
{
|
||||||
|
$this->target = $checkedOutTo;
|
||||||
|
$this->item = $licenseSeat;
|
||||||
|
$this->admin = $checkedInBy;
|
||||||
|
$this->note = $note;
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||||
|
|
||||||
|
return new Envelope(
|
||||||
|
from: $from,
|
||||||
|
subject: trans('mail.License_Checkin_Notification'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
markdown: 'mail.markdown.checkin-license',
|
||||||
|
with: [
|
||||||
|
'license_seat' => $this->item,
|
||||||
|
'license' => $this->item->license,
|
||||||
|
'admin' => $this->admin,
|
||||||
|
'note' => $this->note,
|
||||||
|
'target' => $this->target,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
82
app/Mail/CheckoutAccessoryMail.php
Normal file
82
app/Mail/CheckoutAccessoryMail.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\Accessory;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class CheckoutAccessoryMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Accessory $accessory, $checkedOutTo, User $checkedOutBy,$note, $acceptance)
|
||||||
|
{
|
||||||
|
$this->item = $accessory;
|
||||||
|
$this->admin = $checkedOutBy;
|
||||||
|
$this->note = $note;
|
||||||
|
$this->checkout_qty = $accessory->checkout_qty;
|
||||||
|
$this->target = $checkedOutTo;
|
||||||
|
$this->acceptance = $acceptance;
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||||
|
|
||||||
|
return new Envelope(
|
||||||
|
from: $from,
|
||||||
|
subject: (trans('mail.Accessory_Checkout_Notification')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
Log::debug($this->item->getImageUrl());
|
||||||
|
$eula = $this->item->getEula();
|
||||||
|
$req_accept = $this->item->requireAcceptance();
|
||||||
|
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
||||||
|
|
||||||
|
return new Content(
|
||||||
|
markdown: 'mail.markdown.checkout-accessory',
|
||||||
|
with: [
|
||||||
|
'item' => $this->item,
|
||||||
|
'admin' => $this->admin,
|
||||||
|
'note' => $this->note,
|
||||||
|
'target' => $this->target,
|
||||||
|
'eula' => $eula,
|
||||||
|
'req_accept' => $req_accept,
|
||||||
|
'accept_url' => $accept_url,
|
||||||
|
'checkout_qty' => $this->checkout_qty,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
110
app/Mail/CheckoutAssetMail.php
Normal file
110
app/Mail/CheckoutAssetMail.php
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailables\Attachment;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CheckoutAssetMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $note, $acceptance)
|
||||||
|
{
|
||||||
|
$this->item = $asset;
|
||||||
|
$this->admin = $checkedOutBy;
|
||||||
|
$this->note = $note;
|
||||||
|
$this->target = $checkedOutTo;
|
||||||
|
$this->acceptance = $acceptance;
|
||||||
|
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
|
|
||||||
|
$this->last_checkout = '';
|
||||||
|
$this->expected_checkin = '';
|
||||||
|
|
||||||
|
if ($this->item->last_checkout) {
|
||||||
|
$this->last_checkout = Helper::getFormattedDateObject($this->item->last_checkout, 'date',
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->item->expected_checkin) {
|
||||||
|
$this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||||
|
|
||||||
|
return new Envelope(
|
||||||
|
from: $from,
|
||||||
|
subject: trans('mail.Asset_Checkout_Notification'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mail representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return Content
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
$this->item->load('assetstatus');
|
||||||
|
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
|
||||||
|
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
|
||||||
|
$fields = [];
|
||||||
|
|
||||||
|
// Check if the item has custom fields associated with it
|
||||||
|
if (($this->item->model) && ($this->item->model->fieldset)) {
|
||||||
|
$fields = $this->item->model->fieldset->fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
||||||
|
|
||||||
|
return new Content(
|
||||||
|
markdown: 'mail.markdown.checkout-asset',
|
||||||
|
with: [
|
||||||
|
'item' => $this->item,
|
||||||
|
'admin' => $this->admin,
|
||||||
|
'status' => $this->item->assetstatus?->name,
|
||||||
|
'note' => $this->note,
|
||||||
|
'target' => $this->target,
|
||||||
|
'fields' => $fields,
|
||||||
|
'eula' => $eula,
|
||||||
|
'req_accept' => $req_accept,
|
||||||
|
'accept_url' => $accept_url,
|
||||||
|
'last_checkout' => $this->last_checkout,
|
||||||
|
'expected_checkin' => $this->expected_checkin,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
84
app/Mail/CheckoutConsumableMail.php
Normal file
84
app/Mail/CheckoutConsumableMail.php
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\Consumable;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class CheckoutConsumableMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Consumable $consumable, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
|
||||||
|
{
|
||||||
|
$this->item = $consumable;
|
||||||
|
$this->admin = $checkedOutBy;
|
||||||
|
$this->note = $note;
|
||||||
|
$this->target = $checkedOutTo;
|
||||||
|
$this->acceptance = $acceptance;
|
||||||
|
$this->qty = $consumable->checkout_qty;
|
||||||
|
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||||
|
|
||||||
|
return new Envelope(
|
||||||
|
from: $from,
|
||||||
|
subject: trans('mail.Confirm_consumable_delivery'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
|
||||||
|
$eula = $this->item->getEula();
|
||||||
|
$req_accept = $this->item->requireAcceptance();
|
||||||
|
|
||||||
|
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
||||||
|
|
||||||
|
return new Content(
|
||||||
|
markdown: 'mail.markdown.checkout-consumable',
|
||||||
|
with: [
|
||||||
|
'item' => $this->item,
|
||||||
|
'admin' => $this->admin,
|
||||||
|
'note' => $this->note,
|
||||||
|
'target' => $this->target,
|
||||||
|
'eula' => $eula,
|
||||||
|
'req_accept' => $req_accept,
|
||||||
|
'accept_url' => $accept_url,
|
||||||
|
'qty' => $this->qty,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
80
app/Mail/CheckoutLicenseMail.php
Normal file
80
app/Mail/CheckoutLicenseMail.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\LicenseSeat;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CheckoutLicenseMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(LicenseSeat $licenseSeat, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
|
||||||
|
{
|
||||||
|
$this->item = $licenseSeat;
|
||||||
|
$this->admin = $checkedOutBy;
|
||||||
|
$this->note = $note;
|
||||||
|
$this->target = $checkedOutTo;
|
||||||
|
$this->acceptance = $acceptance;
|
||||||
|
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||||
|
|
||||||
|
return new Envelope(
|
||||||
|
from: $from,
|
||||||
|
subject: trans('mail.Confirm_license_delivery'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
|
||||||
|
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
|
||||||
|
|
||||||
|
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
||||||
|
return new Content(
|
||||||
|
markdown: 'mail.markdown.checkout-license',
|
||||||
|
with: [
|
||||||
|
'license_seat' => $this->item,
|
||||||
|
'license' => $this->item->license,
|
||||||
|
'admin' => $this->admin,
|
||||||
|
'note' => $this->note,
|
||||||
|
'target' => $this->target,
|
||||||
|
'eula' => $eula,
|
||||||
|
'req_accept' => $req_accept,
|
||||||
|
'accept_url' => $accept_url,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,8 @@ class Actionlog extends SnipeModel
|
||||||
'created_by',
|
'created_by',
|
||||||
'remote_ip',
|
'remote_ip',
|
||||||
'user_agent',
|
'user_agent',
|
||||||
|
'item_type',
|
||||||
|
'target_type',
|
||||||
'action_source'
|
'action_source'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -64,10 +66,10 @@ class Actionlog extends SnipeModel
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $searchableRelations = [
|
protected $searchableRelations = [
|
||||||
'company' => ['name'],
|
'company' => ['name'],
|
||||||
'adminuser' => ['first_name','last_name','username', 'email'],
|
'adminuser' => ['first_name','last_name','username', 'email'],
|
||||||
'user' => ['first_name','last_name','username', 'email'],
|
'user' => ['first_name','last_name','username', 'email'],
|
||||||
'assets' => ['asset_tag','name'],
|
'assets' => ['asset_tag','name'],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -68,6 +68,7 @@ class AssetModel extends SnipeModel
|
||||||
'model_number',
|
'model_number',
|
||||||
'name',
|
'name',
|
||||||
'notes',
|
'notes',
|
||||||
|
'requestable',
|
||||||
];
|
];
|
||||||
|
|
||||||
use Searchable;
|
use Searchable;
|
||||||
|
@ -328,4 +329,14 @@ class AssetModel extends SnipeModel
|
||||||
{
|
{
|
||||||
return $query->leftJoin('custom_fieldsets', 'models.fieldset_id', '=', 'custom_fieldsets.id')->orderBy('custom_fieldsets.name', $order);
|
return $query->leftJoin('custom_fieldsets', 'models.fieldset_id', '=', 'custom_fieldsets.id')->orderBy('custom_fieldsets.name', $order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query builder scope to order on created_by name
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function scopeOrderByCreatedByName($query, $order)
|
||||||
|
{
|
||||||
|
return $query->leftJoin('users as admin_sort', 'models.created_by', '=', 'admin_sort.id')->select('models.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ final class Company extends SnipeModel
|
||||||
if ($current_user->company_id != null) {
|
if ($current_user->company_id != null) {
|
||||||
return $current_user->company_id;
|
return $current_user->company_id;
|
||||||
} else {
|
} else {
|
||||||
return static::getIdFromInput($unescaped_input);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ trait CompanyableTrait
|
||||||
* This trait is used to scope models to the current company. To use this scope on companyable models,
|
* This trait is used to scope models to the current company. To use this scope on companyable models,
|
||||||
* we use the "use Companyable;" statement at the top of the mode.
|
* we use the "use Companyable;" statement at the top of the mode.
|
||||||
*
|
*
|
||||||
* We CANNOT USE THIS ON USERS, as it causes an infinite loop and prevents users from logging in, since this scope will be
|
|
||||||
* applied to the currently logged in (or logging in) user in addition to the user model for viewing lists of users.
|
|
||||||
*
|
|
||||||
* @see \App\Models\Company\Company::scopeCompanyables()
|
* @see \App\Models\Company\Company::scopeCompanyables()
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -38,6 +38,7 @@ class Component extends SnipeModel
|
||||||
'min_amt' => 'integer|min:0|nullable',
|
'min_amt' => 'integer|min:0|nullable',
|
||||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||||
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
||||||
|
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,6 +61,8 @@ class Component extends SnipeModel
|
||||||
'company_id',
|
'company_id',
|
||||||
'supplier_id',
|
'supplier_id',
|
||||||
'location_id',
|
'location_id',
|
||||||
|
'manufacturer_id',
|
||||||
|
'model_number',
|
||||||
'name',
|
'name',
|
||||||
'purchase_cost',
|
'purchase_cost',
|
||||||
'purchase_date',
|
'purchase_date',
|
||||||
|
@ -77,7 +80,15 @@ class Component extends SnipeModel
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $searchableAttributes = ['name', 'order_number', 'serial', 'purchase_cost', 'purchase_date', 'notes'];
|
protected $searchableAttributes = [
|
||||||
|
'name',
|
||||||
|
'order_number',
|
||||||
|
'serial',
|
||||||
|
'purchase_cost',
|
||||||
|
'purchase_date',
|
||||||
|
'notes',
|
||||||
|
'model_number',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The relations and their attributes that should be included when searching the model.
|
* The relations and their attributes that should be included when searching the model.
|
||||||
|
@ -89,6 +100,7 @@ class Component extends SnipeModel
|
||||||
'company' => ['name'],
|
'company' => ['name'],
|
||||||
'location' => ['name'],
|
'location' => ['name'],
|
||||||
'supplier' => ['name'],
|
'supplier' => ['name'],
|
||||||
|
'manufacturer' => ['name'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,6 +195,19 @@ class Component extends SnipeModel
|
||||||
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
|
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes the item -> manufacturer relationship
|
||||||
|
*
|
||||||
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
|
* @since [v3.0]
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||||
|
*/
|
||||||
|
public function manufacturer()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes the component -> action logs relationship
|
* Establishes the component -> action logs relationship
|
||||||
*
|
*
|
||||||
|
@ -311,6 +336,19 @@ class Component extends SnipeModel
|
||||||
return $query->leftJoin('suppliers', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
|
return $query->leftJoin('suppliers', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query builder scope to order on manufacturer
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||||
|
* @param text $order Order
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||||
|
*/
|
||||||
|
public function scopeOrderManufacturer($query, $order)
|
||||||
|
{
|
||||||
|
return $query->leftJoin('manufacturers', 'components.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
|
||||||
|
}
|
||||||
|
|
||||||
public function scopeOrderByCreatedBy($query, $order)
|
public function scopeOrderByCreatedBy($query, $order)
|
||||||
{
|
{
|
||||||
return $query->leftJoin('users as admin_sort', 'components.created_by', '=', 'admin_sort.id')->select('components.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
|
return $query->leftJoin('users as admin_sort', 'components.created_by', '=', 'admin_sort.id')->select('components.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Rules\AlphaEncrypted;
|
||||||
|
use App\Rules\NumericEncrypted;
|
||||||
use Gate;
|
use Gate;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
@ -95,6 +97,19 @@ class CustomFieldset extends Model
|
||||||
array_push($rule, $field->attributes['format']);
|
array_push($rule, $field->attributes['format']);
|
||||||
$rules[$field->db_column_name()] = $rule;
|
$rules[$field->db_column_name()] = $rule;
|
||||||
|
|
||||||
|
|
||||||
|
// these are to replace the standard 'numeric' and 'alpha' rules if the custom field is also encrypted.
|
||||||
|
// the values need to be decrypted first, because encrypted strings are alphanumeric
|
||||||
|
if ($field->format === 'NUMERIC' && $field->field_encrypted) {
|
||||||
|
$numericKey = array_search('numeric', $rules[$field->db_column_name()]);
|
||||||
|
$rules[$field->db_column_name()][$numericKey] = new NumericEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field->format === 'ALPHA' && $field->field_encrypted) {
|
||||||
|
$alphaKey = array_search('alpha', $rules[$field->db_column_name()]);
|
||||||
|
$rules[$field->db_column_name()][$alphaKey] = new AlphaEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
// add not_array to rules for all fields but checkboxes
|
// add not_array to rules for all fields but checkboxes
|
||||||
if ($field->element != 'checkbox') {
|
if ($field->element != 'checkbox') {
|
||||||
$rules[$field->db_column_name()][] = 'not_array';
|
$rules[$field->db_column_name()][] = 'not_array';
|
||||||
|
|
|
@ -14,4 +14,16 @@ class Import extends Model
|
||||||
'first_row' => 'array',
|
'first_row' => 'array',
|
||||||
'field_map' => 'json',
|
'field_map' => 'json',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes the license -> admin user relationship
|
||||||
|
*
|
||||||
|
* @author A. Gianotto <snipe@snipe.net>
|
||||||
|
* @since [v2.0]
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||||
|
*/
|
||||||
|
public function adminuser()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(\App\Models\User::class, 'created_by');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,14 +49,7 @@ class LabelWriter_1933081 extends LabelWriter
|
||||||
);
|
);
|
||||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||||
} else {
|
}
|
||||||
static::writeText(
|
|
||||||
$pdf, $record->get('tag'),
|
|
||||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
|
||||||
'freesans', 'b', self::TAG_SIZE, 'R',
|
|
||||||
$usableWidth, self::TAG_SIZE, true, 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($record->has('title')) {
|
if ($record->has('title')) {
|
||||||
static::writeText(
|
static::writeText(
|
||||||
|
|
|
@ -49,13 +49,6 @@ class LabelWriter_2112283 extends LabelWriter
|
||||||
);
|
);
|
||||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||||
} else {
|
|
||||||
static::writeText(
|
|
||||||
$pdf, $record->get('tag'),
|
|
||||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
|
||||||
'freesans', 'b', self::TAG_SIZE, 'R',
|
|
||||||
$usableWidth, self::TAG_SIZE, true, 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($record->has('title')) {
|
if ($record->has('title')) {
|
||||||
|
|
|
@ -50,13 +50,6 @@ class LabelWriter_30252 extends LabelWriter
|
||||||
);
|
);
|
||||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||||
} else {
|
|
||||||
static::writeText(
|
|
||||||
$pdf, $record->get('tag'),
|
|
||||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
|
||||||
'freemono', 'b', self::TAG_SIZE, 'R',
|
|
||||||
$usableWidth, self::TAG_SIZE, true, 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($record->has('title')) {
|
if ($record->has('title')) {
|
||||||
|
|
|
@ -184,7 +184,7 @@ class License extends Depreciable
|
||||||
$logAction->item_type = self::class;
|
$logAction->item_type = self::class;
|
||||||
$logAction->item_id = $license->id;
|
$logAction->item_id = $license->id;
|
||||||
$logAction->created_by = auth()->id() ?: 1; // We don't have an id while running the importer from CLI.
|
$logAction->created_by = auth()->id() ?: 1; // We don't have an id while running the importer from CLI.
|
||||||
$logAction->note = "deleted ${change} seats";
|
$logAction->note = "deleted {$change} seats";
|
||||||
$logAction->target_id = null;
|
$logAction->target_id = null;
|
||||||
$logAction->logaction('delete seats');
|
$logAction->logaction('delete seats');
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ class License extends Depreciable
|
||||||
$logAction->item_type = self::class;
|
$logAction->item_type = self::class;
|
||||||
$logAction->item_id = $license->id;
|
$logAction->item_id = $license->id;
|
||||||
$logAction->created_by = auth()->id() ?: 1; // Importer.
|
$logAction->created_by = auth()->id() ?: 1; // Importer.
|
||||||
$logAction->note = "added ${change} seats";
|
$logAction->note = "added {$change} seats";
|
||||||
$logAction->target_id = null;
|
$logAction->target_id = null;
|
||||||
$logAction->logaction('add seats');
|
$logAction->logaction('add seats');
|
||||||
}
|
}
|
||||||
|
@ -743,4 +743,4 @@ class License extends Depreciable
|
||||||
{
|
{
|
||||||
return $query->leftJoin('users as admin_sort', 'licenses.created_by', '=', 'admin_sort.id')->select('licenses.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
|
return $query->leftJoin('users as admin_sort', 'licenses.created_by', '=', 'admin_sort.id')->select('licenses.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ class Manufacturer extends SnipeModel
|
||||||
&& (($this->licenses_count ?? $this->licenses()->count()) === 0)
|
&& (($this->licenses_count ?? $this->licenses()->count()) === 0)
|
||||||
&& (($this->consumables_count ?? $this->consumables()->count()) === 0)
|
&& (($this->consumables_count ?? $this->consumables()->count()) === 0)
|
||||||
&& (($this->accessories_count ?? $this->accessories()->count()) === 0)
|
&& (($this->accessories_count ?? $this->accessories()->count()) === 0)
|
||||||
|
&& (($this->components_count ?? $this->components()->count()) === 0)
|
||||||
&& ($this->deleted_at == '');
|
&& ($this->deleted_at == '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +107,10 @@ class Manufacturer extends SnipeModel
|
||||||
return $this->hasMany(\App\Models\Consumable::class, 'manufacturer_id');
|
return $this->hasMany(\App\Models\Consumable::class, 'manufacturer_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function components()
|
||||||
|
{
|
||||||
|
return $this->hasMany(\App\Models\Component::class, 'manufacturer_id');
|
||||||
|
}
|
||||||
|
|
||||||
public function adminuser()
|
public function adminuser()
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,9 +6,15 @@ use App\Models\Setting;
|
||||||
|
|
||||||
class AdminRecipient extends Recipient
|
class AdminRecipient extends Recipient
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected $email;
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$settings = Setting::getSettings();
|
$settings = Setting::getSettings();
|
||||||
$this->email = trim($settings->admin_cc_email);
|
$this->email = trim($settings->admin_cc_email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEmail(){
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ class Setting extends Model
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'label2_asset_logo' => 'boolean',
|
'label2_asset_logo' => 'boolean',
|
||||||
|
'require_checkinout_notes' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,9 +6,11 @@ use App\Models\Accessory;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Messages\SlackMessage;
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use NotificationChannels\GoogleChat\Card;
|
use NotificationChannels\GoogleChat\Card;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||||
|
@ -55,22 +57,9 @@ class CheckinAccessoryNotification extends Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||||
$notifyBy[] = 'slack';
|
$notifyBy[] = SlackWebhookChannel::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Only send notifications to users that have email addresses
|
|
||||||
*/
|
|
||||||
if ($this->target instanceof User && $this->target->email != '') {
|
|
||||||
Log::debug('The target is a user');
|
|
||||||
|
|
||||||
if ($this->item->checkin_email()) {
|
|
||||||
$notifyBy[] = 'mail';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::debug('checkin_email on this category is '.$this->item->checkin_email());
|
|
||||||
|
|
||||||
return $notifyBy;
|
return $notifyBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,18 +92,29 @@ class CheckinAccessoryNotification extends Notification
|
||||||
$admin = $this->admin;
|
$admin = $this->admin;
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
|
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||||
|
return MicrosoftTeamsMessage::create()
|
||||||
|
->to($this->settings->webhook_endpoint)
|
||||||
|
->type('success')
|
||||||
|
->addStartGroupToSection('activityTitle')
|
||||||
|
->title(trans('Accessory_Checkin_Notification'))
|
||||||
|
->addStartGroupToSection('activityText')
|
||||||
|
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
|
||||||
|
->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '')
|
||||||
|
->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName())
|
||||||
|
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
||||||
|
->fact(trans('mail.notes'), $note ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
return MicrosoftTeamsMessage::create()
|
$message = trans('mail.Accessory_Checkin_Notification');
|
||||||
->to($this->settings->webhook_endpoint)
|
$details = [
|
||||||
->type('success')
|
trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name),
|
||||||
->addStartGroupToSection('activityTitle')
|
trans('mail.checked_into') => $item->location->name ? $item->location->name : '',
|
||||||
->title(trans('Accessory_Checkin_Notification'))
|
trans('mail.Accessory_Checkin_Notification'). ' by' => $admin->present()->fullName(),
|
||||||
->addStartGroupToSection('activityText')
|
trans('admin/consumables/general.remaining')=> $item->numRemaining(),
|
||||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
|
trans('mail.notes') => $note ?: '',
|
||||||
->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '')
|
];
|
||||||
->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName())
|
return array($message, $details);
|
||||||
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
|
||||||
->fact(trans('mail.notes'), $note ?: '');
|
|
||||||
}
|
}
|
||||||
public function toGoogleChat()
|
public function toGoogleChat()
|
||||||
{
|
{
|
||||||
|
@ -142,24 +142,4 @@ class CheckinAccessoryNotification extends Notification
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mail representation of the notification.
|
|
||||||
*
|
|
||||||
* @param mixed $notifiable
|
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
|
||||||
*/
|
|
||||||
public function toMail()
|
|
||||||
{
|
|
||||||
Log::debug('to email called');
|
|
||||||
|
|
||||||
return (new MailMessage)->markdown('notifications.markdown.checkin-accessory',
|
|
||||||
[
|
|
||||||
'item' => $this->item,
|
|
||||||
'admin' => $this->admin,
|
|
||||||
'note' => $this->note,
|
|
||||||
'target' => $this->target,
|
|
||||||
])
|
|
||||||
->subject(trans('mail.Accessory_Checkin_Notification'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,11 @@ use App\Models\Asset;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Messages\SlackMessage;
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use NotificationChannels\GoogleChat\Card;
|
use NotificationChannels\GoogleChat\Card;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||||
|
@ -50,7 +52,6 @@ class CheckinAssetNotification extends Notification
|
||||||
*/
|
*/
|
||||||
public function via()
|
public function via()
|
||||||
{
|
{
|
||||||
$notifyBy = [];
|
|
||||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||||
|
|
||||||
$notifyBy[] = GoogleChatChannel::class;
|
$notifyBy[] = GoogleChatChannel::class;
|
||||||
|
@ -62,15 +63,7 @@ class CheckinAssetNotification extends Notification
|
||||||
}
|
}
|
||||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||||
Log::debug('use webhook');
|
Log::debug('use webhook');
|
||||||
$notifyBy[] = 'slack';
|
$notifyBy[] = SlackWebhookChannel::class;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only send checkin notifications to users if the category
|
|
||||||
* has the corresponding checkbox checked.
|
|
||||||
*/
|
|
||||||
if ($this->item->checkin_email() && $this->target instanceof User && $this->target->email != '') {
|
|
||||||
$notifyBy[] = 'mail';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $notifyBy;
|
return $notifyBy;
|
||||||
|
@ -106,16 +99,30 @@ class CheckinAssetNotification extends Notification
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
|
|
||||||
return MicrosoftTeamsMessage::create()
|
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||||
->to($this->settings->webhook_endpoint)
|
return MicrosoftTeamsMessage::create()
|
||||||
->type('success')
|
->to($this->settings->webhook_endpoint)
|
||||||
->title(trans('mail.Asset_Checkin_Notification'))
|
->type('success')
|
||||||
->addStartGroupToSection('activityText')
|
->title(trans('mail.Asset_Checkin_Notification'))
|
||||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText')
|
->addStartGroupToSection('activityText')
|
||||||
->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '')
|
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText')
|
||||||
->fact(trans('mail.Asset_Checkin_Notification')." by ", $admin->present()->fullName())
|
->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '')
|
||||||
->fact(trans('admin/hardware/form.status'), $item->assetstatus->name)
|
->fact(trans('mail.Asset_Checkin_Notification') . " by ", $admin->present()->fullName())
|
||||||
->fact(trans('mail.notes'), $note ?: '');
|
->fact(trans('admin/hardware/form.status'), $item->assetstatus->name)
|
||||||
|
->fact(trans('mail.notes'), $note ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$message = trans('mail.Asset_Checkin_Notification');
|
||||||
|
$details = [
|
||||||
|
trans('mail.asset') => htmlspecialchars_decode($item->present()->name),
|
||||||
|
trans('mail.checked_into') => $item->location->name ? $item->location->name : '',
|
||||||
|
trans('mail.Asset_Checkin_Notification')." by " => $admin->present()->fullName(),
|
||||||
|
trans('admin/hardware/form.status') => $item->assetstatus->name,
|
||||||
|
trans('mail.notes') => $note ?: '',
|
||||||
|
];
|
||||||
|
|
||||||
|
return array($message, $details);
|
||||||
}
|
}
|
||||||
public function toGoogleChat()
|
public function toGoogleChat()
|
||||||
{
|
{
|
||||||
|
@ -142,35 +149,5 @@ class CheckinAssetNotification extends Notification
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mail representation of the notification.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
|
||||||
*/
|
|
||||||
public function toMail()
|
|
||||||
{
|
|
||||||
$fields = [];
|
|
||||||
|
|
||||||
// Check if the item has custom fields associated with it
|
|
||||||
if (($this->item->model) && ($this->item->model->fieldset)) {
|
|
||||||
$fields = $this->item->model->fieldset->fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = (new MailMessage)->markdown('notifications.markdown.checkin-asset',
|
|
||||||
[
|
|
||||||
'item' => $this->item,
|
|
||||||
'status' => $this->item->assetstatus?->name,
|
|
||||||
'admin' => $this->admin,
|
|
||||||
'note' => $this->note,
|
|
||||||
'target' => $this->target,
|
|
||||||
'fields' => $fields,
|
|
||||||
'expected_checkin' => $this->expected_checkin,
|
|
||||||
])
|
|
||||||
->subject(trans('mail.Asset_Checkin_Notification'));
|
|
||||||
|
|
||||||
return $message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ use App\Models\LicenseSeat;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Messages\SlackMessage;
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use NotificationChannels\GoogleChat\Card;
|
use NotificationChannels\GoogleChat\Card;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||||
|
@ -58,15 +60,7 @@ class CheckinLicenseSeatNotification extends Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||||
$notifyBy[] = 'slack';
|
$notifyBy[] = SlackWebhookChannel::class;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only send checkin notifications to users if the category
|
|
||||||
* has the corresponding checkbox checked.
|
|
||||||
*/
|
|
||||||
if ($this->item->checkin_email() && $this->target instanceof User && $this->target->email != '') {
|
|
||||||
$notifyBy[] = 'mail';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $notifyBy;
|
return $notifyBy;
|
||||||
|
@ -109,18 +103,30 @@ class CheckinLicenseSeatNotification extends Notification
|
||||||
$admin = $this->admin;
|
$admin = $this->admin;
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
|
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||||
|
return MicrosoftTeamsMessage::create()
|
||||||
|
->to($this->settings->webhook_endpoint)
|
||||||
|
->type('success')
|
||||||
|
->addStartGroupToSection('activityTitle')
|
||||||
|
->title(trans('mail.License_Checkin_Notification'))
|
||||||
|
->addStartGroupToSection('activityText')
|
||||||
|
->fact(htmlspecialchars_decode($item->present()->name), '', 'header')
|
||||||
|
->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool')
|
||||||
|
->fact(trans('mail.checkedin_from'), $target->present()->fullName())
|
||||||
|
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
|
||||||
|
->fact(trans('mail.notes'), $note ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
return MicrosoftTeamsMessage::create()
|
$message = trans('mail.License_Checkin_Notification');
|
||||||
->to($this->settings->webhook_endpoint)
|
$details = [
|
||||||
->type('success')
|
trans('mail.checkedin_from')=> $target->present()->fullName(),
|
||||||
->addStartGroupToSection('activityTitle')
|
trans('mail.license_for') => htmlspecialchars_decode($item->present()->name),
|
||||||
->title(trans('mail.License_Checkin_Notification'))
|
trans('mail.License_Checkin_Notification')." by " => $admin->present()->fullName() ?: 'CLI tool',
|
||||||
->addStartGroupToSection('activityText')
|
trans('admin/consumables/general.remaining') => $item->availCount()->count(),
|
||||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'header')
|
trans('mail.notes') => $note ?: '',
|
||||||
->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool')
|
];
|
||||||
->fact(trans('mail.checkedin_from'), $target->present()->fullName())
|
|
||||||
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
|
return array($message, $details);
|
||||||
->fact(trans('mail.notes'), $note ?: '');
|
|
||||||
}
|
}
|
||||||
public function toGoogleChat()
|
public function toGoogleChat()
|
||||||
{
|
{
|
||||||
|
@ -149,23 +155,4 @@ class CheckinLicenseSeatNotification extends Notification
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mail representation of the notification.
|
|
||||||
*
|
|
||||||
* @param mixed $notifiable
|
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
|
||||||
*/
|
|
||||||
public function toMail()
|
|
||||||
{
|
|
||||||
return (new MailMessage)->markdown('notifications.markdown.checkin-license',
|
|
||||||
[
|
|
||||||
'item' => $this->item,
|
|
||||||
'admin' => $this->admin,
|
|
||||||
'note' => $this->note,
|
|
||||||
'target' => $this->target,
|
|
||||||
])
|
|
||||||
->subject(trans('mail.License_Checkin_Notification'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Messages\SlackMessage;
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use NotificationChannels\GoogleChat\Card;
|
use NotificationChannels\GoogleChat\Card;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||||
|
@ -120,6 +121,7 @@ class CheckoutAccessoryNotification extends Notification
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
|
|
||||||
|
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||||
return MicrosoftTeamsMessage::create()
|
return MicrosoftTeamsMessage::create()
|
||||||
->to($this->settings->webhook_endpoint)
|
->to($this->settings->webhook_endpoint)
|
||||||
->type('success')
|
->type('success')
|
||||||
|
@ -133,7 +135,19 @@ class CheckoutAccessoryNotification extends Notification
|
||||||
->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->present()->fullName())
|
->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->present()->fullName())
|
||||||
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
||||||
->fact(trans('mail.notes'), $note ?: '');
|
->fact(trans('mail.notes'), $note ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = trans('mail.Accessory_Checkout_Notification');
|
||||||
|
$details = [
|
||||||
|
trans('mail.assigned_to') => $target->present()->name,
|
||||||
|
trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name),
|
||||||
|
trans('general.qty') => $this->checkout_qty,
|
||||||
|
trans('mail.checkedout_from') => $item->location->name ? $item->location->name : '',
|
||||||
|
trans('mail.Accessory_Checkout_Notification'). ' by' => $admin->present()->fullName(),
|
||||||
|
trans('admin/consumables/general.remaining')=> $item->numRemaining(),
|
||||||
|
trans('mail.notes') => $note ?: '',
|
||||||
|
];
|
||||||
|
return array($message, $details);
|
||||||
}
|
}
|
||||||
public function toGoogleChat()
|
public function toGoogleChat()
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,9 +8,10 @@ use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||||
use Illuminate\Notifications\Messages\SlackMessage;
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use NotificationChannels\GoogleChat\Card;
|
use NotificationChannels\GoogleChat\Card;
|
||||||
use NotificationChannels\GoogleChat\Enums\Icon;
|
use NotificationChannels\GoogleChat\Enums\Icon;
|
||||||
use NotificationChannels\GoogleChat\Enums\ImageStyle;
|
use NotificationChannels\GoogleChat\Enums\ImageStyle;
|
||||||
|
@ -21,6 +22,9 @@ use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Osama\LaravelTeamsNotification\Logging\TeamsLoggingChannel;
|
||||||
|
use Osama\LaravelTeamsNotification\TeamsNotification;
|
||||||
|
|
||||||
class CheckoutAssetNotification extends Notification
|
class CheckoutAssetNotification extends Notification
|
||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
@ -32,14 +36,11 @@ class CheckoutAssetNotification extends Notification
|
||||||
*/
|
*/
|
||||||
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
|
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
|
||||||
{
|
{
|
||||||
|
$this->settings = Setting::getSettings();
|
||||||
$this->item = $asset;
|
$this->item = $asset;
|
||||||
$this->admin = $checkedOutBy;
|
$this->admin = $checkedOutBy;
|
||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
$this->target = $checkedOutTo;
|
$this->target = $checkedOutTo;
|
||||||
$this->acceptance = $acceptance;
|
|
||||||
|
|
||||||
$this->settings = Setting::getSettings();
|
|
||||||
|
|
||||||
$this->last_checkout = '';
|
$this->last_checkout = '';
|
||||||
$this->expected_checkin = '';
|
$this->expected_checkin = '';
|
||||||
|
|
||||||
|
@ -53,7 +54,6 @@ class CheckoutAssetNotification extends Notification
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the notification's delivery channels.
|
* Get the notification's delivery channels.
|
||||||
*
|
*
|
||||||
|
@ -62,61 +62,34 @@ class CheckoutAssetNotification extends Notification
|
||||||
public function via()
|
public function via()
|
||||||
{
|
{
|
||||||
$notifyBy = [];
|
$notifyBy = [];
|
||||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
|
||||||
|
if (Setting::getSettings()->webhook_selected === 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||||
|
|
||||||
$notifyBy[] = GoogleChatChannel::class;
|
$notifyBy[] = GoogleChatChannel::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
if (Setting::getSettings()->webhook_selected === 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||||
|
|
||||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general' ) {
|
||||||
|
|
||||||
Log::debug('use webhook');
|
Log::debug('use webhook');
|
||||||
$notifyBy[] = 'slack';
|
$notifyBy[] = SlackWebhookChannel::class;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only send notifications to users that have email addresses
|
|
||||||
*/
|
|
||||||
if ($this->target instanceof User && $this->target->email != '') {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if the asset requires acceptance,
|
|
||||||
* so the user can accept or decline the asset
|
|
||||||
*/
|
|
||||||
if ($this->item->requireAcceptance()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if the item has a EULA, since the user should always receive it
|
|
||||||
*/
|
|
||||||
if ($this->item->getEula()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if an email should be sent at checkin/checkout
|
|
||||||
*/
|
|
||||||
if ($this->item->checkin_email()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $notifyBy;
|
return $notifyBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toSlack()
|
public function toSlack() :SlackMessage
|
||||||
{
|
{
|
||||||
$target = $this->target;
|
$target = $this->target;
|
||||||
$admin = $this->admin;
|
$admin = $this->admin;
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
$botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot';
|
$botname = ($this->settings->webhook_botname) ?: 'Snipe-Bot';
|
||||||
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
|
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
|
||||||
|
|
||||||
$fields = [
|
$fields = [
|
||||||
|
@ -124,7 +97,7 @@ class CheckoutAssetNotification extends Notification
|
||||||
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
|
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (($this->expected_checkin) && ($this->expected_checkin != '')) {
|
if (($this->expected_checkin) && ($this->expected_checkin !== '')) {
|
||||||
$fields['Expected Checkin'] = $this->expected_checkin;
|
$fields['Expected Checkin'] = $this->expected_checkin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +111,7 @@ class CheckoutAssetNotification extends Notification
|
||||||
->content($note);
|
->content($note);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMicrosoftTeams()
|
public function toMicrosoftTeams()
|
||||||
{
|
{
|
||||||
$target = $this->target;
|
$target = $this->target;
|
||||||
|
@ -145,17 +119,26 @@ class CheckoutAssetNotification extends Notification
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
|
|
||||||
return MicrosoftTeamsMessage::create()
|
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||||
->to($this->settings->webhook_endpoint)
|
return MicrosoftTeamsMessage::create()
|
||||||
->type('success')
|
->to($this->settings->webhook_endpoint)
|
||||||
->title(trans('mail.Asset_Checkout_Notification'))
|
->type('success')
|
||||||
->addStartGroupToSection('activityText')
|
->title(trans('mail.Asset_Checkout_Notification'))
|
||||||
->fact(trans('mail.assigned_to'), $target->present()->name)
|
->addStartGroupToSection('activityText')
|
||||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText')
|
->fact(trans('mail.assigned_to'), $target->present()->name)
|
||||||
->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName())
|
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText')
|
||||||
->fact(trans('mail.notes'), $note ?: '');
|
->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName())
|
||||||
|
->fact(trans('mail.notes'), $note ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = trans('mail.Asset_Checkout_Notification');
|
||||||
|
$details = [
|
||||||
|
trans('mail.assigned_to') => $target->present()->name,
|
||||||
|
trans('mail.asset') => htmlspecialchars_decode($item->present()->name),
|
||||||
|
trans('mail.Asset_Checkout_Notification'). ' by' => $admin->present()->fullName(),
|
||||||
|
trans('mail.notes') => $note ?: '',
|
||||||
|
];
|
||||||
|
return array($message, $details);
|
||||||
}
|
}
|
||||||
public function toGoogleChat()
|
public function toGoogleChat()
|
||||||
{
|
{
|
||||||
|
@ -184,42 +167,4 @@ public function toGoogleChat()
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mail representation of the notification.
|
|
||||||
*
|
|
||||||
* @param mixed $notifiable
|
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
|
||||||
*/
|
|
||||||
public function toMail()
|
|
||||||
{ $this->item->load('assetstatus');
|
|
||||||
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
|
|
||||||
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
|
|
||||||
$fields = [];
|
|
||||||
|
|
||||||
// Check if the item has custom fields associated with it
|
|
||||||
if (($this->item->model) && ($this->item->model->fieldset)) {
|
|
||||||
$fields = $this->item->model->fieldset->fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
|
||||||
|
|
||||||
$message = (new MailMessage)->markdown('notifications.markdown.checkout-asset',
|
|
||||||
[
|
|
||||||
'item' => $this->item,
|
|
||||||
'admin' => $this->admin,
|
|
||||||
'status' => $this->item->assetstatus?->name,
|
|
||||||
'note' => $this->note,
|
|
||||||
'target' => $this->target,
|
|
||||||
'fields' => $fields,
|
|
||||||
'eula' => $eula,
|
|
||||||
'req_accept' => $req_accept,
|
|
||||||
'accept_url' => $accept_url,
|
|
||||||
'last_checkout' => $this->last_checkout,
|
|
||||||
'expected_checkin' => $this->expected_checkin,
|
|
||||||
])
|
|
||||||
->subject(trans('mail.Confirm_asset_delivery'));
|
|
||||||
|
|
||||||
return $message;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ use App\Models\Consumable;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Messages\SlackMessage;
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use NotificationChannels\GoogleChat\Card;
|
use NotificationChannels\GoogleChat\Card;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||||
|
@ -38,6 +40,7 @@ class CheckoutConsumableNotification extends Notification
|
||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
$this->target = $checkedOutTo;
|
$this->target = $checkedOutTo;
|
||||||
$this->acceptance = $acceptance;
|
$this->acceptance = $acceptance;
|
||||||
|
$this->qty = $consumable->checkout_qty;
|
||||||
|
|
||||||
$this->settings = Setting::getSettings();
|
$this->settings = Setting::getSettings();
|
||||||
}
|
}
|
||||||
|
@ -61,35 +64,7 @@ class CheckoutConsumableNotification extends Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||||
$notifyBy[] = 'slack';
|
$notifyBy[] = SlackWebhookChannel::class;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only send notifications to users that have email addresses
|
|
||||||
*/
|
|
||||||
if ($this->target instanceof User && $this->target->email != '') {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if the asset requires acceptance,
|
|
||||||
* so the user can accept or decline the asset
|
|
||||||
*/
|
|
||||||
if ($this->item->requireAcceptance()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if the item has a EULA, since the user should always receive it
|
|
||||||
*/
|
|
||||||
if ($this->item->getEula()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if an email should be sent at checkin/checkout
|
|
||||||
*/
|
|
||||||
if ((method_exists($this->item, 'checkin_email')) && ($this->item->checkin_email())) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $notifyBy;
|
return $notifyBy;
|
||||||
|
@ -126,17 +101,30 @@ class CheckoutConsumableNotification extends Notification
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
|
|
||||||
return MicrosoftTeamsMessage::create()
|
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||||
->to($this->settings->webhook_endpoint)
|
return MicrosoftTeamsMessage::create()
|
||||||
->type('success')
|
->to($this->settings->webhook_endpoint)
|
||||||
->addStartGroupToSection('activityTitle')
|
->type('success')
|
||||||
->title(trans('mail.Consumable_checkout_notification'))
|
->addStartGroupToSection('activityTitle')
|
||||||
->addStartGroupToSection('activityText')
|
->title(trans('mail.Consumable_checkout_notification'))
|
||||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
|
->addStartGroupToSection('activityText')
|
||||||
->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName())
|
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
|
||||||
->fact(trans('mail.assigned_to'), $target->present()->fullName())
|
->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName())
|
||||||
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
->fact(trans('mail.assigned_to'), $target->present()->fullName())
|
||||||
->fact(trans('mail.notes'), $note ?: '');
|
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
||||||
|
->fact(trans('mail.notes'), $note ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = trans('mail.Consumable_checkout_notification');
|
||||||
|
$details = [
|
||||||
|
trans('mail.assigned_to') => $target->present()->fullName(),
|
||||||
|
trans('mail.item') => htmlspecialchars_decode($item->present()->name),
|
||||||
|
trans('mail.Consumable_checkout_notification').' by' => $admin->present()->fullName(),
|
||||||
|
trans('admin/consumables/general.remaining') => $item->numRemaining(),
|
||||||
|
trans('mail.notes') => $note ?: '',
|
||||||
|
];
|
||||||
|
|
||||||
|
return array($message, $details);
|
||||||
}
|
}
|
||||||
public function toGoogleChat()
|
public function toGoogleChat()
|
||||||
{
|
{
|
||||||
|
@ -165,30 +153,4 @@ class CheckoutConsumableNotification extends Notification
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mail representation of the notification.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
|
||||||
*/
|
|
||||||
public function toMail()
|
|
||||||
{
|
|
||||||
Log::debug($this->item->getImageUrl());
|
|
||||||
$eula = $this->item->getEula();
|
|
||||||
$req_accept = $this->item->requireAcceptance();
|
|
||||||
|
|
||||||
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
|
||||||
|
|
||||||
return (new MailMessage)->markdown('notifications.markdown.checkout-consumable',
|
|
||||||
[
|
|
||||||
'item' => $this->item,
|
|
||||||
'admin' => $this->admin,
|
|
||||||
'note' => $this->note,
|
|
||||||
'target' => $this->target,
|
|
||||||
'eula' => $eula,
|
|
||||||
'req_accept' => $req_accept,
|
|
||||||
'accept_url' => $accept_url,
|
|
||||||
])
|
|
||||||
->subject(trans('mail.Confirm_consumable_delivery'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ use App\Models\LicenseSeat;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Messages\SlackMessage;
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use NotificationChannels\GoogleChat\Card;
|
use NotificationChannels\GoogleChat\Card;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||||
|
@ -60,35 +62,7 @@ class CheckoutLicenseSeatNotification extends Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||||
$notifyBy[] = 'slack';
|
$notifyBy[] = SlackWebhookChannel::class;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only send notifications to users that have email addresses
|
|
||||||
*/
|
|
||||||
if ($this->target instanceof User && $this->target->email != '') {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if the asset requires acceptance,
|
|
||||||
* so the user can accept or decline the asset
|
|
||||||
*/
|
|
||||||
if ($this->item->requireAcceptance()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if the item has a EULA, since the user should always receive it
|
|
||||||
*/
|
|
||||||
if ($this->item->getEula()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email if an email should be sent at checkin/checkout
|
|
||||||
*/
|
|
||||||
if ($this->item->checkin_email()) {
|
|
||||||
$notifyBy[1] = 'mail';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $notifyBy;
|
return $notifyBy;
|
||||||
|
@ -125,17 +99,29 @@ class CheckoutLicenseSeatNotification extends Notification
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
$note = $this->note;
|
$note = $this->note;
|
||||||
|
|
||||||
return MicrosoftTeamsMessage::create()
|
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||||
->to($this->settings->webhook_endpoint)
|
return MicrosoftTeamsMessage::create()
|
||||||
->type('success')
|
->to($this->settings->webhook_endpoint)
|
||||||
->addStartGroupToSection('activityTitle')
|
->type('success')
|
||||||
->title(trans('mail.License_Checkout_Notification'))
|
->addStartGroupToSection('activityTitle')
|
||||||
->addStartGroupToSection('activityText')
|
->title(trans('mail.License_Checkout_Notification'))
|
||||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
|
->addStartGroupToSection('activityText')
|
||||||
->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName())
|
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
|
||||||
->fact(trans('mail.assigned_to'), $target->present()->fullName())
|
->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName())
|
||||||
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
|
->fact(trans('mail.assigned_to'), $target->present()->fullName())
|
||||||
->fact(trans('mail.notes'), $note ?: '');
|
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
|
||||||
|
->fact(trans('mail.notes'), $note ?: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = trans('mail.License_Checkout_Notification');
|
||||||
|
$details = [
|
||||||
|
trans('mail.assigned_to') => $target->present()->fullName(),
|
||||||
|
trans('mail.license_for') => htmlspecialchars_decode($item->present()->name),
|
||||||
|
trans('mail.License_Checkout_Notification').' by' => $admin->present()->fullName(),
|
||||||
|
trans('admin/consumables/general.remaining') => $item->availCount()->count(),
|
||||||
|
trans('mail.notes') => $note ?: '',
|
||||||
|
];
|
||||||
|
return array($message, $details);
|
||||||
}
|
}
|
||||||
public function toGoogleChat()
|
public function toGoogleChat()
|
||||||
{
|
{
|
||||||
|
@ -164,29 +150,4 @@ class CheckoutLicenseSeatNotification extends Notification
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mail representation of the notification.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
|
||||||
*/
|
|
||||||
public function toMail()
|
|
||||||
{
|
|
||||||
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
|
|
||||||
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
|
|
||||||
|
|
||||||
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
|
||||||
|
|
||||||
return (new MailMessage)->markdown('notifications.markdown.checkout-license',
|
|
||||||
[
|
|
||||||
'item' => $this->item,
|
|
||||||
'admin' => $this->admin,
|
|
||||||
'note' => $this->note,
|
|
||||||
'target' => $this->target,
|
|
||||||
'eula' => $eula,
|
|
||||||
'req_accept' => $req_accept,
|
|
||||||
'accept_url' => $accept_url,
|
|
||||||
])
|
|
||||||
->subject(trans('mail.Confirm_license_delivery'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,27 +42,27 @@ class ActionlogPresenter extends Presenter
|
||||||
// User related icons
|
// User related icons
|
||||||
if ($this->itemType() == 'user') {
|
if ($this->itemType() == 'user') {
|
||||||
|
|
||||||
if ($this->actionType()=='2fa reset') {
|
if ($this->action_type == '2fa reset') {
|
||||||
return 'fa-solid fa-mobile-screen';
|
return 'fa-solid fa-mobile-screen';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='create new') {
|
if ($this->action_type == 'create new') {
|
||||||
return 'fa-solid fa-user-plus';
|
return 'fa-solid fa-user-plus';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='merged') {
|
if ($this->action_type == 'merged') {
|
||||||
return 'fa-solid fa-people-arrows';
|
return 'fa-solid fa-people-arrows';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='delete') {
|
if ($this->action_type == 'delete') {
|
||||||
return 'fa-solid fa-user-minus';
|
return 'fa-solid fa-user-minus';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='delete') {
|
if ($this->action_type == 'delete') {
|
||||||
return 'fa-solid fa-user-minus';
|
return 'fa-solid fa-user-minus';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='update') {
|
if ($this->action_type == 'update') {
|
||||||
return 'fa-solid fa-user-pen';
|
return 'fa-solid fa-user-pen';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,31 +70,31 @@ class ActionlogPresenter extends Presenter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything else
|
// Everything else
|
||||||
if ($this->actionType()=='create new') {
|
if ($this->action_type == 'create new') {
|
||||||
return 'fa-solid fa-plus';
|
return 'fa-solid fa-plus';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='delete') {
|
if ($this->action_type == 'delete') {
|
||||||
return 'fa-solid fa-trash';
|
return 'fa-solid fa-trash';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='update') {
|
if ($this->action_type == 'update') {
|
||||||
return 'fa-solid fa-pen';
|
return 'fa-solid fa-pen';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='restore') {
|
if ($this->action_type == 'restore') {
|
||||||
return 'fa-solid fa-trash-arrow-up';
|
return 'fa-solid fa-trash-arrow-up';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='upload') {
|
if ($this->action_type == 'upload') {
|
||||||
return 'fas fa-paperclip';
|
return 'fas fa-paperclip';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='checkout') {
|
if ($this->action_type == 'checkout') {
|
||||||
return 'fa-solid fa-rotate-left';
|
return 'fa-solid fa-rotate-left';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->actionType()=='checkin from') {
|
if ($this->action_type == 'checkin from') {
|
||||||
return 'fa-solid fa-rotate-right';
|
return 'fa-solid fa-rotate-right';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,20 @@ class ComponentPresenter extends Presenter
|
||||||
'title' => trans('general.supplier'),
|
'title' => trans('general.supplier'),
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
'formatter' => 'suppliersLinkObjFormatter',
|
'formatter' => 'suppliersLinkObjFormatter',
|
||||||
],
|
], [
|
||||||
[
|
'field' => 'model_number',
|
||||||
|
'searchable' => true,
|
||||||
|
'sortable' => true,
|
||||||
|
'title' => trans('admin/models/table.modelnumber'),
|
||||||
|
], [
|
||||||
|
'field' => 'manufacturer',
|
||||||
|
'searchable' => true,
|
||||||
|
'sortable' => true,
|
||||||
|
'switchable' => true,
|
||||||
|
'title' => trans('general.manufacturer'),
|
||||||
|
'visible' => false,
|
||||||
|
'formatter' => 'manufacturersLinkObjFormatter',
|
||||||
|
], [
|
||||||
'field' => 'qty',
|
'field' => 'qty',
|
||||||
'searchable' => false,
|
'searchable' => false,
|
||||||
'sortable' => true,
|
'sortable' => true,
|
||||||
|
|
|
@ -124,8 +124,15 @@ class ManufacturerPresenter extends Presenter
|
||||||
'title' => trans('general.accessories'),
|
'title' => trans('general.accessories'),
|
||||||
'visible' => true,
|
'visible' => true,
|
||||||
'class' => 'css-accessory',
|
'class' => 'css-accessory',
|
||||||
],
|
], [
|
||||||
[
|
'field' => 'components_count',
|
||||||
|
'searchable' => false,
|
||||||
|
'sortable' => true,
|
||||||
|
'switchable' => true,
|
||||||
|
'title' => trans('general.components'),
|
||||||
|
'visible' => true,
|
||||||
|
'class' => 'css-component',
|
||||||
|
], [
|
||||||
'field' => 'created_by',
|
'field' => 'created_by',
|
||||||
'searchable' => false,
|
'searchable' => false,
|
||||||
'sortable' => true,
|
'sortable' => true,
|
||||||
|
|
29
app/Rules/AlphaEncrypted.php
Normal file
29
app/Rules/AlphaEncrypted.php
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
|
||||||
|
class AlphaEncrypted implements ValidationRule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the validation rule.
|
||||||
|
*
|
||||||
|
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||||
|
*/
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
|
||||||
|
$decrypted = Crypt::decrypt($value);
|
||||||
|
if (!ctype_alpha($decrypted) && !is_null($decrypted)) {
|
||||||
|
$fail(trans('validation.alpha', ['attribute' => $attributeName]));
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
$fail(trans('general.something_went_wrong'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
app/Rules/NumericEncrypted.php
Normal file
31
app/Rules/NumericEncrypted.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
|
||||||
|
class NumericEncrypted implements ValidationRule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the validation rule.
|
||||||
|
*
|
||||||
|
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||||
|
*/
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
|
||||||
|
$decrypted = Crypt::decrypt($value);
|
||||||
|
if (!is_numeric($decrypted) && !is_null($decrypted)) {
|
||||||
|
$fail(trans('validation.numeric', ['attribute' => $attributeName]));
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e->getMessage());
|
||||||
|
$fail(trans('general.something_went_wrong'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@
|
||||||
"intervention/image": "^2.5",
|
"intervention/image": "^2.5",
|
||||||
"javiereguiluz/easyslugger": "^1.0",
|
"javiereguiluz/easyslugger": "^1.0",
|
||||||
"laravel-notification-channels/google-chat": "^3.0",
|
"laravel-notification-channels/google-chat": "^3.0",
|
||||||
"laravel-notification-channels/microsoft-teams": "^1.1",
|
"laravel-notification-channels/microsoft-teams": "^1.2",
|
||||||
"laravel/framework": "^10.0",
|
"laravel/framework": "^10.0",
|
||||||
"laravel/helpers": "^1.4",
|
"laravel/helpers": "^1.4",
|
||||||
"laravel/passport": "^11.0",
|
"laravel/passport": "^11.0",
|
||||||
|
@ -55,6 +55,7 @@
|
||||||
"nunomaduro/collision": "^7.0",
|
"nunomaduro/collision": "^7.0",
|
||||||
"okvpn/clock-lts": "^1.0",
|
"okvpn/clock-lts": "^1.0",
|
||||||
"onelogin/php-saml": "^3.4",
|
"onelogin/php-saml": "^3.4",
|
||||||
|
"osa-eg/laravel-teams-notification": "^2.1",
|
||||||
"paragonie/constant_time_encoding": "^2.3",
|
"paragonie/constant_time_encoding": "^2.3",
|
||||||
"paragonie/sodium_compat": "^1.19",
|
"paragonie/sodium_compat": "^1.19",
|
||||||
"phpdocumentor/reflection-docblock": "^5.1",
|
"phpdocumentor/reflection-docblock": "^5.1",
|
||||||
|
@ -74,7 +75,6 @@
|
||||||
"ext-exif": "*"
|
"ext-exif": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"brianium/paratest": "^7.0",
|
|
||||||
"fakerphp/faker": "^1.16",
|
"fakerphp/faker": "^1.16",
|
||||||
"larastan/larastan": "^2.9",
|
"larastan/larastan": "^2.9",
|
||||||
"mockery/mockery": "^1.4",
|
"mockery/mockery": "^1.4",
|
||||||
|
|
925
composer.lock
generated
925
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -280,7 +280,6 @@ return [
|
||||||
Illuminate\Redis\RedisServiceProvider::class,
|
Illuminate\Redis\RedisServiceProvider::class,
|
||||||
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
||||||
Illuminate\Session\SessionServiceProvider::class,
|
Illuminate\Session\SessionServiceProvider::class,
|
||||||
// Illuminate\Translation\TranslationServiceProvider::class, //replaced on next line
|
|
||||||
App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own
|
App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own
|
||||||
Illuminate\Validation\ValidationServiceProvider::class,
|
Illuminate\Validation\ValidationServiceProvider::class,
|
||||||
Illuminate\View\ViewServiceProvider::class,
|
Illuminate\View\ViewServiceProvider::class,
|
||||||
|
@ -373,7 +372,7 @@ return [
|
||||||
'Image' => Intervention\Image\ImageServiceProvider::class,
|
'Image' => Intervention\Image\ImageServiceProvider::class,
|
||||||
'Carbon' => Carbon\Carbon::class,
|
'Carbon' => Carbon\Carbon::class,
|
||||||
'Helper' => App\Helpers\Helper::class,
|
'Helper' => App\Helpers\Helper::class,
|
||||||
// makes it much easier to use 'Helper::blah' in blades (which is where we usually use this)
|
'StorageHelper' => App\Helpers\StorageHelper::class,
|
||||||
'Icon' => App\Helpers\IconHelper::class,
|
'Icon' => App\Helpers\IconHelper::class,
|
||||||
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
|
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
return array (
|
return array (
|
||||||
'app_version' => 'v7.0.13',
|
'app_version' => 'v7.1.15',
|
||||||
'full_app_version' => 'v7.0.13 - build 15514-gdc0949da7',
|
'full_app_version' => 'v7.1.15 - build 16052-g25bfd3e84',
|
||||||
'build_version' => '15514',
|
'build_version' => '16052',
|
||||||
'prerelease_version' => '',
|
'prerelease_version' => '',
|
||||||
'hash_version' => 'gdc0949da7',
|
'hash_version' => 'g25bfd3e84',
|
||||||
'full_hash' => 'v7.0.13-265-gdc0949da7',
|
'full_hash' => 'v7.1.15-105-g25bfd3e84',
|
||||||
'branch' => 'develop',
|
'branch' => 'master',
|
||||||
);
|
);
|
|
@ -7,6 +7,7 @@ use App\Models\Asset;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Component;
|
use App\Models\Component;
|
||||||
|
use App\Models\Manufacturer;
|
||||||
use App\Models\Consumable;
|
use App\Models\Consumable;
|
||||||
use App\Models\Location;
|
use App\Models\Location;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
@ -30,6 +31,7 @@ class ComponentFactory extends Factory
|
||||||
*/
|
*/
|
||||||
public function definition()
|
public function definition()
|
||||||
{
|
{
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'name' => $this->faker->text(20),
|
'name' => $this->faker->text(20),
|
||||||
'category_id' => Category::factory(),
|
'category_id' => Category::factory(),
|
||||||
|
@ -42,12 +44,14 @@ class ComponentFactory extends Factory
|
||||||
'min_amt' => $this->faker->numberBetween($min = 1, $max = 2),
|
'min_amt' => $this->faker->numberBetween($min = 1, $max = 2),
|
||||||
'company_id' => Company::factory(),
|
'company_id' => Company::factory(),
|
||||||
'supplier_id' => Supplier::factory(),
|
'supplier_id' => Supplier::factory(),
|
||||||
|
'model_number' => $this->faker->numberBetween(1000000, 50000000),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ramCrucial4()
|
public function ramCrucial4()
|
||||||
{
|
{
|
||||||
return $this->state(function () {
|
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
|
||||||
|
return $this->state(function () use ($manufacturer) {
|
||||||
return [
|
return [
|
||||||
'name' => 'Crucial 4GB DDR3L-1600 SODIMM',
|
'name' => 'Crucial 4GB DDR3L-1600 SODIMM',
|
||||||
'category_id' => function () {
|
'category_id' => function () {
|
||||||
|
@ -55,6 +59,7 @@ class ComponentFactory extends Factory
|
||||||
},
|
},
|
||||||
'qty' => 10,
|
'qty' => 10,
|
||||||
'min_amt' => 2,
|
'min_amt' => 2,
|
||||||
|
'manufacturer_id' => $manufacturer->id,
|
||||||
'location_id' => Location::factory(),
|
'location_id' => Location::factory(),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@ -62,7 +67,8 @@ class ComponentFactory extends Factory
|
||||||
|
|
||||||
public function ramCrucial8()
|
public function ramCrucial8()
|
||||||
{
|
{
|
||||||
return $this->state(function () {
|
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
|
||||||
|
return $this->state(function () use ($manufacturer) {
|
||||||
return [
|
return [
|
||||||
'name' => 'Crucial 8GB DDR3L-1600 SODIMM Memory for Mac',
|
'name' => 'Crucial 8GB DDR3L-1600 SODIMM Memory for Mac',
|
||||||
'category_id' => function () {
|
'category_id' => function () {
|
||||||
|
@ -70,13 +76,15 @@ class ComponentFactory extends Factory
|
||||||
},
|
},
|
||||||
'qty' => 10,
|
'qty' => 10,
|
||||||
'min_amt' => 2,
|
'min_amt' => 2,
|
||||||
|
'manufacturer_id' => $manufacturer->id,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ssdCrucial120()
|
public function ssdCrucial120()
|
||||||
{
|
{
|
||||||
return $this->state(function () {
|
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
|
||||||
|
return $this->state(function () use ($manufacturer) {
|
||||||
return [
|
return [
|
||||||
'name' => 'Crucial BX300 120GB SATA Internal SSD',
|
'name' => 'Crucial BX300 120GB SATA Internal SSD',
|
||||||
'category_id' => function () {
|
'category_id' => function () {
|
||||||
|
@ -84,13 +92,15 @@ class ComponentFactory extends Factory
|
||||||
},
|
},
|
||||||
'qty' => 10,
|
'qty' => 10,
|
||||||
'min_amt' => 2,
|
'min_amt' => 2,
|
||||||
|
'manufacturer_id' => $manufacturer->id,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ssdCrucial240()
|
public function ssdCrucial240()
|
||||||
{
|
{
|
||||||
return $this->state(function () {
|
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
|
||||||
|
return $this->state(function () use ($manufacturer) {
|
||||||
return [
|
return [
|
||||||
'name' => 'Crucial BX300 240GB SATA Internal SSD',
|
'name' => 'Crucial BX300 240GB SATA Internal SSD',
|
||||||
'category_id' => function () {
|
'category_id' => function () {
|
||||||
|
@ -98,6 +108,7 @@ class ComponentFactory extends Factory
|
||||||
},
|
},
|
||||||
'qty' => 10,
|
'qty' => 10,
|
||||||
'min_amt' => 2,
|
'min_amt' => 2,
|
||||||
|
'manufacturer_id' => $manufacturer->id,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,4 +143,26 @@ class ImportFactory extends Factory
|
||||||
return $attributes;
|
return $attributes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an asset model import type.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function assetmodel()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$fileBuilder = Importing\AssetModelsImportFileBuilder::new();
|
||||||
|
|
||||||
|
$attributes['name'] = "{$attributes['name']} Asset Model";
|
||||||
|
$attributes['import_type'] = 'assetModel';
|
||||||
|
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||||
|
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class ChangeWebhookSettingsVariableType extends Migration
|
||||||
public function down()
|
public function down()
|
||||||
{
|
{
|
||||||
Schema::table('settings', function (Blueprint $table) {
|
Schema::table('settings', function (Blueprint $table) {
|
||||||
$table->varchar('webhook_endpoint')->change();
|
$table->string('webhook_endpoint')->change();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?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('settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('require_checkinout_notes')->nullable()->default(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('settings', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('settings', 'require_checkinout_notes')) {
|
||||||
|
$table->dropColumn('require_checkinout_notes');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -42,7 +42,7 @@ return new class extends Migration
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->existing_table_list() as $table) {
|
foreach ($this->existing_table_list() as $table) {
|
||||||
if (Schema::hasColumn($table, 'user_id')) {
|
if (Schema::hasColumn($table, 'created_by')) {
|
||||||
Schema::table($table, function (Blueprint $table) {
|
Schema::table($table, function (Blueprint $table) {
|
||||||
$table->renameColumn('created_by', 'user_id');
|
$table->renameColumn('created_by', 'user_id');
|
||||||
});
|
});
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue