30 Commits

Author SHA1 Message Date
Jp
6eeedbeeb0 feat(transmittal): replace PDF export with Excel export and add logo
- Remove PDF export functionality from TransmittalResource and CreateTransmittal page
- Add new TransmittalExcelExport class for formatted Excel exports with company logo
- Update export jobs to generate unique filenames per user and refresh user data
- Replace PDF export action with Excel export in table actions
- Comment out logo in PDF view template as it's no longer used
- Fix import alias for Excel export action in GeneralLedger
2026-02-19 03:11:17 +08:00
Jp
b95f23f223 upgrade to filament v4 2026-02-19 01:26:02 +08:00
Jp
90c92650b7 upgrade to filament v4 2026-02-19 01:25:41 +08:00
Jp
2bf12aa4e8 fix: correct notification class usage in PDF export job
The notification was using the wrong class (Notifications instead of Notification). This caused the export completion notification to fail. Updated the import and instantiation to use the correct Filament Notification class.
2026-02-18 23:13:46 +08:00
Jp
df66727379 refactor: use correct Filament Action import in export jobs
Update import statements to use Filament\Notifications\Actions\Action instead of the incorrect Actions\Action and NotificationAction aliases. This ensures consistency with the Filament notification system.
2026-02-18 23:07:29 +08:00
1d6238e9cb Merge pull request #9 from kingjaypee12/jp/updates-on-sales
Jp/updates on sales
2026-02-18 22:58:34 +08:00
Jp
d8077f200a feat: replace Excel export with PDF export for transmittals
- Add new TransmittalPDFExportJob to generate PDFs using dompdf
- Remove old Excel export implementation (TransmittalsExport)
- Update ExportCompleteJob to use new PDF job instead of Excel
- Add TestQueueJob for queue testing with new route
- Update notification label from "Download File" to "Download PDF File"
- Fix auth() helper usage by importing Auth facade consistently
2026-02-18 22:57:34 +08:00
Jp
7899ed75ea feat(sales): add discount support with ledger accounting
- Add discount_type column to transactions table via migration
- Update Sale model to use fillable instead of guarded for better security
- Implement discount account ledger creation when discount is applied
- Fix net amount calculation to include discount in CreateSaleAction
- Remove unused "Exempt" column from sale transaction table
- Make discount_type required when discount is enabled in form
- Update form data mutation to properly handle discount calculations
2026-02-18 21:40:39 +08:00
Jp
5d427cdea4 feat: add discount management and PDF export for transmittals
- Create Discount model, migration, and Filament resource with relation to Client
- Add PDF export functionality for transmittals using DomPDF
- Include discount type selection in sales transactions
- Fix account filtering logic in expense resource
- Update export job to generate PDF instead of Excel
2026-02-18 01:42:44 +08:00
2644be0505 Merge pull request #8 from kingjaypee12/jp/sales-clean-up
feat(SaleResource): redirect to client after sale creation
2026-02-16 02:08:01 +08:00
Jp
9ddb71f03d feat(SaleResource): redirect to client after sale creation
When a client is associated with the sale, redirect to the client's view page with the sales relation manager active instead of the default list page. This improves the user workflow by keeping the context focused on the client.
2026-02-16 02:06:59 +08:00
eadcc0b3d7 Merge pull request #7 from kingjaypee12/jp/sales-clean-up
Jp/sales clean up
2026-02-16 02:03:02 +08:00
Jp
8c6fa6cb08 refactor(client): extract base account generation to command
Move the logic for generating base accounts for a new client from the ClientObserver into a dedicated command class (GenerateBaseAccountCommand). This improves code organization and reusability.

- The command is now used in the ClientObserver::created method.
- The command is also made available as a manual action in the AccountsRelationManager table header, allowing admins to generate base accounts for existing clients that lack them.
- Added necessary imports to the CreateSale page, though the command is not directly used there in this diff, suggesting preparatory work for future integration.
2026-02-16 02:02:26 +08:00
Jp
7bcfaff311 refactor: extract transaction creation and account syncing to actions
- Introduce CreateRecordTransactionsAction to handle transaction creation for any model
- Introduce SyncAccountsAction to encapsulate account synchronization logic
- Refactor CreateSaleAction to use new actions and handle full sale creation flow
- Simplify CreateExpense and CreateSale pages by delegating to actions
- Ensure proper transaction handling with database rollback on failure
2026-02-16 01:48:25 +08:00
Jp
e04689acca refactor(sales): replace service with command and action pattern
- Replace SaleService with CreateSaleCommand and CreateSaleAction for better separation of concerns
- Move sale creation logic into dedicated command class following command pattern
- Update CreateSale.php to use new action instead of direct service call
- Wrap sale creation in database transaction for data consistency
2026-02-16 01:22:00 +08:00
3cf5f6db6a Merge pull request #6 from kingjaypee12/fix/sales-related-table
feat: add account linking and improve sales table
2026-02-15 18:57:49 +08:00
Jp
f5c8ec04ad feat: add account linking and improve sales table
- Add many-to-many relationships between Sale/Expense and Account models
- Create pivot tables for account_sale and account_expense with migrations
- Implement account syncing during sale/expense creation and editing
- Add accounts_list attribute to display comma-separated account names
- Introduce SaleService with DTO for sale creation logic
- Simplify sales table columns to show branch, reference, date, and creator
- Calculate and store aggregated financial fields from transactions
- Make series field read-only instead of disabled in sale form
2026-02-15 18:57:18 +08:00
7bbe6e2d2a Merge pull request #5 from kingjaypee12/jp/fix-sales-breadcrumbs
feat(client): add client context to sales and expenses creation
2026-02-15 17:44:32 +08:00
Jp
fbc01bf1a4 feat(client): add client context to sales and expenses creation
- Disable branch selection in sale form when creating from client context
- Pass client ID to create pages via URL parameter
- Update breadcrumbs to reflect client navigation path
- Simplify relation managers by reusing resource tables and adding custom create actions
2026-02-15 17:43:33 +08:00
Jp
ff07f6f810 fix: remove debug statement from account options query
The dd() statement was accidentally left in production code, causing debug output to be displayed. This removes it to ensure proper functionality.
2026-02-14 14:14:33 +08:00
Jp
2bd8e99f64 fix: remove branch filter and add debug output for revenue accounts
The branch filter was incorrectly limiting available revenue accounts in certain scenarios. Temporarily added debug output to investigate account query results.
2026-02-14 14:13:24 +08:00
Jp
950e5613e6 Merge https://git.jpaleviado.site/kingjaypee12/MKM 2026-02-12 16:30:41 +08:00
Jp
fc118b8a6c Merge branch 'main' of https://github.com/kingjaypee12/MKM-App 2026-02-12 16:29:36 +08:00
Jp
a80e9a7b1c feat: add Laravel Horizon for queue monitoring and management
- Install Horizon package and configure with default settings
- Add HorizonServiceProvider with access gate for super_admin role only
- Integrate Horizon dashboard link into Filament admin panel navigation
- Update composer dependencies including Horizon and related package updates
2026-02-12 16:29:30 +08:00
d793abec9e Merge pull request #4 from kingjaypee12/fix/error-on-production
feat(TransmittalResource): enhance search functionality across relate…
2026-02-12 16:29:16 +08:00
fcac27b34d Merge pull request 'feat(TransmittalResource): enhance search functionality across related models' (#5) from fix/error-on-production into main
Reviewed-on: #5
2026-02-12 08:15:26 +00:00
22ea384d52 Merge pull request #3 from kingjaypee12/fix/error-on-production
Fix/error on production
2026-02-11 05:52:54 +08:00
7174bd6c90 Merge pull request 'feat: implement FilamentUser interface for User model' (#4) from fix/error-on-production into main
Reviewed-on: #4
2026-02-10 21:45:13 +00:00
4f9ec9ebfb Merge pull request 'fix: force HTTPS scheme in all environments' (#3) from fix/error-on-production into main
Reviewed-on: #3
2026-02-10 21:42:11 +00:00
f63be7fa5e Merge pull request 'fix: enforce HTTPS in production environment' (#2) from fix/error-on-production into main
Reviewed-on: #2
2026-02-10 21:38:32 +00:00
150 changed files with 5652 additions and 4112 deletions

11
.devdbrc Normal file
View File

@@ -0,0 +1,11 @@
[
{
"name": "My test MySQL database",
"type": "mysql",
"host": "192.168.100.105",
"port": "3306",
"username": "root",
"password": "root",
"database": "mkm_admin"
}
]

View File

@@ -2,9 +2,10 @@
namespace App\Actions; namespace App\Actions;
use Closure;
use Spatie\LaravelData\Data; use Spatie\LaravelData\Data;
abstract class BaseAction abstract class BaseAction
{ {
abstract public function __invoke(Data $payload, \Closure $next) ; abstract public function __invoke(Data $payload, Closure $next) ;
} }

View File

@@ -2,6 +2,9 @@
namespace App\Actions\Branch; namespace App\Actions\Branch;
use Closure;
use Exception;
use LogicException;
use App\Actions\BaseAction; use App\Actions\BaseAction;
use App\Commands\Series\CreateSeriesCommand; use App\Commands\Series\CreateSeriesCommand;
use App\DataObjects\CreateBranchDTO; use App\DataObjects\CreateBranchDTO;
@@ -15,7 +18,7 @@ class StoreBranchSeries extends BaseAction
private CreateSeriesCommand $createSeriesCommand private CreateSeriesCommand $createSeriesCommand
) {} ) {}
public function __invoke(CreateBranchDTO|Data $payload, \Closure $next) public function __invoke(CreateBranchDTO|Data $payload, Closure $next)
{ {
try { try {
$seriesPayload = CreateSeriesDTO::from(['branch_id' => $payload->branch->id, 'series' => $payload->data['series'], 'is_start' => true]); $seriesPayload = CreateSeriesDTO::from(['branch_id' => $payload->branch->id, 'series' => $payload->data['series'], 'is_start' => true]);
@@ -23,8 +26,8 @@ class StoreBranchSeries extends BaseAction
$payload->series = $this->createSeriesCommand->execute($seriesPayload->toArray()); $payload->series = $this->createSeriesCommand->execute($seriesPayload->toArray());
return $next($payload); return $next($payload);
} catch (\Exception $exception) { } catch (Exception $exception) {
throw new \LogicException('Failed to create branch series', $exception->getMessage()); throw new LogicException('Failed to create branch series', $exception->getMessage());
} }
} }
} }

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Actions\Sales;
use Exception;
use App\Actions\Transactions\CreateRecordTransactionsAction;
use App\Commands\Sales\CreateSaleCommand;
use App\Commands\Series\CreateSeriesCommand;
use App\Models\Sale;
use Illuminate\Support\Facades\DB;
class CreateSaleAction
{
/**
* Create a new class instance.
*/
public function __construct(
private CreateSaleCommand $createSaleCommand,
private CreateSeriesCommand $createSeriesCommand,
){ }
public function __invoke(array $data, array $transactions): Sale
{
$record = $this->createSaleCommand->execute($data);
try {
DB::beginTransaction();
//create transactions for the sale
app(CreateRecordTransactionsAction::class)($record, $transactions);
$accountIds = collect($transactions)
->pluck('account_id')
->filter()
->unique()
->values()
->all();
//sync accounts to sale
app(SyncAccountsAction::class)($record, $accountIds);
//increment current_series
$this->createSeriesCommand->execute([
'branch_id' => $record->branch_id,
'series' => $record->reference_number,
]);
DB::commit();
} catch (Exception $exception) {
DB::rollBack();
throw new Exception('Failed to save transactions : '.$exception->getMessage());
}
return $record;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Actions\Sales;
use App\Models\Sale;
use Illuminate\Support\Facades\DB;
class SyncAccountsAction
{
public function __invoke(Sale $sale, array $accounts): void
{
DB::transaction(function () use ($sale, $accounts) {
$sale->accounts()->sync($accounts);
}, attempts: 2);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Actions\Transactions;
use App\DataObjects\CreateTransactionDTO;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Pipeline;
class CreateRecordTransactionsAction
{
public function __invoke(Model $record, array $transactions): void
{
foreach ($transactions as $transaction) {
$tData = [
'branch_id' => $record->branch_id,
'happened_on' => $record->happened_on,
...$transaction,
];
$payload = new CreateTransactionDTO(data: $tData, transactionable: $record);
Pipeline::send(passable: $payload)->through(
[
CreateTransactionAction::class,
]
)->thenReturn();
}
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Transactions; namespace App\Actions\Transactions;
use App\Models\Expense;
use App\Actions\Balances\CreateBalanceAction; use App\Actions\Balances\CreateBalanceAction;
use App\Actions\BaseAction; use App\Actions\BaseAction;
use App\Actions\Ledgers\CreateLedgerAction; use App\Actions\Ledgers\CreateLedgerAction;
@@ -24,20 +25,26 @@ class CreateTransactionAction extends BaseAction
$this->cashAccountLedger($payload); $this->cashAccountLedger($payload);
if ($payload->transaction->discount !== 0) {
$this->discountAccountLedger($payload);
}
return $next($payload); return $next($payload);
} }
public function transactionAccountLedger($payload): void public function transactionAccountLedger($payload): void
{ {
$branch = $payload->transaction->branch; $branch = $payload->transaction->branch;
$isExpense = $payload->transactionable instanceof \App\Models\Expense; $isExpense = $payload->transactionable instanceof Expense;
$type = $isExpense ? 'debit' : 'credit'; $type = $isExpense ? 'debit' : 'credit';
$discount = $payload->transaction->discount ?? 0.00;
if ($branch->isClientVatable) { if ($branch->isClientVatable) {
//create transaction account ledger //create transaction account ledger
$ledgerPayload = new CreateLedgerDTO( $ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id, branch_id: $payload->transactionable->branch_id,
amount: $payload->transaction->net_amount ?? 0.00, amount: $payload->transaction->net_amount + $discount ?? 0.00,
transaction: $payload->transaction, transaction: $payload->transaction,
account: $payload->transaction->account, account: $payload->transaction->account,
type: $type, type: $type,
@@ -96,7 +103,7 @@ class CreateTransactionAction extends BaseAction
public function withHoldingAccountLedger($payload): void public function withHoldingAccountLedger($payload): void
{ {
$isExpense = $payload->transactionable instanceof \App\Models\Expense; $isExpense = $payload->transactionable instanceof Expense;
$accountName = $isExpense ? 'Payable Withholding Tax' : 'Creditable Withholding Tax'; $accountName = $isExpense ? 'Payable Withholding Tax' : 'Creditable Withholding Tax';
$type = $isExpense ? 'credit' : 'debit'; $type = $isExpense ? 'credit' : 'debit';
$clientId = $payload->transactionable->branch->client_id; $clientId = $payload->transactionable->branch->client_id;
@@ -122,7 +129,7 @@ class CreateTransactionAction extends BaseAction
public function cashAccountLedger($payload): void public function cashAccountLedger($payload): void
{ {
$isExpense = $payload->transactionable instanceof \App\Models\Expense; $isExpense = $payload->transactionable instanceof Expense;
$type = $isExpense ? 'credit' : 'debit'; $type = $isExpense ? 'credit' : 'debit';
$wht = $isExpense ? ($payload->transaction->payable_withholding_tax ?? 0) : ($payload->transaction->creditable_withholding_tax ?? 0); $wht = $isExpense ? ($payload->transaction->payable_withholding_tax ?? 0) : ($payload->transaction->creditable_withholding_tax ?? 0);
$amount = ($payload->transaction->gross_amount ?? 0) - $wht; $amount = ($payload->transaction->gross_amount ?? 0) - $wht;
@@ -144,4 +151,28 @@ class CreateTransactionAction extends BaseAction
$this->ledgerPipe($ledgerPayload); $this->ledgerPipe($ledgerPayload);
} }
} }
public function discountAccountLedger($payload): void
{
$isExpense = $payload->transactionable instanceof Expense;
$type = $isExpense ? 'credit' : 'debit';
$amount = $payload->transaction->discount ?? 0.00;
$clientId = $payload->transactionable->branch->client_id;
$discountAccount = Account::query()
->where('account', 'Sales Discount')
->where('client_id', $clientId)
->first();
if ($discountAccount && $amount > 0) {
$ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id,
amount: $amount,
transaction: $payload->transaction,
account: $discountAccount,
type: $type,
);
$this->ledgerPipe($ledgerPayload);
}
}
} }

View File

@@ -2,6 +2,9 @@
namespace App\Actions\Transmittal; namespace App\Actions\Transmittal;
use Closure;
use Exception;
use LogicException;
use App\Actions\BaseAction; use App\Actions\BaseAction;
use App\Commands\Transmittal\StoreTransmittalCommand; use App\Commands\Transmittal\StoreTransmittalCommand;
use App\DataObjects\CreateTransmittalDTO; use App\DataObjects\CreateTransmittalDTO;
@@ -15,15 +18,15 @@ class CreateTransmittal extends BaseAction
private readonly StoreTransmittalCommand $storeTransmittalCommand private readonly StoreTransmittalCommand $storeTransmittalCommand
) {} ) {}
public function __invoke(CreateTransmittalDTO | Data $payload, \Closure $next) public function __invoke(CreateTransmittalDTO | Data $payload, Closure $next)
{ {
try { try {
$payload->transmittal = $this->storeTransmittalCommand->execute(Arr::except($payload->data, ['files'])); $payload->transmittal = $this->storeTransmittalCommand->execute(Arr::except($payload->data, ['files']));
return $next($payload); return $next($payload);
} catch (\Exception $exception) } catch (Exception $exception)
{ {
throw new \LogicException('Error creating transmittal: ' . $exception->getMessage()); throw new LogicException('Error creating transmittal: ' . $exception->getMessage());
} }
} }
} }

View File

@@ -2,6 +2,9 @@
namespace App\Actions\Transmittal; namespace App\Actions\Transmittal;
use Closure;
use Exception;
use LogicException;
use App\Actions\BaseAction; use App\Actions\BaseAction;
use App\Commands\Transmittal\StoreCommentCommand; use App\Commands\Transmittal\StoreCommentCommand;
use App\Commands\Transmittal\StoreFileCommand; use App\Commands\Transmittal\StoreFileCommand;
@@ -22,7 +25,7 @@ class CreateTransmittalFiles extends BaseAction
private readonly StoreRemarkCommand $storeRemarkCommand private readonly StoreRemarkCommand $storeRemarkCommand
) {} ) {}
public function __invoke(CreateTransmittalDTO | Data $payload, \Closure $next) public function __invoke(CreateTransmittalDTO | Data $payload, Closure $next)
{ {
try { try {
$files = Arr::only($payload->data, 'files'); $files = Arr::only($payload->data, 'files');
@@ -55,9 +58,9 @@ class CreateTransmittalFiles extends BaseAction
$payload->files = $filesCreated; $payload->files = $filesCreated;
return $next($payload); return $next($payload);
} catch (\Exception $exception) } catch (Exception $exception)
{ {
throw new \LogicException('Error creating transmittal files: ' . $exception->getMessage()); throw new LogicException('Error creating transmittal files: ' . $exception->getMessage());
} }
} }
} }

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Commands\Clients;
use App\Commands\Command;
use Illuminate\Support\Facades\DB;
class GenerateBaseAccountCommand
{
/**
* Create a new class instance.
*/
public function __construct()
{
//
}
/**
* Execute the command.
*/
public function execute($client): void
{
DB::transaction(function () use ($client) {
$client->accounts()->createMany([
[
'account_type_id' => 1,
'account' => 'Cash',
'normal_balance' => 'debit',
],
[
'account_type_id' => 1,
'account' => 'Input Tax',
'normal_balance' => 'debit',
],
[
'account_type_id' => 1,
'account' => 'Creditable Withholding Tax',
'normal_balance' => 'debit',
],
[
'account_type_id' => 2,
'account' => 'Output Tax',
'normal_balance' => 'credit',
],
[
'account_type_id' => 2,
'account' => 'Payable Withholding Tax',
'normal_balance' => 'credit',
],
[
'account_type_id' => 5,
'account' => 'Vat Exempt Revenue',
'normal_balance' => 'credit',
],
[
'account_type_id' => 4,
'account' => 'Sales Discount',
'normal_balance' => 'debit',
],
]);
});
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Commands\Sales;
use Carbon\Carbon;
use App\Commands\Command;
use App\DataObjects\CreateSaleDTO;
use App\Models\Sale;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class CreateSaleCommand implements Command
{
public function execute(array $data): Sale
{
return DB::transaction(function () use ($data, &$sale) {
$tData = new CreateSaleDTO(
reference_number: $data['current_series'],
happened_on: Carbon::parse($data['happened_on']),
branch_id: $data['branch_id'],
user_id: Auth::user()->id,
);
return Sale::create($tData->toArray());
});
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\DataObjects;
use Carbon\Carbon;
readonly class CreateSaleDTO
{
/**
* Create a new class instance.
*/
public function __construct(
protected string $reference_number,
protected Carbon $happened_on,
protected int $branch_id,
protected int $user_id,
)
{}
public function toArray(): array
{
return [
'reference_number' => $this->reference_number,
'happened_on' => $this->happened_on,
'branch_id' => $this->branch_id,
'user_id' => $this->user_id,
];
}
}

View File

@@ -1,144 +0,0 @@
<?php
namespace App\Exports;
use App\Models\Transmittal;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\View;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithDefaultStyles;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Style;
class TransmittalsExport implements FromCollection, ShouldAutoSize, ShouldQueue, WithDefaultStyles, WithHeadings, WithMapping
{
use Exportable, Queueable, SerializesModels;
public function __construct(
private readonly array $id
) {}
public function view(): \Illuminate\Contracts\View\View
{
$transmittals = Transmittal::query()->with(['client', 'branch', 'files.notes', 'files.remarks'])->whereIn('id', Arr::flatten($this->id))->get();
return View::make('transmittal.export.transmittal-export-table')->with(['transmittals' => $transmittals]);
}
/**
* @throws Exception
*/
public function defaultStyles(Style $defaultStyle)
{
return $defaultStyle->applyFromArray([
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
}
public function headings(): array
{
return [
'series',
'files',
'notes',
'remarks',
];
}
public function collection()
{
$transmittals = Transmittal::query()->with(['client', 'branch', 'files.notes', 'files.remarks'])
->withCount(['files', 'notes', 'remarks'])->with(['files' => function ($files) {
$files->withCount(['notes', 'remarks']);
}])
->whereIn('id', Arr::flatten($this->id))->get();
return $transmittals;
}
public function map($transmittal): array
{
$data = [];
$firstFile = $transmittal->files->first();
$data[] = [
$transmittal->series,
$firstFile?->description,
$firstFile->notes->first()?->comment,
$firstFile->remarks->first()?->remark,
];
//iterate comments and remarks for first file
$notes = $firstFile->notes->pluck('comment');
$remarks = $firstFile->remarks->pluck('remark');
$fileNoteCount = count($notes);
$fileRemarkCount = count($remarks);
$fileRowCount = $fileNoteCount;
if ($fileRemarkCount > $fileNoteCount) {
$fileRowCount = $fileRemarkCount;
}
for ($i = 1; $i < $fileRowCount; $i++) {
$data[] = [
'',
'',
$notes[$i] ?? '',
$remarks[$i] ?? '',
];
}
//file iteration except for first file
$fileRowCounter = 0;
foreach ($transmittal->files as $file) {
$notes = $file->notes->pluck('comment');
$remarks = $file->remarks->pluck('remark');
$fileNoteCount = count($notes);
$fileRemarkCount = count($remarks);
$fileRowCount = $fileNoteCount;
if ($fileRemarkCount > $fileNoteCount) {
$fileRowCount = $fileRemarkCount;
}
if ($fileRowCounter != 0) {
$data[] = [
'',
$file->description,
$file->notes->first()?->comment ?? '',
$file->remarks->first()?->remark ?? '',
];
//iterate for remaining notes and remarks
for ($i = 1; $i < $fileRowCount; $i++) {
$data[] = [
'',
'',
$notes[$i] ?? '',
$remarks[$i] ?? '',
];
}
}
$fileRowCounter++;
}
return $data;
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace App\Filament\Exports;
use App\Models\Transmittal;
use pxlrbt\FilamentExcel\Columns\Column;
use pxlrbt\FilamentExcel\Exports\ExcelExport;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
class TransmittalExcelExport extends ExcelExport
{
public function setUp(): void
{
$this->fromTable()
->withFilename('transmittals-' . now()->format('Y-m-d'))
->modifyQueryUsing(
fn ($query) => $query->with([
'client',
'branch',
'files.notes',
'files.remarks',
])
)
->withColumns([
Column::make('series')
->heading('series'),
Column::make('client')
->heading('Client')
->formatStateUsing(fn (Transmittal $record) => $record->client?->company),
Column::make('branch')
->heading('Branch')
->formatStateUsing(fn (Transmittal $record) => $record->branch?->code),
Column::make('files')
->heading('files')
->formatStateUsing(
fn (Transmittal $record) => $record->files
->pluck('description')
->filter()
->join(PHP_EOL)
),
]);
}
public function registerEvents(): array
{
$recordIds = $this->getRecordIds();
return [
AfterSheet::class => function (AfterSheet $event) use ($recordIds) {
$sheet = $event->sheet->getDelegate();
$sheet->insertNewRowBefore(1, 2);
$sheet->mergeCells('A1:F1');
$sheet->getRowDimension(1)->setRowHeight(90);
$sheet->getStyle('A1:F1')
->getAlignment()
->setHorizontal(Alignment::HORIZONTAL_CENTER);
$logo = new Drawing();
$logo->setName('MKM Logo');
$logo->setDescription('MKM Tax & Accounting Services');
$logo->setPath(public_path('images/logo-light.png'));
$logo->setHeight(100);
$logo->setOffsetX(120);
$logo->setOffsetY(10);
$logo->setCoordinates('A1');
$logo->setWorksheet($sheet);
$transmittal = Transmittal::with(['client', 'branch', 'files.notes', 'files.remarks'])->find($recordIds[0] ?? null);
if ($transmittal) {
$sheet->mergeCells('A2:F2');
$sheet->setCellValue('A2', $transmittal->client?->company);
$sheet->getStyle('A2')->getFont()->setBold(true);
$sheet->getStyle('A2')->getAlignment()->setHorizontal('center');
}
// Headings row
$sheet->setCellValue('A3', 'series');
$sheet->setCellValue('B3', 'Client');
$sheet->setCellValue('C3', 'Branch');
$sheet->setCellValue('D3', 'files');
$sheet->setCellValue('E3', 'notes');
$sheet->setCellValue('F3', 'remarks');
$sheet->getStyle('A3:F3')->getFont()->setBold(true);
$sheet->getStyle('A3:F3')->getAlignment()->setHorizontal('center');
$rows = [];
$firstRow = true;
if ($transmittal) {
foreach ($transmittal->files as $file) {
$fileNotes = $file->notes->pluck('comment')->values();
$fileRemarks = $file->remarks->pluck('remark')->values();
$maxLines = max(1, $fileNotes->count(), $fileRemarks->count());
for ($i = 0; $i < $maxLines; $i++) {
$rows[] = [
'series' => $firstRow ? $transmittal->series : '',
'client' => $firstRow ? $transmittal->client?->company : '',
'branch' => $firstRow ? $transmittal->branch?->code : '',
'file' => $i === 0 ? $file->description : '',
'note' => $fileNotes[$i] ?? '',
'remark' => $fileRemarks[$i] ?? '',
];
$firstRow = false;
}
}
}
$currentRow = 4;
foreach ($rows as $dataRow) {
$sheet->setCellValue("A{$currentRow}", $dataRow['series']);
$sheet->setCellValue("B{$currentRow}", $dataRow['client']);
$sheet->setCellValue("C{$currentRow}", $dataRow['branch']);
$sheet->setCellValue("D{$currentRow}", $dataRow['file']);
$sheet->setCellValue("E{$currentRow}", $dataRow['note']);
$sheet->setCellValue("F{$currentRow}", $dataRow['remark']);
$currentRow++;
}
$lastRow = max($currentRow - 1, 3);
$sheet->getStyle("A3:F{$lastRow}")
->getBorders()
->getAllBorders()
->setBorderStyle(Border::BORDER_THIN);
},
];
}
}

View File

@@ -1,14 +1,20 @@
<?php <?php
namespace App\Filament\Resources; namespace App\Filament\Resources\Branches;
use Filament\Schemas\Schema;
use Filament\Actions\EditAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Filament\Resources\Branches\Pages\ListBranches;
use App\Filament\Resources\Branches\Pages\CreateBranch;
use App\Filament\Resources\Branches\Pages\EditBranch;
use App\Filament\Resources\BranchResource\Pages; use App\Filament\Resources\BranchResource\Pages;
use App\Filament\Resources\BranchResource\RelationManagers\BalancesRelationManager; use App\Filament\Resources\Branches\RelationManagers\BalancesRelationManager;
use App\Filament\Resources\BranchResource\RelationManagers\ExpenseRelationManager; use App\Filament\Resources\Branches\RelationManagers\ExpenseRelationManager;
use App\Models\Branch; use App\Models\Branch;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Support\RawJs; use Filament\Support\RawJs;
use Filament\Tables; use Filament\Tables;
@@ -19,16 +25,16 @@ class BranchResource extends Resource
{ {
protected static ?string $model = Branch::class; protected static ?string $model = Branch::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-rectangle-stack';
protected static bool $shouldRegisterNavigation = false; protected static bool $shouldRegisterNavigation = false;
protected static ?string $recordTitleAttribute = 'code'; protected static ?string $recordTitleAttribute = 'code';
public static function form(Form $form): Form public static function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Select::make('client_id')->relationship('client', 'id') Select::make('client_id')->relationship('client', 'id')
->getOptionLabelFromRecordUsing(fn ($record) => $record->company) ->getOptionLabelFromRecordUsing(fn ($record) => $record->company)
->disabled() ->disabled()
@@ -63,12 +69,12 @@ class BranchResource extends Resource
->filters([ ->filters([
// //
]) ])
->actions([ ->recordActions([
Tables\Actions\EditAction::make(), EditAction::make(),
]) ])
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
]), ]),
]); ]);
} }
@@ -85,9 +91,9 @@ class BranchResource extends Resource
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => Pages\ListBranches::route('/'), 'index' => ListBranches::route('/'),
'create' => Pages\CreateBranch::route('/create'), 'create' => CreateBranch::route('/create'),
'edit' => Pages\EditBranch::route('/{record}/edit'), 'edit' => EditBranch::route('/{record}/edit'),
]; ];
} }
} }

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Resources\BranchResource\Pages; namespace App\Filament\Resources\Branches\Pages;
use App\Filament\Resources\BranchResource; use App\Filament\Resources\Branches\BranchResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;

View File

@@ -1,9 +1,10 @@
<?php <?php
namespace App\Filament\Resources\BranchResource\Pages; namespace App\Filament\Resources\Branches\Pages;
use App\Filament\Resources\BranchResource; use Filament\Actions\DeleteAction;
use App\Filament\Resources\ClientResource; use App\Filament\Resources\Branches\BranchResource;
use App\Filament\Resources\Clients\ClientResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -30,7 +31,7 @@ class EditBranch extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\DeleteAction::make(), DeleteAction::make(),
]; ];
} }
} }

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\BranchResource\Pages; namespace App\Filament\Resources\Branches\Pages;
use App\Filament\Resources\BranchResource; use Filament\Actions\CreateAction;
use App\Filament\Resources\Branches\BranchResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListBranches extends ListRecords
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make(), CreateAction::make(),
]; ];
} }
} }

View File

@@ -1,10 +1,17 @@
<?php <?php
namespace App\Filament\Resources\BranchResource\RelationManagers; namespace App\Filament\Resources\Branches\RelationManagers;
use Filament\Schemas\Schema;
use Filament\Forms\Components\TextInput;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Models\Account; use App\Models\Account;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
@@ -22,11 +29,11 @@ class AccountsRelationManager extends RelationManager
}); });
} }
public function form(Form $form): Form public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Forms\Components\TextInput::make('branch_id') TextInput::make('branch_id')
->required() ->required()
->maxLength(255), ->maxLength(255),
]); ]);
@@ -37,25 +44,25 @@ class AccountsRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('branch_id') ->recordTitleAttribute('branch_id')
->columns([ ->columns([
Tables\Columns\TextColumn::make('account'), TextColumn::make('account'),
Tables\Columns\TextColumn::make('branch_id'), TextColumn::make('branch_id'),
Tables\Columns\TextColumn::make('normal_balance'), TextColumn::make('normal_balance'),
Tables\Columns\TextColumn::make('starting_balance'), TextColumn::make('starting_balance'),
Tables\Columns\TextColumn::make('current_balance'), TextColumn::make('current_balance'),
]) ])
->filters([ ->filters([
// //
]) ])
->headerActions([ ->headerActions([
Tables\Actions\CreateAction::make(), CreateAction::make(),
]) ])
->actions([ ->recordActions([
Tables\Actions\EditAction::make(), EditAction::make(),
Tables\Actions\DeleteAction::make(), DeleteAction::make(),
]) ])
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
]), ]),
]); ]);
} }

View File

@@ -1,13 +1,23 @@
<?php <?php
namespace App\Filament\Resources\BranchResource\RelationManagers; namespace App\Filament\Resources\Branches\RelationManagers;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use App\DataObjects\CreateAccountDTO; use App\DataObjects\CreateAccountDTO;
use App\Models\Account; use App\Models\Account;
use App\Models\AccountType; use App\Models\AccountType;
use App\Processes\Account\CreateAccountProcess; use App\Processes\Account\CreateAccountProcess;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
@@ -32,8 +42,8 @@ class BalancesRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('branch_id') ->recordTitleAttribute('branch_id')
->columns([ ->columns([
Tables\Columns\TextColumn::make('account')->sortable(), TextColumn::make('account')->sortable(),
Tables\Columns\TextColumn::make('accountType.normal_balance') TextColumn::make('accountType.normal_balance')
->badge() ->badge()
->color(fn (string $state): string => match ($state) { ->color(fn (string $state): string => match ($state) {
'Debit' => 'success', 'Debit' => 'success',
@@ -41,23 +51,23 @@ class BalancesRelationManager extends RelationManager
}) })
->sortable() ->sortable()
->formatStateUsing(fn ($state): string => ucfirst($state)), ->formatStateUsing(fn ($state): string => ucfirst($state)),
Tables\Columns\TextColumn::make('starting_balance')->label('Starting Balance'), TextColumn::make('starting_balance')->label('Starting Balance'),
Tables\Columns\TextColumn::make('current_balance')->label('Current Balance'), TextColumn::make('current_balance')->label('Current Balance'),
]) ])
->filters([ ->filters([
// //
]) ])
->headerActions([ ->headerActions([
Tables\Actions\CreateAction::make() CreateAction::make()
->using(fn (array $data) => $this->saveAccount($data)), ->using(fn (array $data) => $this->saveAccount($data)),
]) ])
->actions([ ->recordActions([
Tables\Actions\EditAction::make(), EditAction::make(),
Tables\Actions\DeleteAction::make(), DeleteAction::make(),
]) ])
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
]), ]),
]); ]);
} }
@@ -72,24 +82,24 @@ class BalancesRelationManager extends RelationManager
return app(CreateAccountProcess::class)->run($payload); return app(CreateAccountProcess::class)->run($payload);
} }
public function form(Form $form): Form public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema($this->getAccountForm()) ->components($this->getAccountForm())
->columns(1); ->columns(1);
} }
public function getAccountForm(): array public function getAccountForm(): array
{ {
return [ return [
Forms\Components\Grid::make() Grid::make()
->schema([ ->schema([
Forms\Components\Select::make('account_type_id') Select::make('account_type_id')
->label('Account Type') ->label('Account Type')
->relationship('accountType', 'type'), ->relationship('accountType', 'type'),
Forms\Components\TextInput::make('account'), TextInput::make('account'),
Forms\Components\Textarea::make('description'), Textarea::make('description'),
Forms\Components\TextInput::make('starting_balance') TextInput::make('starting_balance')
->integer(), ->integer(),
])->columns(1), ])->columns(1),
]; ];

View File

@@ -1,21 +1,22 @@
<?php <?php
namespace App\Filament\Resources\BranchResource\RelationManagers; namespace App\Filament\Resources\Branches\RelationManagers;
use App\Filament\Resources\ExpenseResource; use Filament\Schemas\Schema;
use Filament\Forms\Form; use Filament\Actions\CreateAction;
use App\Filament\Resources\Expenses\Pages\CreateExpense;
use App\Filament\Resources\Expenses\ExpenseResource;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Table; use Filament\Tables\Table;
class ExpenseRelationManager extends RelationManager class ExpenseRelationManager extends RelationManager
{ {
protected static string $relationship = 'expenses'; protected static string $relationship = 'expenses';
public function form(Form $form): Form public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema(ExpenseResource::getExpenseFormFields()); ->components(ExpenseResource::getExpenseFormFields());
} }
public function table(Table $table): Table public function table(Table $table): Table
@@ -25,8 +26,8 @@ class ExpenseRelationManager extends RelationManager
->columns(ExpenseResource::getTableColumns()) ->columns(ExpenseResource::getTableColumns())
->headerActions([ ->headerActions([
CreateAction::make() CreateAction::make()
->mutateFormDataUsing( ->mutateDataUsing(
fn (array $data): array => app(ExpenseResource\Pages\CreateExpense::class) fn (array $data): array => app(CreateExpense::class)
->getFormDataMutation($data) ->getFormDataMutation($data)
), ),
]); ]);

View File

@@ -1,133 +0,0 @@
<?php
namespace App\Filament\Resources;
use App\DataObjects\CreateBranchDTO;
use App\Filament\Resources\ClientResource\Pages\EditClient;
use App\Filament\Resources\ClientResource\Pages\ListClients;
use App\Filament\Resources\ClientResource\Pages\ViewClient;
use App\Filament\Resources\ClientResource\Pages\GeneralLedger;
use App\Filament\Resources\ClientResource\Pages\TrialBalance;
use App\Filament\Resources\ClientResource\RelationManagers\AccountsRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\BranchesRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\ExpensesRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\JournalsRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\SalesRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\TransmittalsRelationManager;
use App\Models\Branch;
use App\Models\Client;
use App\Processes\Branch\CreateBranchProcess;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Filament\Pages\SubNavigationPosition;
use Filament\Resources\Pages\Page;
class ClientResource extends Resource
{
protected static ?string $model = Client::class;
protected static ?string $navigationIcon = 'heroicon-o-user';
protected static ?string $recordTitleAttribute = 'company';
protected static SubNavigationPosition $subNavigationPosition = SubNavigationPosition::Top;
public static function authorizeView(Model $record): void
{
parent::authorizeView($record);
}
public static function getRecordSubNavigation(Page $page): array
{
return $page->generateNavigationItems([
ViewClient::class,
EditClient::class,
GeneralLedger::class,
TrialBalance::class,
]);
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('firstname')->label('First Name')->required(),
Forms\Components\TextInput::make('middlename')->label('Middle Name')->nullable(),
Forms\Components\TextInput::make('lastname')->label('Last Name')->required(),
Forms\Components\Grid::make()->schema([
Forms\Components\TextInput::make('company')->label('Company')->required(),
Forms\Components\Select::make('type_id')
->relationship('type', 'type')
->label('Type')->required(),
])->columns(2),
])->columns(3);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('firstname')->label('First Name')->searchable(),
Tables\Columns\TextColumn::make('middlename')->label('Middle Name'),
Tables\Columns\TextColumn::make('lastname')->label('Last Name'),
Tables\Columns\TextColumn::make('company')->label('Company')->searchable(),
Tables\Columns\TextColumn::make('type.type')->label('Type'),
])
->filters([
Tables\Filters\Filter::make('Vatable')
->query(fn (Builder $query) => $query->orWhereHas('type', function (Builder $query) {
$query->where('type', 'Vatable');
})),
Tables\Filters\Filter::make('Non-Vatable')
->query(fn (Builder $query) => $query->orWhereHas('type', function (Builder $query) {
$query->where('type', 'Non Vatable');
})),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make()->requiresConfirmation(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
AccountsRelationManager::class,
BranchesRelationManager::class,
TransmittalsRelationManager::class,
SalesRelationManager::class,
ExpensesRelationManager::class,
JournalsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'view' => ViewClient::route('/{record}'),
'edit' => EditClient::route('/{record}/edit'),
'index' => ListClients::route('/'),
'general-ledger' => GeneralLedger::route('/{record}/general-ledger'),
'trial-balance' => TrialBalance::route('/{record}/trial-balance'),
];
}
public static function saveBranch($data): Branch
{
$createBranchProcess = new CreateBranchProcess;
$payload = new CreateBranchDTO(data: $data);
return $createBranchProcess->run($payload)->branch;
}
}

View File

@@ -1,59 +0,0 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
use App\Filament\Exports\ClientAccountsExporter;
use App\Models\Account;
use Filament\Actions\Exports\Enums\ExportFormat;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
class AccountsRelationManager extends RelationManager
{
protected static string $relationship = 'accounts';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('account')->required(),
Forms\Components\Textarea::make('description')->nullable(),
Forms\Components\Select::make('account_type_id')
->relationship('accountType', 'type')
->required(),
Forms\Components\Select::make('normal_balance')->options(
['debit' => 'Debit', 'credit' => 'Credit']
)->required(),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('client_id')
->columns([
Tables\Columns\TextColumn::make('account')->description(fn (Account $record): string => $record->description ?? ''),
Tables\Columns\TextColumn::make('accountType.type'),
Tables\Columns\TextColumn::make('accountType.normal_balance')->label('Normal Balance'),
])
->filters([
//
])
->headerActions([
Tables\Actions\ExportAction::make('Export Accounts')->exporter(ClientAccountsExporter::class)->formats([ExportFormat::Csv]),
Tables\Actions\CreateAction::make()->label('New Account')->icon('heroicon-o-plus')->slideOver(),
])
->actions([
Tables\Actions\EditAction::make()->slideOver(),
Tables\Actions\DeleteAction::make()->requiresConfirmation(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make()->icon('heroicon-s-trash')->requiresConfirmation(),
]),
]);
}
}

View File

@@ -1,54 +0,0 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
use App\Filament\Resources\ExpenseResource;
use App\Models\Expense;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class ExpensesRelationManager extends RelationManager
{
protected static string $relationship = 'expenses';
protected static ?string $title = 'Expenses';
public function form(Form $form): Form
{
return $form
->schema([]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('supplier')
->columns([
TextColumn::make('supplier'),
TextColumn::make('reference_number'),
TextColumn::make('voucher_number'),
TextColumn::make('branch.code'),
TextColumn::make('happened_on'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->url(fn () => ExpenseResource::getUrl('create', ['client_id' => $this->getOwnerRecord()->id])),
])
->actions([
Tables\Actions\EditAction::make()
->url(fn (Expense $record) => ExpenseResource::getUrl('edit', ['record' => $record])),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
}

View File

@@ -1,59 +0,0 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
use App\Filament\Resources\SaleResource;
use App\Models\Sale;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class SalesRelationManager extends RelationManager
{
protected static string $relationship = 'sales';
protected static ?string $title = 'Sales';
public function form(Form $form): Form
{
return $form->schema([]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('title')
->columns([
TextColumn::make('id')->label('ID')->sortable(),
TextColumn::make('branch.code')->label('Branch')->sortable(),
TextColumn::make('happened_on')->label('Date')->date()->sortable(),
TextColumn::make('gross_amount')->label('Gross Amount')->numeric()->sortable(),
TextColumn::make('exempt')->label('Exempt')->numeric()->sortable(),
TextColumn::make('vatable_amount')->label('Vatable Amount')->numeric()->sortable(),
TextColumn::make('output_tax')->label('Output Tax')->numeric()->sortable(),
TextColumn::make('payable_withholding_tax')->label('Payable Withholding Tax')->numeric()->sortable(),
TextColumn::make('discount')->label('Discount')->numeric()->sortable(),
TextColumn::make('net_amount')->label('Net Amount')->numeric()->sortable(),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->url(fn () => SaleResource::getUrl('create', ['client_id' => $this->getOwnerRecord()->id])),
])
->actions([
Tables\Actions\EditAction::make()
->url(fn (Sale $record) => SaleResource::getUrl('edit', ['record' => $record])),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace App\Filament\Resources\Clients;
use Filament\Pages\Enums\SubNavigationPosition;
use Filament\Schemas\Schema;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\Filter;
use Filament\Actions\ViewAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\DataObjects\CreateBranchDTO;
use App\Filament\Resources\Clients\Pages\EditClient;
use App\Filament\Resources\Clients\Pages\ListClients;
use App\Filament\Resources\Clients\Pages\ViewClient;
use App\Filament\Resources\Clients\Pages\GeneralLedger;
use App\Filament\Resources\Clients\Pages\TrialBalance;
use App\Filament\Resources\Clients\RelationManagers\AccountsRelationManager;
use App\Filament\Resources\Clients\RelationManagers\BranchesRelationManager;
use App\Filament\Resources\Clients\RelationManagers\DiscountRelationManager;
use App\Filament\Resources\Clients\RelationManagers\ExpensesRelationManager;
use App\Filament\Resources\Clients\RelationManagers\JournalsRelationManager;
use App\Filament\Resources\Clients\RelationManagers\SalesRelationManager;
use App\Filament\Resources\Clients\RelationManagers\TransmittalsRelationManager;
use App\Models\Branch;
use App\Models\Client;
use App\Processes\Branch\CreateBranchProcess;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Filament\Resources\Pages\Page;
class ClientResource extends Resource
{
protected static ?string $model = Client::class;
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-user';
protected static ?string $recordTitleAttribute = 'company';
protected static ?\Filament\Pages\Enums\SubNavigationPosition $subNavigationPosition = SubNavigationPosition::Top;
public static function authorizeView(Model $record): void
{
parent::authorizeView($record);
}
public static function getRecordSubNavigation(Page $page): array
{
return $page->generateNavigationItems([
ViewClient::class,
EditClient::class,
GeneralLedger::class,
TrialBalance::class,
]);
}
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('firstname')->label('First Name')->required(),
TextInput::make('middlename')->label('Middle Name')->nullable(),
TextInput::make('lastname')->label('Last Name')->required(),
Grid::make()->schema([
TextInput::make('company')->label('Company')->required(),
Select::make('type_id')
->relationship('type', 'type')
->label('Type')->required(),
])->columns(2),
])->columns(3);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('firstname')->label('First Name')->searchable(),
TextColumn::make('middlename')->label('Middle Name'),
TextColumn::make('lastname')->label('Last Name'),
TextColumn::make('company')->label('Company')->searchable(),
TextColumn::make('type.type')->label('Type'),
])
->filters([
Filter::make('Vatable')
->query(fn (Builder $query) => $query->orWhereHas('type', function (Builder $query) {
$query->where('type', 'Vatable');
})),
Filter::make('Non-Vatable')
->query(fn (Builder $query) => $query->orWhereHas('type', function (Builder $query) {
$query->where('type', 'Non Vatable');
})),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
DeleteAction::make()->requiresConfirmation(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
AccountsRelationManager::class,
BranchesRelationManager::class,
TransmittalsRelationManager::class,
SalesRelationManager::class,
ExpensesRelationManager::class,
JournalsRelationManager::class,
DiscountRelationManager::class,
];
}
public static function getPages(): array
{
return [
'view' => ViewClient::route('/{record}'),
'edit' => EditClient::route('/{record}/edit'),
'index' => ListClients::route('/'),
'general-ledger' => GeneralLedger::route('/{record}/general-ledger'),
'trial-balance' => TrialBalance::route('/{record}/trial-balance'),
];
}
public static function saveBranch($data): Branch
{
$createBranchProcess = new CreateBranchProcess;
$payload = new CreateBranchDTO(data: $data);
return $createBranchProcess->run($payload)->branch;
}
}

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\Pages; namespace App\Filament\Resources\Clients\Pages;
use App\Filament\Resources\ClientResource; use App\Filament\Resources\Clients\ClientResource;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
class CreateClient extends CreateRecord class CreateClient extends CreateRecord

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\Pages; namespace App\Filament\Resources\Clients\Pages;
use App\Filament\Resources\ClientResource; use Filament\Actions\DeleteAction;
use App\Filament\Resources\Clients\ClientResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
@@ -13,7 +14,7 @@ class EditClient extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\DeleteAction::make()->icon('heroicon-s-trash')->requiresConfirmation(), DeleteAction::make()->icon('heroicon-s-trash')->requiresConfirmation(),
]; ];
} }

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\Pages; namespace App\Filament\Resources\Clients\Pages;
use App\Filament\Resources\ClientResource; use App\Filament\Resources\Clients\ClientResource;
use App\Models\Ledger; use App\Models\Ledger;
use Filament\Resources\Pages\Page; use Filament\Resources\Pages\Page;
use Filament\Tables\Concerns\InteractsWithTable; use Filament\Tables\Concerns\InteractsWithTable;
@@ -14,7 +14,7 @@ use Filament\Tables\Filters\Filter;
use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\DatePicker;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Filament\Resources\Pages\Concerns\InteractsWithRecord; use Filament\Resources\Pages\Concerns\InteractsWithRecord;
use pxlrbt\FilamentExcel\Actions\Tables\ExportAction; use pxlrbt\FilamentExcel\Actions\ExportAction as ExcelExportAction;
use pxlrbt\FilamentExcel\Exports\ExcelExport; use pxlrbt\FilamentExcel\Exports\ExcelExport;
use pxlrbt\FilamentExcel\Columns\Column; use pxlrbt\FilamentExcel\Columns\Column;
@@ -25,9 +25,9 @@ class GeneralLedger extends Page implements HasTable
protected static string $resource = ClientResource::class; protected static string $resource = ClientResource::class;
protected static ?string $navigationIcon = 'heroicon-o-document-text'; protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.resources.client-resource.pages.general-ledger'; protected string $view = 'filament.resources.client-resource.pages.general-ledger';
public function mount(int | string $record): void public function mount(int | string $record): void
{ {
@@ -79,7 +79,7 @@ class GeneralLedger extends Page implements HasTable
->searchable() ->searchable()
->preload(), ->preload(),
Filter::make('date_range') Filter::make('date_range')
->form([ ->schema([
DatePicker::make('from'), DatePicker::make('from'),
DatePicker::make('to'), DatePicker::make('to'),
]) ])
@@ -108,7 +108,7 @@ class GeneralLedger extends Page implements HasTable
'account.account', 'account.account',
]) ])
->headerActions([ ->headerActions([
ExportAction::make() ExcelExportAction::make()
->label('Export General Ledger') ->label('Export General Ledger')
->exports([ ->exports([
ExcelExport::make() ExcelExport::make()

View File

@@ -1,9 +1,12 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\Pages; namespace App\Filament\Resources\Clients\Pages;
use Filament\Actions\ExportAction;
use Filament\Actions\Exports\Enums\ExportFormat;
use Filament\Actions\CreateAction;
use App\Filament\Exports\ClientExporter; use App\Filament\Exports\ClientExporter;
use App\Filament\Resources\ClientResource; use App\Filament\Resources\Clients\ClientResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
@@ -14,12 +17,12 @@ class ListClients extends ListRecords
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\ExportAction::make('Export clients') ExportAction::make('Export clients')
->exporter(ClientExporter::class) ->exporter(ClientExporter::class)
->formats([ ->formats([
Actions\Exports\Enums\ExportFormat::Csv, ExportFormat::Csv,
]), ]),
Actions\CreateAction::make()->slideOver(), CreateAction::make()->slideOver(),
]; ];
} }
} }

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\Pages; namespace App\Filament\Resources\Clients\Pages;
use App\Filament\Resources\ClientResource; use App\Filament\Resources\Clients\ClientResource;
use App\Models\Account; use App\Models\Account;
use Filament\Resources\Pages\Page; use Filament\Resources\Pages\Page;
use Filament\Tables\Concerns\InteractsWithTable; use Filament\Tables\Concerns\InteractsWithTable;
@@ -21,9 +21,9 @@ class TrialBalance extends Page implements HasTable
protected static string $resource = ClientResource::class; protected static string $resource = ClientResource::class;
protected static ?string $navigationIcon = 'heroicon-o-scale'; protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-scale';
protected static string $view = 'filament.resources.client-resource.pages.trial-balance'; protected string $view = 'filament.resources.client-resource.pages.trial-balance';
public function mount(int | string $record): void public function mount(int | string $record): void
{ {

View File

@@ -1,21 +1,21 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\Pages; namespace App\Filament\Resources\Clients\Pages;
use App\Filament\Resources\ClientResource; use Filament\Schemas\Schema;
use Filament\Infolists\Components\Grid; use Filament\Schemas\Components\Section;
use Filament\Infolists\Components\Section; use Filament\Schemas\Components\Grid;
use App\Filament\Resources\Clients\ClientResource;
use Filament\Infolists\Components\TextEntry; use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Infolist;
use Filament\Resources\Pages\ViewRecord; use Filament\Resources\Pages\ViewRecord;
class ViewClient extends ViewRecord class ViewClient extends ViewRecord
{ {
protected static string $resource = ClientResource::class; protected static string $resource = ClientResource::class;
public function infolist(Infolist $infolist): Infolist public function infolist(Schema $schema): Schema
{ {
return $infolist return $schema
->schema([ ->schema([
Section::make()->schema([ Section::make()->schema([
Grid::make()->schema([ Grid::make()->schema([
@@ -25,7 +25,7 @@ class ViewClient extends ViewRecord
TextEntry::make('company')->label('Company'), TextEntry::make('company')->label('Company'),
TextEntry::make('type.type')->label('Type'), TextEntry::make('type.type')->label('Type'),
])->columns(3), ])->columns(3),
]), ])->columnSpanFull(),
// Section::make('Branches')->schema([ // Section::make('Branches')->schema([
// RepeatableEntry::make('branches') // RepeatableEntry::make('branches')

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Filament\Resources\Clients\RelationManagers;
use Filament\Schemas\Schema;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Select;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\Action;
use Filament\Actions\ExportAction;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Commands\Clients\GenerateBaseAccountCommand;
use App\Filament\Exports\ClientAccountsExporter;
use App\Models\Account;
use Filament\Actions\Exports\Enums\ExportFormat;
use Filament\Forms;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
class AccountsRelationManager extends RelationManager
{
protected static string $relationship = 'accounts';
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('account')->required(),
Textarea::make('description')->nullable(),
Select::make('account_type_id')
->relationship('accountType', 'type')
->required(),
Select::make('normal_balance')->options(
['debit' => 'Debit', 'credit' => 'Credit']
)->required(),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('client_id')
->columns([
TextColumn::make('account')->description(fn (Account $record): string => $record->description ?? ''),
TextColumn::make('accountType.type'),
TextColumn::make('accountType.normal_balance')->label('Normal Balance'),
])
->filters([
//
])
->headerActions([
Action::make('generate-base-accounts')
->requiresConfirmation()
->label('Generate Base Accounts')
->action(function () {
$client = $this->getOwnerRecord();
if (! $client ) {
return;
}
if($client->accounts()->count() > 0) {
return;
}
app(GenerateBaseAccountCommand::class)->execute($client);
}),
ExportAction::make('Export Accounts')->exporter(ClientAccountsExporter::class)->formats([ExportFormat::Csv]),
CreateAction::make()->label('New Account')->icon('heroicon-o-plus')->slideOver(),
])
->recordActions([
EditAction::make()->slideOver(),
DeleteAction::make()->requiresConfirmation(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make()->icon('heroicon-s-trash')->requiresConfirmation(),
]),
]);
}
}

View File

@@ -1,13 +1,21 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\RelationManagers; namespace App\Filament\Resources\Clients\RelationManagers;
use App\Filament\Resources\BranchResource\Pages\EditBranch; use Filament\Schemas\Schema;
use App\Filament\Resources\ClientResource; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\TextInput;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Filament\Resources\Branches\Pages\EditBranch;
use App\Filament\Resources\Clients\ClientResource;
use App\Models\Branch; use App\Models\Branch;
use App\Processes\Branch\CreateBranchProcess; use App\Processes\Branch\CreateBranchProcess;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\RawJs; use Filament\Support\RawJs;
use Filament\Tables; use Filament\Tables;
@@ -27,19 +35,19 @@ class BranchesRelationManager extends RelationManager
$this->createBranchProcess = new CreateBranchProcess; $this->createBranchProcess = new CreateBranchProcess;
} }
public function form(Form $form): Form public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Forms\Components\Hidden::make('id'), Hidden::make('id'),
Forms\Components\TextInput::make('code')->required() TextInput::make('code')->required()
->unique( ->unique(
'branches', 'branches',
'code', 'code',
ignoreRecord: true, ignoreRecord: true,
modifyRuleUsing: fn (Unique $rule) => $rule->where('client_id', $this->getOwnerRecord()->id) modifyRuleUsing: fn (Unique $rule) => $rule->where('client_id', $this->getOwnerRecord()->id)
), ),
Forms\Components\TextInput::make('series')->label('Current Series') TextInput::make('series')->label('Current Series')
->required() ->required()
->numeric() ->numeric()
->integer() ->integer()
@@ -56,29 +64,29 @@ class BranchesRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('client_id') ->recordTitleAttribute('client_id')
->columns([ ->columns([
Tables\Columns\TextColumn::make('code')->label('Branch Code'), TextColumn::make('code')->label('Branch Code'),
Tables\Columns\TextColumn::make('current_series')->label('Current Series'), TextColumn::make('current_series')->label('Current Series'),
]) ])
->filters([ ->filters([
// //
]) ])
->headerActions([ ->headerActions([
Tables\Actions\CreateAction::make() CreateAction::make()
->mutateFormDataUsing(fn ($data) => $this->appendCientId($data)) ->mutateDataUsing(fn ($data) => $this->appendCientId($data))
->using(fn ($data) => $this->saveBranch($data)), ->using(fn ($data) => $this->saveBranch($data)),
]) ])
->actions([ ->recordActions([
// Tables\Actions\ViewAction::make()->url(fn ($record) => EditBranch::getUrl(['record' => $record->id])), // Tables\Actions\ViewAction::make()->url(fn ($record) => EditBranch::getUrl(['record' => $record->id])),
Tables\Actions\EditAction::make() EditAction::make()
->fillForm(fn ($record) => ['id' => $record->id, 'code' => $record->code, 'series' => $record->current_series]) ->fillForm(fn ($record) => ['id' => $record->id, 'code' => $record->code, 'series' => $record->current_series])
->mutateFormDataUsing(fn ($data) => $this->appendCientId($data)) ->mutateDataUsing(fn ($data) => $this->appendCientId($data))
->using(fn ($data) => $this->saveBranch($data)) ->using(fn ($data) => $this->saveBranch($data))
->url(fn ($record) => EditBranch::getUrl(['record' => $record->id])), ->url(fn ($record) => EditBranch::getUrl(['record' => $record->id])),
Tables\Actions\DeleteAction::make(), DeleteAction::make(),
]) ])
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
]), ]),
]); ]);
} }

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Filament\Resources\Clients\RelationManagers;
use Filament\Schemas\Schema;
use Filament\Forms\Components\TextInput;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class DiscountRelationManager extends RelationManager
{
protected static string $relationship = 'discounts';
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('discount')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('discount')
->columns([
TextColumn::make('discount'),
])
->filters([
//
])
->headerActions([
CreateAction::make(),
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Filament\Resources\Clients\RelationManagers;
use Filament\Schemas\Schema;
use Filament\Actions\Action;
use App\Filament\Resources\Expenses\ExpenseResource;
use App\Models\Expense;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class ExpensesRelationManager extends RelationManager
{
protected static string $relationship = 'expenses';
protected static ?string $title = 'Expenses';
public function form(Schema $schema): Schema
{
return $schema
->components([]);
}
public function table(Table $table): Table
{
return ExpenseResource::table($table)->headerActions([
Action::make('New Expense')->action('openCreateForm'),
]);
}
public function openCreateForm()
{
return redirect()->route('filament.admin.resources.expenses.create', ['client_id' => $this->getOwnerRecord()->id]);
}
}

View File

@@ -1,7 +1,16 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\RelationManagers; namespace App\Filament\Resources\Clients\RelationManagers;
use Filament\Schemas\Schema;
use Closure;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\CreateAction;
use App\Models\Account;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Actions\Balances\CreateBalanceAction; use App\Actions\Balances\CreateBalanceAction;
use App\Actions\Ledgers\CreateLedgerAction; use App\Actions\Ledgers\CreateLedgerAction;
use App\DataObjects\CreateLedgerDTO; use App\DataObjects\CreateLedgerDTO;
@@ -14,7 +23,6 @@ use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
@@ -29,10 +37,10 @@ class JournalsRelationManager extends RelationManager
protected static ?string $title = 'Journal Entries (Adjustments)'; protected static ?string $title = 'Journal Entries (Adjustments)';
public function form(Form $form): Form public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Select::make('branch_id') Select::make('branch_id')
->label('Branch') ->label('Branch')
->options(fn () => Branch::where('client_id', $this->getOwnerRecord()->id)->pluck('code', 'id')) ->options(fn () => Branch::where('client_id', $this->getOwnerRecord()->id)->pluck('code', 'id'))
@@ -78,7 +86,7 @@ class JournalsRelationManager extends RelationManager
->minItems(2) ->minItems(2)
->live() ->live()
->rules([ ->rules([
fn (): \Closure => function (string $attribute, $value, \Closure $fail) { fn (): Closure => function (string $attribute, $value, Closure $fail) {
$debit = collect($value)->sum('debit_amount'); $debit = collect($value)->sum('debit_amount');
$credit = collect($value)->sum('credit_amount'); $credit = collect($value)->sum('credit_amount');
if (abs($debit - $credit) > 0.01) { if (abs($debit - $credit) > 0.01) {
@@ -97,18 +105,18 @@ class JournalsRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('description') ->recordTitleAttribute('description')
->columns([ ->columns([
Tables\Columns\TextColumn::make('happened_on') TextColumn::make('happened_on')
->date() ->date()
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('series') TextColumn::make('series')
->searchable(), ->searchable(),
Tables\Columns\TextColumn::make('description') TextColumn::make('description')
->limit(50), ->limit(50),
Tables\Columns\TextColumn::make('total_debit') TextColumn::make('total_debit')
->label('Total Debit') ->label('Total Debit')
->state(fn (Journal $record) => $record->ledgers->sum('debit_amount')) ->state(fn (Journal $record) => $record->ledgers->sum('debit_amount'))
->money('PHP'), ->money('PHP'),
Tables\Columns\TextColumn::make('total_credit') TextColumn::make('total_credit')
->label('Total Credit') ->label('Total Credit')
->state(fn (Journal $record) => $record->ledgers->sum('credit_amount')) ->state(fn (Journal $record) => $record->ledgers->sum('credit_amount'))
->money('PHP'), ->money('PHP'),
@@ -117,7 +125,7 @@ class JournalsRelationManager extends RelationManager
// //
]) ])
->headerActions([ ->headerActions([
Tables\Actions\CreateAction::make() CreateAction::make()
->label('Add Adjustment Entry') ->label('Add Adjustment Entry')
->using(function (array $data, string $model) { ->using(function (array $data, string $model) {
return DB::transaction(function () use ($data, $model) { return DB::transaction(function () use ($data, $model) {
@@ -134,7 +142,7 @@ class JournalsRelationManager extends RelationManager
ledger: null, // Will be created ledger: null, // Will be created
transaction: null, // No transaction transaction: null, // No transaction
journal: $journal, journal: $journal,
account: \App\Models\Account::find($ledger['account_id']), account: Account::find($ledger['account_id']),
type: ($ledger['debit_amount'] > 0) ? 'debit' : 'credit' type: ($ledger['debit_amount'] > 0) ? 'debit' : 'credit'
); );
@@ -148,13 +156,13 @@ class JournalsRelationManager extends RelationManager
}); });
}), }),
]) ])
->actions([ ->recordActions([
Tables\Actions\EditAction::make(), EditAction::make(),
Tables\Actions\DeleteAction::make(), DeleteAction::make(),
]) ])
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
]), ]),
]); ]);
} }

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Filament\Resources\Clients\RelationManagers;
use Filament\Schemas\Schema;
use Filament\Actions\Action;
use App\Filament\Resources\Sales\SaleResource;
use App\Models\Sale;
use Filament\Forms;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class SalesRelationManager extends RelationManager
{
protected static string $relationship = 'sales';
protected static ?string $title = 'Sales';
public function form(Schema $schema): Schema
{
return $schema->components([]);
}
public function table(Table $table): Table
{
return SaleResource::table($table)->headerActions([
Action::make('New Sale')->action('openCreateForm'),
]);
}
public function openCreateForm()
{
return redirect()->route('filament.admin.resources.sales.create', ['client_id' => $this->getOwnerRecord()->id]);
}
}

View File

@@ -1,9 +1,10 @@
<?php <?php
namespace App\Filament\Resources\ClientResource\RelationManagers; namespace App\Filament\Resources\Clients\RelationManagers;
use App\Filament\Resources\TransmittalResource; use Filament\Schemas\Schema;
use Filament\Forms\Form; use Filament\Actions\Action;
use App\Filament\Resources\Transmittals\TransmittalResource;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
@@ -18,9 +19,9 @@ class TransmittalsRelationManager extends RelationManager
return auth()->user()->can('update_transmittal'); return auth()->user()->can('update_transmittal');
} }
public function form(Form $form): Form public function form(Schema $schema): Schema
{ {
return TransmittalResource::form($form) return TransmittalResource::form($schema)
->fill( ->fill(
['client_id' => $this->getOwnerRecord()->id] ['client_id' => $this->getOwnerRecord()->id]
); );
@@ -29,7 +30,7 @@ class TransmittalsRelationManager extends RelationManager
public function table(Table $table): Table public function table(Table $table): Table
{ {
return TransmittalResource::table($table)->headerActions([ return TransmittalResource::table($table)->headerActions([
Tables\Actions\Action::make('New Transmittal')->action('openCreateForm'), Action::make('New Transmittal')->action('openCreateForm'),
]); ]);
} }

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Filament\Resources\Discounts;
use Filament\Schemas\Schema;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Hidden;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\EditAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Filament\Resources\Discounts\Pages\ListDiscounts;
use App\Filament\Resources\Discounts\Pages\CreateDiscount;
use App\Filament\Resources\Discounts\Pages\EditDiscount;
use App\Filament\Resources\DiscountResource\Pages;
use App\Filament\Resources\DiscountResource\RelationManagers;
use App\Models\Discount;
use Filament\Forms;
use Filament\Forms\Get;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class DiscountResource extends Resource
{
protected static ?string $model = Discount::class;
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-rectangle-stack';
protected static bool $shouldRegisterNavigation = false;
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('discount')
->label('Discount')
->required(),
Hidden::make('client_id')
->default(fn () => request()->client_id),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('discount')
->label('Discount')
->searchable(),
])
->filters([
//
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListDiscounts::route('/'),
'create' => CreateDiscount::route('/create'),
'edit' => EditDiscount::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\Discounts\Pages;
use App\Filament\Resources\Discounts\DiscountResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateDiscount extends CreateRecord
{
protected static string $resource = DiscountResource::class;
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Filament\Resources\Discounts\Pages;
use Filament\Actions\DeleteAction;
use App\Filament\Resources\Discounts\DiscountResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditDiscount extends EditRecord
{
protected static string $resource = DiscountResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Filament\Resources\Discounts\Pages;
use Filament\Actions\CreateAction;
use App\Filament\Resources\Discounts\DiscountResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListDiscounts extends ListRecords
{
protected static string $resource = DiscountResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -1,59 +0,0 @@
<?php
namespace App\Filament\Resources\ExpenseResource\Pages;
use App\Actions\Transactions\CreateTransactionAction;
use App\DataObjects\CreateTransactionDTO;
use App\Filament\Resources\ExpenseResource;
use Exception;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Pipeline;
use Symfony\Component\Console\Exception\LogicException;
class CreateExpense extends CreateRecord
{
protected static string $resource = ExpenseResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
return $this->getFormDataMutation($data);
}
public function getFormDataMutation(array $data): array
{
return Arr::except($data, ['client', 'transactions']);
}
protected function afterCreate(): void
{
$transactions = $this->form->getState()['transactions'] ?? [];
try {
$branch = $this->getRecord()->branch;
foreach ($transactions as $transaction) {
$data = [
'branch_id' => $branch->id,
'happened_on' => $this->getRecord()->happened_on,
...$transaction,
];
$payload = new CreateTransactionDTO(data: $data, transactionable: $this->getRecord());
Pipeline::send(passable: $payload)->through(
[
CreateTransactionAction::class,
]
)->thenReturn();
}
$this->commitDatabaseTransaction();
} catch (Exception $exception) {
$this->rollBackDatabaseTransaction();
throw new LogicException('Failed to save transactions : '.$exception->getMessage());
}
}
}

View File

@@ -1,7 +1,18 @@
<?php <?php
namespace App\Filament\Resources; namespace App\Filament\Resources\Expenses;
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Tables\Columns\TextColumn;
use App\Filament\Resources\Expenses\Pages\ListExpenses;
use App\Filament\Resources\Expenses\Pages\CreateExpense;
use App\Filament\Resources\Expenses\Pages\EditExpense;
use App\Commands\Expenses\GenerateVoucher; use App\Commands\Expenses\GenerateVoucher;
use App\Filament\Resources\ExpenseResource\Pages; use App\Filament\Resources\ExpenseResource\Pages;
use App\Models\Account; use App\Models\Account;
@@ -14,9 +25,6 @@ use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
@@ -29,14 +37,14 @@ class ExpenseResource extends Resource
protected static bool $isVatable; protected static bool $isVatable;
protected static ?string $navigationIcon = 'heroicon-o-banknotes'; protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-banknotes';
protected static bool $shouldRegisterNavigation = false; protected static bool $shouldRegisterNavigation = false;
public static function form(Form $form): Form public static function form(Schema $schema): Schema
{ {
return $form return $schema
->schema(static::getExpenseFormFields()); ->components(static::getExpenseFormFields());
} }
public static function getExpenseFormFields(): array public static function getExpenseFormFields(): array
@@ -195,11 +203,11 @@ class ExpenseResource extends Resource
'client_id' => $get('../../client'), 'client_id' => $get('../../client'),
]); ]);
if ($get('../../branch_id')) { // if ($get('../../branch_id')) {
$query->whereHas('balances', function ($query) use ($get) { // $query->whereHas('balances', function ($query) use ($get) {
return $query->where('branch_id', $get('../../branch_id')); // return $query->where('branch_id', $get('../../branch_id'));
}); // });
} // }
$query->whereHas('accountType', function ($query) { $query->whereHas('accountType', function ($query) {
return $query->where('type', 'Expenses'); return $query->where('type', 'Expenses');
@@ -208,7 +216,6 @@ class ExpenseResource extends Resource
return $query->get()->pluck('account', 'id'); return $query->get()->pluck('account', 'id');
} }
#[NoReturn]
public static function setDefaultFormValues(Get $get, Set $set, ?string $old, ?string $state): void public static function setDefaultFormValues(Get $get, Set $set, ?string $old, ?string $state): void
{ {
@@ -253,13 +260,13 @@ class ExpenseResource extends Resource
->filters([ ->filters([
// //
]) ])
->actions([ ->recordActions([
Tables\Actions\DeleteAction::make(), DeleteAction::make(),
Tables\Actions\EditAction::make(), EditAction::make(),
]) ])
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
]), ]),
]); ]);
} }
@@ -267,12 +274,13 @@ class ExpenseResource extends Resource
public static function getTableColumns(): array public static function getTableColumns(): array
{ {
return [ return [
Tables\Columns\TextColumn::make('supplier'), TextColumn::make('supplier'),
Tables\Columns\TextColumn::make('reference_number'), TextColumn::make('reference_number'),
Tables\Columns\TextColumn::make('voucher_number'), TextColumn::make('voucher_number'),
Tables\Columns\TextColumn::make('branch.client.company'), TextColumn::make('branch.client.company'),
Tables\Columns\TextColumn::make('branch.code'), TextColumn::make('branch.code'),
Tables\Columns\TextColumn::make('happened_on'), TextColumn::make('happened_on'),
TextColumn::make('accounts_list')->label('Accounts'),
]; ];
} }
@@ -286,9 +294,9 @@ class ExpenseResource extends Resource
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => Pages\ListExpenses::route('/'), 'index' => ListExpenses::route('/'),
'create' => Pages\CreateExpense::route('/create'), 'create' => CreateExpense::route('/create'),
'edit' => Pages\EditExpense::route('/{record}/edit'), 'edit' => EditExpense::route('/{record}/edit'),
]; ];
} }
} }

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Filament\Resources\Expenses\Pages;
use App\Actions\Transactions\CreateRecordTransactionsAction;
use App\Filament\Resources\Clients\ClientResource;
use App\Filament\Resources\Expenses\ExpenseResource;
use App\Models\Client;
use Exception;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr;
use Symfony\Component\Console\Exception\LogicException;
class CreateExpense extends CreateRecord
{
protected static string $resource = ExpenseResource::class;
public ?int $clientId = null;
public function mount(): void
{
parent::mount();
$this->clientId = request()->integer('client_id');
}
public function getBreadcrumbs(): array
{
$client = $this->getClient();
if (! $client) {
return parent::getBreadcrumbs();
}
return [
ClientResource::getUrl('view', ['record' => $client->id]) => $client->company,
ClientResource::getUrl('view', ['record' => $client->id]).'?activeRelationManager=4' => 'Expenses',
$this->getResource()::getUrl('create', ['client_id' => $client->id]) => 'Create',
];
}
protected function getClient(): Client|null
{
if (! $this->clientId) {
return null;
}
return Client::find($this->clientId);
}
protected function mutateFormDataBeforeCreate(array $data): array
{
return $this->getFormDataMutation($data);
}
public function getFormDataMutation(array $data): array
{
$transactions = $data['transactions'] ?? [];
$data['gross_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['gross_amount'] ?? 0));
$data['exempt'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['exempt'] ?? 0));
$data['zero_rated'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['zero_rated'] ?? 0));
$data['vatable_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['vatable_amount'] ?? 0));
$data['input_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['input_tax'] ?? 0));
$data['payable_withholding_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['payable_withholding_tax'] ?? 0));
$data['net_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['net_amount'] ?? 0));
return Arr::except($data, ['client', 'transactions']);
}
protected function afterCreate(): void
{
$transactions = $this->form->getState()['transactions'] ?? [];
try {
app(CreateRecordTransactionsAction::class)($this->getRecord(), $transactions);
$accountIds = collect($transactions)
->pluck('account_id')
->filter()
->unique()
->values()
->all();
$this->getRecord()->accounts()->sync($accountIds);
$this->commitDatabaseTransaction();
} catch (Exception $exception) {
$this->rollBackDatabaseTransaction();
throw new LogicException('Failed to save transactions : '.$exception->getMessage());
}
}
}

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\ExpenseResource\Pages; namespace App\Filament\Resources\Expenses\Pages;
use App\Filament\Resources\ExpenseResource; use Filament\Actions\DeleteAction;
use App\Filament\Resources\Expenses\ExpenseResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
@@ -13,7 +14,7 @@ class EditExpense extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\DeleteAction::make(), DeleteAction::make(),
]; ];
} }

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\ExpenseResource\Pages; namespace App\Filament\Resources\Expenses\Pages;
use App\Filament\Resources\ExpenseResource; use Filament\Actions\CreateAction;
use App\Filament\Resources\Expenses\ExpenseResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListExpenses extends ListRecords
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make(), CreateAction::make(),
]; ];
} }
} }

View File

@@ -1,8 +1,10 @@
<?php <?php
namespace App\Filament\Resources\Shield\RoleResource\Pages; declare(strict_types=1);
use App\Filament\Resources\Shield\RoleResource; namespace App\Filament\Resources\Roles\Pages;
use App\Filament\Resources\Roles\Roles\RoleResource;
use BezhanSalleh\FilamentShield\Support\Utils; use BezhanSalleh\FilamentShield\Support\Utils;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
@@ -10,29 +12,30 @@ use Illuminate\Support\Collection;
class CreateRole extends CreateRecord class CreateRole extends CreateRecord
{ {
protected static string $resource = RoleResource::class;
public Collection $permissions; public Collection $permissions;
protected static string $resource = RoleResource::class;
protected function mutateFormDataBeforeCreate(array $data): array protected function mutateFormDataBeforeCreate(array $data): array
{ {
$this->permissions = collect($data) $this->permissions = collect($data)
->filter(function ($permission, $key) { ->filter(fn (mixed $permission, string $key): bool => ! in_array($key, ['name', 'guard_name', 'select_all', Utils::getTenantModelForeignKey()]))
return ! in_array($key, ['name', 'guard_name', 'select_all']);
})
->values() ->values()
->flatten() ->flatten()
->unique(); ->unique();
if (Utils::isTenancyEnabled() && Arr::has($data, Utils::getTenantModelForeignKey()) && filled($data[Utils::getTenantModelForeignKey()])) {
return Arr::only($data, ['name', 'guard_name', Utils::getTenantModelForeignKey()]);
}
return Arr::only($data, ['name', 'guard_name']); return Arr::only($data, ['name', 'guard_name']);
} }
protected function afterCreate(): void protected function afterCreate(): void
{ {
$permissionModels = collect(); $permissionModels = collect();
$this->permissions->each(function ($permission) use ($permissionModels) { $this->permissions->each(function (string $permission) use ($permissionModels): void {
$permissionModels->push(Utils::getPermissionModel()::firstOrCreate([ $permissionModels->push(Utils::getPermissionModel()::firstOrCreate([
/** @phpstan-ignore-next-line */
'name' => $permission, 'name' => $permission,
'guard_name' => $this->data['guard_name'], 'guard_name' => $this->data['guard_name'],
])); ]));

View File

@@ -1,50 +1,55 @@
<?php <?php
namespace App\Filament\Resources\Shield\RoleResource\Pages; declare(strict_types=1);
use App\Filament\Resources\Shield\RoleResource; namespace App\Filament\Resources\Roles\Pages;
use App\Filament\Resources\Roles\Roles\RoleResource;
use BezhanSalleh\FilamentShield\Support\Utils; use BezhanSalleh\FilamentShield\Support\Utils;
use Filament\Actions; use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class EditRole extends EditRecord class EditRole extends EditRecord
{ {
protected static string $resource = RoleResource::class;
public Collection $permissions; public Collection $permissions;
protected static string $resource = RoleResource::class;
protected function getActions(): array protected function getActions(): array
{ {
return [ return [
Actions\DeleteAction::make(), DeleteAction::make(),
]; ];
} }
protected function mutateFormDataBeforeSave(array $data): array protected function mutateFormDataBeforeSave(array $data): array
{ {
$this->permissions = collect($data) $this->permissions = collect($data)
->filter(function ($permission, $key) { ->filter(fn (mixed $permission, string $key): bool => ! in_array($key, ['name', 'guard_name', 'select_all', Utils::getTenantModelForeignKey()]))
return ! in_array($key, ['name', 'guard_name', 'select_all']);
})
->values() ->values()
->flatten() ->flatten()
->unique(); ->unique();
if (Utils::isTenancyEnabled() && Arr::has($data, Utils::getTenantModelForeignKey()) && filled($data[Utils::getTenantModelForeignKey()])) {
return Arr::only($data, ['name', 'guard_name', Utils::getTenantModelForeignKey()]);
}
return Arr::only($data, ['name', 'guard_name']); return Arr::only($data, ['name', 'guard_name']);
} }
protected function afterSave(): void protected function afterSave(): void
{ {
$permissionModels = collect(); $permissionModels = collect();
$this->permissions->each(function ($permission) use ($permissionModels) { $this->permissions->each(function (string $permission) use ($permissionModels): void {
$permissionModels->push(Utils::getPermissionModel()::firstOrCreate([ $permissionModels->push(Utils::getPermissionModel()::firstOrCreate([
'name' => $permission, 'name' => $permission,
'guard_name' => $this->data['guard_name'], 'guard_name' => $this->data['guard_name'],
])); ]));
}); });
// @phpstan-ignore-next-line
$this->record->syncPermissions($permissionModels); $this->record->syncPermissions($permissionModels);
} }
} }

View File

@@ -1,9 +1,11 @@
<?php <?php
namespace App\Filament\Resources\Shield\RoleResource\Pages; declare(strict_types=1);
use App\Filament\Resources\Shield\RoleResource; namespace App\Filament\Resources\Roles\Pages;
use Filament\Actions;
use App\Filament\Resources\Roles\Roles\RoleResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
class ListRoles extends ListRecords class ListRoles extends ListRecords
@@ -13,7 +15,7 @@ class ListRoles extends ListRecords
protected function getActions(): array protected function getActions(): array
{ {
return [ return [
Actions\CreateAction::make(), CreateAction::make(),
]; ];
} }
} }

View File

@@ -1,9 +1,11 @@
<?php <?php
namespace App\Filament\Resources\Shield\RoleResource\Pages; declare(strict_types=1);
use App\Filament\Resources\Shield\RoleResource; namespace App\Filament\Resources\Roles\Pages;
use Filament\Actions;
use App\Filament\Resources\Roles\Roles\RoleResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord; use Filament\Resources\Pages\ViewRecord;
class ViewRole extends ViewRecord class ViewRole extends ViewRecord
@@ -13,7 +15,7 @@ class ViewRole extends ViewRecord
protected function getActions(): array protected function getActions(): array
{ {
return [ return [
Actions\EditAction::make(), EditAction::make(),
]; ];
} }
} }

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Roles\Roles;
use BezhanSalleh\FilamentShield\FilamentShieldPlugin;
use App\Filament\Resources\Roles\Pages\CreateRole;
use App\Filament\Resources\Roles\Pages\EditRole;
use App\Filament\Resources\Roles\Pages\ListRoles;
use App\Filament\Resources\Roles\Pages\ViewRole;
use BezhanSalleh\FilamentShield\Support\Utils;
use BezhanSalleh\FilamentShield\Traits\HasShieldFormComponents;
use BezhanSalleh\PluginEssentials\Concerns\Resource as Essentials;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Facades\Filament;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Panel;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Unique;
class RoleResource extends Resource
{
use Essentials\BelongsToParent;
use Essentials\BelongsToTenant;
use Essentials\HasGlobalSearch;
use Essentials\HasLabels;
use Essentials\HasNavigation;
use HasShieldFormComponents;
protected static ?string $recordTitleAttribute = 'name';
public static function form(Schema $schema): Schema
{
return $schema
->components([
Grid::make()
->schema([
Section::make()
->schema([
TextInput::make('name')
->label(__('filament-shield::filament-shield.field.name'))
->unique(
ignoreRecord: true, /** @phpstan-ignore-next-line */
modifyRuleUsing: fn (Unique $rule): Unique => Utils::isTenancyEnabled() ? $rule->where(Utils::getTenantModelForeignKey(), Filament::getTenant()?->id) : $rule
)
->required()
->maxLength(255),
TextInput::make('guard_name')
->label(__('filament-shield::filament-shield.field.guard_name'))
->default(Utils::getFilamentAuthGuard())
->nullable()
->maxLength(255),
Select::make(config('permission.column_names.team_foreign_key'))
->label(__('filament-shield::filament-shield.field.team'))
->placeholder(__('filament-shield::filament-shield.field.team.placeholder'))
/** @phpstan-ignore-next-line */
->default(Filament::getTenant()?->id)
->options(fn (): array => in_array(Utils::getTenantModel(), [null, '', '0'], true) ? [] : Utils::getTenantModel()::pluck('name', 'id')->toArray())
->visible(fn (): bool => static::shield()->isCentralApp() && Utils::isTenancyEnabled())
->dehydrated(fn (): bool => static::shield()->isCentralApp() && Utils::isTenancyEnabled()),
static::getSelectAllFormComponent(),
])
->columns([
'sm' => 2,
'lg' => 3,
])
->columnSpanFull(),
])
->columnSpanFull(),
static::getShieldFormComponents(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
->weight(FontWeight::Medium)
->label(__('filament-shield::filament-shield.column.name'))
->formatStateUsing(fn (string $state): string => Str::headline($state))
->searchable(),
TextColumn::make('guard_name')
->badge()
->color('warning')
->label(__('filament-shield::filament-shield.column.guard_name')),
TextColumn::make('team.name')
->default('Global')
->badge()
->color(fn (mixed $state): string => str($state)->contains('Global') ? 'gray' : 'primary')
->label(__('filament-shield::filament-shield.column.team'))
->searchable()
->visible(fn (): bool => static::shield()->isCentralApp() && Utils::isTenancyEnabled()),
TextColumn::make('permissions_count')
->badge()
->label(__('filament-shield::filament-shield.column.permissions'))
->counts('permissions')
->color('primary'),
TextColumn::make('updated_at')
->label(__('filament-shield::filament-shield.column.updated_at'))
->dateTime(),
])
->filters([
//
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListRoles::route('/'),
'create' => CreateRole::route('/create'),
'view' => ViewRole::route('/{record}'),
'edit' => EditRole::route('/{record}/edit'),
];
}
public static function getModel(): string
{
return Utils::getRoleModel();
}
public static function getSlug(?Panel $panel = null): string
{
return Utils::getResourceSlug();
}
public static function getCluster(): ?string
{
return Utils::getResourceCluster();
}
public static function getEssentialsPlugin(): ?FilamentShieldPlugin
{
return FilamentShieldPlugin::get();
}
}

View File

@@ -1,262 +0,0 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\SaleResource\Pages;
use App\Models\Account;
use App\Models\Branch;
use App\Models\Client;
use App\Models\Sale;
use Awcodes\TableRepeater\Components\TableRepeater;
use Awcodes\TableRepeater\Header;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class SaleResource extends Resource
{
protected static ?string $model = Sale::class;
protected static ?string $navigationIcon = 'heroicon-o-currency-dollar';
protected static bool $shouldRegisterNavigation = false;
public static function form(Form $form): Form
{
return $form
->schema([
Select::make('client')
->default(request()->query('client_id'))
->options(Client::query()->get()->pluck('company', 'id'))
->afterStateUpdated(function ($set, $get) {
$set('branch_id', '');
})
->required()
->live(),
Select::make('branch_id')
->relationship('branch', 'code')
->options(fn ($get) => Branch::query()->where('client_id', $get('client'))->get()->pluck('code', 'id'))
->required()
->afterStateUpdated(function ($set, $get) {
$set('current_series', static::getSeries($get));
$set('transactions.*.branch_id', $get('branch_id'));
})
->live(),
TextInput::make('current_series')
->label('Series')
->disabled(),
DatePicker::make('happened_on')->label('Date')
->required()
->afterStateUpdated(function ($set, $get) {
$set('transactions.*.happened_on', $get('happened_on'));
})
->native(false),
Checkbox::make('with_discount')->label('With Discount?')->default(false)->live(),
TableRepeater::make('transactions')
->headers(fn (Get $get): array => static::getTransactionTableHeader($get))
->relationship('transactions')
->schema(fn (Get $get): array => static::getTransactionTableFormSchema($get))
->visible(fn (Get $get) => $get('branch_id') != null)
->columnSpan('full'),
]);
}
public static function getSeries(Get $get): string
{
$branch = Branch::find($get('branch_id'));
if ($branch) {
$currentSeries = $branch->current_series;
return str_pad($currentSeries + 1, 6, '0', STR_PAD_LEFT);
}
return '';
}
private static function getTransactionTableHeader(Get $get): array
{
if ($get('with_discount')) {
return [
Header::make('Charge Account'),
Header::make('Description'),
Header::make('Gross Amount'),
Header::make('Exempt'),
Header::make('Vatable Amount'),
Header::make('Output Tax'),
Header::make('Withholding Tax'),
Header::make('Discount'),
Header::make('Net Amount'),
];
}
return [
Header::make('Charge Account'),
Header::make('Description'),
Header::make('Gross Amount'),
Header::make('Exempt'),
Header::make('Vatable Amount'),
Header::make('Output Tax'),
Header::make('Withholding Tax'),
Header::make('Net Amount'),
];
}
private static function getTransactionTableFormSchema(Get $get): array
{
return [
Select::make('account_id')->options(fn ($get) => static::getAccountOptions($get)),
TextInput::make('description')->label('Description'),
Hidden::make('branch_id')->default(fn (Get $get) => $get('../../branch_id')),
TextInput::make('gross_amount')
->numeric()
->live(false, 500)
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
static::setDefaultFormValues($get, $set, $old, $state);
})->default(0),
TextInput::make('exempt')
->numeric()
->live()
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
static::setDefaultFormValues($get, $set, $old, $state);
})->default(0),
TextInput::make('vatable_amount')
->numeric()
->nullable()
->live()
->readOnly()
->default(0),
Hidden::make('happened_on')->default(fn (Get $get) => $get('../../happened_on')),
Hidden::make('with_discount')->default(fn (Get $get) => $get('../../with_discount')),
TextInput::make('output_tax')
->numeric()
->live()
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
static::setDefaultFormValues($get, $set, $old, $state);
})->default(0),
TextInput::make('payable_withholding_tax')
->numeric()
->live()
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
static::setDefaultFormValues($get, $set, $old, $state);
})->default(0),
TextInput::make('discount')
->numeric()
->readOnly()
->visible(fn (Get $get) => $get('../../with_discount'))
->live(),
TextInput::make('net_amount')->numeric()->default(0),
];
}
private static function getAccountOptions($get)
{
$query = Account::query();
$query->where([
'client_id' => $get('../../client'),
]);
if ($get('../../branch_id')) {
$query->whereHas('balances', function ($query) use ($get) {
return $query->where('branch_id', $get('../../branch_id'));
});
}
$query->whereHas('accountType', function ($query) {
return $query->where('type', 'Revenue');
});
return $query->get()->pluck('account', 'id');
}
private static function setDefaultFormValues(Get $get, Set $set, ?string $old, ?string $state)
{
$exempt = (float) $get('exempt');
$withHoldingTax = (float) $get('payable_withholding_tax');
$vatableSales = $get('gross_amount');
$vatableAmount = 0;
if ($vatableSales) {
$vatableAmount = $vatableSales / 1.12;
}
$discount = $exempt * .20;
$outputTax = $vatableAmount * 0.12;
//default net amount
$netAmount = (int) $vatableSales - $get('payable_withholding_tax');
//net amount if vatable
if (ExpenseResource::getIsVatable($get)) {
$netAmount = ($vatableAmount + $exempt) - $withHoldingTax;
}
//if discounted
if ($get('../../with_discount')) {
$netAmount = $netAmount - $discount;
}
$set('output_tax', number_format($outputTax, 2, '.', ''));
$set('discount', number_format($discount, 2, '.', ''));
$set('vatable_amount', number_format($vatableAmount, 2, '.', ''));
$set('net_amount', number_format($netAmount, 2, '.', ''));
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('id')->label('ID')->sortable(),
TextColumn::make('client.name')->label('Client')->sortable(),
TextColumn::make('branch.name')->label('Branch')->sortable(),
TextColumn::make('happened_on')->label('Date')->date()->sortable(),
TextColumn::make('gross_amount')->label('Gross Amount')->numeric()->sortable(),
TextColumn::make('exempt')->label('Exempt')->numeric()->sortable(),
TextColumn::make('vatable_amount')->label('Vatable Amount')->numeric()->sortable(),
TextColumn::make('output_tax')->label('Output Tax')->numeric()->sortable(),
TextColumn::make('payable_withholding_tax')->label('Payable Withholding Tax')->numeric()->sortable(),
TextColumn::make('discount')->label('Discount')->numeric()->sortable(),
TextColumn::make('net_amount')->label('Net Amount')->numeric()->sortable(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSales::route('/'),
'create' => Pages\CreateSale::route('/create'),
'edit' => Pages\EditSale::route('/{record}/edit'),
];
}
}

View File

@@ -1,71 +0,0 @@
<?php
namespace App\Filament\Resources\SaleResource\Pages;
use App\Actions\Transactions\CreateTransactionAction;
use App\DataObjects\CreateTransactionDTO;
use App\Filament\Resources\SaleResource;
use App\Models\Branch;
use App\Models\Sale;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Pipeline;
class CreateSale extends CreateRecord
{
protected static string $resource = SaleResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
return $this->getFormDataMutation($data);
}
protected function handleRecordCreation(array $data): Model
{
$transactions = $this->data['transactions'] ?? [];
return $this->processCreate($data, $transactions);
}
public function getFormDataMutation(array $data): array
{
return Arr::except($data, ['client', 'transactions', 'with_discount']);
}
public function processCreate(array $data, array $transactions): Model
{
try {
DB::beginTransaction();
$record = Sale::create($data);
$branch = $record->branch;
foreach ($transactions as $transaction) {
$tData = [
'branch_id' => $branch->id,
'happened_on' => $record->happened_on,
...$transaction,
];
$payload = new CreateTransactionDTO(data: $tData, transactionable: $record);
Pipeline::send(passable: $payload)->through(
[
CreateTransactionAction::class,
]
)->thenReturn();
}
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
throw new \Exception('Failed to save transactions : '.$exception->getMessage());
}
return $record;
}
protected function afterCreate(): void
{
$branch = Branch::find($this->data['branch_id']);
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace App\Filament\Resources\Sales\Pages;
use App\Actions\Sales\CreateSaleAction;
use App\Actions\Sales\SyncAccountsAction;
use App\Actions\Transactions\CreateRecordTransactionsAction;
use App\Commands\Clients\GenerateBaseAccountCommand;
use App\Filament\Resources\Clients\ClientResource;
use App\Filament\Resources\Sales\SaleResource;
use App\Models\Branch;
use App\Models\Client;
use App\Models\Sale;
use App\Services\Sales\SaleService;
use Filament\Actions\Action;
use Filament\Forms\Components\Actions;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
class CreateSale extends CreateRecord
{
protected static string $resource = SaleResource::class;
public ?int $clientId = null;
public function mount(): void
{
parent::mount();
$this->clientId = request()->integer('client_id');
}
public function getBreadcrumbs(): array
{
$client = $this->getClient();
if (! $client) {
return parent::getBreadcrumbs();
}
return [
ClientResource::getUrl('view', ['record' => $client->id]) => $client->company,
ClientResource::getUrl('view', ['record' => $client->id]).'?activeRelationManager=3' => 'Sales',
$this->getResource()::getUrl('create', ['client_id' => $client->id]) => 'Create',
];
}
protected function getClient(): Client|null
{
if (! $this->clientId) {
return null;
}
return Client::find($this->clientId);
}
protected function mutateFormDataBeforeCreate(array $data): array
{
return $this->getFormDataMutation($data);
}
protected function handleRecordCreation(array $data): Model
{
$transactions = $this->data['transactions'] ?? [];
return $this->processCreate($data, $transactions);
}
public function getFormDataMutation(array $data): array
{
$transactions = $data['transactions'] ?? [];
$data['gross_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['gross_amount'] ?? 0));
$data['exempt'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['exempt'] ?? 0));
$data['vatable_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['vatable_amount'] ?? 0));
$data['output_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['output_tax'] ?? 0));
$data['payable_withholding_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['payable_withholding_tax'] ?? 0));
$discount = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['discount'] ?? 0));
$data['discount'] = $discount;
$data['net_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['net_amount'] ?? 0)) + $discount;
return Arr::except($data, ['client', 'transactions']);
}
public function processCreate(array $data, array $transactions): Model
{
$record = app(CreateSaleAction::class)($this->getFormDataMutation($data), $transactions);
return $record;
}
protected function getRedirectUrl(): string
{
$client = $this->getClient();
if (! $client) {
return parent::getRedirectUrl();
}
return ClientResource::getUrl('view', ['record' => $client->id]).'?activeRelationManager=3';
}
}

View File

@@ -1,10 +1,12 @@
<?php <?php
namespace App\Filament\Resources\SaleResource\Pages; namespace App\Filament\Resources\Sales\Pages;
use Filament\Actions\DeleteAction;
use Exception;
use App\Actions\Transactions\CreateTransactionAction; use App\Actions\Transactions\CreateTransactionAction;
use App\DataObjects\CreateTransactionDTO; use App\DataObjects\CreateTransactionDTO;
use App\Filament\Resources\SaleResource; use App\Filament\Resources\Sales\SaleResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -19,7 +21,7 @@ class EditSale extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\DeleteAction::make(), DeleteAction::make(),
]; ];
} }
@@ -36,6 +38,16 @@ class EditSale extends EditRecord
public function getFormDataMutation(array $data): array public function getFormDataMutation(array $data): array
{ {
$transactions = $data['transactions'] ?? [];
$data['gross_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['gross_amount'] ?? 0));
$data['exempt'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['exempt'] ?? 0));
$data['vatable_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['vatable_amount'] ?? 0));
$data['output_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['output_tax'] ?? 0));
$data['payable_withholding_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['payable_withholding_tax'] ?? 0));
$data['discount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['discount'] ?? 0));
$data['net_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['net_amount'] ?? 0));
return Arr::except($data, ['client', 'transactions', 'with_discount']); return Arr::except($data, ['client', 'transactions', 'with_discount']);
} }
@@ -71,10 +83,20 @@ class EditSale extends EditRecord
] ]
)->thenReturn(); )->thenReturn();
} }
$accountIds = collect($transactions)
->pluck('account_id')
->filter()
->unique()
->values()
->all();
$record->accounts()->sync($accountIds);
DB::commit(); DB::commit();
} catch (\Exception $exception) { } catch (Exception $exception) {
DB::rollBack(); DB::rollBack();
throw new \Exception('Failed to save transactions : '.$exception->getMessage()); throw new Exception('Failed to save transactions : '.$exception->getMessage());
} }
return $record; return $record;

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\SaleResource\Pages; namespace App\Filament\Resources\Sales\Pages;
use App\Filament\Resources\SaleResource; use Filament\Actions\CreateAction;
use App\Filament\Resources\Sales\SaleResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListSales extends ListRecords
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make(), CreateAction::make(),
]; ];
} }
} }

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Filament\Resources\Sales;
use App\Filament\Resources\Expenses\ExpenseResource;
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Actions\EditAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Filament\Resources\Sales\Pages\ListSales;
use App\Filament\Resources\Sales\Pages\CreateSale;
use App\Filament\Resources\Sales\Pages\EditSale;
use App\Filament\Resources\SaleResource\Pages;
use App\Filament\Resources\Sales\Schemas\CreateSaleSchema;
use App\Models\Account;
use App\Models\Branch;
use App\Models\Client;
use App\Models\Discount;
use App\Models\Sale;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Repeater\TableColumn;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Icetalker\FilamentTableRepeater\Forms\Components\TableRepeater;
class SaleResource extends Resource
{
protected static ?string $model = Sale::class;
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-currency-dollar';
protected static bool $shouldRegisterNavigation = false;
public static function form(Schema $schema): Schema
{
return (new CreateSaleSchema())->configure($schema);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('branch.code')->label('Branch')->sortable(),
TextColumn::make('reference_number')->label('Reference Number')->sortable(),
TextColumn::make('happened_on')->label('Date')->date()->sortable(),
TextColumn::make('user.name')->label('Created By')->sortable(),
])
->filters([
//
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListSales::route('/'),
'create' => CreateSale::route('/create'),
'edit' => EditSale::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace App\Filament\Resources\Sales\Schemas;
use App\Filament\Resources\Expenses\ExpenseResource;
use App\Models\Account;
use App\Models\Branch;
use App\Models\Client;
use App\Models\Discount;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Icetalker\FilamentTableRepeater\Forms\Components\TableRepeater;
class CreateSaleSchema
{
/**
* Create a new class instance.
*/
public function configure(Schema $schema): Schema
{
return $schema
->components([
Select::make('client')
->default(fn () => request()->integer('client_id'))
->options(Client::query()->get()->pluck('company', 'id'))
->afterStateUpdated(function ($set, $get) {
$set('branch_id', '');
})
->required()
->live(),
Select::make('branch_id')
->relationship('branch', 'code')
->options(fn ($get) => Branch::query()->where('client_id', $get('client'))->get()->pluck('code', 'id'))
->required()
->afterStateUpdated(function ($set, $get) {
$set('current_series', $this->getSeries($get));
$set('transactions.*.branch_id', $get('branch_id'));
})
->live(),
TextInput::make('current_series')
->label('Series')
->readOnly(),
DatePicker::make('happened_on')->label('Date')
->required()
->afterStateUpdated(function ($set, $get) {
$set('transactions.*.happened_on', $get('happened_on'));
})
->native(false),
Checkbox::make('with_discount')->label('With Discount?')->default(false)->live(),
TableRepeater::make('transactions')
->relationship('transactions')
->schema([
Select::make('account_id')->options(fn (Get $get) => $this->getAccountOptions($get)),
TextInput::make('description')->label('Description'),
TextInput::make('gross_amount')
->numeric()
->live(false, 500)
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
$this->setDefaultFormValues($get, $set, $old, $state);
})->default(0),
TextInput::make('vatable_amount')
->numeric()
->required(),
TextInput::make('vatable_amount')
->numeric()
->required(),
TextInput::make('output_tax')
->numeric()
->live()
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
$this->setDefaultFormValues($get, $set, $old, $state);
})->default(0),
TextInput::make('payable_withholding_tax')
->numeric()
->live()
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
$this->setDefaultFormValues($get, $set, $old, $state);
})->default(0),
TextInput::make('discount')
->numeric()
->disabled(fn (Get $get) => !$get('../../with_discount'))
->live(),
Select::make('discount_type')
->options(fn (Get $get) => $this->getDiscountOptions($get))
->disabled(fn (Get $get) => !$get('../../with_discount'))
->required(fn (Get $get) => $get('../../with_discount')),
TextInput::make('net_amount')->numeric()->default(0),
Hidden::make('branch_id')->default(fn (Get $get) => $get('../../branch_id')),
Hidden::make('happened_on')->default(fn (Get $get) => $get('../../happened_on')),
Hidden::make('with_discount')->default(fn (Get $get) => $get('../../with_discount')),
Hidden::make('exempt')->default(0),
])
->visible(fn (Get $get) => $get('branch_id') != null)
->reorderable()
->cloneable()
->collapsible()
->minItems(1)
->columnSpan('full'),
]);
}
public function getSeries(Get $get): string
{
$branch = Branch::find($get('branch_id'));
if ($branch) {
$currentSeries = $branch->current_series;
return str_pad($currentSeries + 1, 6, '0', STR_PAD_LEFT);
}
return '';
}
public function getTableComponents(Get $get): array
{
return [
];
}
private function setDefaultFormValues(Get $get, Set $set, ?string $old, ?string $state)
{
$exempt = (float) $get('exempt');
$withHoldingTax = (float) $get('payable_withholding_tax');
$vatableSales = $get('gross_amount');
$vatableAmount = 0;
if ($vatableSales) {
$vatableAmount = $vatableSales / 1.12;
}
$discount = $exempt * .20;
$outputTax = $vatableAmount * 0.12;
// default net amount
$netAmount = (int) $vatableSales - $get('payable_withholding_tax');
// net amount if vatable
if (ExpenseResource::getIsVatable($get)) {
$netAmount = ($vatableAmount + $exempt) - $withHoldingTax;
}
// if discounted
if ($get('../../with_discount')) {
$netAmount = $netAmount - $discount;
}
$set('output_tax', number_format($outputTax, 2, '.', ''));
// $set('discount', number_format($discount, 2, '.', ''));
$set('vatable_amount', number_format($vatableAmount, 2, '.', ''));
$set('net_amount', number_format($netAmount, 2, '.', ''));
}
private function getDiscountOptions(Get $get)
{
$query = Discount::query()->where('client_id', $get('../../client'));
return $query->pluck('discount', 'id');
}
private function getAccountOptions($get)
{
$query = Account::query();
$query->where([
'client_id' => $get('../../client'),
]);
// if ($get('../../branch_id')) {
// $query->whereHas('balances', function ($query) use ($get) {
// return $query->where('branch_id', $get('../../branch_id'));
// });
// }
$query->whereHas('accountType', function ($query) {
return $query->where('type', 'Revenue');
});
return $query->get()->pluck('account', 'id');
}
}

View File

@@ -1,400 +0,0 @@
<?php
namespace App\Filament\Resources\Shield;
use App\Filament\Resources\Shield\RoleResource\Pages;
use BezhanSalleh\FilamentShield\Contracts\HasShieldPermissions;
use BezhanSalleh\FilamentShield\Facades\FilamentShield;
use BezhanSalleh\FilamentShield\FilamentShieldPlugin;
use BezhanSalleh\FilamentShield\Forms\ShieldSelectAllToggle;
use BezhanSalleh\FilamentShield\Support\Utils;
use Filament\Forms;
use Filament\Forms\Components\Component;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
class RoleResource extends Resource implements HasShieldPermissions
{
protected static ?string $recordTitleAttribute = 'name';
public static function getPermissionPrefixes(): array
{
return [
'view',
'view_any',
'create',
'update',
'delete',
'delete_any',
];
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Grid::make()
->schema([
Forms\Components\Section::make()
->schema([
Forms\Components\TextInput::make('name')
->label(__('filament-shield::filament-shield.field.name'))
->unique(ignoreRecord: true)
->required()
->maxLength(255),
Forms\Components\TextInput::make('guard_name')
->label(__('filament-shield::filament-shield.field.guard_name'))
->default(Utils::getFilamentAuthGuard())
->nullable()
->maxLength(255),
ShieldSelectAllToggle::make('select_all')
->onIcon('heroicon-s-shield-check')
->offIcon('heroicon-s-shield-exclamation')
->label(__('filament-shield::filament-shield.field.select_all.name'))
->helperText(fn (): HtmlString => new HtmlString(__('filament-shield::filament-shield.field.select_all.message')))
->dehydrated(fn ($state): bool => $state),
])
->columns([
'sm' => 2,
'lg' => 3,
]),
]),
Forms\Components\Tabs::make('Permissions')
->contained()
->tabs([
static::getTabFormComponentForResources(),
static::getTabFormComponentForPage(),
static::getTabFormComponentForWidget(),
static::getTabFormComponentForCustomPermissions(),
])
->columnSpan('full'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->badge()
->label(__('filament-shield::filament-shield.column.name'))
->formatStateUsing(fn ($state): string => Str::headline($state))
->colors(['primary'])
->searchable(),
Tables\Columns\TextColumn::make('guard_name')
->badge()
->label(__('filament-shield::filament-shield.column.guard_name')),
Tables\Columns\TextColumn::make('permissions_count')
->badge()
->label(__('filament-shield::filament-shield.column.permissions'))
->counts('permissions')
->colors(['success']),
Tables\Columns\TextColumn::make('updated_at')
->label(__('filament-shield::filament-shield.column.updated_at'))
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListRoles::route('/'),
'create' => Pages\CreateRole::route('/create'),
'view' => Pages\ViewRole::route('/{record}'),
'edit' => Pages\EditRole::route('/{record}/edit'),
];
}
public static function getCluster(): ?string
{
return Utils::getResourceCluster() ?? static::$cluster;
}
public static function getModel(): string
{
return Utils::getRoleModel();
}
public static function getModelLabel(): string
{
return __('filament-shield::filament-shield.resource.label.role');
}
public static function getPluralModelLabel(): string
{
return __('filament-shield::filament-shield.resource.label.roles');
}
public static function shouldRegisterNavigation(): bool
{
return Utils::isResourceNavigationRegistered();
}
public static function getNavigationGroup(): ?string
{
return 'Security Settings';
}
public static function getNavigationLabel(): string
{
return __('filament-shield::filament-shield.nav.role.label');
}
public static function getNavigationIcon(): string
{
return __('filament-shield::filament-shield.nav.role.icon');
}
public static function getNavigationSort(): ?int
{
return Utils::getResourceNavigationSort();
}
public static function getSlug(): string
{
return Utils::getResourceSlug();
}
public static function getNavigationBadge(): ?string
{
return Utils::isResourceNavigationBadgeEnabled()
? strval(static::getEloquentQuery()->count())
: null;
}
public static function isScopedToTenant(): bool
{
return Utils::isScopedToTenant();
}
public static function canGloballySearch(): bool
{
return Utils::isResourceGloballySearchable() && count(static::getGloballySearchableAttributes()) && static::canViewAny();
}
public static function getResourceEntitiesSchema(): ?array
{
return collect(FilamentShield::getResources())
->sortKeys()
->map(function ($entity) {
$sectionLabel = strval(
static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedResourceLabel($entity['fqcn'])
: $entity['model']
);
return Forms\Components\Section::make($sectionLabel)
->description(fn () => new HtmlString('<span style="word-break: break-word;">'.Utils::showModelPath($entity['fqcn']).'</span>'))
->compact()
->schema([
static::getCheckBoxListComponentForResource($entity),
])
->columnSpan(static::shield()->getSectionColumnSpan())
->collapsible();
})
->toArray();
}
public static function getResourceTabBadgeCount(): ?int
{
return collect(FilamentShield::getResources())
->map(fn ($resource) => count(static::getResourcePermissionOptions($resource)))
->sum();
}
public static function getResourcePermissionOptions(array $entity): array
{
return collect(Utils::getResourcePermissionPrefixes($entity['fqcn']))
->flatMap(function ($permission) use ($entity) {
$name = $permission.'_'.$entity['resource'];
$label = static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedResourcePermissionLabel($permission)
: $name;
return [
$name => $label,
];
})
->toArray();
}
public static function setPermissionStateForRecordPermissions(Component $component, string $operation, array $permissions, ?Model $record): void
{
if (in_array($operation, ['edit', 'view'])) {
if (blank($record)) {
return;
}
if ($component->isVisible() && count($permissions) > 0) {
$component->state(
collect($permissions)
/** @phpstan-ignore-next-line */
->filter(fn ($value, $key) => $record->checkPermissionTo($key))
->keys()
->toArray()
);
}
}
}
public static function getPageOptions(): array
{
return collect(FilamentShield::getPages())
->flatMap(fn ($page) => [
$page['permission'] => static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedPageLabel($page['class'])
: $page['permission'],
])
->toArray();
}
public static function getWidgetOptions(): array
{
return collect(FilamentShield::getWidgets())
->flatMap(fn ($widget) => [
$widget['permission'] => static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedWidgetLabel($widget['class'])
: $widget['permission'],
])
->toArray();
}
public static function getCustomPermissionOptions(): ?array
{
return FilamentShield::getCustomPermissions()
->mapWithKeys(fn ($customPermission) => [
$customPermission => static::shield()->hasLocalizedPermissionLabels() ? str($customPermission)->headline()->toString() : $customPermission,
])
->toArray();
}
public static function getTabFormComponentForResources(): Component
{
return static::shield()->hasSimpleResourcePermissionView()
? static::getTabFormComponentForSimpleResourcePermissionsView()
: Forms\Components\Tabs\Tab::make('resources')
->label(__('filament-shield::filament-shield.resources'))
->visible(fn (): bool => (bool) Utils::isResourceEntityEnabled())
->badge(static::getResourceTabBadgeCount())
->schema([
Forms\Components\Grid::make()
->schema(static::getResourceEntitiesSchema())
->columns(static::shield()->getGridColumns()),
]);
}
public static function getCheckBoxListComponentForResource(array $entity): Component
{
$permissionsArray = static::getResourcePermissionOptions($entity);
return static::getCheckboxListFormComponent($entity['resource'], $permissionsArray, false);
}
public static function getTabFormComponentForPage(): Component
{
$options = static::getPageOptions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('pages')
->label(__('filament-shield::filament-shield.pages'))
->visible(fn (): bool => (bool) Utils::isPageEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('pages_tab', $options),
]);
}
public static function getTabFormComponentForWidget(): Component
{
$options = static::getWidgetOptions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('widgets')
->label(__('filament-shield::filament-shield.widgets'))
->visible(fn (): bool => (bool) Utils::isWidgetEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('widgets_tab', $options),
]);
}
public static function getTabFormComponentForCustomPermissions(): Component
{
$options = static::getCustomPermissionOptions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('custom')
->label(__('filament-shield::filament-shield.custom'))
->visible(fn (): bool => (bool) Utils::isCustomPermissionEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('custom_permissions', $options),
]);
}
public static function getTabFormComponentForSimpleResourcePermissionsView(): Component
{
$options = FilamentShield::getAllResourcePermissions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('resources')
->label(__('filament-shield::filament-shield.resources'))
->visible(fn (): bool => (bool) Utils::isResourceEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('resources_tab', $options),
]);
}
public static function getCheckboxListFormComponent(string $name, array $options, bool $searchable = true): Component
{
return Forms\Components\CheckboxList::make($name)
->label('')
->options(fn (): array => $options)
->searchable($searchable)
->afterStateHydrated(
fn (Component $component, string $operation, ?Model $record) => static::setPermissionStateForRecordPermissions(
component: $component,
operation: $operation,
permissions: $options,
record: $record
)
)
->dehydrated(fn ($state) => ! blank($state))
->bulkToggleable()
->gridDirection('row')
->columns(static::shield()->getCheckboxListColumns())
->columnSpan(static::shield()->getCheckboxListColumnSpan());
}
public static function shield(): FilamentShieldPlugin
{
return FilamentShieldPlugin::get();
}
}

View File

@@ -1,55 +0,0 @@
<?php
namespace App\Filament\Resources\TransmittalResource\Pages;
use App\Filament\Resources\TransmittalResource;
use Filament\Actions\Action;
use Filament\Resources\Pages\CreateRecord;
use Throwable;
class CreateTransmittal extends CreateRecord
{
protected static string $resource = TransmittalResource::class;
public bool $willExport = false;
/**
* @throws Throwable
*/
public function createAndExport(): void
{
$this->willExport = true;
$this->create();
}
public function afterCreate()
{
if ($this->willExport) {
TransmittalResource::exportTransmittal([$this->record->id]);
}
}
public function getCreatedNotificationMessage(): ?string
{
if ($this->willExport) {
return 'Transmittal Was Created Successfully!, Check your notification for file download link';
}
return 'Transmittal Was Created Successfully!';
}
protected function getFormActions(): array
{
return [
$this->getCreateFormAction(),
$this->getCreateAnotherFormAction(),
Action::make('Create and Export')->action('createAndExport')->color('success'),
$this->getCancelFormAction(),
];
}
protected function getRedirectUrl(): string
{
return $this->previousUrl ?? $this->getResource()::getUrl('index');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Filament\Resources\Transmittals\Pages;
use App\Filament\Resources\Transmittals\TransmittalResource;
use Filament\Resources\Pages\CreateRecord;
class CreateTransmittal extends CreateRecord
{
protected static string $resource = TransmittalResource::class;
protected function getRedirectUrl(): string
{
return $this->previousUrl ?? $this->getResource()::getUrl('index');
}
}

View File

@@ -1,8 +1,10 @@
<?php <?php
namespace App\Filament\Resources\TransmittalResource\Pages; namespace App\Filament\Resources\Transmittals\Pages;
use App\Filament\Resources\TransmittalResource; use Filament\Actions\ViewAction;
use Filament\Actions\DeleteAction;
use App\Filament\Resources\Transmittals\TransmittalResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
@@ -13,8 +15,8 @@ class EditTransmittal extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\ViewAction::make(), ViewAction::make(),
Actions\DeleteAction::make(), DeleteAction::make(),
]; ];
} }
} }

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\TransmittalResource\Pages; namespace App\Filament\Resources\Transmittals\Pages;
use App\Filament\Resources\TransmittalResource; use Filament\Actions\CreateAction;
use App\Filament\Resources\Transmittals\TransmittalResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListTransmittals extends ListRecords
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make(), CreateAction::make(),
]; ];
} }
} }

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\TransmittalResource\Pages; namespace App\Filament\Resources\Transmittals\Pages;
use App\Filament\Resources\TransmittalResource; use Filament\Actions\EditAction;
use App\Filament\Resources\Transmittals\TransmittalResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ViewRecord; use Filament\Resources\Pages\ViewRecord;
@@ -13,7 +14,7 @@ class ViewTransmittal extends ViewRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\EditAction::make(), EditAction::make(),
]; ];
} }
} }

View File

@@ -1,12 +1,26 @@
<?php <?php
namespace App\Filament\Resources; namespace App\Filament\Resources\Transmittals;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\Layout\View;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\Action;
use Filament\Actions\ViewAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Schemas\Schema;
use App\Filament\Resources\Transmittals\Pages\ListTransmittals;
use App\Filament\Resources\Transmittals\Pages\CreateTransmittal;
use App\Filament\Resources\Transmittals\Pages\ViewTransmittal;
use App\Filament\Resources\Transmittals\Pages\EditTransmittal;
use App\Commands\Transmittal\GenerateTransmittalSeries; use App\Commands\Transmittal\GenerateTransmittalSeries;
use App\Commands\Transmittal\StoreTransmittalCommand; use App\Commands\Transmittal\StoreTransmittalCommand;
use App\Exports\TransmittalsExport;
use App\Filament\Resources\TransmittalResource\Pages; use App\Filament\Resources\TransmittalResource\Pages;
use App\Jobs\ExportCompleteJob; use App\Filament\Exports\TransmittalExcelExport;
use App\Models\Branch; use App\Models\Branch;
use App\Models\Client; use App\Models\Client;
use App\Models\Transmittal; use App\Models\Transmittal;
@@ -17,22 +31,20 @@ use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Support\Enums\FontWeight; use Filament\Support\Enums\FontWeight;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth;
use Malzariey\FilamentDaterangepickerFilter\Filters\DateRangeFilter; use Malzariey\FilamentDaterangepickerFilter\Filters\DateRangeFilter;
class TransmittalResource extends Resource class TransmittalResource extends Resource
{ {
protected static ?string $model = Transmittal::class; protected static ?string $model = Transmittal::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-rectangle-stack';
public static function getEloquentQuery(): Builder public static function getEloquentQuery(): Builder
{ {
@@ -43,8 +55,8 @@ class TransmittalResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\Layout\Split::make([ Split::make([
Tables\Columns\TextColumn::make('series') TextColumn::make('series')
->searchable(query: function (Builder $query, string $search): Builder { ->searchable(query: function (Builder $query, string $search): Builder {
$wildcardSearch = '%' . str_replace(' ', '%', $search) . '%'; $wildcardSearch = '%' . str_replace(' ', '%', $search) . '%';
@@ -61,15 +73,15 @@ class TransmittalResource extends Resource
->label('Series') ->label('Series')
->weight(FontWeight::Bold) ->weight(FontWeight::Bold)
->columnSpan(2), ->columnSpan(2),
Tables\Columns\Layout\Stack::make([ Stack::make([
Tables\Columns\TextColumn::make('client.company') TextColumn::make('client.company')
->searchable(query: function (Builder $query, string $search): Builder { ->searchable(query: function (Builder $query, string $search): Builder {
return $query->whereHas('client', function (Builder $query) use ($search) { return $query->whereHas('client', function (Builder $query) use ($search) {
$query->where('company', 'like', '%' . str_replace(' ', '%', $search) . '%'); $query->where('company', 'like', '%' . str_replace(' ', '%', $search) . '%');
}); });
}) })
->weight(FontWeight::SemiBold)->label('Client'), ->weight(FontWeight::SemiBold)->label('Client'),
Tables\Columns\TextColumn::make('branch.code') TextColumn::make('branch.code')
->searchable(query: function (Builder $query, string $search): Builder { ->searchable(query: function (Builder $query, string $search): Builder {
return $query->whereHas('branch', function (Builder $query) use ($search) { return $query->whereHas('branch', function (Builder $query) use ($search) {
$query->where('code', 'like', '%' . str_replace(' ', '%', $search) . '%'); $query->where('code', 'like', '%' . str_replace(' ', '%', $search) . '%');
@@ -78,7 +90,7 @@ class TransmittalResource extends Resource
->label('Branch'), ->label('Branch'),
]), ]),
]), ]),
Tables\Columns\Layout\View::make('transmittal.tables.collapsible-files-component')->collapsible(), View::make('transmittal.tables.collapsible-files-component')->collapsible(),
]) ])
->filters([ ->filters([
SelectFilter::make('client_id')->label('Client filter')->options(function () { SelectFilter::make('client_id')->label('Client filter')->options(function () {
@@ -97,14 +109,10 @@ class TransmittalResource extends Resource
]) ])
->heading('Transmittals') ->heading('Transmittals')
->description('Click on toggle button at the end of table row to show additional details.') ->description('Click on toggle button at the end of table row to show additional details.')
->actions(static::getTableActions()) ->recordActions(static::getTableActions())
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
Tables\Actions\BulkAction::make('Bulk Export')->action(function ($records) {
static::exportTransmittal(Arr::flatten($records->pluck('id')));
}),
]), ]),
]); ]);
} }
@@ -112,9 +120,20 @@ class TransmittalResource extends Resource
public static function getTableActions(): array public static function getTableActions(): array
{ {
return [ return [
Tables\Actions\Action::make('Export')->action(fn ($record) => static::exportTransmittal([$record->id])), Action::make('Export')
Tables\Actions\ViewAction::make(), ->label('Export as Excel')
Tables\Actions\Action::make('Update Status') ->icon('heroicon-o-arrow-down-tray')
->action(function (Transmittal $record, $livewire) {
$export = TransmittalExcelExport::make('transmittal');
return app()->call([$export, 'hydrate'], [
'livewire' => $livewire,
'records' => collect([$record]),
'formData' => null,
])->export();
}),
ViewAction::make(),
Action::make('Update Status')
->fillForm(function ($record) { ->fillForm(function ($record) {
return [ return [
'user_id' => $record->user_id, 'user_id' => $record->user_id,
@@ -123,7 +142,7 @@ class TransmittalResource extends Resource
'received_by' => $record->received_by, 'received_by' => $record->received_by,
]; ];
}) })
->form([ ->schema([
Select::make('user_id')->label('Dispatch By') Select::make('user_id')->label('Dispatch By')
->relationship('user', 'name') ->relationship('user', 'name')
->searchable() ->searchable()
@@ -139,42 +158,22 @@ class TransmittalResource extends Resource
}) })
->icon('heroicon-o-pencil-square') ->icon('heroicon-o-pencil-square')
->slideOver() ->slideOver()
->hidden(! auth()->user()->can('update_transmittal')), ->hidden(! Auth::user()->can('update_transmittal')),
Tables\Actions\EditAction::make(), EditAction::make(),
Tables\Actions\DeleteAction::make(), DeleteAction::make(),
]; ];
} }
public static function exportTransmittal(array $id): void public static function form(Schema $schema): Schema
{ {
$recipient = auth()->user(); return $schema
->components(static::getFormSchema());
static::generateExportNotification();
(new TransmittalsExport([$id]))->store('public/transmittal-export.xlsx')->chain([
app(ExportCompleteJob::class, ['user' => $recipient]),
]);
}
public static function generateExportNotification(): Notification
{
return Notification::make()
->title('Your export will be ready. check your notification for file download link.')
->success()
->send();
}
public static function form(Form $form): Form
{
return $form
->schema(static::getFormSchema());
} }
public static function getFormSchema(): array public static function getFormSchema(): array
{ {
return [ return [
Forms\Components\Select::make('client_id') Select::make('client_id')
->options(function () { ->options(function () {
return Client::query()->get()->pluck('company', 'id'); return Client::query()->get()->pluck('company', 'id');
}) })
@@ -182,11 +181,11 @@ class TransmittalResource extends Resource
->reactive() ->reactive()
->required() ->required()
->required()->columnSpan(3), ->required()->columnSpan(3),
Forms\Components\Select::make('branch_id')->label('Branch')->relationship('branch')->options(function (callable $get) { Select::make('branch_id')->label('Branch')->relationship('branch')->options(function (callable $get) {
return Branch::query()->where('client_id', $get('client_id'))->get()->pluck('code', 'id'); return Branch::query()->where('client_id', $get('client_id'))->get()->pluck('code', 'id');
})->required(), })->required(),
Forms\Components\TextInput::make('series')->readOnly()->default((new GenerateTransmittalSeries)->execute([]))->unique('transmittals', ignoreRecord: true), TextInput::make('series')->readOnly()->default((new GenerateTransmittalSeries)->execute([]))->unique('transmittals', ignoreRecord: true),
Forms\Components\DatePicker::make('date_created') DatePicker::make('date_created')
->native(false) ->native(false)
->required()->default(now()), ->required()->default(now()),
@@ -199,13 +198,13 @@ class TransmittalResource extends Resource
->relationship('notes') ->relationship('notes')
->label('Note') ->label('Note')
->schema([ ->schema([
Forms\Components\TextInput::make('comment')->label('Note')->required(), TextInput::make('comment')->label('Note')->required(),
]), ]),
Repeater::make('remarks') Repeater::make('remarks')
->relationship('remarks') ->relationship('remarks')
->label('Remarks') ->label('Remarks')
->schema([ ->schema([
Forms\Components\TextInput::make('remark')->label('Remark')->required(), TextInput::make('remark')->label('Remark')->required(),
]), ]),
]) ])
->columns(3) ->columns(3)
@@ -223,10 +222,10 @@ class TransmittalResource extends Resource
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => Pages\ListTransmittals::route('/'), 'index' => ListTransmittals::route('/'),
'create' => Pages\CreateTransmittal::route('/create'), 'create' => CreateTransmittal::route('/create'),
'view' => Pages\ViewTransmittal::route('/{record}'), 'view' => ViewTransmittal::route('/{record}'),
'edit' => Pages\EditTransmittal::route('/{record}/edit'), 'edit' => EditTransmittal::route('/{record}/edit'),
]; ];
} }
} }

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Resources\UserResource\Pages; namespace App\Filament\Resources\Users\Pages;
use App\Filament\Resources\UserResource; use App\Filament\Resources\Users\UserResource;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
class CreateUser extends CreateRecord class CreateUser extends CreateRecord

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\UserResource\Pages; namespace App\Filament\Resources\Users\Pages;
use App\Filament\Resources\UserResource; use Filament\Actions\DeleteAction;
use App\Filament\Resources\Users\UserResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
@@ -13,7 +14,7 @@ class EditUser extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\DeleteAction::make(), DeleteAction::make(),
]; ];
} }
} }

View File

@@ -1,8 +1,9 @@
<?php <?php
namespace App\Filament\Resources\UserResource\Pages; namespace App\Filament\Resources\Users\Pages;
use App\Filament\Resources\UserResource; use Filament\Actions\CreateAction;
use App\Filament\Resources\Users\UserResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListUsers extends ListRecords
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make()->icon('heroicon-o-user-plus')->slideOver(), CreateAction::make()->icon('heroicon-o-user-plus')->slideOver(),
]; ];
} }
} }

View File

@@ -1,13 +1,20 @@
<?php <?php
namespace App\Filament\Resources; namespace App\Filament\Resources\Users;
use Filament\Schemas\Schema;
use Filament\Forms\Components\CheckboxList;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use App\Filament\Resources\Users\Pages\ListUsers;
use App\Filament\Admin\Resources\UserResource\Pages; use App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource\RelationManagers; use App\Filament\Admin\Resources\UserResource\RelationManagers;
use App\Models\User; use App\Models\User;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
@@ -16,21 +23,21 @@ class UserResource extends Resource
{ {
protected static ?string $model = User::class; protected static ?string $model = User::class;
protected static ?string $navigationIcon = 'heroicon-o-user-group'; protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-user-group';
protected static ?string $navigationGroup = 'Security Settings'; protected static string | \UnitEnum | null $navigationGroup = 'Security Settings';
public static function form(Form $form): Form public static function form(Schema $schema): Schema
{ {
return $form return $schema
->columns([ ->columns([
'default' => 2, 'default' => 2,
]) ])
->schema([ ->components([
TextInput::make('name')->required()->columnSpan(2), TextInput::make('name')->required()->columnSpan(2),
TextInput::make('email')->required()->email()->columnSpan(fn () : int => $form->getOperation() === 'edit' ? 2 : 1), TextInput::make('email')->required()->email()->columnSpan(fn () : int => $schema->getOperation() === 'edit' ? 2 : 1),
TextInput::make('password')->required()->password()->hiddenOn('edit'), TextInput::make('password')->required()->password()->hiddenOn('edit'),
Forms\Components\CheckboxList::make('roles') CheckboxList::make('roles')
->relationship('roles', 'name') ->relationship('roles', 'name')
->searchable() ->searchable()
]); ]);
@@ -40,20 +47,20 @@ class UserResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->searchable(), TextColumn::make('name')->searchable(),
Tables\Columns\TextColumn::make('email')->searchable(), TextColumn::make('email')->searchable(),
]) ])
->filters([ ->filters([
// //
]) ])
->actions([ ->recordActions([
Tables\Actions\EditAction::make()->slideOver(), EditAction::make()->slideOver(),
Tables\Actions\DeleteAction::make()->requiresConfirmation() DeleteAction::make()->requiresConfirmation()
]) ])
->bulkActions([ ->toolbarActions([
Tables\Actions\BulkActionGroup::make([ BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), DeleteBulkAction::make(),
]), ]),
]); ]);
} }
@@ -68,7 +75,7 @@ class UserResource extends Resource
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => \App\Filament\Resources\UserResource\Pages\ListUsers::route('/'), 'index' => ListUsers::route('/'),
]; ];
} }
} }

View File

@@ -2,7 +2,10 @@
namespace App\Jobs; namespace App\Jobs;
use Filament\Notifications\Actions\Action as NotificationAction; use Filament\Actions\Action;
use App\Models\Transmittal;
use App\Models\User;
use Barryvdh\DomPDF\Facade\Pdf;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@@ -13,30 +16,41 @@ use Illuminate\Support\Facades\Storage;
class ExportCompleteJob implements ShouldQueue class ExportCompleteJob implements ShouldQueue
{ {
use Dispatchable;
use InteractsWithQueue;
use Queueable; use Queueable;
use SerializesModels;
public function __construct(private User $user, private array $ids)
/**
* Create a new job instance.
*/
public function __construct(private $user)
{ {
} }
/**
* Execute the job.
*/
public function handle(): void public function handle(): void
{ {
Notification::make()->success() $this->user->refresh();
$transmittals = Transmittal::query()
->with(['client', 'branch', 'files.notes', 'files.remarks'])
->whereIn('id', $this->ids)
->get();
$pdf = Pdf::loadView('transmittal.export.transmittal-export-pdf', [
'transmittals' => $transmittals,
]);
$filename = 'transmittal-export-'.$this->user->id.'-'.now()->format('YmdHis').'.pdf';
Storage::disk('public')->put($filename, $pdf->output());
Notification::make()
->success()
->title('Export Completed') ->title('Export Completed')
->actions([ ->actions([
NotificationAction::make('download_transmittal-export.xlsx') Action::make('download_transmittal_export')
->label('Download File') ->label('Download PDF File')
->url(url: Storage::url('public/transmittal-export.xlsx') ,shouldOpenInNewTab: true) ->url(Storage::url($filename), true)
->markAsRead(), ->markAsRead(),
] ])
)
->sendToDatabase($this->user); ->sendToDatabase($this->user);
} }
} }

28
app/Jobs/TestQueueJob.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class TestQueueJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public function __construct()
{
}
public function handle(): void
{
Log::info('TestQueueJob executed');
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Jobs;
use Filament\Actions\Action;
use App\Models\Transmittal;
use App\Models\User;
use Barryvdh\DomPDF\Facade\Pdf;
use Filament\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
class TransmittalPDFExportJob implements ShouldQueue
{
use Queueable, Dispatchable, InteractsWithQueue, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(private User $user, private array $ids)
{}
/**
* Execute the job.
*/
public function handle(): void
{
$this->user->refresh();
$transmittals = Transmittal::query()
->with(['client', 'branch', 'files.notes', 'files.remarks'])
->whereIn('id', $this->ids)
->get();
$pdf = Pdf::loadView('transmittal.export.transmittal-export-pdf', [
'transmittals' => $transmittals,
]);
$filename = 'transmittal-export-'.$this->user->id.'-'.now()->format('YmdHis').'.pdf';
Storage::disk('public')->put($filename, $pdf->output());
Notification::make()
->success()
->title('Export Completed')
->actions([
Action::make('download_transmittal_export')
->label('Download PDF File')
->url(Storage::url($filename), true)
->markAsRead(),
])
->sendToDatabase($this->user);
}
}

View File

@@ -24,7 +24,7 @@ class LoginForm extends Form
/** /**
* Attempt to authenticate the request's credentials. * Attempt to authenticate the request's credentials.
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
*/ */
public function authenticate(): void public function authenticate(): void
{ {

View File

@@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasOne;
use App\Observers\AccountObserver; use App\Observers\AccountObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -47,7 +48,7 @@ class Account extends Model
return $this->hasMany(Balance::class); return $this->hasMany(Balance::class);
} }
public function latestBalance(): \Illuminate\Database\Eloquent\Relations\HasOne public function latestBalance(): HasOne
{ {
return $this->hasOne(Balance::class)->latestOfMany(); return $this->hasOne(Balance::class)->latestOfMany();
} }

View File

@@ -15,7 +15,7 @@ class AccountType extends Model
/** /**
* Get all of the accounts for the AccountType * Get all of the accounts for the AccountType
* *
* @return \Illuminate\Database\Eloquent\Relations\HasMany * @return HasMany
*/ */
public function accounts(): HasMany public function accounts(): HasMany
{ {

View File

@@ -20,7 +20,7 @@ class Balance extends Model
/** /**
* Get the ledger that owns the Balance * Get the ledger that owns the Balance
* *
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return BelongsTo
*/ */
public function ledger(): BelongsTo public function ledger(): BelongsTo
{ {

View File

@@ -78,4 +78,9 @@ class Client extends Model
{ {
return $this->hasManyThrough(Journal::class, Branch::class); return $this->hasManyThrough(Journal::class, Branch::class);
} }
public function discounts(): HasMany
{
return $this->hasMany(Discount::class);
}
} }

10
app/Models/Discount.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Discount extends Model
{
protected $fillable = ['discount'];
}

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
class Expense extends Model class Expense extends Model
@@ -43,4 +44,14 @@ class Expense extends Model
{ {
return $this->belongsTo(Branch::class); return $this->belongsTo(Branch::class);
} }
public function accounts(): BelongsToMany
{
return $this->belongsToMany(Account::class, 'account_expense')->withTimestamps();
}
public function getAccountsListAttribute(): string
{
return $this->accounts->pluck('account')->implode(', ');
}
} }

View File

@@ -55,7 +55,7 @@ class Ledger extends Model
/** /**
* Get all of the balances for the Ledger * Get all of the balances for the Ledger
* *
* @return \Illuminate\Database\Eloquent\Relations\HasMany * @return HasMany
*/ */
public function balances(): HasMany public function balances(): HasMany
{ {

View File

@@ -14,7 +14,7 @@ class PermissionType extends Model
/** /**
* Get all of the permissions for the PermissionType * Get all of the permissions for the PermissionType
* *
* @return \Illuminate\Database\Eloquent\Relations\HasMany * @return HasMany
*/ */
public function permissions(): HasMany public function permissions(): HasMany
{ {

View File

@@ -5,13 +5,21 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
class Sale extends Model class Sale extends Model
{ {
use HasFactory; use HasFactory;
protected $guarded = []; protected $fillable = [
'branch_id',
'user_id',
'client_id',
'happened_on',
'reference_number',
'buyer'
];
protected $casts = [ protected $casts = [
'happened_on' => 'date:Y-m-d', 'happened_on' => 'date:Y-m-d',
@@ -43,4 +51,19 @@ class Sale extends Model
{ {
return $this->belongsTo(Branch::class); return $this->belongsTo(Branch::class);
} }
public function accounts(): BelongsToMany
{
return $this->belongsToMany(Account::class, 'account_sale')->withTimestamps();
}
public function getAccountsListAttribute(): string
{
return $this->accounts->pluck('account')->implode(', ');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
} }

View File

@@ -2,6 +2,7 @@
namespace App\Observers; namespace App\Observers;
use App\Commands\Clients\GenerateBaseAccountCommand;
use App\Models\Client; use App\Models\Client;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -12,45 +13,7 @@ class ClientObserver
*/ */
public function created(Client $client): void public function created(Client $client): void
{ {
DB::transaction(function () use ($client) { app(GenerateBaseAccountCommand::class)->execute($client);
$client->accounts()->createMany([
[
'account_type_id' => 1,
'account' => 'Cash',
'normal_balance' => 'debit',
],
[
'account_type_id' => 1,
'account' => 'Input Tax',
'normal_balance' => 'debit',
],
[
'account_type_id' => 1,
'account' => 'Creditable Withholding Tax',
'normal_balance' => 'debit',
],
[
'account_type_id' => 2,
'account' => 'Output Tax',
'normal_balance' => 'credit',
],
[
'account_type_id' => 2,
'account' => 'Payable Withholding Tax',
'normal_balance' => 'credit',
],
[
'account_type_id' => 5,
'account' => 'Vat Exempt Revenue',
'normal_balance' => 'credit',
],
[
'account_type_id' => 4,
'account' => 'Sales Discount',
'normal_balance' => 'debit',
],
]);
});
} }
/** /**

View File

@@ -1,108 +1,70 @@
<?php <?php
declare(strict_types=1);
namespace App\Policies; namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Branch; use App\Models\Branch;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class BranchPolicy class BranchPolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
/** public function viewAny(AuthUser $authUser): bool
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{ {
return $user->can('view_any_branch'); return $authUser->can('ViewAny:Branch');
} }
/** public function view(AuthUser $authUser, Branch $branch): bool
* Determine whether the user can view the model.
*/
public function view(User $user, Branch $branch): bool
{ {
return $user->can('view_branch'); return $authUser->can('View:Branch');
} }
/** public function create(AuthUser $authUser): bool
* Determine whether the user can create models.
*/
public function create(User $user): bool
{ {
return $user->can('create_branch'); return $authUser->can('Create:Branch');
} }
/** public function update(AuthUser $authUser, Branch $branch): bool
* Determine whether the user can update the model.
*/
public function update(User $user, Branch $branch): bool
{ {
return $user->can('update_branch'); return $authUser->can('Update:Branch');
} }
/** public function delete(AuthUser $authUser, Branch $branch): bool
* Determine whether the user can delete the model.
*/
public function delete(User $user, Branch $branch): bool
{ {
return $user->can('delete_branch'); return $authUser->can('Delete:Branch');
} }
/** public function restore(AuthUser $authUser, Branch $branch): bool
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{ {
return $user->can('delete_any_branch'); return $authUser->can('Restore:Branch');
} }
/** public function forceDelete(AuthUser $authUser, Branch $branch): bool
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Branch $branch): bool
{ {
return $user->can('force_delete_branch'); return $authUser->can('ForceDelete:Branch');
} }
/** public function forceDeleteAny(AuthUser $authUser): bool
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{ {
return $user->can('force_delete_any_branch'); return $authUser->can('ForceDeleteAny:Branch');
} }
/** public function restoreAny(AuthUser $authUser): bool
* Determine whether the user can restore.
*/
public function restore(User $user, Branch $branch): bool
{ {
return $user->can('restore_branch'); return $authUser->can('RestoreAny:Branch');
} }
/** public function replicate(AuthUser $authUser, Branch $branch): bool
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{ {
return $user->can('restore_any_branch'); return $authUser->can('Replicate:Branch');
} }
/** public function reorder(AuthUser $authUser): bool
* Determine whether the user can replicate.
*/
public function replicate(User $user, Branch $branch): bool
{ {
return $user->can('replicate_branch'); return $authUser->can('Reorder:Branch');
} }
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_branch');
}
} }

View File

@@ -1,108 +1,70 @@
<?php <?php
declare(strict_types=1);
namespace App\Policies; namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Client; use App\Models\Client;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class ClientPolicy class ClientPolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
/** public function viewAny(AuthUser $authUser): bool
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{ {
return $user->can('view_any_client'); return $authUser->can('ViewAny:Client');
} }
/** public function view(AuthUser $authUser, Client $client): bool
* Determine whether the user can view the model.
*/
public function view(User $user, Client $client): bool
{ {
return $user->can('view_client'); return $authUser->can('View:Client');
} }
/** public function create(AuthUser $authUser): bool
* Determine whether the user can create models.
*/
public function create(User $user): bool
{ {
return $user->can('create_client'); return $authUser->can('Create:Client');
} }
/** public function update(AuthUser $authUser, Client $client): bool
* Determine whether the user can update the model.
*/
public function update(User $user, Client $client): bool
{ {
return $user->can('update_client'); return $authUser->can('Update:Client');
} }
/** public function delete(AuthUser $authUser, Client $client): bool
* Determine whether the user can delete the model.
*/
public function delete(User $user, Client $client): bool
{ {
return $user->can('delete_client'); return $authUser->can('Delete:Client');
} }
/** public function restore(AuthUser $authUser, Client $client): bool
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{ {
return $user->can('delete_any_client'); return $authUser->can('Restore:Client');
} }
/** public function forceDelete(AuthUser $authUser, Client $client): bool
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Client $client): bool
{ {
return $user->can('force_delete_client'); return $authUser->can('ForceDelete:Client');
} }
/** public function forceDeleteAny(AuthUser $authUser): bool
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{ {
return $user->can('force_delete_any_client'); return $authUser->can('ForceDeleteAny:Client');
} }
/** public function restoreAny(AuthUser $authUser): bool
* Determine whether the user can restore.
*/
public function restore(User $user, Client $client): bool
{ {
return $user->can('restore_client'); return $authUser->can('RestoreAny:Client');
} }
/** public function replicate(AuthUser $authUser, Client $client): bool
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{ {
return $user->can('restore_any_client'); return $authUser->can('Replicate:Client');
} }
/** public function reorder(AuthUser $authUser): bool
* Determine whether the user can replicate.
*/
public function replicate(User $user, Client $client): bool
{ {
return $user->can('replicate_client'); return $authUser->can('Reorder:Client');
} }
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_client');
}
} }

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Discount;
use Illuminate\Auth\Access\HandlesAuthorization;
class DiscountPolicy
{
use HandlesAuthorization;
public function viewAny(AuthUser $authUser): bool
{
return $authUser->can('ViewAny:Discount');
}
public function view(AuthUser $authUser, Discount $discount): bool
{
return $authUser->can('View:Discount');
}
public function create(AuthUser $authUser): bool
{
return $authUser->can('Create:Discount');
}
public function update(AuthUser $authUser, Discount $discount): bool
{
return $authUser->can('Update:Discount');
}
public function delete(AuthUser $authUser, Discount $discount): bool
{
return $authUser->can('Delete:Discount');
}
public function restore(AuthUser $authUser, Discount $discount): bool
{
return $authUser->can('Restore:Discount');
}
public function forceDelete(AuthUser $authUser, Discount $discount): bool
{
return $authUser->can('ForceDelete:Discount');
}
public function forceDeleteAny(AuthUser $authUser): bool
{
return $authUser->can('ForceDeleteAny:Discount');
}
public function restoreAny(AuthUser $authUser): bool
{
return $authUser->can('RestoreAny:Discount');
}
public function replicate(AuthUser $authUser, Discount $discount): bool
{
return $authUser->can('Replicate:Discount');
}
public function reorder(AuthUser $authUser): bool
{
return $authUser->can('Reorder:Discount');
}
}

View File

@@ -1,108 +1,70 @@
<?php <?php
declare(strict_types=1);
namespace App\Policies; namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Expense; use App\Models\Expense;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class ExpensePolicy class ExpensePolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
/** public function viewAny(AuthUser $authUser): bool
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{ {
return $user->can('view_any_expense'); return $authUser->can('ViewAny:Expense');
} }
/** public function view(AuthUser $authUser, Expense $expense): bool
* Determine whether the user can view the model.
*/
public function view(User $user, Expense $expense): bool
{ {
return $user->can('view_expense'); return $authUser->can('View:Expense');
} }
/** public function create(AuthUser $authUser): bool
* Determine whether the user can create models.
*/
public function create(User $user): bool
{ {
return $user->can('create_expense'); return $authUser->can('Create:Expense');
} }
/** public function update(AuthUser $authUser, Expense $expense): bool
* Determine whether the user can update the model.
*/
public function update(User $user, Expense $expense): bool
{ {
return $user->can('update_expense'); return $authUser->can('Update:Expense');
} }
/** public function delete(AuthUser $authUser, Expense $expense): bool
* Determine whether the user can delete the model.
*/
public function delete(User $user, Expense $expense): bool
{ {
return $user->can('delete_expense'); return $authUser->can('Delete:Expense');
} }
/** public function restore(AuthUser $authUser, Expense $expense): bool
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{ {
return $user->can('delete_any_expense'); return $authUser->can('Restore:Expense');
} }
/** public function forceDelete(AuthUser $authUser, Expense $expense): bool
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Expense $expense): bool
{ {
return $user->can('force_delete_expense'); return $authUser->can('ForceDelete:Expense');
} }
/** public function forceDeleteAny(AuthUser $authUser): bool
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{ {
return $user->can('force_delete_any_expense'); return $authUser->can('ForceDeleteAny:Expense');
} }
/** public function restoreAny(AuthUser $authUser): bool
* Determine whether the user can restore.
*/
public function restore(User $user, Expense $expense): bool
{ {
return $user->can('restore_expense'); return $authUser->can('RestoreAny:Expense');
} }
/** public function replicate(AuthUser $authUser, Expense $expense): bool
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{ {
return $user->can('restore_any_expense'); return $authUser->can('Replicate:Expense');
} }
/** public function reorder(AuthUser $authUser): bool
* Determine whether the user can replicate.
*/
public function replicate(User $user, Expense $expense): bool
{ {
return $user->can('replicate_expense'); return $authUser->can('Reorder:Expense');
} }
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_expense');
}
} }

View File

@@ -1,108 +1,70 @@
<?php <?php
declare(strict_types=1);
namespace App\Policies; namespace App\Policies;
use App\Models\User; use Illuminate\Foundation\Auth\User as AuthUser;
use Illuminate\Auth\Access\HandlesAuthorization;
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Role;
use Illuminate\Auth\Access\HandlesAuthorization;
class RolePolicy class RolePolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
/** public function viewAny(AuthUser $authUser): bool
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{ {
return $user->can('view_any_shield::role'); return $authUser->can('ViewAny:Role');
} }
/** public function view(AuthUser $authUser, Role $role): bool
* Determine whether the user can view the model.
*/
public function view(User $user, Role $role): bool
{ {
return $user->can('view_shield::role'); return $authUser->can('View:Role');
} }
/** public function create(AuthUser $authUser): bool
* Determine whether the user can create models.
*/
public function create(User $user): bool
{ {
return $user->can('create_shield::role'); return $authUser->can('Create:Role');
} }
/** public function update(AuthUser $authUser, Role $role): bool
* Determine whether the user can update the model.
*/
public function update(User $user, Role $role): bool
{ {
return $user->can('update_shield::role'); return $authUser->can('Update:Role');
} }
/** public function delete(AuthUser $authUser, Role $role): bool
* Determine whether the user can delete the model.
*/
public function delete(User $user, Role $role): bool
{ {
return $user->can('delete_shield::role'); return $authUser->can('Delete:Role');
} }
/** public function restore(AuthUser $authUser, Role $role): bool
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{ {
return $user->can('delete_any_shield::role'); return $authUser->can('Restore:Role');
} }
/** public function forceDelete(AuthUser $authUser, Role $role): bool
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Role $role): bool
{ {
return $user->can('{{ ForceDelete }}'); return $authUser->can('ForceDelete:Role');
} }
/** public function forceDeleteAny(AuthUser $authUser): bool
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{ {
return $user->can('{{ ForceDeleteAny }}'); return $authUser->can('ForceDeleteAny:Role');
} }
/** public function restoreAny(AuthUser $authUser): bool
* Determine whether the user can restore.
*/
public function restore(User $user, Role $role): bool
{ {
return $user->can('{{ Restore }}'); return $authUser->can('RestoreAny:Role');
} }
/** public function replicate(AuthUser $authUser, Role $role): bool
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{ {
return $user->can('{{ RestoreAny }}'); return $authUser->can('Replicate:Role');
} }
/** public function reorder(AuthUser $authUser): bool
* Determine whether the user can replicate.
*/
public function replicate(User $user, Role $role): bool
{ {
return $user->can('{{ Replicate }}'); return $authUser->can('Reorder:Role');
} }
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('{{ Reorder }}');
}
} }

View File

@@ -1,108 +1,70 @@
<?php <?php
declare(strict_types=1);
namespace App\Policies; namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Sale; use App\Models\Sale;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class SalePolicy class SalePolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
/** public function viewAny(AuthUser $authUser): bool
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{ {
return $user->can('view_any_sale'); return $authUser->can('ViewAny:Sale');
} }
/** public function view(AuthUser $authUser, Sale $sale): bool
* Determine whether the user can view the model.
*/
public function view(User $user, Sale $sale): bool
{ {
return $user->can('view_sale'); return $authUser->can('View:Sale');
} }
/** public function create(AuthUser $authUser): bool
* Determine whether the user can create models.
*/
public function create(User $user): bool
{ {
return $user->can('create_sale'); return $authUser->can('Create:Sale');
} }
/** public function update(AuthUser $authUser, Sale $sale): bool
* Determine whether the user can update the model.
*/
public function update(User $user, Sale $sale): bool
{ {
return $user->can('update_sale'); return $authUser->can('Update:Sale');
} }
/** public function delete(AuthUser $authUser, Sale $sale): bool
* Determine whether the user can delete the model.
*/
public function delete(User $user, Sale $sale): bool
{ {
return $user->can('delete_sale'); return $authUser->can('Delete:Sale');
} }
/** public function restore(AuthUser $authUser, Sale $sale): bool
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{ {
return $user->can('delete_any_sale'); return $authUser->can('Restore:Sale');
} }
/** public function forceDelete(AuthUser $authUser, Sale $sale): bool
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Sale $sale): bool
{ {
return $user->can('force_delete_sale'); return $authUser->can('ForceDelete:Sale');
} }
/** public function forceDeleteAny(AuthUser $authUser): bool
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{ {
return $user->can('force_delete_any_sale'); return $authUser->can('ForceDeleteAny:Sale');
} }
/** public function restoreAny(AuthUser $authUser): bool
* Determine whether the user can restore.
*/
public function restore(User $user, Sale $sale): bool
{ {
return $user->can('restore_sale'); return $authUser->can('RestoreAny:Sale');
} }
/** public function replicate(AuthUser $authUser, Sale $sale): bool
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{ {
return $user->can('restore_any_sale'); return $authUser->can('Replicate:Sale');
} }
/** public function reorder(AuthUser $authUser): bool
* Determine whether the user can replicate.
*/
public function replicate(User $user, Sale $sale): bool
{ {
return $user->can('replicate_sale'); return $authUser->can('Reorder:Sale');
} }
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_sale');
}
} }

View File

@@ -1,108 +1,70 @@
<?php <?php
declare(strict_types=1);
namespace App\Policies; namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Transmittal; use App\Models\Transmittal;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class TransmittalPolicy class TransmittalPolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
/** public function viewAny(AuthUser $authUser): bool
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{ {
return $user->can('view_any_transmittal'); return $authUser->can('ViewAny:Transmittal');
} }
/** public function view(AuthUser $authUser, Transmittal $transmittal): bool
* Determine whether the user can view the model.
*/
public function view(User $user, Transmittal $transmittal): bool
{ {
return $user->can('view_transmittal'); return $authUser->can('View:Transmittal');
} }
/** public function create(AuthUser $authUser): bool
* Determine whether the user can create models.
*/
public function create(User $user): bool
{ {
return $user->can('create_transmittal'); return $authUser->can('Create:Transmittal');
} }
/** public function update(AuthUser $authUser, Transmittal $transmittal): bool
* Determine whether the user can update the model.
*/
public function update(User $user, Transmittal $transmittal): bool
{ {
return $user->can('update_transmittal'); return $authUser->can('Update:Transmittal');
} }
/** public function delete(AuthUser $authUser, Transmittal $transmittal): bool
* Determine whether the user can delete the model.
*/
public function delete(User $user, Transmittal $transmittal): bool
{ {
return $user->can('delete_transmittal'); return $authUser->can('Delete:Transmittal');
} }
/** public function restore(AuthUser $authUser, Transmittal $transmittal): bool
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{ {
return $user->can('delete_any_transmittal'); return $authUser->can('Restore:Transmittal');
} }
/** public function forceDelete(AuthUser $authUser, Transmittal $transmittal): bool
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Transmittal $transmittal): bool
{ {
return $user->can('force_delete_transmittal'); return $authUser->can('ForceDelete:Transmittal');
} }
/** public function forceDeleteAny(AuthUser $authUser): bool
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{ {
return $user->can('force_delete_any_transmittal'); return $authUser->can('ForceDeleteAny:Transmittal');
} }
/** public function restoreAny(AuthUser $authUser): bool
* Determine whether the user can restore.
*/
public function restore(User $user, Transmittal $transmittal): bool
{ {
return $user->can('restore_transmittal'); return $authUser->can('RestoreAny:Transmittal');
} }
/** public function replicate(AuthUser $authUser, Transmittal $transmittal): bool
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{ {
return $user->can('restore_any_transmittal'); return $authUser->can('Replicate:Transmittal');
} }
/** public function reorder(AuthUser $authUser): bool
* Determine whether the user can replicate.
*/
public function replicate(User $user, Transmittal $transmittal): bool
{ {
return $user->can('replicate_transmittal'); return $authUser->can('Reorder:Transmittal');
} }
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_transmittal');
}
} }

View File

@@ -2,106 +2,66 @@
namespace App\Policies; namespace App\Policies;
use App\Models\User; use Illuminate\Foundation\Auth\User as AuthUser;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy class UserPolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
/** public function viewAny(AuthUser $authUser): bool
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{ {
return $user->can('view_any_user'); return $authUser->can('ViewAny:User');
} }
/** public function view(AuthUser $authUser): bool
* Determine whether the user can view the model.
*/
public function view(User $user): bool
{ {
return $user->can('view_user'); return $authUser->can('View:User');
} }
/** public function create(AuthUser $authUser): bool
* Determine whether the user can create models.
*/
public function create(User $user): bool
{ {
return $user->can('create_user'); return $authUser->can('Create:User');
} }
/** public function update(AuthUser $authUser): bool
* Determine whether the user can update the model.
*/
public function update(User $user): bool
{ {
return $user->can('update_user'); return $authUser->can('Update:User');
} }
/** public function delete(AuthUser $authUser): bool
* Determine whether the user can delete the model.
*/
public function delete(User $user): bool
{ {
return $user->can('delete_user'); return $authUser->can('Delete:User');
} }
/** public function restore(AuthUser $authUser): bool
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{ {
return $user->can('delete_any_user'); return $authUser->can('Restore:User');
} }
/** public function forceDelete(AuthUser $authUser): bool
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user): bool
{ {
return $user->can('force_delete_user'); return $authUser->can('ForceDelete:User');
} }
/** public function forceDeleteAny(AuthUser $authUser): bool
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{ {
return $user->can('force_delete_any_user'); return $authUser->can('ForceDeleteAny:User');
} }
/** public function restoreAny(AuthUser $authUser): bool
* Determine whether the user can restore.
*/
public function restore(User $user): bool
{ {
return $user->can('restore_user'); return $authUser->can('RestoreAny:User');
} }
/** public function replicate(AuthUser $authUser): bool
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{ {
return $user->can('restore_any_user'); return $authUser->can('Replicate:User');
} }
/** public function reorder(AuthUser $authUser): bool
* Determine whether the user can bulk restore.
*/
public function replicate(User $user): bool
{ {
return $user->can('replicate_user'); return $authUser->can('Reorder:User');
} }
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_user');
}
} }

View File

@@ -2,7 +2,10 @@
namespace App\Providers\Filament; namespace App\Providers\Filament;
use Filament\Pages\Dashboard;
use Filament\Widgets\AccountWidget;
use BezhanSalleh\FilamentShield\FilamentShieldPlugin; use BezhanSalleh\FilamentShield\FilamentShieldPlugin;
use Filament\Navigation\NavigationItem;
use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents; use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent; use Filament\Http\Middleware\DispatchServingFilamentEvent;
@@ -17,6 +20,7 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession; use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\Middleware\ShareErrorsFromSession; use Illuminate\View\Middleware\ShareErrorsFromSession;
class AdminPanelProvider extends PanelProvider class AdminPanelProvider extends PanelProvider
@@ -37,12 +41,19 @@ class AdminPanelProvider extends PanelProvider
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([ ->pages([
Pages\Dashboard::class, Dashboard::class,
])
->navigationItems([
NavigationItem::make('Horizon')
->url(fn (): string => url(config('horizon.path')))
->icon('heroicon-o-queue-list')
->group('System')
->visible(fn (): bool => Auth::user()?->hasRole('super_admin') ?? false),
]) ])
->databaseNotifications() ->databaseNotifications()
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([ ->widgets([
Widgets\AccountWidget::class, AccountWidget::class,
]) ])
->middleware([ ->middleware([
EncryptCookies::class, EncryptCookies::class,

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\HorizonApplicationServiceProvider;
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
parent::boot();
// Horizon::routeSmsNotificationsTo('15556667777');
// Horizon::routeMailNotificationsTo('example@example.com');
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
}
/**
* Register the Horizon gate.
*
* This gate determines who can access Horizon in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return $user->hasRole('super_admin');
});
}
}

View File

@@ -9,22 +9,25 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"awcodes/filament-table-repeater": "^3.0", "barryvdh/laravel-dompdf": "^2.0",
"bezhansalleh/filament-shield": "^3.2", "bezhansalleh/filament-shield": "^4.1",
"filament/filament": "^3.2", "filament/filament": "~4.0",
"icetalker/filament-table-repeater": "^2.0",
"laravel/framework": "^11.9", "laravel/framework": "^11.9",
"laravel/horizon": "^5.44",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"livewire/livewire": "^3.4", "livewire/livewire": "^3.4",
"livewire/volt": "^1.0", "livewire/volt": "^1.0",
"maatwebsite/excel": "^3.1", "maatwebsite/excel": "^3.1",
"malzariey/filament-daterangepicker-filter": "^2.8", "malzariey/filament-daterangepicker-filter": "^4.0",
"pxlrbt/filament-excel": "^2.3", "pxlrbt/filament-excel": "^3.0",
"spatie/laravel-data": "^4.7", "spatie/laravel-data": "^4.7",
"staudenmeir/belongs-to-through": "^2.16", "staudenmeir/belongs-to-through": "^2.16",
"yemenopensource/filament-excel": "*" "yemenopensource/filament-excel": "*"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
"filament/upgrade": "4.0",
"laravel/boost": "^2.1", "laravel/boost": "^2.1",
"laravel/breeze": "^2.1", "laravel/breeze": "^2.1",
"laravel/pint": "^1.13", "laravel/pint": "^1.13",

2098
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,89 +1,263 @@
<?php <?php
declare(strict_types=1);
return [ return [
/*
|--------------------------------------------------------------------------
| Shield Resource
|--------------------------------------------------------------------------
|
| Here you may configure the built-in role management resource. You can
| customize the URL, choose whether to show model paths, group it under
| a cluster, and decide which permission tabs to display.
|
*/
'shield_resource' => [ 'shield_resource' => [
'should_register_navigation' => true,
'slug' => 'shield/roles', 'slug' => 'shield/roles',
'navigation_sort' => -1,
'navigation_badge' => true,
'navigation_group' => true,
'is_globally_searchable' => true,
'show_model_path' => true, 'show_model_path' => true,
'is_scoped_to_tenant' => true,
'cluster' => null, 'cluster' => null,
'tabs' => [
'pages' => true,
'widgets' => true,
'resources' => true,
'custom_permissions' => false,
],
], ],
'auth_provider_model' => [ /*
'fqcn' => 'App\\Models\\User', |--------------------------------------------------------------------------
], | Multi-Tenancy
|--------------------------------------------------------------------------
|
| When your application supports teams, Shield will automatically detect
| and configure the tenant model during setup. This enables tenant-scoped
| roles and permissions throughout your application.
|
*/
'tenant_model' => null,
/*
|--------------------------------------------------------------------------
| User Model
|--------------------------------------------------------------------------
|
| This value contains the class name of your user model. This model will
| be used for role assignments and must implement the HasRoles trait
| provided by the Spatie\Permission package.
|
*/
'auth_provider_model' => 'App\\Models\\User',
/*
|--------------------------------------------------------------------------
| Super Admin
|--------------------------------------------------------------------------
|
| Here you may define a super admin that has unrestricted access to your
| application. You can choose to implement this via Laravel's gate system
| or as a traditional role with all permissions explicitly assigned.
|
*/
'super_admin' => [ 'super_admin' => [
'enabled' => true, 'enabled' => true,
'name' => 'super_admin', 'name' => 'super_admin',
'define_via_gate' => false, 'define_via_gate' => false,
'intercept_gate' => 'before', // after 'intercept_gate' => 'before',
], ],
/*
|--------------------------------------------------------------------------
| Panel User
|--------------------------------------------------------------------------
|
| When enabled, Shield will create a basic panel user role that can be
| assigned to users who should have access to your Filament panels but
| don't need any specific permissions beyond basic authentication.
|
*/
'panel_user' => [ 'panel_user' => [
'enabled' => true, 'enabled' => true,
'name' => 'panel_user', 'name' => 'panel_user',
], ],
'permission_prefixes' => [ /*
'resource' => [ |--------------------------------------------------------------------------
'view', | Permission Builder
'view_any', |--------------------------------------------------------------------------
|
| You can customize how permission keys are generated to match your
| preferred naming convention and organizational standards. Shield uses
| these settings when creating permission names from your resources.
|
| Supported formats: snake, kebab, pascal, camel, upper_snake, lower_snake
|
*/
'permissions' => [
'separator' => ':',
'case' => 'pascal',
'generate' => true,
],
/*
|--------------------------------------------------------------------------
| Policies
|--------------------------------------------------------------------------
|
| Shield can automatically generate Laravel policies for your resources.
| When merge is enabled, the methods below will be combined with any
| resource-specific methods you define in the resources section.
|
*/
'policies' => [
'path' => app_path('Policies'),
'merge' => true,
'generate' => true,
'methods' => [
'viewAny', 'view', 'create', 'update', 'delete', 'restore',
'forceDelete', 'forceDeleteAny', 'restoreAny', 'replicate', 'reorder',
],
'single_parameter_methods' => [
'viewAny',
'create', 'create',
'update', 'deleteAny',
'restore', 'forceDeleteAny',
'restore_any', 'restoreAny',
'replicate',
'reorder', 'reorder',
'delete',
'delete_any',
'force_delete',
'force_delete_any',
], ],
'page' => 'page',
'widget' => 'widget',
], ],
'entities' => [ /*
'pages' => true, |--------------------------------------------------------------------------
'widgets' => true, | Localization
'resources' => true, |--------------------------------------------------------------------------
'custom_permissions' => true, |
| Shield supports multiple languages out of the box. When enabled, you
| can provide translated labels for permissions to create a more
| localized experience for your international users.
|
*/
'localization' => [
'enabled' => false,
'key' => 'filament-shield::filament-shield.resource_permission_prefixes_labels',
], ],
'generator' => [ /*
'option' => 'policies_and_permissions', |--------------------------------------------------------------------------
'policy_directory' => 'Policies', | Resources
'policy_namespace' => 'Policies', |--------------------------------------------------------------------------
], |
| Here you can fine-tune permissions for specific Filament resources.
| Use the 'manage' array to override the default policy methods for
| individual resources, giving you granular control over permissions.
|
*/
'exclude' => [ 'resources' => [
'enabled' => true, 'subject' => 'model',
'manage' => [
'pages' => [ \BezhanSalleh\FilamentShield\Resources\Roles\RoleResource::class => [
'Dashboard', 'viewAny',
'view',
'create',
'update',
'delete',
],
], ],
'exclude' => [
'widgets' => [ //
'AccountWidget', 'FilamentInfoWidget',
], ],
'resources' => [],
], ],
/*
|--------------------------------------------------------------------------
| Pages
|--------------------------------------------------------------------------
|
| Most Filament pages only require view permissions. Pages listed in the
| exclude array will be skipped during permission generation and won't
| appear in your role management interface.
|
*/
'pages' => [
'subject' => 'class',
'prefix' => 'view',
'exclude' => [
\Filament\Pages\Dashboard::class,
],
],
/*
|--------------------------------------------------------------------------
| Widgets
|--------------------------------------------------------------------------
|
| Like pages, widgets typically only need view permissions. Add widgets
| to the exclude array if you don't want them to appear in your role
| management interface.
|
*/
'widgets' => [
'subject' => 'class',
'prefix' => 'view',
'exclude' => [
\Filament\Widgets\AccountWidget::class,
\Filament\Widgets\FilamentInfoWidget::class,
],
],
/*
|--------------------------------------------------------------------------
| Custom Permissions
|--------------------------------------------------------------------------
|
| Sometimes you need permissions that don't map to resources, pages, or
| widgets. Define any custom permissions here and they'll be available
| when editing roles in your application.
|
*/
'custom_permissions' => [],
/*
|--------------------------------------------------------------------------
| Entity Discovery
|--------------------------------------------------------------------------
|
| By default, Shield only looks for entities in your default Filament
| panel. Enable these options if you're using multiple panels and want
| Shield to discover entities across all of them.
|
*/
'discovery' => [ 'discovery' => [
'discover_all_resources' => true, 'discover_all_resources' => false,
'discover_all_widgets' => true, 'discover_all_widgets' => false,
'discover_all_pages' => true, 'discover_all_pages' => false,
], ],
'register_role_policy' => [ /*
'enabled' => true, |--------------------------------------------------------------------------
], | Role Policy
|--------------------------------------------------------------------------
|
| Shield can automatically register a policy for role management itself.
| This lets you control who can manage roles using Laravel's built-in
| authorization system. Requires a RolePolicy class in your app.
|
*/
'register_role_policy' => true,
]; ];

178
config/horizon.php Normal file
View File

@@ -0,0 +1,178 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Horizon Domain
|--------------------------------------------------------------------------
|
| This value is the prefix that will be used by Horizon to generate its
| routes. You are free to change this value to anything you like,
| such as "admin/horizon" or anything else you'd like it to be.
|
*/
'domain' => env('HORIZON_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Horizon Path
|--------------------------------------------------------------------------
|
| This is the URI path where Horizon will be accessible from. Feel free
| to change this path to anything you like. Note that the path will be
| applied after the "domain" value defined above.
|
*/
'path' => env('HORIZON_PATH', 'horizon'),
/*
|--------------------------------------------------------------------------
| Horizon Redis Connection
|--------------------------------------------------------------------------
|
| This is the name of the Redis connection where Horizon will store the
| information about your jobs. This connection should be defined in
| your "database" configuration file.
|
*/
'use' => 'default',
/*
|--------------------------------------------------------------------------
| Horizon Redis Prefix
|--------------------------------------------------------------------------
|
| This prefix will be used when storing all Horizon data in Redis. You
| may modify the prefix if you are running multiple Horizon instances
| on the same server to avoid any collision between instances.
|
*/
'prefix' => env(
'HORIZON_PREFIX',
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
),
/*
|--------------------------------------------------------------------------
| Horizon Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will get attached onto each Horizon route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => ['web', 'auth'],
/*
|--------------------------------------------------------------------------
| Queue Wait Time Thresholds
|--------------------------------------------------------------------------
|
| This option allows you to configure when the queue "wait time" is
| considered "long", helping you identify which queues are being
| backed up and need more workers to handle the incoming load.
|
*/
'waits' => [
'redis:default' => 60,
],
/*
|--------------------------------------------------------------------------
| Job Trimming Times
|--------------------------------------------------------------------------
|
| These options determine how many minutes Horizon will keep different
| types of jobs in its database. This helps keep your database small
| and fast, while still giving you some history for debugging.
|
*/
'trim' => [
'recent' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
/*
|--------------------------------------------------------------------------
| Metrics Configuration
|--------------------------------------------------------------------------
|
| Here you may configure how many minutes Horizon will keep metrics.
| These metrics include things like throughput and average wait time
| for each of your queues, giving you insight into your performance.
|
*/
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
/*
|--------------------------------------------------------------------------
| Fast Termination
|--------------------------------------------------------------------------
|
| When this option is enabled, Horizon's "terminate" command will not
| wait for all jobs to finish before terminating the workers. This
| can be useful when you need to quickly restart your workers.
|
*/
'fast_termination' => false,
/*
|--------------------------------------------------------------------------
| Memory Limit (MB)
|--------------------------------------------------------------------------
|
| This value describes the maximum amount of memory the Horizon master
| process may consume before it is terminated and restarted. This
| helps prevent any memory leaks from affecting the entire server.
|
*/
'memory_limit' => 64,
/*
|--------------------------------------------------------------------------
| Queue Worker Configuration
|--------------------------------------------------------------------------
|
| Here you may define the queue worker settings used by your application
| in all environments. These settings determine how many workers will
| be used for each environment's respective queues.
|
*/
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
];

View File

@@ -1,8 +1,8 @@
<?php <?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
@@ -17,15 +17,11 @@ return new class extends Migration
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id'; $pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id'; $pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
if (empty($tableNames)) { throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
Schema::create($tableNames['permissions'], function (Blueprint $table) { Schema::create($tableNames['permissions'], static function (Blueprint $table) {
//$table->engine('InnoDB'); // $table->engine('InnoDB');
$table->bigIncrements('id'); // permission id $table->bigIncrements('id'); // permission id
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25); $table->string('guard_name'); // For MyISAM use string('guard_name', 25);
@@ -34,8 +30,8 @@ return new class extends Migration
$table->unique(['name', 'guard_name']); $table->unique(['name', 'guard_name']);
}); });
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
//$table->engine('InnoDB'); // $table->engine('InnoDB');
$table->bigIncrements('id'); // role id $table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
@@ -51,7 +47,7 @@ return new class extends Migration
} }
}); });
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission); $table->unsignedBigInteger($pivotPermission);
$table->string('model_type'); $table->string('model_type');
@@ -75,7 +71,7 @@ return new class extends Migration
}); });
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole); $table->unsignedBigInteger($pivotRole);
$table->string('model_type'); $table->string('model_type');
@@ -98,7 +94,7 @@ return new class extends Migration
} }
}); });
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission); $table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole); $table->unsignedBigInteger($pivotRole);
@@ -127,9 +123,7 @@ return new class extends Migration
{ {
$tableNames = config('permission.table_names'); $tableNames = config('permission.table_names');
if (empty($tableNames)) { throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop($tableNames['role_has_permissions']); Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']); Schema::drop($tableNames['model_has_roles']);

Some files were not shown because too many files have changed in this diff Show More