Skip to content

Commit 4dc3c30

Browse files
committed
Merge remote-tracking branch 'origin/develop'
2 parents 397cc17 + 947ccf9 commit 4dc3c30

File tree

10 files changed

+281
-3
lines changed

10 files changed

+281
-3
lines changed

app/Http/Controllers/Api/AssetsController.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Http\Requests\StoreAssetRequest;
77
use App\Http\Requests\UpdateAssetRequest;
88
use App\Http\Traits\MigratesLegacyAssetLocations;
9+
use App\Http\Transformers\ComponentsTransformer;
910
use App\Models\AccessoryCheckout;
1011
use App\Models\CheckoutAcceptance;
1112
use App\Models\LicenseSeat;
@@ -1322,6 +1323,18 @@ public function assignedAccessories(Request $request, Asset $asset) : JsonRespon
13221323
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
13231324
}
13241325

1326+
public function assignedComponents(Request $request, Asset $asset): JsonResponse|array
1327+
{
1328+
$this->authorize('view', Asset::class);
1329+
$this->authorize('view', $asset);
1330+
1331+
$asset->loadCount('components');
1332+
$total = $asset->components_count;
1333+
1334+
$components = $asset->load(['components' => fn($query) => $query->applyOffsetAndLimit($total)])->components;
1335+
1336+
return (new ComponentsTransformer)->transformComponents($components, $total);
1337+
}
13251338

13261339
/**
13271340
* Generate asset labels by tag

app/Http/Controllers/Assets/BulkAssetsController.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,9 +621,25 @@ public function showCheckout() : View
621621
{
622622
$this->authorize('checkout', Asset::class);
623623

624+
$alreadyAssigned = collect();
625+
626+
if (old('selected_assets') && is_array(old('selected_assets'))) {
627+
$assets = Asset::findMany(old('selected_assets'));
628+
629+
[$assignable, $alreadyAssigned] = $assets->partition(function (Asset $asset) {
630+
return !$asset->assigned_to;
631+
});
632+
633+
session()->flashInput(['selected_assets' => $assignable->pluck('id')->values()->toArray()]);
634+
}
635+
624636
$do_not_change = ['' => trans('general.do_not_change')];
625637
$status_label_list = $do_not_change + Helper::deployableStatusLabelList();
626-
return view('hardware/bulk-checkout')->with('statusLabel_list', $status_label_list);
638+
639+
return view('hardware/bulk-checkout', [
640+
'statusLabel_list' => $status_label_list,
641+
'removed_assets' => $alreadyAssigned,
642+
]);
627643
}
628644

629645
/**

app/Http/Controllers/ReportsController.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,14 @@ public function postCustom(CustomAssetReportRequest $request) : StreamedResponse
685685
$assets->whereBetween('assets.purchase_date', [$request->input('purchase_start'), $request->input('purchase_end')]);
686686
}
687687

688+
if ($request->filled('purchase_cost_start')) {
689+
if ($request->filled('purchase_cost_end')) {
690+
$assets->whereBetween('assets.purchase_cost', [$request->input('purchase_cost_start'), $request->input('purchase_cost_end')]);
691+
} else {
692+
$assets->where('assets.purchase_cost', ">", $request->input('purchase_cost_start'));
693+
}
694+
}
695+
688696
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
689697
$created_start = Carbon::parse($request->input('created_start'))->startOfDay();
690698
$created_end = Carbon::parse($request->input('created_end'))->endOfDay();

app/Http/Requests/CustomAssetReportRequest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ public function authorize()
1414
return true;
1515
}
1616

17+
18+
public function prepareForValidation()
19+
{
20+
if($this->filled('purchase_cost_end') && !$this->filled('purchase_cost_start')){
21+
$this->merge(['purchase_cost_start' => 0 ]);
22+
}
23+
}
24+
25+
1726
/**
1827
* Get the validation rules that apply to the request.
1928
*
@@ -24,6 +33,7 @@ public function rules()
2433
return [
2534
'purchase_start' => 'date|date_format:Y-m-d|nullable',
2635
'purchase_end' => 'date|date_format:Y-m-d|nullable',
36+
'purchase_cost_end' => 'numeric|nullable|gte:purchase_cost_start',
2737
'created_start' => 'date|date_format:Y-m-d|nullable',
2838
'created_end' => 'date|date_format:Y-m-d|nullable',
2939
'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace App\Models\Labels\Tapes\Brother;
4+
5+
class TZe_24mm_E extends TZe_24mm
6+
{
7+
private const BARCODE_MARGIN = 1.50;
8+
private const TAG_SIZE = 2.00;
9+
private const TITLE_SIZE = 2.80;
10+
private const TITLE_MARGIN = 0.50;
11+
private const LABEL_SIZE = 2.00;
12+
private const LABEL_MARGIN = - 0.35;
13+
private const FIELD_SIZE = 2.80;
14+
private const FIELD_MARGIN = 0.15;
15+
private const BARCODE1D_SIZE = - 1.00;
16+
17+
public function getUnit() { return 'mm'; }
18+
public function getWidth() { return 45.0; }
19+
public function getHeight() { return 15; }
20+
public function getSupportAssetTag() { return true; }
21+
public function getSupport1DBarcode() { return true; }
22+
public function getSupport2DBarcode() { return true; }
23+
public function getSupportFields() { return 3; }
24+
public function getSupportLogo() { return false; }
25+
public function getSupportTitle() { return true; }
26+
27+
public function preparePDF($pdf) {}
28+
29+
public function write($pdf, $record) {
30+
$pa = $this->getPrintableArea();
31+
32+
$currentX = $pa->x1;
33+
$currentY = $pa->y1;
34+
$usableWidth = $pa->w;
35+
36+
37+
$usableHeight = $pa->h - self::BARCODE1D_SIZE;
38+
$barcodeSize = $usableHeight - self::TAG_SIZE;
39+
40+
if ($record->has('barcode2d')) {
41+
static::writeText(
42+
$pdf, $record->get('tag'),
43+
$pa->x1, $pa->y2 - self::TAG_SIZE - self::BARCODE1D_SIZE,
44+
'freesans', 'b', self::TAG_SIZE, 'C',
45+
$barcodeSize, self::TAG_SIZE, true, 0
46+
);
47+
static::write2DBarcode(
48+
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
49+
$currentX, $currentY,
50+
$barcodeSize, $barcodeSize
51+
);
52+
$currentX += $barcodeSize + self::BARCODE_MARGIN;
53+
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
54+
} else {
55+
static::writeText(
56+
$pdf, $record->get('tag'),
57+
$pa->x1, $pa->y2 - self::TAG_SIZE - self::BARCODE1D_SIZE,
58+
'freesans', 'B', self::TAG_SIZE, 'R',
59+
$usableWidth, self::TAG_SIZE, true, 0
60+
);
61+
}
62+
63+
if ($record->has('title')) {
64+
static::writeText(
65+
$pdf, $record->get('title'),
66+
$currentX, $currentY,
67+
'freesans', 'B', self::TITLE_SIZE, 'L',
68+
$usableWidth, self::TITLE_SIZE, true, 0
69+
);
70+
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
71+
}
72+
73+
foreach ($record->get('fields') as $field) {
74+
// Write label and value on the same line
75+
// Calculate label width with proportional character spacing
76+
$labelWidth = $pdf->GetStringWidth($field['label'], 'freesans', '', self::LABEL_SIZE);
77+
$charCount = strlen($field['label']);
78+
$spacingPerChar = 0.5;
79+
$totalSpacing = $charCount * $spacingPerChar;
80+
$adjustedWidth = $labelWidth + $totalSpacing;
81+
82+
static::writeText(
83+
$pdf, $field['label'],
84+
$currentX, $currentY,
85+
'freesans', 'B', self::LABEL_SIZE, 'L',
86+
$adjustedWidth, self::LABEL_SIZE, true, 0, $spacingPerChar
87+
);
88+
89+
static::writeText(
90+
$pdf, $field['value'],
91+
$currentX + $adjustedWidth + 2, $currentY,
92+
'freesans', 'B', self::FIELD_SIZE, 'L',
93+
$usableWidth - $adjustedWidth - 2, self::FIELD_SIZE, true, 0, 0.3
94+
);
95+
96+
$currentY += max(self::LABEL_SIZE, self::FIELD_SIZE) + self::FIELD_MARGIN;
97+
}
98+
99+
100+
if ($record->has('barcode1d')) {
101+
static::write1DBarcode(
102+
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
103+
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
104+
);
105+
}
106+
}
107+
}

resources/lang/en-US/general.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@
522522
'error_user_company_multiple' => 'One or more of the checkout target company and asset company do not match',
523523
'error_user_company_accept_view' => 'An Asset assigned to you belongs to a different company so you can\'t accept nor deny it, please check with your manager',
524524
'error_assets_already_checked_out' => 'One or more of the assets are already checked out',
525+
'assigned_assets_removed' => 'The following were removed from the selected assets because they are already checked out',
525526
'importer' => [
526527
'checked_out_to_fullname' => 'Checked Out to: Full Name',
527528
'checked_out_to_first_name' => 'Checked Out to: First Name',

resources/views/hardware/bulk-checkout.blade.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,26 @@
2727
<form class="form-horizontal" method="post" action="" autocomplete="off">
2828
{{ csrf_field() }}
2929

30+
@if ($removed_assets->isNotEmpty())
31+
<div class="box box-solid box-warning">
32+
<div class="box-header with-border">
33+
<span class="box-title col-xs-12">Warning</span>
34+
</div>
35+
<div class="box-body">
36+
<p>{{ trans('general.assigned_assets_removed') }}</p>
37+
<ul>
38+
@foreach($removed_assets as $removed_asset)
39+
<li>
40+
<a href="{{ route('hardware.show', $removed_asset->id) }}">
41+
{{ $removed_asset->present()->fullName }}
42+
</a>
43+
</li>
44+
@endforeach
45+
</ul>
46+
</div>
47+
</div>
48+
@endif
49+
3050
@include ('partials.forms.edit.asset-select', [
3151
'translated_name' => trans('general.assets'),
3252
'fieldname' => 'selected_assets[]',
@@ -147,7 +167,6 @@
147167
return true; // ensure form still submits
148168
});
149169
150-
$('#assigned_assets_select').select2('open');
151170
setTimeout(function () {
152171
const $searchField = $('.select2-search__field');
153172
const $results = $('.select2-results');

resources/views/reports/custom.blade.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,25 @@ class="form-control"
426426

427427
</div>
428428

429-
<!-- Created Date -->
429+
<!-- Purchase Cost -->
430+
<div class="form-group purchase-range{{ ($errors->has('purchase_cost_start') || $errors->has('purchase_cost_end')) ? ' has-error' : '' }}">
431+
<label for="purchase_cost_start" class="col-md-3 control-label">{{ trans('admin/hardware/form.cost') }}</label>
432+
<div class="input-group col-md-7">
433+
<input type="number" min="0" step="0.01" class="form-control" name="purchase_cost_start" aria-label="purchase_cost_start" value="{{ $template->textValue('purchase_cost_start', old('purchase_cost_start')) }}">
434+
<span class="input-group-addon">{{ strtolower(trans('general.to')) }}</span>
435+
<input type="number" min="0" step="0.01" class="form-control" name="purchase_cost_end" aria-label="purchase_cost_end" value="{{ $template->textValue('purchase_cost_end', old('purchase_cost_end')) }}">
436+
</div>
437+
438+
@if ($errors->has('purchase_cost_start') || $errors->has('purchase_cost_end'))
439+
<div class="col-md-9 col-lg-offset-3">
440+
{!! $errors->first('purchase_cost_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
441+
{!! $errors->first('purchase_cost_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
442+
</div>
443+
@endif
444+
445+
</div>
446+
447+
<!-- Created Date -->
430448
<div class="form-group created-range{{ ($errors->has('created_start') || $errors->has('created_end')) ? ' has-error' : '' }}">
431449
<label for="created_start" class="col-md-3 control-label">{{ trans('general.created_at') }} </label>
432450
<div class="input-daterange input-group col-md-7" id="created-range-datepicker">

routes/api.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,13 @@
571571
'assignedAccessories'
572572
]
573573
)->name('api.assets.assigned_accessories');
574+
575+
Route::get('{asset}/assigned/components',
576+
[
577+
Api\AssetsController::class,
578+
'assignedComponents'
579+
]
580+
)->name('api.assets.assigned_components');
574581
/** End assigned routes */
575582

576583
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Tests\Feature\Assets\Api;
4+
5+
use App\Models\Asset;
6+
use App\Models\Company;
7+
use App\Models\Component;
8+
use App\Models\User;
9+
use Illuminate\Testing\Fluent\AssertableJson;
10+
use Tests\TestCase;
11+
12+
class AssignedComponentsTest extends TestCase
13+
{
14+
public function test_requires_permission()
15+
{
16+
$this->actingAsForApi(User::factory()->create())
17+
->getJson(route('api.assets.assigned_components', Asset::factory()->create()))
18+
->assertForbidden();
19+
}
20+
21+
public function test_adheres_to_company_scoping()
22+
{
23+
$this->settings->enableMultipleFullCompanySupport();
24+
25+
[$companyA, $companyB] = Company::factory()->count(2)->create();
26+
27+
$asset = Asset::factory()->for($companyA)->create();
28+
29+
$user = User::factory()->for($companyB)->viewAssets()->create();
30+
31+
$this->actingAsForApi($user)
32+
->getJson(route('api.assets.assigned_components', $asset))
33+
->assertOk()
34+
->assertStatusMessageIs('error')
35+
->assertMessagesAre('Asset not found');
36+
}
37+
38+
public function test_can_get_components_assigned_to_specific_asset()
39+
{
40+
$unassociatedComponent = Component::factory()->create();
41+
42+
$asset = Asset::factory()->hasComponents(2)->create();
43+
44+
$componentsAssignedToAsset = $asset->components;
45+
46+
$this->actingAsForApi(User::factory()->viewAssets()->create())
47+
->getJson(route('api.assets.assigned_components', $asset))
48+
->assertOk()
49+
->assertResponseContainsInRows($componentsAssignedToAsset)
50+
->assertResponseDoesNotContainInRows($unassociatedComponent)
51+
->assertJson(function (AssertableJson $json) {
52+
$json->where('total', 2)
53+
->count('rows', 2)
54+
->etc();
55+
});
56+
}
57+
58+
public function test_adheres_to_offset_and_limit()
59+
{
60+
$asset = Asset::factory()->hasComponents(2)->create();
61+
62+
$componentsAssignedToAsset = $asset->components;
63+
64+
$this->actingAsForApi(User::factory()->viewAssets()->create())
65+
->getJson(route('api.assets.assigned_components', [
66+
'asset' => $asset,
67+
'offset' => 1,
68+
'limit' => 1,
69+
]))
70+
->assertOk()
71+
->assertResponseDoesNotContainInRows($componentsAssignedToAsset->first())
72+
->assertResponseContainsInRows($componentsAssignedToAsset->last())
73+
->assertJson(function (AssertableJson $json) {
74+
$json->where('total', 2)
75+
->count('rows', 1)
76+
->etc();
77+
});
78+
}
79+
}

0 commit comments

Comments
 (0)