feat: updates

This commit is contained in:
JP
2024-08-15 20:11:21 +08:00
parent 52431a2c61
commit 3af7207ec3
61 changed files with 1674 additions and 480 deletions

1
.devcontainer.json Normal file
View File

@@ -0,0 +1 @@
{"image":"mcr.microsoft.com/devcontainers/base:ubuntu"}

8
.gitignore vendored
View File

@@ -18,3 +18,11 @@ yarn-error.log
/.fleet /.fleet
/.idea /.idea
/.vscode /.vscode
/caddy
frankenphp
frankenphp-worker.php
/caddy
frankenphp
frankenphp-worker.php

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Actions\Ledgers;
use App\Actions\BaseAction;
use App\Commands\Ledgers\CreateLedgerCommand;
use App\DataObjects\CreateLedgerDTO;
use Closure;
use Spatie\LaravelData\Data;
class CreateLedgerAction extends BaseAction
{
public function __construct(
private readonly CreateLedgerCommand $createLedgerCommand,
) {}
public function __invoke(CreateLedgerDTO|Data $payload, Closure $next)
{
$this->createLedgerCommand->execute($payload->data);
return $next($payload);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace App\Actions\Transactions;
use App\Actions\BaseAction;
use App\Actions\Ledgers\CreateLedgerAction;
use App\DataObjects\CreateLedgerDTO;
use App\DataObjects\CreateTransactionDTO;
use App\Models\Account;
use Closure;
use Illuminate\Support\Facades\Pipeline;
use Spatie\LaravelData\Data;
class CreateTransactionAction extends BaseAction
{
public function __invoke(CreateTransactionDTO|Data $payload, Closure $next)
{
$payload->transaction = $payload->transactionable->transactions()->create($payload->data);
$this->transactionAccountLedger($payload);
$this->withHoldingAccountLedger($payload);
return $next($payload);
}
public function transactionAccountLedger($payload): void
{
$branch = $payload->transaction->branch;
if ($branch->isClientVatable) {
//create transaction account ledger
$ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id,
amount: $payload->transaction->net_amount ?? 0.00,
transaction: $payload->transaction,
account: $payload->transaction->account,
);
$this->ledgerPipe($ledgerPayload);
$this->inputTaxAccountLedger($payload);
} else {
//create transaction account ledger
$ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id,
amount: $payload->transaction->gross_amount ?? 0.00,
transaction: $payload->transaction,
account: $payload->transaction->account,
);
$this->ledgerPipe($ledgerPayload);
}
}
public function ledgerPipe(CreateLedgerDTO $ledgerPayload): mixed
{
return Pipeline::send(passable: $ledgerPayload)->through([
CreateLedgerAction::class,
])->thenReturn();
}
public function inputTaxAccountLedger($payload): void
{
$inputTax = Account::query()->where('account', 'Input Tax')->whereHas('balances', function ($balance) use ($payload) {
return $balance->where('branch_id', $payload->transactionable->branch_id);
})->first();
$ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id,
amount: $payload->transactionable->input_tax ?? 0.00,
transaction: $payload->transaction,
account: $inputTax,
);
$this->ledgerPipe($ledgerPayload);
}
public function withHoldingAccountLedger($payload): void
{
$withholdingAccount = Account::query()->where('account', 'Payable Withholding Tax')->whereHas('balances', function ($balance) use ($payload) {
return $balance->where('branch_id', $payload->transactionable->branch_id);
})->first();
$ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id,
amount: $payload->transaction->payable_withholding_tax,
transaction: $payload->transaction,
account: $withholdingAccount,
);
$this->ledgerPipe($ledgerPayload);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Commands\Expenses;
use App\Models\Branch;
use App\Models\Expense;
class GenerateVoucher
{
public static function execute(Branch $branch): string
{
$year = now()->format('y');
$lastVoucher = Expense::query()->where('branch_id', $branch->id)->orderBy('created_at', 'desc')->first();
if (! $lastVoucher) {
return $year.'-'.str_pad(1, 6, '0', STR_PAD_LEFT);
}
$voucherNumber = (int) explode('-', $lastVoucher->voucher_number)[1];
return $year.'-'.str_pad($voucherNumber + 1, 6, '0', STR_PAD_LEFT);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Commands\Ledgers;
use App\Commands\Command;
use App\Models\Ledger;
use Illuminate\Support\Facades\DB;
class CreateLedgerCommand implements Command
{
public function execute(array $data): mixed
{
return DB::transaction(function () use ($data) {
return Ledger::query()->updateOrCreate([
'id' => $data['id'] ?? null,
'transaction_id' => $data['transaction_id'] ?? null,
'account_id' => $data['account_id'] ?? null,
], [
'credit_amount' => $data['credit_amount'] ?? null,
'debit_amount' => $data['debit_amount'] ?? null,
'description' => $data['description'] ?? null,
'branch_id' => $data['branch_id'] ?? null,
'client_id' => $data['client_id'] ?? null,
]);
}, attempts: 2);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Commands\Transactions;
use App\Commands\Command;
class CreateTransactionCommand implements Command
{
public function execute(array $data): mixed
{
// TODO: Implement execute() method.
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\DataObjects;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Spatie\LaravelData\Attributes\Computed;
use Spatie\LaravelData\Data;
class CreateLedgerDTO extends Data
{
#[Computed]
public array $data;
#[Computed]
public int $transaction_id;
#[Computed]
public int $account_id;
#[Computed]
public int $client_id;
#[Computed]
public ?string $description;
#[Computed]
public float $credit_amount;
#[Computed]
public float $debit_amount;
public function __construct(
public int $branch_id,
public float $amount,
public ?Model $ledger = null,
public ?Model $transaction = null,
public ?Model $account = null,
) {
$this->transaction_id = $this->transaction->id;
$this->account_id = $this->account->id;
$this->client_id = $this->transaction->branch->client_id;
$this->credit_amount = $this->transaction->account_type == 'credit' ? $this->amount : 0.00;
$this->debit_amount = $this->transaction->account_type == 'debit' ? $this->amount : 0.00;
$this->description = $this->transaction->description;
$this->data = Arr::except($this->toArray(), ['transaction', 'ledger', 'account', 'amount']);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\DataObjects;
use Illuminate\Database\Eloquent\Model;
use Spatie\LaravelData\Data;
class CreateTransactionDTO extends Data
{
public function __construct(
public array $data,
public ?Model $transaction = null,
public ?Model $transactionable = null,
) {}
}

View File

@@ -4,6 +4,7 @@ namespace App\Filament\Resources;
use App\Filament\Resources\BranchResource\Pages; use App\Filament\Resources\BranchResource\Pages;
use App\Filament\Resources\BranchResource\RelationManagers\BalancesRelationManager; use App\Filament\Resources\BranchResource\RelationManagers\BalancesRelationManager;
use App\Filament\Resources\BranchResource\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;
@@ -75,6 +76,7 @@ class BranchResource extends Resource
return [ return [
// AccountsRelationManager::make(), // AccountsRelationManager::make(),
BalancesRelationManager::make(), BalancesRelationManager::make(),
ExpenseRelationManager::make(),
]; ];
} }

View File

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

View File

@@ -37,7 +37,7 @@ class AccountsRelationManager extends RelationManager
->columns([ ->columns([
Tables\Columns\TextColumn::make('account')->description(fn (Account $record): string => $record->description ?? ''), Tables\Columns\TextColumn::make('account')->description(fn (Account $record): string => $record->description ?? ''),
Tables\Columns\TextColumn::make('accountType.type'), Tables\Columns\TextColumn::make('accountType.type'),
Tables\Columns\TextColumn::make('normal_balance'), Tables\Columns\TextColumn::make('accountType.normal_balance'),
]) ])
->filters([ ->filters([
// //

View File

@@ -2,12 +2,23 @@
namespace App\Filament\Resources; namespace App\Filament\Resources;
use App\Commands\Expenses\GenerateVoucher;
use App\Filament\Resources\ExpenseResource\Pages; use App\Filament\Resources\ExpenseResource\Pages;
use App\Models\Account;
use App\Models\Branch;
use App\Models\Client;
use App\Models\Expense; use App\Models\Expense;
use Awcodes\TableRepeater\Components\TableRepeater;
use Awcodes\TableRepeater\Header;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Support\Collection;
class ExpenseResource extends Resource class ExpenseResource extends Resource
{ {
@@ -18,21 +29,69 @@ class ExpenseResource extends Resource
public static function form(Form $form): Form public static function form(Form $form): Form
{ {
return $form return $form
->schema([ ->schema(static::getExpenseFormFields());
// }
]);
public static function getExpenseFormFields(): array
{
return [
Select::make('client')
->options(Client::query()->get()->pluck('company', 'id'))->live(),
Select::make('branch_id')->options(fn ($get) => Branch::query()->where('client_id', $get('client'))->get()->pluck('code', 'id'))
->afterStateUpdated(fn ($state, $set) => $set('voucher_number', GenerateVoucher::execute(Branch::find($state))))->live(),
TextInput::make('supplier')->label('Supplier Name'),
TextInput::make('reference_number')->label('Reference Number'),
TextInput::make('voucher_number')->label('Voucher Number'),
DatePicker::make('happened_on')->label('Date')->native(false),
TableRepeater::make('transactions')
->headers([
Header::make('Charge Account'),
Header::make('Description'),
Header::make('Gross Amount'),
Header::make('Withholding Tax'),
Header::make('Net Amount'),
])
->schema([
Select::make('account_id')->options(fn ($get) => static::getAccountOptions($get)),
TextInput::make('description')->label('Description'),
TextInput::make('gross_amount'),
TextInput::make('payable_withholding_tax'),
TextInput::make('net_amount'),
])->columnSpan('full'),
];
}
public static function getAccountOptions(Get $get): Collection
{
$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', 'Expenses');
});
return $query->get()->pluck('account', 'id');
} }
public static function table(Table $table): Table public static function table(Table $table): Table
{ {
return $table return $table
->columns([ ->columns(static::getTableColumns())
//
])
->filters([ ->filters([
// //
]) ])
->actions([ ->actions([
Tables\Actions\DeleteAction::make(),
Tables\Actions\EditAction::make(), Tables\Actions\EditAction::make(),
]) ])
->bulkActions([ ->bulkActions([
@@ -42,6 +101,18 @@ class ExpenseResource extends Resource
]); ]);
} }
public static function getTableColumns(): array
{
return [
Tables\Columns\TextColumn::make('supplier'),
Tables\Columns\TextColumn::make('reference_number'),
Tables\Columns\TextColumn::make('voucher_number'),
Tables\Columns\TextColumn::make('branch.client.company'),
Tables\Columns\TextColumn::make('branch.code'),
Tables\Columns\TextColumn::make('happened_on'),
];
}
public static function getRelations(): array public static function getRelations(): array
{ {
return [ return [

View File

@@ -2,10 +2,57 @@
namespace App\Filament\Resources\ExpenseResource\Pages; namespace App\Filament\Resources\ExpenseResource\Pages;
use App\Actions\Transactions\CreateTransactionAction;
use App\DataObjects\CreateTransactionDTO;
use App\Filament\Resources\ExpenseResource; use App\Filament\Resources\ExpenseResource;
use Exception;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Pipeline;
use Symfony\Component\Console\Exception\LogicException;
class CreateExpense extends CreateRecord class CreateExpense extends CreateRecord
{ {
protected static string $resource = ExpenseResource::class; 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()
{
$transactions = Arr::only($this->form->getState(), ['transactions']);
try {
$branch = $this->getRecord()->branch;
foreach ($transactions as $transaction) {
$data = [
'branch_id' => $branch->id,
'happened_on' => $this->getRecord()->happened_on,
...Arr::first($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

@@ -209,7 +209,7 @@ class RoleResource extends Resource implements HasShieldPermissions
); );
return Forms\Components\Section::make($sectionLabel) return Forms\Components\Section::make($sectionLabel)
->description(fn () => new HtmlString('<span style="word-break: break-word;">' . Utils::showModelPath($entity['fqcn']) . '</span>')) ->description(fn () => new HtmlString('<span style="word-break: break-word;">'.Utils::showModelPath($entity['fqcn']).'</span>'))
->compact() ->compact()
->schema([ ->schema([
static::getCheckBoxListComponentForResource($entity), static::getCheckBoxListComponentForResource($entity),
@@ -231,7 +231,7 @@ class RoleResource extends Resource implements HasShieldPermissions
{ {
return collect(Utils::getResourcePermissionPrefixes($entity['fqcn'])) return collect(Utils::getResourcePermissionPrefixes($entity['fqcn']))
->flatMap(function ($permission) use ($entity) { ->flatMap(function ($permission) use ($entity) {
$name = $permission . '_' . $entity['resource']; $name = $permission.'_'.$entity['resource'];
$label = static::shield()->hasLocalizedPermissionLabels() $label = static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedResourcePermissionLabel($permission) ? FilamentShield::getLocalizedResourcePermissionLabel($permission)
: $name; : $name;

View File

@@ -16,6 +16,11 @@ class Account extends Model
protected $guarded = []; protected $guarded = [];
public function getTypeAttribute(): string
{
return $this->accountType->type;
}
public function accountType(): BelongsTo public function accountType(): BelongsTo
{ {
return $this->belongsTo(AccountType::class); return $this->belongsTo(AccountType::class);

View File

@@ -18,14 +18,6 @@ class Branch extends Model
protected $guarded = []; protected $guarded = [];
/**
* Get the client that owns the Branch
*/
public function client(): BelongsTo
{
return $this->belongsTo(Client::class);
}
public function getCurrentSeriesAttribute() public function getCurrentSeriesAttribute()
{ {
if ($this->series()->count() > 0) { if ($this->series()->count() > 0) {
@@ -43,8 +35,21 @@ class Branch extends Model
return $this->hasMany(Series::class); return $this->hasMany(Series::class);
} }
public function getIsClientVatableAttribute(): bool
{
return $this->client->vatable;
}
/** /**
* Get all of the sales for the Branch * Get the client that owns the Branch
*/
public function client(): BelongsTo
{
return $this->belongsTo(Client::class);
}
/**
* Get all the sales for the Branch
*/ */
public function sales(): HasMany public function sales(): HasMany
{ {
@@ -52,7 +57,7 @@ class Branch extends Model
} }
/** /**
* Get all of the expenses for the Branch * Get all the expenses for the Branch
*/ */
public function expenses(): HasMany public function expenses(): HasMany
{ {
@@ -68,4 +73,9 @@ class Branch extends Model
{ {
return $this->belongsToThrough(Account::class, Balance::class); return $this->belongsToThrough(Account::class, Balance::class);
} }
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
} }

View File

@@ -24,13 +24,13 @@ class Client extends Model
return $this->lname.', '.$this->fname.' '.$this->mname; return $this->lname.', '.$this->fname.' '.$this->mname;
} }
public function getVatableAttribute() public function getVatableAttribute(): bool
{ {
return $this->type->type == 'Vatable' ? true : false; return $this->type->type == 'Vatable' ? true : false;
} }
/** /**
* Get all of the branches for the Client * Get all the branches for the Client
*/ */
public function branches(): HasMany public function branches(): HasMany
{ {

View File

@@ -4,8 +4,29 @@ 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\MorphToMany;
class Expense extends Model class Expense extends Model
{ {
use HasFactory; use HasFactory;
protected $guarded = [];
protected $casts = [
'happened_on' => 'date:Y-m-d',
];
/**
* Get all the transactions for the Sale
*/
public function transactions(): MorphToMany
{
return $this->morphToMany(Transaction::class, 'transactionable');
}
public function branch(): BelongsTo
{
return $this->belongsTo(Branch::class);
}
} }

View File

@@ -4,8 +4,14 @@ 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\MorphToMany;
class Sale extends Model class Sale extends Model
{ {
use HasFactory; use HasFactory;
public function transactions(): MorphToMany
{
return $this->morphToMany(Transaction::class, 'transactionable');
}
} }

View File

@@ -6,6 +6,8 @@ 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\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Transaction extends Model class Transaction extends Model
{ {
@@ -13,43 +15,36 @@ class Transaction extends Model
protected $guarded = []; protected $guarded = [];
/** public function transactionable(): MorphTo
* Get the expense that owns the Transaction
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function expense(): BelongsTo
{ {
return $this->belongsTo(Expense::class); return $this->morphTo();
}
/**
* Get the sale that owns the Transaction
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function sale(): BelongsTo
{
return $this->belongsTo(Sale::class);
} }
/** /**
* Get the account that owns the Transaction * Get the account that owns the Transaction
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public function account(): BelongsTo public function account(): BelongsTo
{ {
return $this->belongsTo(Account::class); return $this->belongsTo(Account::class);
} }
public function getAccountTypeAttribute(): string
{
return $this->account->type;
}
/** /**
* Get the ledgers associated with the Transaction * Get the ledgers associated with the Transaction
* *
* @return \Illuminate\Database\Eloquent\Relations\HasOne * @return HasOne
*/ */
public function ledgers(): HasMany public function ledgers(): HasMany
{ {
return $this->hasMany(Ledger::class); return $this->hasMany(Ledger::class);
} }
public function branch(): BelongsTo
{
return $this->belongsTo(Branch::class);
}
} }

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Observers;
use App\Commands\Ledgers\CreateLedgerCommand;
use App\Models\Expense;
use Illuminate\Support\Facades\DB;
class ExpenseObserver
{
/**
* Handle the Expense "created" event.
*/
public function created(Expense $expense): void
{
DB::transaction(
callback: function () use ($expense) {
$branch = $expense->branch;
//check if client is vatable
if ($branch->isClientVatable) {
// create a ledgers for vatable
$data = [
'transaction_id' => $expense->transaction_id,
];
$payload = new CreateLedgerCommand(data: $data);
} else {
// create a lkedgers for non vatable
}
//create cash transaction
},
attempts: 2
);
}
/**
* Handle the Expense "updated" event.
*/
public function updated(Expense $expense): void
{
//
}
/**
* Handle the Expense "deleted" event.
*/
public function deleted(Expense $expense): void
{
//
}
/**
* Handle the Expense "restored" event.
*/
public function restored(Expense $expense): void
{
//
}
/**
* Handle the Expense "force deleted" event.
*/
public function forceDeleted(Expense $expense): void
{
//
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Policies;
use App\Models\Branch;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class BranchPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_branch');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Branch $branch): bool
{
return $user->can('view_branch');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_branch');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Branch $branch): bool
{
return $user->can('update_branch');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Branch $branch): bool
{
return $user->can('delete_branch');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_branch');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Branch $branch): bool
{
return $user->can('force_delete_branch');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_branch');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Branch $branch): bool
{
return $user->can('restore_branch');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_branch');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Branch $branch): bool
{
return $user->can('replicate_branch');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_branch');
}
}

View File

@@ -2,8 +2,8 @@
namespace App\Policies; namespace App\Policies;
use App\Models\User;
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

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Policies;
use App\Models\Expense;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ExpensePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_expense');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Expense $expense): bool
{
return $user->can('view_expense');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_expense');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Expense $expense): bool
{
return $user->can('update_expense');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Expense $expense): bool
{
return $user->can('delete_expense');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_expense');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Expense $expense): bool
{
return $user->can('force_delete_expense');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_expense');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Expense $expense): bool
{
return $user->can('restore_expense');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_expense');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Expense $expense): bool
{
return $user->can('replicate_expense');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_expense');
}
}

View File

@@ -3,8 +3,8 @@
namespace App\Policies; namespace App\Policies;
use App\Models\User; use App\Models\User;
use Spatie\Permission\Models\Role;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
use Spatie\Permission\Models\Role;
class RolePolicy class RolePolicy
{ {

108
app/Policies/SalePolicy.php Normal file
View File

@@ -0,0 +1,108 @@
<?php
namespace App\Policies;
use App\Models\Sale;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class SalePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_sale');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Sale $sale): bool
{
return $user->can('view_sale');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_sale');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Sale $sale): bool
{
return $user->can('update_sale');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Sale $sale): bool
{
return $user->can('delete_sale');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_sale');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Sale $sale): bool
{
return $user->can('force_delete_sale');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_sale');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Sale $sale): bool
{
return $user->can('restore_sale');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_sale');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Sale $sale): bool
{
return $user->can('replicate_sale');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_sale');
}
}

View File

@@ -2,8 +2,8 @@
namespace App\Policies; namespace App\Policies;
use App\Models\User;
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

View File

@@ -3,7 +3,6 @@
namespace App\Policies; namespace App\Policies;
use App\Models\User; use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy class UserPolicy
@@ -12,9 +11,6 @@ class UserPolicy
/** /**
* Determine whether the user can view any models. * Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
@@ -23,9 +19,6 @@ class UserPolicy
/** /**
* Determine whether the user can view the model. * Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function view(User $user): bool public function view(User $user): bool
{ {
@@ -34,9 +27,6 @@ class UserPolicy
/** /**
* Determine whether the user can create models. * Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
@@ -45,9 +35,6 @@ class UserPolicy
/** /**
* Determine whether the user can update the model. * Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function update(User $user): bool public function update(User $user): bool
{ {
@@ -56,9 +43,6 @@ class UserPolicy
/** /**
* Determine whether the user can delete the model. * Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function delete(User $user): bool public function delete(User $user): bool
{ {
@@ -67,9 +51,6 @@ class UserPolicy
/** /**
* Determine whether the user can bulk delete. * Determine whether the user can bulk delete.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function deleteAny(User $user): bool public function deleteAny(User $user): bool
{ {
@@ -78,9 +59,6 @@ class UserPolicy
/** /**
* Determine whether the user can permanently delete. * Determine whether the user can permanently delete.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function forceDelete(User $user): bool public function forceDelete(User $user): bool
{ {
@@ -89,9 +67,6 @@ class UserPolicy
/** /**
* Determine whether the user can permanently bulk delete. * Determine whether the user can permanently bulk delete.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function forceDeleteAny(User $user): bool public function forceDeleteAny(User $user): bool
{ {
@@ -100,9 +75,6 @@ class UserPolicy
/** /**
* Determine whether the user can restore. * Determine whether the user can restore.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function restore(User $user): bool public function restore(User $user): bool
{ {
@@ -111,9 +83,6 @@ class UserPolicy
/** /**
* Determine whether the user can bulk restore. * Determine whether the user can bulk restore.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function restoreAny(User $user): bool public function restoreAny(User $user): bool
{ {
@@ -122,9 +91,6 @@ class UserPolicy
/** /**
* Determine whether the user can bulk restore. * Determine whether the user can bulk restore.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function replicate(User $user): bool public function replicate(User $user): bool
{ {
@@ -133,9 +99,6 @@ class UserPolicy
/** /**
* Determine whether the user can reorder. * Determine whether the user can reorder.
*
* @param \App\Models\User $user
* @return bool
*/ */
public function reorder(User $user): bool public function reorder(User $user): bool
{ {

View File

@@ -2,7 +2,10 @@
namespace App\Providers; namespace App\Providers;
use App\Policies\RolePolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Spatie\Permission\Models\Role;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@@ -19,6 +22,6 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot(): void public function boot(): void
{ {
// Gate::policy(Role::class, RolePolicy::class);
} }
} }

View File

@@ -3,5 +3,6 @@
return [ return [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
App\Providers\Filament\AdminPanelProvider::class, App\Providers\Filament\AdminPanelProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\VoltServiceProvider::class, App\Providers\VoltServiceProvider::class,
]; ];

View File

@@ -9,6 +9,7 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"awcodes/filament-table-repeater": "^3.0",
"bezhansalleh/filament-shield": "^3.2", "bezhansalleh/filament-shield": "^3.2",
"filament/filament": "^3.2", "filament/filament": "^3.2",
"laravel/framework": "^11.9", "laravel/framework": "^11.9",

601
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class () extends Migration { return new class extends Migration
{
/** /**
* Run the migrations. * Run the migrations.
* *
@@ -30,10 +31,9 @@ return new class () extends Migration {
$table->decimal('payable_withholding_tax')->nullable(); $table->decimal('payable_withholding_tax')->nullable();
$table->decimal('creditable_withholding_tax')->nullable(); $table->decimal('creditable_withholding_tax')->nullable();
$table->foreignId('expense_id')->nullable()->constrained()->onDelete('cascade'); $table->morphs('transactionable');
$table->foreignId('sale_id')->nullable()->constrained()->onDelete('cascade');
$table->foreignId('branch_id')->constrained()->onDelete('cascade'); $table->foreignId('branch_id')->constrained()->onDelete('cascade');
$table->foreignId('client_id')->constrained()->onDelete('cascade');
$table->date('happened_on'); $table->date('happened_on');
$table->timestamps(); $table->timestamps();
}); });

View File

@@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class () extends Migration { return new class extends Migration
{
/** /**
* Run the migrations. * Run the migrations.
* *
@@ -12,7 +13,7 @@ return new class () extends Migration {
*/ */
public function up() public function up()
{ {
Schema::create('transmittal', function (Blueprint $table) { Schema::create('transmittals', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('series'); $table->string('series');
$table->foreignId('client_id')->constrained(); $table->foreignId('client_id')->constrained();

View File

@@ -14,8 +14,8 @@ return new class extends Migration
public function up() public function up()
{ {
Schema::table('transactions', function (Blueprint $table) { Schema::table('transactions', function (Blueprint $table) {
$table->boolean('with_discount')->after('client_id')->nullable(); $table->boolean('with_discount')->nullable();
$table->decimal('discount')->after('client_id')->nullable(); $table->decimal('discount')->nullable();
}); });
} }

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('transactionables', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('transaction_id');
$table->unsignedBigInteger('transactionable_id');
$table->string('transactionable_type');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('transactionables');
}
};

View File

@@ -0,0 +1,68 @@
<?php
namespace Database\Seeders;
use BezhanSalleh\FilamentShield\Support\Utils;
use Illuminate\Database\Seeder;
use Spatie\Permission\PermissionRegistrar;
class ShieldSeeder extends Seeder
{
public function run(): void
{
app()[PermissionRegistrar::class]->forgetCachedPermissions();
$rolesWithPermissions = '[{"name":"super_admin","guard_name":"web","permissions":[]}]';
$directPermissions = '[]';
static::makeRolesWithPermissions($rolesWithPermissions);
static::makeDirectPermissions($directPermissions);
$this->command->info('Shield Seeding Completed.');
}
protected static function makeRolesWithPermissions(string $rolesWithPermissions): void
{
if (! blank($rolePlusPermissions = json_decode($rolesWithPermissions, true))) {
/** @var Model $roleModel */
$roleModel = Utils::getRoleModel();
/** @var Model $permissionModel */
$permissionModel = Utils::getPermissionModel();
foreach ($rolePlusPermissions as $rolePlusPermission) {
$role = $roleModel::firstOrCreate([
'name' => $rolePlusPermission['name'],
'guard_name' => $rolePlusPermission['guard_name'],
]);
if (! blank($rolePlusPermission['permissions'])) {
$permissionModels = collect($rolePlusPermission['permissions'])
->map(fn ($permission) => $permissionModel::firstOrCreate([
'name' => $permission,
'guard_name' => $rolePlusPermission['guard_name'],
]))
->all();
$role->syncPermissions($permissionModels);
}
}
}
}
public static function makeDirectPermissions(string $directPermissions): void
{
if (! blank($permissions = json_decode($directPermissions, true))) {
/** @var Model $permissionModel */
$permissionModel = Utils::getPermissionModel();
foreach ($permissions as $permission) {
if ($permissionModel::whereName($permission)->doesntExist()) {
$permissionModel::create([
'name' => $permission['name'],
'guard_name' => $permission['guard_name'],
]);
}
}
}
}
}

58
docker-compose.yml Normal file
View File

@@ -0,0 +1,58 @@
services:
laravel.test:
build:
context: ./vendor/laravel/sail/runtimes/8.3
dockerfile: Dockerfile
args:
WWWGROUP: '${WWWGROUP}'
image: sail-8.3/app
extra_hosts:
- 'host.docker.internal:host-gateway'
ports:
- '${APP_PORT:-80}:80'
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
environment:
WWWUSER: '${WWWUSER}'
LARAVEL_SAIL: 1
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
IGNITION_LOCAL_SITES_PATH: '${PWD}'
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port=80"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/data
volumes:
- '.:/var/www/html'
networks:
- sail
depends_on:
- mysql
mysql:
image: 'mysql/mysql-server:8.0'
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: '%'
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
- 'sail-mysql:/var/lib/mysql'
- './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
networks:
- sail
healthcheck:
test:
- CMD
- mysqladmin
- ping
- '-p${DB_PASSWORD}'
retries: 3
timeout: 5s
networks:
sail:
driver: bridge
volumes:
sail-mysql:
driver: local

62
package-lock.json generated
View File

@@ -6,13 +6,13 @@
"": { "": {
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.14",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.20",
"axios": "^1.6.4", "axios": "^1.6.4",
"laravel-vite-plugin": "^1.0", "laravel-vite-plugin": "^1.0",
"postcss": "^8.4.40", "postcss": "^8.4.41",
"postcss-nesting": "^12.1.5", "postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.7", "tailwindcss": "^3.4.9",
"vite": "^5.0" "vite": "^5.0"
} }
}, },
@@ -771,9 +771,9 @@
} }
}, },
"node_modules/@tailwindcss/typography": { "node_modules/@tailwindcss/typography": {
"version": "0.5.13", "version": "0.5.14",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.14.tgz",
"integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==", "integrity": "sha512-ZvOCjUbsJBjL9CxQBn+VEnFpouzuKhxh2dH8xMIWHILL+HfOYtlAkWcyoon8LlzE53d2Yo6YO6pahKKNW3q1YQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"lodash.castarray": "^4.4.0", "lodash.castarray": "^4.4.0",
@@ -860,9 +860,9 @@
"dev": true "dev": true
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.19", "version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -879,11 +879,11 @@
} }
], ],
"dependencies": { "dependencies": {
"browserslist": "^4.23.0", "browserslist": "^4.23.3",
"caniuse-lite": "^1.0.30001599", "caniuse-lite": "^1.0.30001646",
"fraction.js": "^4.3.7", "fraction.js": "^4.3.7",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"picocolors": "^1.0.0", "picocolors": "^1.0.1",
"postcss-value-parser": "^4.2.0" "postcss-value-parser": "^4.2.0"
}, },
"bin": { "bin": {
@@ -947,9 +947,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.23.2", "version": "4.23.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
"integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -966,9 +966,9 @@
} }
], ],
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001640", "caniuse-lite": "^1.0.30001646",
"electron-to-chromium": "^1.4.820", "electron-to-chromium": "^1.5.4",
"node-releases": "^2.0.14", "node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.0" "update-browserslist-db": "^1.1.0"
}, },
"bin": { "bin": {
@@ -988,9 +988,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001643", "version": "1.0.30001651",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -1136,9 +1136,9 @@
"dev": true "dev": true
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.1", "version": "1.5.6",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.1.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz",
"integrity": "sha512-FKbOCOQ5QRB3VlIbl1LZQefWIYwszlBloaXcY2rbfpu9ioJnNh3TK03YtIDKDo3WKBi8u+YV4+Fn2CkEozgf4w==", "integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==",
"dev": true "dev": true
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
@@ -1748,9 +1748,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.40", "version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -2234,9 +2234,9 @@
} }
}, },
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.7", "version": "3.4.9",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz",
"integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==", "integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",

View File

@@ -8,13 +8,13 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.14",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.20",
"axios": "^1.6.4", "axios": "^1.6.4",
"laravel-vite-plugin": "^1.0", "laravel-vite-plugin": "^1.0",
"postcss": "^8.4.40", "postcss": "^8.4.41",
"postcss-nesting": "^12.1.5", "postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.7", "tailwindcss": "^3.4.9",
"vite": "^5.0" "vite": "^5.0"
} }
} }

View File

@@ -22,8 +22,7 @@
<env name="APP_MAINTENANCE_DRIVER" value="file"/> <env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/> <env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/> <env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> --> <env name="DB_DATABASE" value="testing"/>
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="MAIL_MAILER" value="array"/> <env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/> <env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/> <env name="QUEUE_CONNECTION" value="sync"/>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
function r({state:i}){return{state:i,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=o=>o===null?0:Array.isArray(o)?o.length:typeof o!="object"?0:Object.keys(o).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
function t({initialHeight:e}){return{init:function(){this.render()},render:function(){this.$el.scrollHeight>0&&(this.$el.style.height=e+"rem",this.$el.style.height=this.$el.scrollHeight+"px")}}}export{t as default}; function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
function c(){return{collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,init:function(){this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1})},mountAction:function(e,s=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,s)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let s=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])t.dataset.group===e&&s.push(t.value);return s},getRecordsOnPage:function(){let e=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(s.value);return e},selectRecords:function(e){for(let s of e)this.isRecordSelected(s)||this.selectedRecords.push(s)},deselectRecords:function(e){for(let s of e){let t=this.selectedRecords.indexOf(s);t!==-1&&this.selectedRecords.splice(t,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(s=>this.isRecordSelected(s))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]}}}export{c as default}; function n(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let o=s.indexOf(this.lastChecked),r=s.indexOf(t),l=[o,r].sort((i,d)=>i-d),c=[];for(let i=l[0];i<=l[1];i++)s[i].checked=t.checked,c.push(s[i].value);t.checked?this.selectRecords(c):this.deselectRecords(c)}this.lastChecked=t}}}export{n as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,5 +6,6 @@ export default {
'./app/Filament/**/*.php', './app/Filament/**/*.php',
'./resources/views/filament/**/*.blade.php', './resources/views/filament/**/*.blade.php',
'./vendor/filament/**/*.blade.php', './vendor/filament/**/*.blade.php',
'./vendor/awcodes/filament-table-repeater/resources/**/*.blade.php'
], ],
} }

View File

@@ -1,8 +1,9 @@
@import '/vendor/filament/filament/resources/css/theme.css'; @import '/vendor/filament/filament/resources/css/theme.css';
@import '/vendor/awcodes/filament-table-repeater/resources/css/plugin.css';
@config 'tailwind.config.js'; @config 'tailwind.config.js';
.fi-btn , .fi-input-wrp, .fi-tabs, .fi-sidebar-item-button, .fi-section { .fi-btn, .fi-input-wrp, .fi-tabs, .fi-sidebar-item-button, .fi-section {
@apply rounded-sm !important; @apply rounded-sm !important;
} }
@@ -10,7 +11,7 @@
@apply mx-0 rounded-sm ring-0 shadow !important; @apply mx-0 rounded-sm ring-0 shadow !important;
} }
.fi-resource-relation-managers , .fi-ta { .fi-resource-relation-managers, .fi-ta {
@apply gap-y-0 shadow-none bg-none; @apply gap-y-0 shadow-none bg-none;
} }
@@ -26,7 +27,7 @@
@apply bg-primary-500 bg-opacity-10 !important; @apply bg-primary-500 bg-opacity-10 !important;
} }
.fi-ta-table> thead > tr { .fi-ta-table > thead > tr {
@apply bg-primary-400 bg-opacity-40 !important; @apply bg-primary-400 bg-opacity-40 !important;
} }

View File

@@ -1,8 +1,9 @@
<?php <?php
use App\Models\Transmittal;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::view('/', 'welcome'); Route::redirect('/', '/admin');
Route::view('dashboard', 'dashboard') Route::view('dashboard', 'dashboard')
->middleware(['auth', 'verified']) ->middleware(['auth', 'verified'])
@@ -12,10 +13,9 @@ Route::view('profile', 'profile')
->middleware(['auth']) ->middleware(['auth'])
->name('profile'); ->name('profile');
Route::get('preview-transmittal', function () {
Route::get('preview-transmittal',function () { return view('transmittal.export.transmittal-export-table')->with(['transmittals' => Transmittal::withCount(['files', 'notes', 'remarks'])->with(['files' => function ($files) {
return view('transmittal.export.transmittal-export-table')->with(['transmittals' => \App\Models\Transmittal::withCount(['files','notes','remarks'])->with(['files' => function($files) { $files->withCount(['notes', 'remarks']);
$files->withCount(['notes','remarks']);
}])->get()]); }])->get()]);
}); });