Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
4285 commits
Select commit Hold shift + click to select a range
e1c755f
Moved css for advanced search in it's own files
cz-lucas Oct 8, 2025
04181a4
Moved js for floating buttons in it's own file
cz-lucas Oct 8, 2025
f85c21c
Resolved merge conflict and added new build of js
cz-lucas Oct 9, 2025
06b813e
Added di-container
cz-lucas Oct 9, 2025
3286180
Floating buttons are now using the di-container
cz-lucas Oct 9, 2025
52712e4
Fixed that key wasn't found
cz-lucas Oct 10, 2025
c2dfad2
Fixed category query test
cz-lucas Oct 10, 2025
f8ed515
Fixed company query test
cz-lucas Oct 10, 2025
3336cff
Fixed location query test
cz-lucas Oct 10, 2025
2eb5a6a
Fixed manufacturer query test
cz-lucas Oct 10, 2025
59cf77d
Fixed model query test
cz-lucas Oct 10, 2025
74c0b4f
Fixed rtd-location query test
cz-lucas Oct 10, 2025
648f93e
Fixed status query test
cz-lucas Oct 10, 2025
6fc9c3d
Fixed supplier query test
cz-lucas Oct 10, 2025
1eb7c8d
changed filterStructure
JonasGoseberg Oct 10, 2025
37b8210
Fixed api category query test
cz-lucas Oct 10, 2025
a893a1b
Fixed api combined query test
cz-lucas Oct 10, 2025
1411127
Fixed api company query test
cz-lucas Oct 10, 2025
883fa7a
Fixed api location query test
cz-lucas Oct 10, 2025
c11aaaf
Fixed api manufacturer query test
cz-lucas Oct 10, 2025
3944a7b
Fixed api model query test
cz-lucas Oct 10, 2025
0b5d8f6
Fixed api rtd-location query test
cz-lucas Oct 10, 2025
69426b0
Fixed api status-label query test
cz-lucas Oct 10, 2025
d93491d
Fixed more tests and changed wrong naming
cz-lucas Oct 10, 2025
73fae6a
dump comment
JonasGoseberg Oct 13, 2025
d84f176
added use Schema
JonasGoseberg Oct 13, 2025
1e0151d
changed filterStructure for API/CustomFieldQueryTests
JonasGoseberg Oct 13, 2025
aeceb3d
fixed CustomFielqueryTests
JonasGoseberg Oct 13, 2025
0e68edd
removed comments
JonasGoseberg Oct 13, 2025
b09e72d
fixed S1lInjectionQuerxTest.php
JonasGoseberg Oct 13, 2025
3fb5732
fixed tests for id and Name
JonasGoseberg Oct 14, 2025
bc24b64
changed filter structure in test
JonasGoseberg Oct 14, 2025
2a8d48d
fixed id and name
JonasGoseberg Oct 14, 2025
0a622b5
review changes
JonasGoseberg Oct 15, 2025
9b03a02
fixed some tests
JonasGoseberg Oct 14, 2025
9055409
fixed assignedToTests
JonasGoseberg Oct 15, 2025
eed7280
fixed typo
JonasGoseberg Oct 15, 2025
2abf032
fixed typos in translations
JonasGoseberg Oct 16, 2025
3a943ec
Removed deprecated testcases
cz-lucas Oct 16, 2025
4f59ded
Fixed test_warranty_expires_attribute
cz-lucas Oct 16, 2025
a8a27c8
Fixed that modal can't set groups in backend
cz-lucas Oct 16, 2025
e73b596
Fixed that the visibility is always set to private in the modal
cz-lucas Oct 16, 2025
f0242bc
Fixed that visibility needs to be set to private and then again to pu…
cz-lucas Oct 16, 2025
aeaf6de
changed flag to false in settingspage
JonasGoseberg Oct 16, 2025
9972367
Fixed that filter can only be updated without a set group
cz-lucas Oct 17, 2025
31fbf93
Predefined-filter dropdown is now cleared when pressing clear button
cz-lucas Oct 17, 2025
0c6afcb
Fixed some async stuff so that the table will be updated after all pr…
cz-lucas Oct 17, 2025
4beb87d
fixed deleting public filters with permissions
JonasGoseberg Oct 17, 2025
bdd4241
removed FilterView
JonasGoseberg Oct 17, 2025
a9325a4
Changed values in getExtendedPrefix
cz-lucas Oct 17, 2025
e235ad0
Updated logc in getExtendedPrefix
cz-lucas Oct 17, 2025
053884e
Changed filter operator buttons
cz-lucas Oct 17, 2025
0b88881
Changed namespace for assetQueryTests
cz-lucas Oct 20, 2025
569b2fc
Changed namespace for predefinedFilterTests
cz-lucas Oct 20, 2025
a89c58a
fixed permissionRestriction to View SettingsPage
JonasGoseberg Oct 20, 2025
a33809b
switched to filter->UserHasPermission
JonasGoseberg Oct 20, 2025
5e75190
fixed Routes
JonasGoseberg Oct 20, 2025
50a35b6
resoved empty values
JonasGoseberg Oct 21, 2025
022979f
marked test as skipped because randomly failing
JonasGoseberg Oct 21, 2025
c1cb70c
Filteroptions are now resetted for non-customfields when pressing the…
cz-lucas Oct 21, 2025
3864306
Filteroptions are now resetted for all fields when pressing the clear…
cz-lucas Oct 21, 2025
4ae2b4e
SIT-79-fixed operators and logic
JonasGoseberg Oct 21, 2025
faf5e71
Livewire event bridging
cz-lucas Oct 21, 2025
cd5c461
Fixed wildcard filter reset
cz-lucas Oct 21, 2025
743c7e2
removed logs
JonasGoseberg Oct 21, 2025
6045c7a
minor fix
JonasGoseberg Oct 21, 2025
6979477
automatic matrix seeder
Klausw-1 Oct 21, 2025
920e7e0
String are now added to the dropdown
cz-lucas Oct 21, 2025
2c05e46
Built JS
cz-lucas Oct 22, 2025
06ba7e3
Moved filter collectors in two methods
cz-lucas Oct 22, 2025
11a4109
Added some queueMicrotasks
cz-lucas Oct 22, 2025
a0ec3b1
Placeholder text in the dropdowns is now shown
cz-lucas Oct 22, 2025
784ce3d
Filteroption will now be set when applying a predefined filter
cz-lucas Oct 22, 2025
36ba6f9
added blank group column
JonasGoseberg Oct 23, 2025
4c9731f
added permission_groups and dependencies
JonasGoseberg Oct 23, 2025
b7857b6
removed unused code
JonasGoseberg Oct 23, 2025
ec9fa7b
added tests for structure of response
JonasGoseberg Oct 23, 2025
ee26e87
skipped one test hich is randomly failing
JonasGoseberg Oct 23, 2025
142f157
adjusted l10n
JonasGoseberg Oct 22, 2025
5ffa90c
Fixed that date filter input wont be set
cz-lucas Oct 23, 2025
5b74161
Notifcations are now also shown in the modal if it is open
cz-lucas Oct 23, 2025
3dbd535
Fixed that not all filters there set properly
cz-lucas Oct 23, 2025
74ca934
Fixed space-char between title and colon
cz-lucas Oct 22, 2025
b616c47
Added placeholder values also to text fields
cz-lucas Oct 22, 2025
8e3c2f3
Started with some floating button scroll stuff
cz-lucas Oct 22, 2025
928e8ca
Commented out floating button class switching stuff
cz-lucas Oct 23, 2025
71f9f23
Changed ui stuff
cz-lucas Oct 23, 2025
975c91c
Implemented fixed/scrollable switching
cz-lucas Oct 24, 2025
9179269
Update .gitignore to ignore all files except itself
cz-lucas Oct 24, 2025
425a088
Improved behaviour of the floating buttons
cz-lucas Oct 27, 2025
f06a700
Added warning then creating a predefinedfilter with a duplicate name
cz-lucas Oct 27, 2025
35ee4a4
Added bootstrap date picker
cz-lucas Oct 20, 2025
6965115
Added testcases for new api
cz-lucas Oct 20, 2025
5ff4193
Added logic for queries and some failing tests
cz-lucas Oct 20, 2025
1624239
Fixed some testcases
cz-lucas Oct 20, 2025
8bce688
Fixed some more tests
cz-lucas Oct 21, 2025
b1f8de9
Changed tests
cz-lucas Oct 21, 2025
cd3188f
Some smaller fixes in the UI
cz-lucas Oct 27, 2025
4727849
adjusted filter structure inside tests to cover only only valid struc…
JonasGoseberg Oct 24, 2025
a103bca
adjusted more Tests to the supported Structure
JonasGoseberg Oct 27, 2025
30e18f2
adjusted more tests
JonasGoseberg Oct 27, 2025
25f08fb
WIP refactor ByFilter()
JonasGoseberg Oct 27, 2025
8320c44
Initial plan
Copilot Oct 27, 2025
93775be
Convert assigned_to dropdown to text input in advanced search
Copilot Oct 27, 2025
0679ba0
Fixed failing datequery-tests
cz-lucas Oct 30, 2025
ddf82d5
Fixed failing datequery-tests
cz-lucas Oct 30, 2025
453fbf3
Skipping all date-query tests
cz-lucas Oct 30, 2025
48913aa
Skipping all date-query tests
cz-lucas Oct 30, 2025
22f0f85
Skipping all date-query tests
cz-lucas Oct 30, 2025
dad0c94
Fixed that some dropdowns are expanded even ones that shouldn't
cz-lucas Oct 29, 2025
dc2fdb6
Fixed that placeholder won't be shown completly in the dropdowns
cz-lucas Oct 29, 2025
510fe22
Fixed that daterange is sent twice to the backend in a filter
cz-lucas Oct 30, 2025
23ed984
Fixed datefilter
cz-lucas Oct 30, 2025
8a1bd84
Fixed datequerytests
cz-lucas Oct 30, 2025
68414a2
Fixed tests
cz-lucas Oct 30, 2025
c035300
Fixed that filter operators wont be applied on textfields
cz-lucas Oct 31, 2025
156876c
Data query tests fixed
Klausw-1 Oct 31, 2025
ae6f214
filterservice fix
Klausw-1 Oct 31, 2025
80356d4
company_id fail in tests fixed
Klausw-1 Oct 31, 2025
c9af1b6
fixed the date-filter-logic in the API-Layer
Klausw-1 Oct 31, 2025
3fdceba
stabilize DateQueryTest by scoping assets and using fixed date range
Klausw-1 Oct 31, 2025
caec971
fixed the EOL Date Query Test
Klausw-1 Nov 3, 2025
0eafc7c
Foreign data records in scope fixed with three test assets with uniqu…
Klausw-1 Nov 3, 2025
9f4048a
SIT-125_hotfix language de-if
JonasGoseberg Nov 3, 2025
2e50f19
SU can now view private filters
JonasGoseberg Nov 3, 2025
9e78ff2
used UserHasPermission
JonasGoseberg Nov 3, 2025
914e4bb
Fixed that the date can be set (the clear button is currently broken)
cz-lucas Nov 4, 2025
af3b865
Fixed that date can be cleared
cz-lucas Nov 4, 2025
1e1506c
WIP fixed behavior from floating buttons
JonasGoseberg Nov 6, 2025
5581c03
WIP fixed bug with advancedSearchPanel
JonasGoseberg Nov 7, 2025
bdf821e
added the PermissionPart to the Updated version
JonasGoseberg Nov 5, 2025
62e8e33
added functionality for searching, sorting and pagination
JonasGoseberg Nov 6, 2025
da729a6
removed logs
JonasGoseberg Nov 6, 2025
2220a13
removed logs
JonasGoseberg Nov 6, 2025
a289e6a
Rebuild static assets
cz-lucas Nov 7, 2025
c14d61d
Add example testing environment configuration
cz-lucas Nov 7, 2025
093dde2
fixed the overlapping bug entirely
JonasGoseberg Nov 7, 2025
84068bc
buttons stay centered in panel
JonasGoseberg Nov 7, 2025
552f310
changed width of select2 fields
JonasGoseberg Nov 7, 2025
88c5bae
compiled files
JonasGoseberg Nov 10, 2025
a4b229b
fixed Bug with blinking filterSection
JonasGoseberg Nov 11, 2025
5ab87c7
removed EditModalParameter
JonasGoseberg Nov 11, 2025
95ea35d
Fixed awkward looking field in the datepicker for 'to'
cz-lucas Nov 10, 2025
78b9e41
Moved filterUiController to it's own file
cz-lucas Nov 10, 2025
f88d9e3
Fixed translations for filterUiController.js
cz-lucas Nov 10, 2025
76ab099
added and fixed l10n
JonasGoseberg Nov 14, 2025
2dd22d8
fixed behavior of permissions (create / update)
JonasGoseberg Nov 14, 2025
e50ef93
Started fixing the assignedTo-filter
cz-lucas Nov 11, 2025
a9854a8
An eternity later ... still not working
cz-lucas Nov 12, 2025
3afe701
More is now working...
cz-lucas Nov 12, 2025
f4d1185
Fixed assignedToLocation
cz-lucas Nov 13, 2025
b653a81
Added assigntedTo for assignedToAsset-queryies
cz-lucas Nov 14, 2025
0e2cdc2
Added assignedToUser
cz-lucas Nov 14, 2025
d2031d3
Added assetQueryApiTests
cz-lucas Nov 17, 2025
77246b5
Implemented frontend for assignedTo
cz-lucas Nov 17, 2025
4c45267
Applied suggestion from @JonasGoseberg in imbus/snipe-it#93
cz-lucas Nov 17, 2025
fc06656
Fixed that Select2-dropdown in advanced-search won't be over the navi…
cz-lucas Nov 17, 2025
66ed9f7
Fixed that search always sents the assignedTo field
cz-lucas Nov 17, 2025
78c2fe1
Moved if-statement to fix failing test
cz-lucas Nov 17, 2025
0884394
Fixed that the group-select-dropdown is in the background
cz-lucas Nov 18, 2025
575d1e9
adjusted parameters for UpdateModal in Settingspage
JonasGoseberg Nov 18, 2025
e4b240d
Moved search-inouts-js in it's own file ouside of the blade
cz-lucas Nov 20, 2025
4a7625c
Moved CSS and JS from hardware/index in its own files
cz-lucas Nov 20, 2025
91d0d23
Updated CSS-build so that there are only two css-files loaded
cz-lucas Nov 20, 2025
e9bd9df
Moved JS for the advanced search in its own file
cz-lucas Nov 20, 2025
58ab4b3
Some smaler improvements
cz-lucas Nov 20, 2025
d938475
refactored predefinedFilterService
JonasGoseberg Nov 20, 2025
8f3fa2c
changed some formatting things to align with sonarqubes standard
JonasGoseberg Nov 20, 2025
4f39d20
refactored Filterservice
JonasGoseberg Nov 20, 2025
b7c33c2
adjusted Tests
JonasGoseberg Nov 21, 2025
2722df6
Added advanced search policy
cz-lucas Nov 21, 2025
986aa91
Added advanced-search in selectlist-gate
cz-lucas Nov 24, 2025
fe48489
adjusted l10n for advancedsearch
JonasGoseberg Nov 24, 2025
506f149
predefinedfilter update
Klausw-1 Nov 19, 2025
bd0ccf8
Started with changes for better darkmode support
cz-lucas Nov 25, 2025
5ffed6f
Updated CSS for darkmode for floatingbutton-menu
cz-lucas Nov 26, 2025
652cb4a
Updated CSS for darkmode for dropdowns
cz-lucas Nov 26, 2025
c55746e
some improvements to Sonarcube and artisan insights
JonasGoseberg Nov 25, 2025
cc19346
removed unused keys
JonasGoseberg Nov 26, 2025
dff1130
some change requests - typos
JonasGoseberg Nov 27, 2025
4e26c18
fixed some typos
JonasGoseberg Nov 27, 2025
d5bd3d4
PredefinedFilter fix
Klausw-1 Nov 27, 2025
a290dc9
removed color:green
JonasGoseberg Nov 27, 2025
53cff85
removed outcommented code
JonasGoseberg Nov 28, 2025
fff69e5
translated german comments
JonasGoseberg Nov 28, 2025
ec02749
Added length-limit for the predefinedfilter-name
cz-lucas Nov 28, 2025
6b9732f
Implemented improvments from @JonasGoseberg
cz-lucas Nov 28, 2025
523aa94
fixed using undefined variable and removed some old stuff
JonasGoseberg Nov 28, 2025
be6b18d
Implemented some of the improvements from codacy
cz-lucas Nov 28, 2025
ee41970
adjusted Tests to fixed behavior
JonasGoseberg Nov 28, 2025
4d154ad
Merge branch 'pre-merge' into SIT-202-avoidUsingUndefinedVariables
JonasGoseberg Nov 28, 2025
17730d1
Merge pull request #18266 from Valinwolf/develop
snipe Nov 28, 2025
4494f00
fix variable names to match other commits
JonasGoseberg Dec 1, 2025
5682849
fixed race condition
JonasGoseberg Dec 1, 2025
d8b3502
Renamed variables in frontend
cz-lucas Dec 2, 2025
ebabf5c
use logging
JonasGoseberg Dec 2, 2025
00581eb
Some more changes
cz-lucas Dec 2, 2025
be614ac
Build js
cz-lucas Dec 2, 2025
55bd08e
Merge pull request #119 from imbus/some-more-renaming-stuff
cz-lucas Dec 2, 2025
d5a7445
Merge pull request #117 from imbus/SIT-202-avoidUsingUndefinedVariables
JonasGoseberg Dec 2, 2025
84b7cd8
Merge branch 'pre-merge' into SIT-213-bugfix-racecondition-filterinput
JonasGoseberg Dec 2, 2025
54b089c
Merge pull request #118 from imbus/SIT-213-bugfix-racecondition-filte…
JonasGoseberg Dec 2, 2025
fd6b09f
Merge pull request #120 from imbus/Sit-202-use-logging-and-avoid-dupl…
JonasGoseberg Dec 2, 2025
f48d6ba
fixed generic onject injection sink warning
JonasGoseberg Dec 2, 2025
e83df54
Renamed tests
cz-lucas Dec 2, 2025
00dd811
Some more minor changes
cz-lucas Dec 2, 2025
734bf5c
Merge pull request #121 from imbus/rename-testcases
cz-lucas Dec 2, 2025
a3d8203
run npm
JonasGoseberg Dec 2, 2025
d1e0580
removed unused code
JonasGoseberg Dec 2, 2025
b48bcb3
And more code-style fixes
cz-lucas Dec 2, 2025
3bf6397
refactored some testclasses
JonasGoseberg Dec 2, 2025
5da7277
Merge pull request #122 from imbus/SIT-202-cleanup-codacy
JonasGoseberg Dec 2, 2025
40f8c77
Merge branch 'pre-merge' into and-more-codestyle-improvements
cz-lucas Dec 2, 2025
fc8351d
Fix comprehensibility issues
cz-lucas Dec 2, 2025
734c322
Merge remote-tracking branch 'upstream/develop' into more-minor-impro…
cz-lucas Dec 2, 2025
842e8ab
More formatting stuff later...
cz-lucas Dec 2, 2025
37ce2c0
next round of codacy improvements
JonasGoseberg Dec 2, 2025
4a9d3e5
npm run prod
JonasGoseberg Dec 2, 2025
741f291
Merge pull request #124 from imbus/SIT-202-more-codacy-cleanup
JonasGoseberg Dec 2, 2025
7308ea2
Added escaping
cz-lucas Dec 2, 2025
b4d0680
Merge branch 'pre-merge' of github.com:imbus/snipe-it into more-minor…
cz-lucas Dec 2, 2025
88192ba
Fixed unresolved merged conflict
cz-lucas Dec 2, 2025
f0f19e3
Merge pull request #125 from imbus/more-minor-improvements
cz-lucas Dec 2, 2025
8969e3f
Fixed some more very minor issues
cz-lucas Dec 3, 2025
7dcaee3
Merge pull request #126 from imbus/hotfix-modal-open
cz-lucas Dec 3, 2025
50b8603
npm run prod
JonasGoseberg Dec 3, 2025
398ae41
finally fixed the asset_eol_date test bug
JonasGoseberg Dec 3, 2025
4249652
Removed unused assignedTo-dropdown
cz-lucas Dec 5, 2025
7455102
Fixed some more minor stuff
cz-lucas Dec 3, 2025
4dd0fe8
Fixed 500-error because of missing notification-component
cz-lucas Dec 8, 2025
b30a2aa
Fixed colors for floating buttons
cz-lucas Dec 8, 2025
728eddd
Fixed CSS for multiple-select dropdowns
cz-lucas Dec 8, 2025
6db108d
Fixed CSS of the single-select dropdowns (kind of)
cz-lucas Dec 8, 2025
aab7b63
Fixed 500-error on profile-edit-page
cz-lucas Dec 9, 2025
b3f7625
first round of revert formatting
JonasGoseberg Dec 4, 2025
22698b4
next round of reverting formatting
JonasGoseberg Dec 5, 2025
c45d46e
next round of revert formatting
JonasGoseberg Dec 5, 2025
c329d29
fixed race condition
JonasGoseberg Dec 1, 2025
d079503
Renamed variables in frontend
cz-lucas Dec 2, 2025
b70a2b8
Some more minor changes
cz-lucas Dec 2, 2025
85c87f1
fixed generic onject injection sink warning
JonasGoseberg Dec 2, 2025
f33b703
run npm
JonasGoseberg Dec 2, 2025
e48a881
removed unused code
JonasGoseberg Dec 2, 2025
853e41b
More formatting stuff later...
cz-lucas Dec 2, 2025
b505ad4
next round of codacy improvements
JonasGoseberg Dec 2, 2025
b80c768
Sync public assets with upstream/develop except the ones we created o…
cz-lucas Dec 9, 2025
6959a7f
Reverted permissions of gitignore files
cz-lucas Dec 9, 2025
e66372e
rebase 08-2026
JonasGoseberg Aug 31, 2026
d73e6c1
Start resolving merge conflicts with origin/develop
Copilot Aug 31, 2026
49cbf16
Merge remote-tracking branch 'origin/develop' into rebase-2026_08_17_…
Copilot Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
71 changes: 71 additions & 0 deletions .ai/guidelines/snipe-it-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Snipe-IT Architecture

## Controllers

Two parallel controller trees:

- `app/Http/Controllers/` — web/UI controllers returning Blade views.
- `app/Http/Controllers/Api/` — REST API controllers returning JSON, consumed by datatables and select2.

Both trees use the same subdirectory groupings: `Assets/`, `Licenses/`, `Users/`, `Accessories/`, `Consumables/`, `Components/`, `Kits/`, `Account/`, `Auth/`.

## API Transformers

Every API controller returns data through a **Transformer** in `app/Http/Transformers/`. Never return raw model attributes from an API controller. `DatatablesTransformer` wraps paginated results.

```php
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
```

This supersedes the generic advice to reach for Eloquent API Resources — follow the existing transformer convention.

## Authorization

- All authorization goes through **Policies** in `app/Policies/`.
- `CheckoutablePermissionsPolicy` is the base for assets, licenses, accessories, and consumables.
- Its `checkout()` / `checkin()` methods accept `$item = null`, so `@can('checkout', \App\Models\Asset::class)` works without an instance.

## Routes

- UI routes live in `routes/web.php` **and** in the per-entity files under `routes/web/` (`hardware.php`, `users.php`, `licenses.php`, `accessories.php`, `components.php`, `consumables.php`, `kits.php`, `models.php`, `fields.php`, `locations.php`). Check both when adding or locating a UI route.
- API routes are in `routes/api.php`.
- Breadcrumbs are defined inline with `->breadcrumbs(fn (Trail $trail) => ...)` from `tabuna/breadcrumbs`. **Every UI route should have a breadcrumb.**
- Some route names contain slashes rather than dots. For example, use `route('reports/unaccepted_assets')`.

## Full Multiple Company Support (FMCS)

`Setting::getSettings()->full_multiple_companies_support == '1'` gates company-scoped filtering. The select2 endpoints (`selectlist()` methods) accept a `companyId` query param:

```php
if ((Setting::getSettings()->full_multiple_companies_support == '1') && ($request->filled('companyId'))) {
$query->where('table.company_id', $request->input('companyId'));
}
```

Wire it up from Blade with `data-company-id="{{ $user->company_id }}"`.

## Select2 AJAX Dropdowns

Use `class="js-data-ajax"` with `data-endpoint="hardware|licenses|consumables|..."`. `snipeit.js` auto-initializes these, forwarding `data-company-id` as `companyId` and `data-asset-status-type` as `statusType` to the API.

## Checkout Redirect Flow

After checkout, `Helper::getRedirectOption()` reads `$request->redirect_option`. To redirect back to the assigned user, the form must set:

- `redirect_option=target`
- `checkout_to_type=user`
- `assigned_user={{ $user->id }}`

## Translations

UI strings are translation keys in `resources/lang/en-US/general.php` and its sibling files. Always add a new key rather than hard-coding English in a view.

## Global View Variables

`$snipeSettings` is shared with every view by `SettingsServiceProvider`. Use it directly in Blade — do not pass `Setting::getSettings()` from the controller.

## Key Helper Methods (`app/Helpers/Helper.php`)

- `Helper::deployableStatusLabelList()` — status labels for checkout forms.
- `Helper::defaultChartColors(int $index = 0)` — 10-color chart palette.
- `Helper::getRedirectOption($request, $id, $table, $item_id = null)` — post-checkout redirect logic.
32 changes: 32 additions & 0 deletions .ai/guidelines/snipe-it-stack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Snipe-IT Stack & Tooling

## Frontend Is Laravel Mix, Not Vite

- Assets are built with **Laravel Mix (webpack)** via `webpack.mix.js`. This project has no `vite.config.js` and no Vite manifest.
- There is **no `npm run build` script**. Ignore any generic guidance that tells you to run it. Use:
- `npm run dev` — development build
- `npm run watch` — rebuild on change
- `npm run prod` — production build
- If the user doesn't see a frontend change, ask them to run `npm run dev` or `npm run watch`.

## UI Layer

- **AdminLTE 2 / Bootstrap 3** Blade views. There is no Inertia.
- **Livewire v4 is installed** and used for discrete widgets in `app/Livewire` (e.g. `Importer`, `CustomFieldEditor`, `LdapSettings`). It is not the primary UI layer.
- Default to a Blade view plus a standard controller. Only reach for Livewire when extending an existing Livewire component or when the user asks for it.

## Charts

- **Chart.js v2.9.4**, bundled at `public/js/dist/Chart.min.js`.
- Use the **v2 API**, not v3. For example, the chart type is `horizontalBar` (v3 removed it in favor of `indexAxis`).
- Use `Helper::defaultChartColors()` for the 10-color palette.

## Commands

```bash
# Clear caches after config/route changes
php artisan optimize:clear

# Coverage reports (served by Laravel Herd)
herd coverage
```
12 changes: 12 additions & 0 deletions .ai/guidelines/snipe-it-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Snipe-IT Testing

- Feature tests live in `tests/Feature/`, organized by entity (e.g. `tests/Feature/Assets/AssetsTest.php`). Unit tests live in `tests/Unit/`.
- Feature tests hit the database. The test environment uses `array` drivers for cache, session, and mail.
- Always build test data with model factories. Check for an existing custom state before setting attributes by hand.
- Test methods are named in snake_case: `test_page_renders()`, `test_requires_permission()`. Never camelCase.
- UI GET routes should have both a `test_page_renders` test and a `test_requires_permission` test.

```bash
php artisan test tests/Feature/Assets/AssetsTest.php # single file
php artisan test --filter test_some_method # single method
```
11 changes: 11 additions & 0 deletions .ai/rules/actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
paths:
- 'app/Actions/**'
---

# Actions

## Actions expose a single static run() method
An Action is a class in `app/Actions/<Entity>/` named `<Verb><Entity>Action`, with one `public static function run(...)` and no constructor. Call it statically: `DestroySupplierAction::run(supplier: $supplier)`.

Do not use `handle()`, `execute()`, `__invoke()`, or instantiate the class.
38 changes: 38 additions & 0 deletions .ai/rules/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
paths:
- 'app/Http/Controllers/Api/**'
---

# Api

## Wrap API responses in the standard envelope
Every API response goes through the shared envelope:

`return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('...')));`

Use `'error'` with a `null` payload for failures, and a translation key for the message. There are no Eloquent API Resources in this project.

## Page API lists with offset and limit
API list endpoints (`index()`) page with `offset`/`limit` request params, resolved through the container as `app('api_offset_value')` and `app('api_limit_value')`, then applied with `->skip($offset)->take($limit)->get()`.

Do not use `paginate()`, `simplePaginate()`, or `cursorPaginate()` there.

## Select2 endpoints are the pagination exception
`selectlist()` methods must return a `LengthAwarePaginator`. `SelectlistTransformer::transformSelectlist()` type-hints one and derives select2's `pagination.more`, `total_count`, and `page_count` from `currentPage()`, `lastPage()`, `perPage()`, and `total()`, which drives select2's infinite scroll. A skip/take collection cannot be passed to it.

Use `->paginate(50)`:

```php
$users = $users->paginate(50);

return (new SelectlistTransformer)->transformSelectlist($users);
```

When the results have to be sorted or formatted as a collection first, `->get()` and wrap with `Helper::paginateCollection()`, which builds the paginator from `app('api_current_page')` and `app('api_limit_value')`:

```php
$companies = $companies->orderBy('name', 'ASC')->get();
// ...sorting/formatting...

return (new SelectlistTransformer)->transformSelectlist(Helper::paginateCollection($sorted));
```
9 changes: 9 additions & 0 deletions .ai/rules/app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
paths:
- 'app/**'
---

# App

## Use trans(), never __()
Translate with `trans('admin/hardware/message.some_key')` using short dotted keys from `resources/lang/<locale>/`. Never use `__()` — it appears nowhere in this codebase. Add a new key rather than hard-coding English.
9 changes: 9 additions & 0 deletions .ai/rules/controllers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
paths:
- 'app/Http/Controllers/**'
---

# Controllers

## No DTOs or repository layer
Controllers build Eloquent queries inline and pass models, collections, and arrays around. There are no DTO or repository classes — do not introduce them. Extract to an Action or a Presenter when a controller method gets heavy.
18 changes: 18 additions & 0 deletions .ai/rules/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Project Rules Index

Before planning or editing, find the row whose globs match the file's path and read that rule file.

| Applies to | Rule file |
| --- | --- |
| app/Actions/** | .ai/rules/actions.md |
| app/Http/Controllers/Api/** | .ai/rules/api.md |
| app/** | .ai/rules/app.md |
| app/Http/Controllers/** | .ai/rules/controllers.md |
| app/Livewire/** | .ai/rules/livewire.md |
| database/migrations/** | .ai/rules/migrations.md |
| app/Models/** | .ai/rules/models.md |
| app/Presenters/** | .ai/rules/presenters.md |
| app/Providers/** | .ai/rules/providers.md |
| app/Http/Requests/** | .ai/rules/requests.md |
| tests/** | .ai/rules/tests.md |
| resources/views/** | .ai/rules/views.md |
9 changes: 9 additions & 0 deletions .ai/rules/livewire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
paths:
- 'app/Livewire/**'
---

# Livewire

## Livewire components are class-based with a separate view
A Livewire component is a class in `app/Livewire` plus a kebab-case Blade view in `resources/views/livewire`. Livewire 4 is installed, but this project uses none of its single-file, multi-file, or Volt formats — match the class-plus-view shape.
9 changes: 9 additions & 0 deletions .ai/rules/migrations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
paths:
- 'database/migrations/**'
---

# Migrations

## No foreign-key constraints
Relationship columns are plain `integer('other_id')` columns (nullable and indexed as needed). Do not add `foreignId()`, `foreignIdFor()`, `constrained()`, or `->foreign()->references()` — this schema has no FK constraints and referential integrity is enforced in application code.
27 changes: 27 additions & 0 deletions .ai/rules/models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
paths:
- 'app/Models/**'
---

# Models

## Models validate themselves with watson/validating
Models carry their own validation: `use Watson\Validating\ValidatingTrait` plus a `protected $rules` array. `$model->save()` returns false when validation fails and `$model->getErrors()` holds the messages.

This is a second layer on top of the Form Request, not a replacement for it.

## Declare a presenter on the model
A model that renders in the UI sets `protected $presenter = \App\Presenters\<Entity>Presenter::class` and `use App\Presenters\Presentable`, exposing `$model->present()`.

## Share model behavior through opt-in traits
Cross-cutting model behavior comes from traits in `app/Models/Traits`, opted into per model: `CompanyableTrait` (FMCS scoping), `Loggable` (action log), `Searchable` (API/datatable search), `Requestable`, `Acceptable`, `HasUploads`.

Add behavior as a trait rather than pushing it into a base class.

## Casts go in the $casts property
Declare casts with `protected $casts = [...]`, not a `casts()` method, even though Laravel 12 supports the method form.

## New-style Attribute accessors need @return Attribute generics for Larastan
If a model attribute has a new-style accessor (`protected function locationId(): Attribute`), Larastan drops the property entirely unless the method has a generic PHPDoc: `/** @return Attribute<TGet, TSet> */` (e.g. `Attribute<int|null, int|null>`). Without it, the DB-column extension defers to the accessor extension, the accessor extension requires strict generics, and the property reads as "undefined" — that is why `@property` tags crept into model docblocks (e.g. PR #19480). Fix by adding the `@return Attribute<...>` generic instead of `@property` tags; plain columns without accessors are inferred from migrations automatically and need no annotation.

TGet is the readable type; **TSet is the writable one — the setter's argument, i.e. what may be assigned**, not what the setter stores. Reserve `never` for attributes genuinely never written: no `set:` closure **and** nothing assigns to them (computed values like `expiresDiffInDays()`). No `set:` closure alone is not enough — an accessor decorating a real column still gets assigned elsewhere, and `never` turns each assignment into `Property Setting::$header_color (never) does not accept mixed`; use `mixed` there. Same reasoning for a setter that narrows: `requestable()` stores `(int) filter_var(...)` but accepts an untyped `$value`, so it is `Attribute<int, mixed>`.
11 changes: 11 additions & 0 deletions .ai/rules/presenters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
paths:
- 'app/Presenters/**'
---

# Presenters

## Presenters own display and datatable config
Display formatting and Bootstrap-table column config belong in `app/Presenters/<Entity>Presenter.php`, reached from the model via `$model->present()`.

Keep this logic out of controllers, transformers, and Blade.
14 changes: 14 additions & 0 deletions .ai/rules/providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
paths:
- 'app/Providers/**'
---

# Providers

## Named validation rules live in ValidationServiceProvider
Add a new named validation rule as a `Validator::extend()` (or `extendImplicit()`) closure in `app/Providers/ValidationServiceProvider.php`, then reference it by its string name in `$rules`.

`app/Rules` is reserved for the encrypted-custom-field rule objects — do not add general-purpose rules there.

## Register observers in AppServiceProvider
Wire an observer with `Model::observe(ModelObserver::class)` in `AppServiceProvider::boot()`. Do not use the `#[ObservedBy]` attribute on the model.
16 changes: 16 additions & 0 deletions .ai/rules/requests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
paths:
- 'app/Http/Requests/**'
---

# Requests

## Form Requests are the validation entry point
Validate HTTP input with a Form Request class, not inline `$request->validate()` or `Validator::make()`.

Extend `App\Http\Requests\Request` and declare rules in the `protected $rules` property — the base class returns it from `rules()`. When the request handles file uploads, extend `ImageUploadRequest` instead and call `$request->handleImages($model)` in the controller.

## Always call parent::prepareForValidation()
`ImageUploadRequest` inherits `prepareForValidation()` from the `ConvertsBase64ToFiles` trait, which turns base64 payloads into `UploadedFile` instances before rules run.

If a child request overrides `prepareForValidation()`, it MUST call `parent::prepareForValidation()` — usually first. Forgetting it silently breaks base64 image uploads with no validation error to point at. This has bitten us before.
15 changes: 15 additions & 0 deletions .ai/rules/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
paths:
- 'tests/**'
---

# Tests

## Database refresh comes from the base TestCase
`Tests\TestCase` already applies `LazilyRefreshDatabase` and seeds settings via `InitializesSettings`. Do not add `RefreshDatabase`, `DatabaseTransactions`, or `DatabaseMigrations` to an individual test.

## Test methods are snake_case
Name test methods in snake_case: `test_page_renders()`, `test_requires_permission()`. Never camelCase.

## Authenticate API tests with actingAsForApi()
Use `$this->actingAsForApi($user)` in API tests and `$this->actingAs($user)` in UI tests.
14 changes: 14 additions & 0 deletions .ai/rules/views.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
paths:
- 'resources/views/**'
---

# Views

## Use trans(), never __()
Translate with `trans('general.some_key')` using short dotted keys from `resources/lang/<locale>/`. Never use `__()` — it appears nowhere in this codebase. Add a new key rather than hard-coding English.

## Blade composition: layouts plus anonymous components
Pages `@extends` a layout. Reusable markup is an anonymous Blade component: a file in `resources/views/components/` declaring `@props([...])`, used as `<x-name />`.

Do not create class-based components — there is no `app/View/Components` directory.
Loading