Compare commits

..

10 Commits

17 changed files with 274 additions and 31 deletions

View File

@ -1,5 +1,10 @@
# Changelog
**2025-04-10**
**2025-04-13**
- Fixed #132 - Inches / cm toggle for order page (and show unit on pdf)
- Fixed #148 - Change amount label to unit price on order form (also textarea)
- Fixed #147 - Resource lock sidebar settings
- Fixed #134 - Fix Dashboard 'last 30 days'
- Added #129 - Order product count sum
- Fixed #145 - Default order sort is incorrect (newest will always show on top now)
- Fixed #142 - Packing Slip content field should be required

View File

@ -14,6 +14,7 @@
use App\Models\ServiceFile;
use App\Models\ServiceType;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Field;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
@ -40,6 +41,22 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
function recalculateRowTotal(callable $set, callable $get): void
{
$amounts = collect([
$get('xs'),
$get('s'),
$get('m'),
$get('l'),
$get('xl'),
$get('2xl'),
$get('3xl'),
$get('osfa'),
])->map(fn ($val) => floatval($val ?? 0));
$set('total', $amounts->sum());
}
class OrderResource extends Resource
{
protected static ?string $model = Order::class;
@ -52,6 +69,8 @@ public static function form(Form $form): Form
{
return $form->schema([
Group::make()
->columns(6)
->columnSpan(2)
->schema([
Section::make([
Grid::make(1)
@ -140,9 +159,7 @@ public static function form(Form $form): Form
->columnSpan(1)
->hidden(fn (?Order $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
])
->columns(6)
->columnSpan(2),
]),
TableRepeater::make('order_products')
->label('Garments')
@ -161,29 +178,40 @@ public static function form(Form $form): Form
TextInput::make('xs')
->placeholder('xs')
->rules('numeric'),
TextInput::make('s')
->placeholder('s')
->rules('numeric'),
TextInput::make('m')
->placeholder('m')
->rules('numeric'),
TextInput::make('l')
->placeholder('l')
->rules('numeric'),
TextInput::make('xl')
->placeholder('xl')
->rules('numeric'),
TextInput::make('2xl')
->placeholder('2xl')
->rules('numeric'),
TextInput::make('3xl')
->placeholder('3xl')
->rules('numeric'),
TextInput::make('osfa')
->placeholder('osfa')
->rules('numeric'),
])
->label('Sizes'),
])->label('Sizes'),
Field::make('total_display')
->dehydrated(false)
->label('Total')
->view('forms.components.row-total'),
]),
Repeater::make('services')
@ -207,6 +235,7 @@ public static function form(Form $form): Form
->placeholder('Full name here (example: \'Embroidery\'')
->required(),
])
->createOptionAction(fn ($action) => $action->modalWidth('sm'))
->createOptionUsing(function (array $data): int {
return ServiceType::create($data)->getKey();
}),
@ -230,34 +259,44 @@ public static function form(Form $form): Form
->prefix('h')
->rules('numeric'),
])
->label('Dimensions (inches)')
->label('Dimensions')
->columnSpan(4),
TextInput::make('amount')
->label('Quantity')
->live()
->prefix('#')
->columnSpan(2)
->rules('numeric'),
TextInput::make('amount_price')
->label('Amount')
->label('Unit price')
->prefix('$')
->columnSpan(2)
->rules('numeric'),
]),
Grid::make(9)
Grid::make(19)
->schema([
TextInput::make('serviceFileCode')
->label('Code')
->datalist(ServiceFile::all()->unique('code')->pluck('code')->toArray())
->columnSpan(1)
->columnSpan(2)
->placeholder('A1234'),
Select::make('serviceFileSizeUnit')
->columnSpan(2)
->selectablePlaceholder(false)
->label('Unit')
->options([
'in' => 'Inches',
'cm' => 'cm',
]),
Textarea::make('notes')
->placeholder('Thread colors...')
->columnSpan(8),
->placeholder('Thread colors... (press enter for new line)')
->autosize()
->rows(1)
->columnSpan(15),
]),
])
->reorderable()

View File

@ -63,6 +63,7 @@ protected function handleRecordCreation(array $data): Order
'width' => $service['serviceFileWidth'] ?? null,
'height' => $service['serviceFileHeight'] ?? null,
'setup_number' => $service['serviceFileSetupNumber'] ?? null,
'size_unit' => $service['serviceFileSizeUnit'] ?? 'inch',
]);
ProductService::create([

View File

@ -68,6 +68,7 @@ protected function mutateFormDataBeforeFill(array $data): array
'serviceFileHeight' => $service->serviceFile->height ?? '',
'serviceFileCode' => $service->serviceFile->code ?? '',
'serviceFileSetupNumber' => $service->serviceFile->setup_number ?? '',
'serviceFileSizeUnit' => $service->serviceFile->size_unit ?? 'inch',
];
}
@ -139,6 +140,7 @@ public function handleRecordUpdate(Model $record, array $data): Model
'width' => $service['serviceFileWidth'] ?? null,
'height' => $service['serviceFileHeight'] ?? null,
'setup_number' => $service['serviceFileSetupNumber'] ?? null,
'size_unit' => $service['serviceFileSizeUnit'] ?? 'inch',
]);
ProductService::create([

View File

@ -10,7 +10,6 @@
use Filament\Actions;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\ListRecords;
use Filament\Support\Enums\MaxWidth;
class ListOrders extends ListRecords
{

View File

@ -49,7 +49,7 @@ private function getOrdersPast30Days(): string
->where('order_status', '!=', OrderStatus::READY_FOR_INVOICE)
->where('order_status', '!=', OrderStatus::SHIPPED)
->where('order_status', '!=', OrderStatus::INVOICED)
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
->whereBetween('created_at', [now()->subDays(30), now()])
->count();
}

View File

@ -11,7 +11,7 @@ class InvoiceController extends Controller
public function pdf(int $id)
{
$invoice = Invoice::find($id);
$url = strtolower('invoice-'.$invoice->internal_id.'.pdf');
$url = strtolower($invoice->internal_id.'_'.$invoice->customer->company_name.'.pdf');
Pdf::view('pdf.invoice', ['invoice' => $invoice])
->withBrowsershot(function (Browsershot $browsershot) {

View File

@ -19,6 +19,7 @@ class ServiceFile extends Model
'width',
'height',
'setup_number',
'size_unit',
];
/**

View File

@ -25,7 +25,7 @@
| or any other location as required by the application or its packages.
*/
'version' => '20250410',
'version' => '20250413',
/*
|--------------------------------------------------------------------------

130
config/resource-lock.php Normal file
View File

@ -0,0 +1,130 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Models
|--------------------------------------------------------------------------
|
| The models configuration specifies the classes that represent your application's
| data objects. This configuration is used by the framework to interact with
| the application's data models. You can even implement your own ResourceLock model.
|
*/
'models' => [
'User' => \App\Models\User::class,
// 'ResourceLock' => null,
],
/*
|--------------------------------------------------------------------------
| Filament Resource
|--------------------------------------------------------------------------
|
| The resource lock filament resource displays all the current locks in place.
| You are able to replace the resource Lock with your own resource class.
|
*/
'resource' => [
'class' => \Kenepa\ResourceLock\Resources\LockResource::class,
],
/*
|--------------------------------------------------------------------------
| Resource Unlocker Button
|--------------------------------------------------------------------------
|
| The unlocker configuration specifies whether limited access is enabled for
| the resource unlock button. If limited access is enabled, only specific
| users or roles will be able to unlock locked resources directly from
| the modal.
|
*/
'unlocker' => [
'limited_access' => false,
// 'gate' => ''
],
/*
|--------------------------------------------------------------------------
| Lock Notice
|--------------------------------------------------------------------------
|
| The lock notice contains several configuration options for the modal
| that is display when a resource is locked.
|
*/
'lock_notice' => [
'display_resource_lock_owner' => false,
],
/*
|--------------------------------------------------------------------------
| Resource Lock Manager
|--------------------------------------------------------------------------
|
| The resource lock manager provides a simple way to view all resource locks
| of your application. It provides several ways to quickly unlock all or
| specific resources within your app.
|
*/
'manager' => [
'navigation_badge' => false,
'navigation_icon' => 'heroicon-o-lock-closed',
'navigation_label' => 'Resource Locks',
'plural_label' => 'Resource Locks',
'navigation_group' => 'Settings',
'navigation_sort' => 200,
'limited_access' => false,
'should_register_navigation' => true,
// 'gate' => ''
],
/*
|--------------------------------------------------------------------------
| Lock timeout (in minutes)
|--------------------------------------------------------------------------
|
| The lock_timeout configuration specifies the time interval, in minutes,
| after which a lock on a resource will expire if it has not been manually
| unlocked or released by the user.
|
*/
'lock_timeout' => 10,
/*
|--------------------------------------------------------------------------
| Check Locks before saving
|--------------------------------------------------------------------------
|
| The check_locks_before_saving configuration specifies whether a lock of a resource will be checked
| before saving a resource if a tech-savvy user is able to bypass the locked
| resource modal and attempt to save the resource. In some cases you may want to turns this off.
| It's recommended to keep this on.
|
*/
'check_locks_before_saving' => true,
/*
|--------------------------------------------------------------------------
| Actions
|--------------------------------------------------------------------------
|
| Action classes are simple classes that execute some logic within the package.
| If you want to add your own custom logic you are able to extend your own
| class with class your overwriting.
| Learn more about action classes: https://freek.dev/2442-strategies-for-making-laravel-packages-customizable
|
*/
'actions' => [
'get_resource_lock_owner_action' => \Kenepa\ResourceLock\Actions\GetResourceLockOwnerAction::class,
],
];

View File

@ -19,7 +19,6 @@ public function definition(): array
'name' => $this->faker->word(),
'width' => round($this->faker->randomFloat(2, 0, 10), 1),
'height' => round($this->faker->randomFloat(2, 0, 10), 1),
'unit' => 'inch',
];
}
}

View File

@ -15,7 +15,6 @@ public function up(): void
$table->foreignId('service_file_id')->nullable();
$table->foreignId('service_type_id')->nullable();
// $table->string('service_type')->nullable();
$table->string('placement')->nullable();
$table->string('amount')->nullable();
$table->string('amount_price')->nullable();

View File

@ -16,7 +16,6 @@ public function up(): void
$table->decimal('height')->nullable();
$table->string('unit')->default('inch');
$table->integer('setup_number')->nullable();
// $table->string('notes')->nullable();
$table->softDeletes();
$table->timestamps();
});

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('service_files', function (Blueprint $table) {
$table->string('size_unit')->default('in');
});
}
public function down(): void
{
Schema::table('service_files', function (Blueprint $table) {
$table->dropColumn('size_unit');
});
}
};

View File

@ -217,7 +217,7 @@ class="absolute inset-0 rotate-180 transition"
<div
x-show="! isCollapsed"
class="fi-fo-repeater-item-content px-4 py-2"
class="fi-fo-repeater-item-content px-4 py-3"
>
{{ $item }}
</div>

View File

@ -0,0 +1,43 @@
@php
$statePath = $getStatePath(); // e.g., data.order_products.UUID.total_display
$rowPath = \Illuminate\Support\Str::beforeLast($statePath, '.total_display');
$xsPath = "{$rowPath}.xs";
$sPath = "{$rowPath}.s";
$mPath = "{$rowPath}.m";
$lPath = "{$rowPath}.l";
$xlPath = "{$rowPath}.xl";
$xxlPath = "{$rowPath}.2xl";
$xxxlPath = "{$rowPath}.3xl";
$osfaPath = "{$rowPath}.osfa";
@endphp
<div
x-data="{
xs: @entangle($xsPath),
s: @entangle($sPath),
m: @entangle($mPath),
l: @entangle($lPath),
xl: @entangle($xlPath),
_2xl: @entangle($xxlPath),
_3xl: @entangle($xxxlPath),
osfa: @entangle($osfaPath),
}"
>
<!-- Filament TextInput to show the computed total -->
<input
type="text"
x-bind:value="(
(parseFloat(xs || 0)) +
(parseFloat(s || 0)) +
(parseFloat(m || 0)) +
(parseFloat(l || 0)) +
(parseFloat(xl || 0)) +
(parseFloat(_2xl || 0)) +
(parseFloat(_3xl || 0)) +
(parseFloat(osfa || 0))
).toFixed(0)"
class="fi-input block w-full border-none py-1.5 text-base text-gray-950 transition duration-75 placeholder:text-gray-400 focus:ring-0 disabled:text-gray-500 disabled:[-webkit-text-fill-color:theme(colors.gray.500)] disabled:placeholder:[-webkit-text-fill-color:theme(colors.gray.400)] dark:text-white dark:placeholder:text-gray-500 dark:disabled:text-gray-400 dark:disabled:[-webkit-text-fill-color:theme(colors.gray.400)] dark:disabled:placeholder:[-webkit-text-fill-color:theme(colors.gray.500)] sm:text-sm sm:leading-6 bg-white/0 ps-3 pe-3"
readonly
/>
</div>

View File

@ -155,9 +155,9 @@
<tbody>
@foreach($order->orderProducts as $product)
<tr>
<td><code>{{$product->sku}}</code></td>
<td class="text-uppercase">{{$product->product_name}}</td>
<td class="text-uppercase">{{$product->color}}</td>
<td><code style="font-size: 0.9rem;">{{$product->sku}}</code></td>
<td class="text-uppercase">{{$product->product_name ?? ''}}</td>
<td class="text-uppercase">{{$product->color ?? ''}}</td>
<td style="width: 40px;">{{$product->productSizes()->get()->where('size', 'xs')->first()->amount ?? ''}}</td>
<td style="width: 40px;">{{$product->productSizes()->get()->where('size', 's')->first()->amount ?? ''}}</td>
<td style="width: 40px;">{{$product->productSizes()->get()->where('size', 'm')->first()->amount ?? ''}}</td>
@ -176,7 +176,7 @@
</div>
<div class="row mt-3 text-end" style="font-size: 12px">
<div class="col">
TOTAL QUANTITY: {{$order->totalProductQuantity}}
TOTAL QUANTITY: {{$order->totalProductQuantity ?? ''}}
</div>
</div>
@ -190,6 +190,7 @@
<th class="w-50">Logo Name & Instructions</th>
<th>Width</th>
<th>Height</th>
<th>Unit</th>
<th>QTY</th>
</thead>
@ -198,32 +199,35 @@
<tr>
<td>
<div class="text-uppercase fw-bold">
{{$service->placement}}
{{$service->placement ?? ''}}
</div>
<br>
<div class="text-uppercase">
<code style="font-size: 14px">
{{$service->serviceFile->code}}
{{$service->serviceFile->code ?? ''}}
</code>
</div>
</td>
<td>
<div class="text-uppercase">
{{$service->serviceFile->name}}
{{$service->serviceFile->name ?? ''}}
</div>
<br>
<div class="text-uppercase" style="color: #d63384;">
{{$service->notes}}
{{$service->notes ?? ''}}
</div>
</td>
<td>
{{$service->serviceFile->width}}
</td>
<td>
{{$service->serviceFile->height}}
{{$service->serviceFile->height ?? ''}}
</td>
<td>
{{$service->amount}}
{{$service->serviceFile->size_unit ?? ''}}
</td>
<td>
{{$service->amount ?? ''}}
</td>
</tr>