Merge pull request 'development' (#115) from development into main
Some checks failed
Deploy / deploy (push) Failing after 15s

Reviewed-on: #115
This commit is contained in:
Nisse Lommerde 2025-03-08 19:13:36 +01:00
commit 44ec2068c3
71 changed files with 1068 additions and 1129 deletions

25
.dockerignore Normal file
View File

@ -0,0 +1,25 @@
# .dockerignore
/deploy/docker-compose.yml
/deploy/Dockerfile
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/public/bucket
/storage/*.key
/vendor
.env
.env.example
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode
.git

View File

@ -54,7 +54,7 @@ public static function table(Table $table): Table
TextColumn::make('balance') TextColumn::make('balance')
->getStateUsing(fn (Customer $customer) => $customer->calculateBalance()) ->getStateUsing(fn (Customer $customer) => $customer->calculateBalance())
->money() ->money()
->hidden(! auth()->user()->is_admin), ->hidden(! auth()->user()->is_admin ?? false),
]) ])
->filters([ ->filters([
// //

View File

@ -15,7 +15,9 @@ protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make() Actions\CreateAction::make()
->icon(IconEnum::NEW->value), ->modal()
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => CustomerResource::getUrl('edit', ['record' => $record->id])),
]; ];
} }
} }

View File

@ -3,9 +3,11 @@
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum; use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource\RelationManagers\InvoicesRelationManager; use App\Filament\Admin\Resources\InvoiceReportResource\RelationManagers\InvoicesRelationManager;
use App\Models\InvoiceReport; use App\Models\InvoiceReport;
use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
@ -32,28 +34,34 @@ public static function form(Form $form): Form
return $form return $form
->schema([ ->schema([
Section::make([ Section::make([
Group::make([
Select::make('customer_id') Select::make('customer_id')
->relationship('customer', 'company_name') ->relationship('customer', 'company_name')
->preload() ->preload()
->required() ->required()
->columnSpanFull()
->searchable(), ->searchable(),
ToggleButtons::make('filter_paid')
->boolean() ToggleButtons::make('payment_types')
->required() ->required()
->default(false) ->options(InvoiceStatus::class)
->colors([ ->multiple()
'true' => 'info', ->columnSpanFull()
'false' => 'info',
])
->inline(), ->inline(),
DatePicker::make('date_start') DatePicker::make('date_start')
->required(), ->required()
->columnSpan(1),
DatePicker::make('date_end') DatePicker::make('date_end')
->required() ->required()
->default(today()), ->default(today())
->columnSpan(1),
])->columnSpan(fn (?InvoiceReport $record) => $record === null ? 5 : 3)
->columns(2),
]) ])
->columns(2) ->columns(5)
->columnSpan(2), ->columnSpan(fn ($record) => $record === null ? 3 : 2),
Section::make([ Section::make([
Placeholder::make('created_at') Placeholder::make('created_at')
@ -115,7 +123,7 @@ public static function getPages(): array
{ {
return [ return [
'index' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ListInvoiceReports::route('/'), 'index' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ListInvoiceReports::route('/'),
'create' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\CreateInvoiceReport::route('/create'), // 'create' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\CreateInvoiceReport::route('/create'),
'view' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ViewInvoiceReport::route('/{record}'), 'view' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ViewInvoiceReport::route('/{record}'),
]; ];
} }

View File

@ -2,10 +2,6 @@
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages; namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Filament\Admin\Resources\InvoiceReportResource;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
class CreateInvoiceReport extends CreateRecord class CreateInvoiceReport extends CreateRecord {}
{
protected static string $resource = InvoiceReportResource::class;
}

View File

@ -3,6 +3,7 @@
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages; namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Enums\IconEnum; use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource; use App\Filament\Admin\Resources\InvoiceReportResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
@ -17,7 +18,31 @@ protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make() Actions\CreateAction::make()
->icon(IconEnum::NEW->value), ->modalWidth('xl')
->icon(IconEnum::NEW->value)
->mutateFormDataUsing(function ($data) {
/* Initialize all payment statues to false,
map selected payment types to corresponding status,
assign filtered statuses to specific keys */
$paymentTypes = array_fill_keys(array_map(fn ($status) => $status->name, InvoiceStatus::cases()), false);
if (! empty($data['payment_types'])) {
foreach ($data['payment_types'] as $type) {
$statusName = InvoiceStatus::from($type)->name;
$paymentTypes[$statusName] = true;
}
}
foreach ($paymentTypes as $status => $value) {
$data['with_'.strtolower($status)] = $value;
}
unset($data['payment_types']);
return $data;
})
->successRedirectUrl(fn ($record) => InvoiceReportResource::getUrl('view', ['record' => $record->id])),
]; ];
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages; namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource; use App\Filament\Admin\Resources\InvoiceReportResource;
use App\Models\InvoiceReport; use App\Models\InvoiceReport;
use Filament\Actions\Action; use Filament\Actions\Action;
@ -19,6 +20,19 @@ public function getTitle(): string|Htmlable
return parent::getTitle().' '.$this->record->internal_id; return parent::getTitle().' '.$this->record->internal_id;
} }
public function mutateFormDataBeforeFill(array $data): array
{
foreach (InvoiceStatus::cases() as $case) {
$name = 'with_'.strtolower($case->name);
if ($data[$name]) {
$data['payment_types'][] = $case->value ?? null;
}
}
return $data;
}
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [

View File

@ -18,6 +18,7 @@
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Split; use Filament\Forms\Components\Split;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
@ -47,6 +48,8 @@ public static function form(Form $form): Form
Group::make() Group::make()
->schema([ ->schema([
Section::make([ Section::make([
Group::make([
Select::make('customer_id') Select::make('customer_id')
->required() ->required()
->label('Customer') ->label('Customer')
@ -61,51 +64,37 @@ public static function form(Form $form): Form
->required() ->required()
->default(today()), ->default(today()),
DatePicker::make('due_date'), DatePicker::make('due_date'),
]) ])->columnSpan(2),
->columnSpan(2),
Grid::make(3)
->schema([
ToggleButtons::make('has_gst')
->label('GST')
->boolean('On', 'Off')
->default(true)
// ->inline()
->colors([
'true' => 'info',
'false' => 'info',
]),
ToggleButtons::make('has_pst')
->label('PST')
->boolean('On', 'Off')
->default(false)
// ->inline()
->colors([
'true' => 'info',
'false' => 'info',
]),
ToggleButtons::make('has_hst')
->label('HST')
->boolean('On', 'Off')
->default(false)
->colors([
'true' => 'info',
'false' => 'info',
]),
])->columnSpan(1),
ToggleButtons::make('status') ToggleButtons::make('status')
->options(InvoiceStatus::class) ->options(InvoiceStatus::class)
->required() ->required()
->inline() ->inline()
->default(InvoiceStatus::UNPAID) ->default(InvoiceStatus::UNPAID)
->columnSpan(1), ->columnSpan(2),
Grid::make(3)
->schema([
Toggle::make('has_gst')
->label('GST')
->inline(false)
->default(true),
Toggle::make('has_pst')
->label('PST')
->inline(false)
->default(false),
Toggle::make('has_hst')
->label('HST')
->inline(false)
->default(false),
]),
])->columnSpan(fn (?Invoice $record) => $record === null ? 2 : 1),
]) ])
->columns(2) ->columns(2)
->columnSpan(2), ->columnSpan(fn (?Invoice $record) => $record === null ? 3 : 2),
Section::make() Section::make()
->schema([ ->schema([
@ -334,7 +323,7 @@ public static function getPages(): array
{ {
return [ return [
'index' => \App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices::route('/'), 'index' => \App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices::route('/'),
'create' => \App\Filament\Admin\Resources\InvoiceResource\Pages\CreateInvoice::route('/create'), // 'create' => \App\Filament\Admin\Resources\InvoiceResource\Pages\CreateInvoice::route('/create'),
'edit' => \App\Filament\Admin\Resources\InvoiceResource\Pages\EditInvoice::route('/{record}/edit'), 'edit' => \App\Filament\Admin\Resources\InvoiceResource\Pages\EditInvoice::route('/{record}/edit'),
]; ];
} }

View File

@ -42,7 +42,10 @@ protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make() Actions\CreateAction::make()
->icon(IconEnum::NEW->value), ->modal()
->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id])),
]; ];
} }
} }

View File

@ -459,12 +459,6 @@ public static function table(Table $table): Table
]); ]);
} }
public static function getRelations(): array
{
return [
];
}
public static function getPages(): array public static function getPages(): array
{ {
return [ return [

View File

@ -74,7 +74,7 @@ public function getTabs(): array
'ready_for_invoice' => Tab::make() 'ready_for_invoice' => Tab::make()
->query(fn ($query) => $query->where('status', OrderStatus::READY_FOR_INVOICE)) ->query(fn ($query) => $query->where('status', OrderStatus::READY_FOR_INVOICE))
->icon(OrderStatus::READY_FOR_INVOICE->getIcon()) ->icon(OrderStatus::READY_FOR_INVOICE->getIcon())
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->where('status', OrderStatus::READY_FOR_INVOICE))) ->badge(fn () => Order::query()->where('status', OrderStatus::READY_FOR_INVOICE)->count())
->badgeColor(OrderStatus::READY_FOR_INVOICE->getColor()), ->badgeColor(OrderStatus::READY_FOR_INVOICE->getColor()),
]; ];
} }

View File

@ -8,6 +8,8 @@
use App\Filament\Admin\Resources\PaymentResource\RelationManagers\InvoicesRelationManager; use App\Filament\Admin\Resources\PaymentResource\RelationManagers\InvoicesRelationManager;
use App\Models\Payment; use App\Models\Payment;
use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
@ -32,9 +34,7 @@ public static function form(Form $form): Form
return $form return $form
->schema([ ->schema([
Section::make([ Section::make([
DatePicker::make('date') Group::make([
->default(today())
->columnSpan(2),
Select::make('customer_id') Select::make('customer_id')
->relationship('customer', 'company_name') ->relationship('customer', 'company_name')
@ -42,7 +42,7 @@ public static function form(Form $form): Form
->searchable() ->searchable()
->hidden(fn ($livewire) => $livewire::class === ListInvoices::class) ->hidden(fn ($livewire) => $livewire::class === ListInvoices::class)
->preload() ->preload()
->columnSpan(2), ->columnSpanFull(),
TextInput::make('amount') TextInput::make('amount')
->required() ->required()
@ -50,14 +50,22 @@ public static function form(Form $form): Form
->rules('numeric') ->rules('numeric')
->minValue(0) ->minValue(0)
->maxValue(99999999) ->maxValue(99999999)
->columnSpan(1),
TextInput::make('check_number')
->columnSpan(3), ->columnSpan(3),
Textarea::make('notes') TextInput::make('check_number')
->columnSpan(6),
DatePicker::make('date')
->default(today())
->columnSpan(4), ->columnSpan(4),
])->columns(4),
Placeholder::make('break_2')->columnSpan(3)->hiddenLabel(),
Textarea::make('notes')
->columnSpanFull(),
])->columnSpan(fn (?Payment $record) => $record === null ? 9 : 3)
->columns(9),
])->columns(9),
]); ]);
} }

View File

@ -29,7 +29,9 @@ protected function getHeaderActions(): array
}),*/ }),*/
Actions\CreateAction::make() Actions\CreateAction::make()
->icon(IconEnum::NEW->value), ->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => PaymentResource::getUrl('edit', ['record' => $record->id])),
]; ];
} }
} }

View File

@ -7,6 +7,7 @@
use App\Models\Quote; use App\Models\Quote;
use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid; use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
@ -36,6 +37,8 @@ public static function form(Form $form): Form
Grid::make(3) Grid::make(3)
->schema([ ->schema([
Section::make([ Section::make([
Group::make([
Select::make('customer_id') Select::make('customer_id')
->required() ->required()
->label('Customer') ->label('Customer')
@ -51,6 +54,7 @@ public static function form(Form $form): Form
TextArea::make('notes') TextArea::make('notes')
->rows(3) ->rows(3)
->columnSpan(2), ->columnSpan(2),
]),
]) ])
->columns(2) ->columns(2)
->columnSpan(fn (?Quote $record) => $record === null ? 3 : 2) ->columnSpan(fn (?Quote $record) => $record === null ? 3 : 2)

View File

@ -5,7 +5,6 @@
use App\Enums\IconEnum; use App\Enums\IconEnum;
use App\Filament\Admin\Resources\TaxRateResource\Pages; use App\Filament\Admin\Resources\TaxRateResource\Pages;
use App\Models\TaxRate; use App\Models\TaxRate;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Forms\Form;
@ -36,24 +35,8 @@ public static function form(Form $form): Form
->label('Value in percentage') ->label('Value in percentage')
->numeric() ->numeric()
->prefix('%'), ->prefix('%'),
]) ]),
->columns(1) ]);
->columnSpan(2),
Section::make()
->schema([
Placeholder::make('created_at')
->label('Created')
->content(fn (TaxRate $record): ?string => $record->created_at?->diffForHumans()),
Placeholder::make('updated_at')
->label('Last modified')
->content(fn (TaxRate $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(1)
->hidden(fn (?TaxRate $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
])->columns(3);
} }
public static function table(Table $table): Table public static function table(Table $table): Table
@ -70,7 +53,8 @@ public static function table(Table $table): Table
// //
]) ])
->actions([ ->actions([
Tables\Actions\EditAction::make(), Tables\Actions\EditAction::make()
->modalWidth('xs'),
]) ])
->bulkActions([ ->bulkActions([
Tables\Actions\BulkActionGroup::make([ Tables\Actions\BulkActionGroup::make([

View File

@ -15,7 +15,9 @@ protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make() Actions\CreateAction::make()
->icon(IconEnum::NEW->value), ->modalWidth('xs')
->icon(IconEnum::NEW->value)
->createAnother(false),
]; ];
} }
} }

View File

@ -4,7 +4,6 @@
use App\Enums\IconEnum; use App\Enums\IconEnum;
use App\Models\User; use App\Models\User;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
@ -36,10 +35,8 @@ public static function form(Form $form): Form
->autocomplete('new-username') ->autocomplete('new-username')
->unique() ->unique()
->required() ->required()
->columnSpan(1), ->columnSpanFull(),
Grid::make(2)
->schema([
TextInput::make('password') TextInput::make('password')
->password() ->password()
->autocomplete('new-password') ->autocomplete('new-password')
@ -54,14 +51,13 @@ public static function form(Form $form): Form
->same('password') ->same('password')
->dehydrated(false) ->dehydrated(false)
->required(fn (string $operation) => $operation === 'create'), ->required(fn (string $operation) => $operation === 'create'),
])
->columnSpan(2),
]), ]),
Section::make('Permissions') Section::make('Permissions')
->description('Administrators can access financial information and change settings.') ->description('Administrators can access invoices and settings')
->schema([ ->schema([
Toggle::make('is_admin') Toggle::make('is_admin')
->columnSpanFull()
->label('User is an administrator') ->label('User is an administrator')
->reactive() ->reactive()
->afterStateUpdated(fn ($state, callable $set) => $set('customer_id', null)) ->afterStateUpdated(fn ($state, callable $set) => $set('customer_id', null))
@ -70,7 +66,7 @@ public static function form(Form $form): Form
->columns(2), ->columns(2),
Section::make('Customer Login') Section::make('Customer Login')
->description('If this account is for a customer, select them here.') ->description('If this account is for a customer, select them here')
->schema([ ->schema([
Select::make('customer_id') Select::make('customer_id')
@ -102,7 +98,9 @@ public static function table(Table $table): Table
// //
]) ])
->actions([ ->actions([
Tables\Actions\EditAction::make()->modal(), Tables\Actions\EditAction::make()
->modalWidth('md')
->modal(),
]) ])
->defaultSort('customer_id', 'asc'); ->defaultSort('customer_id', 'asc');
} }

View File

@ -15,6 +15,7 @@ protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make() Actions\CreateAction::make()
->modalWidth('md')
->icon(IconEnum::NEW->value) ->icon(IconEnum::NEW->value)
->modal(), ->modal(),
]; ];

View File

@ -1,43 +0,0 @@
<?php
namespace App\Livewire;
use App\Enums\OrderStatus;
use App\Enums\OrderType;
use App\Models\Customer;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Carbon;
use Livewire\Component;
class CreateOrder extends Component
{
public Collection $customers;
public string $selectedCustomer;
public $contacts;
public function mount(Collection $customers): void
{
$this->customers = $customers;
$this->contacts = $customers->first()->contacts;
}
public function getContacts(): void
{
$this->contacts = Customer::find($this->selectedCustomer)->contacts;
}
public function render(): View
{
return view('livewire.create-order', [
'contacts' => $this->contacts,
'order_types' => OrderType::cases(),
'order_status' => OrderStatus::cases(),
'customers' => $this->customers,
'today' => Carbon::today()->format('Y-m-d'),
'due_default' => Carbon::today()->addDay(10)->format('Y-m-d'),
]);
}
}

View File

@ -1,41 +0,0 @@
<?php
namespace App\Livewire;
use App\Models\Customer;
use Illuminate\Support\Collection;
use Livewire\Component;
class CustomerAndContactSelect extends Component
{
public Collection $customers;
public Collection $contacts;
public string $selectedCustomer;
public function mount(Collection $customers)
{
$this->customers = $customers;
if (isset($this->selectedCustomer)) {
$this->contacts = Customer::find($this->selectedCustomer)->contacts;
} else {
$this->contacts = $customers->first()->contacts;
}
}
public function updateContactList()
{
$this->contacts = Customer::find($this->selectedCustomer)->contacts;
}
public function render()
{
return view('livewire.customer-and-contact-select', [
'customers' => $this->customers,
'contacts' => $this->contacts,
]);
}
}

View File

@ -1,172 +0,0 @@
<?php
namespace App\Livewire;
use Exception;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Application;
use Illuminate\Support\Collection;
use Illuminate\View\View;
use Livewire\Component;
class OrderProductsCreate extends Component
{
/** @var Collection<string, int> */
public Collection $productInputs;
/** @var Collection<string, int> */
public Collection $serviceInputs;
/** @var array<int> */
public array $sizes = [];
/** @var array<int> */
public array $totals = [];
/** @var array<int> */
public array $units = [];
/**
* @var array<int>
*/
public array $prices = [];
/**
* @var array<int>
*/
public array $priceTotals = [];
public int $totalQuantity = 0;
public string $totalPrice = '$0.00';
public function updated(): void
{
try {
foreach ($this->sizes as $index => $size) {
$this->totals[$index] = array_sum($size);
}
} catch (Exception $e) {
}
try {
foreach ($this->units as $index => $unit) {
$this->priceTotals[$index] = $unit * $this->prices[$index];
}
} catch (Exception $e) {
}
$this->totalQuantity = array_sum($this->totals);
$this->totalPrice = '$'.number_format(round(array_sum($this->priceTotals), 2), 2);
}
public function addProductInput(): void
{
$index = $this->productInputs->count();
$this->productInputs->push([
$index => [
'sku' => '',
'product_name' => '',
'product_color' => '',
'size_xs' => '',
'size_s' => '',
'size_m' => '',
'size_l' => '',
'size_xl' => '',
'size_2xl' => '',
'size_3xl' => '',
'size_osfa' => '',
'product_total' => '',
],
]);
}
public function determineAddProductRow(int $index): void
{
if ($index == $this->productInputs->count() - 1) {
$this->addProductInput();
}
}
public function determineAddServiceProductRow(int $index): void
{
if ($index == $this->serviceInputs->count() - 1) {
$this->addServiceInput();
}
}
public function removeProductInput(int $key): void
{
if ($this->productInputs->count() > 1) {
$this->productInputs->pull($key);
}
}
public function addServiceInput(): void
{
$this->serviceInputs->push([
$this->serviceInputs->count() => [
'service_name' => '',
'product_name' => '',
'product_color' => '',
'logo_name' => '',
'setup_number' => '',
'service_width' => '',
'service_height' => '',
'service_setup_unit' => '',
'service_setup_price' => '',
'service_total' => '',
],
]);
}
public function removeServiceInput(int $key): void
{
if ($this->serviceInputs->count() > 1) {
$this->serviceInputs->pull($key);
}
}
public function mount(): void
{
$this->fill([
'productInputs' => collect([
[
'sku' => '',
'product_name' => '',
'product_color' => '',
'size_xs' => '',
'size_s' => '',
'size_m' => '',
'size_l' => '',
'size_xl' => '',
'size_2xl' => '',
'size_3xl' => '',
'size_osfa' => '',
'product_total' => '0',
],
]),
'serviceInputs' => collect([
[
'sku' => '',
'product_name' => '',
'product_color' => '',
'logo_name' => '',
'setup_number' => '',
'service_width' => '',
'service_height' => '',
'service_setup_unit' => '',
'service_setup_price' => '',
'service_total' => '',
],
]),
]);
}
public function render(): \Illuminate\Contracts\View\View|Factory|Application|View
{
return view('livewire.order-products-create');
}
}

View File

@ -1,64 +0,0 @@
<?php
namespace App\Livewire;
use App\Models\Order;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Application;
use Illuminate\Support\Carbon;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\WithPagination;
class OrdersTable extends Component
{
use WithPagination;
protected string $paginationTheme = 'bootstrap';
public bool $showCustomerColumn;
public string $orderType = 'active';
public string $search = '';
public string $title = '';
public string $customer_id = '';
public Carbon $today;
public function mount(bool $showCustomerColumn, string $orderType, string $title, ?string $customer_id = null): void
{
$this->today = Carbon::today();
$this->showCustomerColumn = $showCustomerColumn;
$this->orderType = $orderType;
$this->title = $title;
$this->customer_id = $customer_id ?? '';
}
public function render(): \Illuminate\Contracts\View\View|Factory|Application|View
{
return view('livewire.orders-table', [
'orders' => Order::with('customer')
->when($this->customer_id != null, fn ($q) => $q->where('customer_id', $this->customer_id))
->when($this->orderType === 'active', fn ($q) => $q->active())
->when($this->orderType === 'invoiced', fn ($q) => $q->invoiced())
->when($this->orderType === 'finished', fn ($q) => $q->finished())
->when($this->search !== '', function ($query) {
$query->whereHas('customer', function ($query) {
$query->where('company_name', 'like', '%'.$this->search.'%');
})->orWhere('customer_po', 'like', '%'.$this->search.'%')
->orWhere('internal_po', 'like', '%'.$this->search.'%')
->orWhere('order_date', 'like', '%'.$this->search.'%')
->orWhere('due_date', 'like', '%'.$this->search.'%')
->orWhere('status', 'like', '%'.$this->search.'%');
})
->orderByDesc('rush')
->orderBy('due_date')
->paginate(15)
->withQueryString(),
'today' => $this->today,
]);
}
}

View File

@ -56,8 +56,12 @@ class Invoice extends Model
'total' => 'float', 'total' => 'float',
]; ];
public function scopeSearchByBalance(Builder $query, float $amount): Builder public function scopeSearchByBalance(Builder $query, $amount): Builder
{ {
if (! is_numeric($amount)) {
return $query;
}
return $query->whereRaw('total - (SELECT IFNULL(SUM(applied_amount), 0) return $query->whereRaw('total - (SELECT IFNULL(SUM(applied_amount), 0)
FROM payments FROM payments
INNER JOIN invoice_payment INNER JOIN invoice_payment

View File

@ -17,8 +17,11 @@ class InvoiceReport extends Model
'customer_id', 'customer_id',
'date_start', 'date_start',
'date_end', 'date_end',
'filter_paid',
'subtotal', 'subtotal',
'with_unpaid',
'with_partially_paid',
'with_paid',
'with_void',
'pst', 'pst',
'gst', 'gst',
]; ];
@ -42,10 +45,10 @@ public static function boot(): void
$invoices = Invoice::whereBetween('date', [$model->date_start, $model->date_end]) $invoices = Invoice::whereBetween('date', [$model->date_start, $model->date_end])
->where('customer_id', $model->customer_id) ->where('customer_id', $model->customer_id)
->when($model->filter_paid, function ($query) { ->when(! $model->with_unpaid, fn ($query) => $query->whereNot('status', InvoiceStatus::UNPAID))
$query->where('status', InvoiceStatus::UNPAID) ->when(! $model->with_partially_paid, fn ($query) => $query->whereNot('status', InvoiceStatus::PARTIALLY_PAID))
->orWhere('status', InvoiceStatus::PARTIALLY_PAID); ->when(! $model->with_paid, fn ($query) => $query->whereNot('status', InvoiceStatus::PAID))
}); ->when(! $model->with_void, fn ($query) => $query->whereNot('status', InvoiceStatus::VOID));
$model->invoices()->sync($invoices->pluck('id')->toArray()); $model->invoices()->sync($invoices->pluck('id')->toArray());

View File

@ -14,11 +14,11 @@
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"livewire/livewire": "^3.5", "livewire/livewire": "^3.5",
"mallardduck/blade-lucide-icons": "^1.23", "mallardduck/blade-lucide-icons": "^1.23",
"spatie/laravel-pdf": "^1.5" "spatie/laravel-pdf": "^1.5",
"fakerphp/faker": "^1.23"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-ide-helper": "^3.5", "barryvdh/laravel-ide-helper": "^3.5",
"fakerphp/faker": "^1.23",
"larastan/larastan": "^2.0", "larastan/larastan": "^2.0",
"laravel/pint": "^1.17", "laravel/pint": "^1.17",
"laravel/sail": "^1.26", "laravel/sail": "^1.26",

1121
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,18 @@
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Version
|--------------------------------------------------------------------------
|
| This value is the version of your application. This value is used when
| the framework needs to place the application's version in a notification
| or any other location as required by the application or its packages.
*/
'version' => '1.0.0',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Environment | Application Environment

View File

@ -17,7 +17,10 @@ public function definition(): array
'customer_id' => Customer::all()->shuffle()->first()->id, 'customer_id' => Customer::all()->shuffle()->first()->id,
'date_start' => Carbon::now()->subYear(), 'date_start' => Carbon::now()->subYear(),
'date_end' => Carbon::now(), 'date_end' => Carbon::now(),
'filter_paid' => $this->faker->boolean(40), 'with_unpaid' => $this->faker->boolean(40),
'with_partially_paid' => $this->faker->boolean(40),
'with_paid' => $this->faker->boolean(40),
'with_void' => $this->faker->boolean(40),
'created_at' => Carbon::now(), 'created_at' => Carbon::now(),
'updated_at' => Carbon::now(), 'updated_at' => Carbon::now(),
]; ];

View File

@ -11,16 +11,16 @@ public function up(): void
Schema::create('invoice_reports', function (Blueprint $table) { Schema::create('invoice_reports', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('internal_id')->nullable(); $table->string('internal_id')->nullable();
$table->foreignId('customer_id')->constrained(); $table->foreignId('customer_id')->constrained();
$table->date('date_start'); $table->date('date_start');
$table->date('date_end'); $table->date('date_end');
$table->boolean('filter_paid'); $table->boolean('with_unpaid')->default(false);
$table->boolean('with_partially_paid')->default(false);
$table->boolean('with_paid')->default(false);
$table->boolean('with_void')->default(false);
$table->float('subtotal', 2)->default(0); $table->float('subtotal', 2)->default(0);
$table->float('pst', 2)->default(0); $table->float('pst', 2)->default(0);
$table->float('gst', 2)->default(0); $table->float('gst', 2)->default(0);
// $table->float('total', 2)->default(0);
$table->timestamps(); $table->timestamps();
}); });

91
deploy/Dockerfile Normal file
View File

@ -0,0 +1,91 @@
# deploy/Dockerfile
# stage 1: build stage
FROM php:8.3-fpm-alpine as build
# installing system dependencies and php extensions
RUN apk add --no-cache \
zip \
libzip-dev \
freetype \
libjpeg-turbo \
libpng \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
nodejs \
npm \
icu-dev \
&& docker-php-ext-configure intl \
&& docker-php-ext-install intl \
&& docker-php-ext-configure zip \
&& docker-php-ext-install zip pdo pdo_mysql \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-enable gd
# install composer
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# copy necessary files and change permissions
COPY . .
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 775 /var/www/html/storage \
&& chmod -R 775 /var/www/html/bootstrap/cache
# install php and node.js dependencies
RUN composer install --no-dev --prefer-dist \
&& npm install \
&& npm run build
RUN chown -R www-data:www-data /var/www/html/vendor \
&& chmod -R 775 /var/www/html/vendor
# stage 2: production stage
FROM php:8.3-fpm-alpine
# install nginx
RUN apk add --no-cache \
zip \
libzip-dev \
freetype \
libjpeg-turbo \
libpng \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
oniguruma-dev \
gettext-dev \
freetype-dev \
nginx \
icu-dev \
&& docker-php-ext-configure zip intl \
&& docker-php-ext-install zip pdo pdo_mysql intl \
&& docker-php-ext-enable intl \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-enable gd \
&& docker-php-ext-install bcmath \
&& docker-php-ext-enable bcmath \
&& docker-php-ext-install exif \
&& docker-php-ext-enable exif \
&& docker-php-ext-install gettext \
&& docker-php-ext-enable gettext \
&& docker-php-ext-install opcache \
&& docker-php-ext-enable opcache \
&& rm -rf /var/cache/apk/*
# copy files from the build stage
COPY --from=build /var/www/html /var/www/html
COPY ./deploy/nginx.conf /etc/nginx/http.d/default.conf
COPY ./deploy/php.ini "$PHP_INI_DIR/conf.d/app.ini"
WORKDIR /var/www/html
# add all folders where files are being stored that require persistence. if needed, otherwise remove this line.
VOLUME ["/var/www/html/storage/app"]
CMD ["sh", "-c", "nginx && php-fpm"]

63
deploy/docker-compose.yml Normal file
View File

@ -0,0 +1,63 @@
# deploy/docker-compose.yml
version: '3.8'
services:
laravel:
restart: unless-stopped
container_name: sewtopnotch.com
build:
context: ../
dockerfile: ./deploy/Dockerfile
# allocate as many volumes as necessary, if needed.
volumes:
- ../storage/app:/var/www/html/storage/app
environment:
APP_NAME: ${APP_NAME}
APP_ENV: ${APP_ENV}
APP_DEBUG: ${APP_DEBUG}
APP_KEY: ${APP_KEY}
APP_VERSION: ${APP_VERSION}
APP_URL: ${APP_URL}
DB_CONNECTION: mysql
DB_HOST: database
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
MAIL_MAILER: ${MAIL_MAILER}
MAIL_HOST: ${MAIL_HOST}
MAIL_PORT: ${MAIL_PORT}
MAIL_USERNAME: ${MAIL_USERNAME}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_ENCRYPTION: ${MAIL_ENCRYPTION}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
MAIL_FROM_NAME: ${MAIL_FROM_NAME}
ports:
- "8080:80"
networks:
- n-laravel
depends_on:
- database
database:
restart: unless-stopped
image: mariadb:lts-jammy
volumes:
- v-database:/var/lib/mysql
environment:
MARIADB_DATABASE: ${DB_DATABASE}
MARIADB_USER: ${DB_USERNAME}
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
networks:
- n-laravel
volumes:
v-database:
networks:
n-laravel:
driver: bridge

35
deploy/nginx.conf Normal file
View File

@ -0,0 +1,35 @@
# deploy/nginx.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html/public;
client_max_body_size 10M;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico {
access_log off; log_not_found off;
}
location = /robots.txt {
access_log off; log_not_found off;
}
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}

0
deploy/php.ini Normal file
View File

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{["x-on:blur"]:"createTag()",["x-model"]:"newTag",["x-on:keydown"](t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},["x-on:paste"](){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.