Compare commits

...

2 Commits

Author SHA1 Message Date
b41fae9bd0 Fixes for supplier configuration product links
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-05-18 21:29:32 +10:00
f08dda6d0a Add edit supplier products (phone) 2025-05-18 21:14:02 +10:00
11 changed files with 170 additions and 80 deletions

View File

@ -73,6 +73,20 @@ class SupplierController extends Controller
->with('success','File uploaded');
}
public function product_new(SupplierProductAddEdit $request)
{
$o = Supplier::offeringTypeClass($request->validated('offering_type'));
foreach (Arr::except($request->validated(),['id','offering_type']) as $key => $value)
$o->{$key} = $value;
$o->save();
return redirect()
->back()
->with('success','Saved');
}
public function product_addedit(SupplierProductAddEdit $request,Supplier $o,int $id,string $type)
{
// Quick validation
@ -88,7 +102,7 @@ class SupplierController extends Controller
// @todo these are broadband requirements - get them from the broadband class.
foreach (Arr::only($request->validated(),[
'supplier_detail_id',
'product_id'.
'product_id',
'product_desc',
'base_cost',
'setup_cost',
@ -107,7 +121,7 @@ class SupplierController extends Controller
'extra_down_offpeak',
'extra_up_offpeak',
]) as $key => $value)
$oo->$key = $value;
$oo->{$key} = $value;
// Our boolean values
foreach (Arr::only($request->validated(),['active','extra_shaped','extra_charged']) as $key => $value)
@ -115,6 +129,17 @@ class SupplierController extends Controller
break;
case 'phone':
$oo = Supplier\Phone::findOrNew($id);
foreach (Arr::except($oo->validation(),[
'id',
'offering_type',
]) as $key => $value)
$oo->{$key} = $request->validated($key);
break;
default:
throw new \Exception('Unknown offering type:'.$request->offering_type);
}

View File

@ -5,56 +5,28 @@ namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use App\Models\Supplier;
class SupplierProductAddEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->isWholesaler();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules(Request $request)
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
// @todo these are broadband requirements - perhaps move them to the broadband class.
// @todo Enhance the validation so that extra_* values are not accepted if base_* values are not included.
return [
'id' => 'required|nullable',
'offering_type' => ['required',Rule::in(Supplier::offeringTypeKeys()->toArray())],
'supplier_detail_id' => 'required|exists:supplier_details,id',
'active' => 'sometimes|accepted',
'extra_shaped' => 'sometimes|accepted',
'extra_charged' => 'sometimes|accepted',
'product_id' => 'required|string|min:2',
'product_desc' => 'required|string|min:2',
'base_cost' => 'required|numeric|min:.01',
'setup_cost' => 'nullable|numeric',
'contract_term' => 'nullable|numeric|min:1',
'metric' => 'nullable|numeric|min:1',
'speed' => 'nullable|string|max:64',
'technology' => 'nullable|string|max:255',
'offpeak_start' => 'nullable|date_format:H:i',
'offpeak_end' => 'nullable|date_format:H:i',
'base_down_peak' => 'nullable|numeric',
'base_up_peak' => 'nullable|numeric',
'base_down_offpeak' => 'nullable|numeric',
'base_up_offpeak' => 'nullable|numeric',
'extra_down_peak' => 'nullable|numeric',
'extra_up_peak' => 'nullable|numeric',
'extra_down_offpeak' => 'nullable|numeric',
'extra_up_offpeak' => 'nullable|numeric',
];
return Auth::user()->isWholesaler();
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules(Request $request): array
{
return Supplier::offeringTypeClass($request->offering_type ?? '')->validation();
}
}

View File

@ -148,4 +148,27 @@ class Broadband extends Type
return $result;
}
public function validation(): array
{
// @todo Enhance the validation so that extra_* values are not accepted if base_* values are not included.
return array_merge(parent::validation(),
[
'extra_shaped' => 'sometimes|accepted',
'extra_charged' => 'sometimes|accepted',
'metric' => 'nullable|numeric|min:1',
'speed' => 'nullable|string|max:64',
'technology' => 'nullable|string|max:255',
'offpeak_start' => 'nullable|date_format:H:i',
'offpeak_end' => 'nullable|date_format:H:i',
'base_down_peak' => 'nullable|numeric',
'base_up_peak' => 'nullable|numeric',
'base_down_offpeak' => 'nullable|numeric',
'base_up_offpeak' => 'nullable|numeric',
'extra_down_peak' => 'nullable|numeric',
'extra_up_peak' => 'nullable|numeric',
'extra_down_offpeak' => 'nullable|numeric',
'extra_up_offpeak' => 'nullable|numeric',
]);
}
}

View File

@ -4,9 +4,10 @@ namespace App\Models\Supplier;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Validation\Rule;
use Leenooks\Traits\ScopeActive;
use App\Models\{Invoice,SupplierDetail};
use App\Models\{Invoice, Supplier, SupplierDetail};
use App\Traits\SiteID;
abstract class Type extends Model
@ -87,4 +88,23 @@ abstract class Type extends Model
{
return $this->supplier_detail->supplier;
}
public function validation(): array
{
return [
'id' => [
'nullable',
'numeric',
sprintf('exists:%s,id',static::getTable())
],
'offering_type' => ['required',Rule::in(Supplier::offeringTypeKeys()->toArray())],
'supplier_detail_id' => 'required|exists:supplier_details,id',
'active' => 'sometimes|accepted',
'base_cost' => 'required|numeric|min:0',
'setup_cost' => 'nullable|numeric|min:0',
'contract_term' => 'nullable|numeric|min:1',
'product_id' => 'required|string|min:2',
'product_desc' => 'required|string|min:2',
];
}
}

View File

@ -46,7 +46,7 @@ class SupplierDetail extends Model
* @return Type
* @throws \Exception
*/
public function find(string $type,int $id): Type
public function supplier_product(string $type,int $id): Type
{
switch ($type) {
case 'broadband':

View File

@ -1,6 +1,5 @@
<!-- $o=Product::class -->
@use(App\Models\Product)
<div class="card card-dark">
<div class="card-header">
<h1 class="card-title">Product Configuration</h1>
@ -12,7 +11,7 @@
<div class="row">
<div class="col-12 col-sm-9 col-md-6 col-xl-5">
<x-leenooks::form.select name="product_id" icon="fa-list" label="Product" choose="true" groupby="active" :value="$po?->id ?? ''" :options="Product::get()->sortBy(fn($item)=>($item->active ? '0' : '1').$item->name)->map(fn($item)=>['id'=>$item->id,'value'=>$item->name,'active'=>$item->active])"/>
<x-leenooks::form.select name="product_id" icon="fa-list" label="Product" choose="true" groupby="active" :value="$pdo?->id ?? ''" :options="Product::get()->sortBy(fn($item)=>($item->active ? '0' : '1').$item->name)->map(fn($item)=>['id'=>$item->id,'value'=>$item->name,'active'=>$item->active])"/>
</div>
</div>
</form>

View File

@ -5,9 +5,10 @@
@php
if(isset($spo)) {
$oo = $spo->detail?->find(request()->route()->parameter('type'),request()->route()->parameter('id'));
$oo = $spo->detail?->supplier_product(request()->route()->parameter('type'),request()->route()->parameter('id'));
$oo?->load(['products.products.services']);
}
$breadcrumb = [$spo->name=>route('supplier.details',['spo'=>$spo->id])];
@endphp
@extends('adminlte::layouts.app')
@ -55,7 +56,11 @@ if(isset($spo)) {
</div>
</div>
<div id="type"></div>
<div id="type">
@if(($x=old('offering_type')) && ((! isset($oo)) || (! $oo->exists)))
@include('theme.backend.adminlte.supplier.product.widget.'.$x,['o'=>NULL])
@endif
</div>
<div class="row">
<!-- Buttons -->
@ -153,26 +158,26 @@ if(isset($spo)) {
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
function load_type(type,id) {
$.ajax({
type: 'POST',
dataType: 'html',
cache: false,
data: {errors: {!! $errors !!}, old: {!! json_encode(old()) !!} },
url: '{{ url('a/supplier/product/view') }}/'+type+(id ? '/'+id : ''),
timeout: 2000,
error: function(x) {
//spinner.toggleClass('d-none').toggleClass('fa-spin');
alert('Failed to submit');
},
success: function(data) {
$("div[id=type]").empty().append(data);
}
});
}
function load_type(type,id) {
$.ajax({
type: 'POST',
dataType: 'html',
cache: false,
data: {errors: {!! $errors !!}, old: {!! json_encode(old()) !!} },
url: '{{ url('a/supplier/product/view') }}/'+type+(id ? '/'+id : ''),
timeout: 2000,
error: function(x) {
//spinner.toggleClass('d-none').toggleClass('fa-spin');
alert('Failed to submit');
},
success: function(data) {
$("div[id=type]").empty().append(data);
}
});
}
@if (isset($oo) && $oo->exists)
$(document).ready(function() {
@if(isset($oo) && $oo->exists)
$('#offering_type').attr('style','pointer-events: none;');
load_type('{{$oo->category}}',{{$oo->id}})
@endif

View File

@ -0,0 +1,43 @@
<!-- $o=Supplier/Phone::class -->
<div class="row">
<!-- Supplier Name -->
<div class="col-4">
<x-leenooks::form.text name="product_id" label="Product ID" icon="fa-tag" helper="Supplier's Product ID" :value="$o?->product_id"/>
</div>
<!-- Suppliers Description -->
<div class="col-8">
<x-leenooks::form.text name="product_desc" label="Product Description" icon="fa-file-alt" helper="Supplier's Product Description as it appears on Invoices" :value="$o?->product_desc"/>
</div>
</div>
<div class="row">
<!-- Supplier Active -->
<div class="col-1">
<x-leenooks::form.toggle name="active" label="Active" :value="$o?->active"/>
</div>
<div class="offset-1 col-10">
<div class="row">
<!-- Base Cost -->
<div class="col-12 col-sm-2">
<x-leenooks::form.text class="text-right" name="base_cost" label="Base" helper="Monthly Cost (ex)" :value="$o?->base_cost"/>
</div>
<!-- Setup Cost -->
<div class="col-2">
<x-leenooks::form.text class="text-right" name="setup_cost" label="Setup" helper="Setup Cost (ex)" :value="$o?->setup_cost"/>
</div>
<!-- Contract Term -->
<div class="col-2">
<x-leenooks::form.number class="text-right" name="contract_term" label="Contract Term" helper="Term (mths)" :value="$o?->contract_term"/>
</div>
<!-- Technology -->
<div class="col-2">
<x-leenooks::form.text class="text-right" name="technology" label="Technology" :value="$o?->technology"/>
</div>
</div>
</div>
</div>

View File

@ -37,7 +37,7 @@
<tbody>
@foreach($xx=$offering->items->with(['products.products.services'])->get() as $oo)
<tr>
<td>{{ $oo->id }}</td>
<td><a href="{{ route('supplier.product.type',['id'=>$oo->id,'spo'=>$oo->supplier_detail_id,'type'=>$oo->category]) }}">{{ $oo->id }}</a></td>
<td>{{ $oo->name }}</td>
<td>{{ $oo->name_long }}</td>
<td class="text-right">{{ $oo->active ? 'YES' : 'NO' }}</td>

View File

@ -39,8 +39,8 @@
@foreach($xx=$offering->items->with(['products.products.services','products.products.type.supplied.supplier_detail','products.products.translate'])->get() as $oo)
@foreach($oo->products->pluck('products')->flatten()->filter() as $po)
<tr>
<td><a href="{{ url('a/supplier/product',[$po->supplier->id,$po->supplied->id,$po->supplied->category]) }}">{{ $po->lid }}</a></td>
<td>{{ $po->pid }}</td>
<td><a href="{{ route('product',['pdo'=>$po->id]) }}">{{ $po->lid }}</a></td>
<td>{{ $po->pid }} <small>[<a href="{{ route('supplier.product.type',['id'=>$po->supplier->id,'spo'=>$po->supplied->id,'type'=>$po->supplied->category]) }}">{{ $po->supplied->name }}</a>]</small></td>
<td>{{ $po->name }}</td>
<td class="text-right">{{ $po->active ? 'YES' : 'NO' }}</td>
<td class="text-right">{{ $po->billing_interval_string }}</td>

View File

@ -6,7 +6,6 @@ use Leenooks\Controllers\SwitchUserController;
use App\Http\Controllers\{AdminController,
Auth\LoginController,
Auth\SocialLoginController,
ChargeController,
CheckoutController,
HomeController,
@ -107,7 +106,8 @@ Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function()
// Product Setup
Route::view('product','theme.backend.adminlte.product.home');
Route::view('product/{pdo}','theme.backend.adminlte.product.details',['breadcrumb'=>['Products'=>'a/product']])
->where('pdo','[0-9]+');
->where('pdo','[0-9]+')
->name('product');
Route::post('product/{o?}',[ProductController::class,'addedit'])
->where('o','[0-9]+');
@ -122,14 +122,17 @@ Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function()
Route::view('supplier/cost/{cso}','theme.backend.adminlte.supplier.cost.view')
->where('cso','[0-9]+');
Route::view('supplier/details/{spo}','theme.backend.adminlte.supplier.details')
->where('spo','[0-9]+');
->where('spo','[0-9]+')
->name('supplier.details');
Route::post('supplier/details/{o?}',[SupplierController::class,'addedit'])
->where('o','[0-9]+');
Route::view('supplier/product/new','theme.backend.adminlte.supplier.product.addedit');
Route::post('supplier/product/new',[SupplierController::class,'product_new']);
Route::view('supplier/product/{spo}/{id}/{type}','theme.backend.adminlte.supplier.product.addedit')
->where('spo','[0-9]+')
->where('id','[0-9]+')
->whereIn('type',Supplier::offeringTypeKeys()->toArray());
->whereIn('type',Supplier::offeringTypeKeys()->toArray())
->name('supplier.product.type');
Route::post('supplier/product/{o}/{oo}/{type}',[SupplierController::class,'product_addedit'])
->where('o','[0-9]+')
->where('oo','[0-9]+')