44 Commits

Author SHA1 Message Date
Jp
993d096283 test 2026-02-26 14:02:41 +08:00
Jp
08817af0ca test 2026-02-26 14:00:19 +08:00
Jp
afb7cefdda test 2026-02-26 13:56:10 +08:00
Jp
08fe589ad6 test 2026-02-26 13:54:47 +08:00
Jp
1937e26251 test 2026-02-26 13:46:33 +08:00
Jp
1dbc32722a chore: improve container build and deployment configuration
- Add common system directories to .dockerignore to prevent accidental inclusion
- Reorder Dockerfile instructions for better clarity and performance
- Remove remote deployment targets from Makefile to simplify local development workflow
2026-02-26 13:44:36 +08:00
Jp
a4124ceaa9 test 2026-02-26 13:42:31 +08:00
Jp
8b23298196 test 2026-02-26 13:39:45 +08:00
Jp
3c251e9c3a test 2026-02-26 13:36:40 +08:00
Jp
f635dabbc5 test 2026-02-26 13:34:20 +08:00
Jp
e0f459f9ea refactor(Dockerfile): simplify image build and permission handling
- Use fully qualified image names for clarity
- Remove manual PHP extension installation as base image likely includes them
- Leverage COPY --chown to set permissions directly
- Remove redundant permission fix commands
- Keep same final command and exposed port
2026-02-26 13:33:07 +08:00
Jp
8c414e7e70 build: switch to thecodingmachine PHP image for enhanced features
The base image is changed from the official php:8.4-fpm-alpine to thecodingmachine/php:8.4-v4-fpm-alpine to leverage additional pre-installed extensions, configurations, and utilities provided by the TheCodingMachine Docker images, which simplifies the setup for common PHP development and production environments.
2026-02-26 13:31:46 +08:00
Jp
49226f8aa1 build: simplify Dockerfile using extension installer
- Replace manual apk install and docker-php-ext-install with mlocati/docker-php-extension-installer
- Combine system dependency installation and PHP extension setup into a single step
- Reorder steps for better clarity: copy files before permission fixes
- Maintain same PHP extensions and final permissions
2026-02-26 13:27:58 +08:00
Jp
77ed347e36 build: switch to Alpine base image and simplify Dockerfile
- Use php:8.4-fpm-alpine for smaller image size
- Replace apt-get with apk package manager
- Use docker-php-ext-install instead of external extension installer
- Remove unnecessary comments and clean up cache appropriately
2026-02-26 13:17:33 +08:00
Jp
4ade74421c chore(docker): switch to mlocati's PHP extension installer
The docker-php-ext-install command can have permission issues on some
remote servers when extracting tarballs. Using mlocati/docker-php-extension-installer
provides a more reliable method for installing PHP extensions in Docker
containers.
2026-02-26 12:54:55 +08:00
Jp
8e3913bda8 fix(Dockerfile): replace php extension installer with docker-php-ext-install
The third-party extension installer (mlocati/docker-php-extension-installer) was causing tar permission issues on the remote server. Using the standard `docker-php-ext-install` method resolves this compatibility problem.
2026-02-26 11:44:16 +08:00
Jp
fea727daf4 ci(Docker): add --cleanup flag to install-php-extensions
Add the --cleanup flag to the install-php-extensions command to remove downloaded archives after installation, reducing the final image size.

Use the --no-cache flag for the remote podman build in the deployment script to ensure a clean build from the latest source on the server.
2026-02-26 11:36:50 +08:00
Jp
cea2749252 ci: build image remotely before deploying to server
Previously the image was built locally and pushed to a registry. Now we build directly on the remote server to simplify the deployment flow and avoid registry dependencies.
2026-02-26 11:34:48 +08:00
Jp
5992b28eb5 ci: use rm -f to avoid errors when cleaning up temporary files
The -f flag prevents errors if the files do not exist, ensuring the cleanup step is robust and does not fail the deployment process.
2026-02-26 11:30:32 +08:00
Jp
e6acea80a0 chore(deploy): prepend sudo to kubectl commands in Makefile
Ensure kubectl commands have necessary permissions when generating ConfigMaps during deployment.
2026-02-26 11:29:03 +08:00
Jp
02dbea230c fix(deploy): add sudo to kubectl apply command for proper permissions
The deploy-server target was failing due to insufficient permissions on the remote server. Adding sudo ensures kubectl can apply the deployment configuration.
2026-02-26 11:27:10 +08:00
Jp
07748a7025 chore(Makefile): update server config and improve deployment scripts
- Update default server user, IP, and deployment path to match current environment
- Redirect podman kube down output to /dev/null to reduce noise
- Use /tmp directory for temporary YAML files to avoid WSL permission issues
- Consolidate deployment steps to reduce password prompts during remote deployment
2026-02-26 11:25:54 +08:00
Jp
55d2f61d09 ci: streamline server deployment by reducing SSH prompts
Generate a temporary merged YAML file locally before copying to the server. This reduces the number of SSH/SCP commands from multiple to two, minimizing password prompts and connection overhead.
2026-02-26 11:23:30 +08:00
Jp
aef00b405a chore: remove redundant .env copy commands from deploy targets
The .env file is now expected to be present before running deploy commands. This simplifies the Makefile by removing conditional copy logic that was causing confusion about environment setup.
2026-02-26 11:19:19 +08:00
Jp
ac41090ed3 feat(Makefile): add remote server deployment target
Add deploy-server target to deploy application to a remote Kubernetes cluster.
Includes server configuration variables and uses scp/ssh for file transfer and
kubectl commands for applying configuration and deployment.
2026-02-26 11:14:39 +08:00
Jp
ba9c80b9b4 fix(Dockerfile): set proper permissions and remove CI directories
The previous COPY instruction with --chown flag was not recursively setting permissions. Explicitly set directory and file permissions for the www-data user and remove unnecessary CI directories to reduce image size and potential security exposure.
2026-02-26 11:08:20 +08:00
Jp
618f826e1e ci: improve deployment script verbosity and error handling
- Remove stderr suppression from podman kube down to show potential errors
- Remove @ prefix from kubectl command to show execution in output
- Add comment clarifying the purpose of removing stderr redirection
2026-02-26 11:06:21 +08:00
Jp
baf68078a7 ci: switch from Kubernetes to Podman for local deployment
- Update Dockerfile to use fully qualified composer image name
- Replace kubectl commands with podman kube play/down in Makefile
- Change from Kubernetes Secret to ConfigMap for environment variables
- Set imagePullPolicy to Never and use localhost/ prefix for local images
- Reduce replica count to 1 for local development
- Add stop target to Makefile for easier cleanup
2026-02-26 10:55:30 +08:00
Jp
87fd507618 chore(docker): move WORKDIR declaration earlier and reorder steps
Set working directory before copying files to avoid potential conflicts with host files. Also reorder Dockerfile steps for better logical flow and maintainability.
2026-02-25 22:52:20 +08:00
Jp
a697100103 chore(docker): ignore CI directories and use extension installer
- Add .github and .circleci to .dockerignore to reduce image size
- Replace docker-php-ext-install with mlocati's extension installer for better reliability and maintenance
2026-02-25 22:43:35 +08:00
Jp
dd188724be build(docker): enhance Docker setup with proper dependencies and structure
- Add comprehensive .dockerignore file to exclude unnecessary files
- Update Dockerfile to install required system dependencies and PHP extensions
- Set proper working directory and user permissions
- Use multi-stage build for Composer installation
2026-02-25 22:34:05 +08:00
95301dc1d8 Merge pull request #10 from kingjaypee12/jp/deployment
chore: migrate from docker compose to kubernetes deployment
2026-02-25 22:06:17 +08:00
Jp
673a6ce166 chore: migrate from docker compose to kubernetes deployment
- Replace Docker Compose setup with Kubernetes manifests for deployment
- Simplify Dockerfile to use PHP's built-in server instead of nginx+supervisor
- Add Makefile with build and deploy commands for local development
- Update environment configuration for production deployment
- Remove docker-specific configuration files (nginx.conf, supervisord.conf)
2026-02-25 22:05:41 +08:00
Jp
e57e6d3413 feat(docker): add standalone Docker setup and remove docker-compose
- Replace Laravel Sail docker-compose with a single-container Dockerfile
- Configure nginx and PHP-FPM via supervisor for production-like environment
- Include optimized PHP extensions and Redis support
- Simplify deployment by removing multi-service orchestration
2026-02-19 16:33:03 +08:00
Jp
feba3f1464 style: remove unnecessary !important from CSS utility classes
The !important flag was previously used to override Tailwind's default styles, but it is no longer needed after recent framework updates. This change improves maintainability and follows CSS best practices by avoiding excessive specificity.
2026-02-19 03:18:00 +08:00
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
143 changed files with 4965 additions and 3920 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"
}
]

17
.dockerignore Normal file
View File

@@ -0,0 +1,17 @@
.git
.github
.circleci
.env
.env.local
node_modules
vendor
storage/logs/*
storage/framework/cache/*
bootstrap/cache/*
.phpunit.result.cache
.idea
.vscode
.devcontainer
docker-compose.yml
Makefile
k8s/

View File

@@ -1,9 +1,9 @@
APP_NAME=Laravel
APP_NAME='MKM Tax Services'
APP_ENV=local
APP_KEY=
APP_KEY=base64:Tqv7x1T7GGUVfwzcWOwg6fwrp96dkjV4WCgyRYjdyYY=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost
APP_URL=http://mkmtaxservices.com.test
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
@@ -20,11 +20,14 @@ LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_HOST=192.168.100.105
DB_PORT=3306
DB_DATABASE=mkm_admin
DB_USERNAME=root
DB_PASSWORD=
DB_PASSWORD=root
DB_QUEUE_TABLE=jobs
DB_QUEUE=default
SESSION_DRIVER=database
SESSION_LIFETIME=120
@@ -42,12 +45,12 @@ CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_HOST=192.168.100.105
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_HOST=127.0.0.1
MAIL_HOST=192.168.100.105
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
@@ -62,3 +65,5 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
OCTANE_SERVER=frankenphp

40
Dockerfile Normal file
View File

@@ -0,0 +1,40 @@
FROM docker.io/alpine:3.21
RUN apk add --no-cache \
php84 \
php84-fpm \
php84-pdo_mysql \
php84-mbstring \
php84-exif \
php84-pcntl \
php84-bcmath \
php84-gd \
php84-zip \
php84-intl \
php84-curl \
php84-tokenizer \
php84-xml \
php84-xmlwriter \
php84-xmlreader \
php84-dom \
php84-session \
php84-fileinfo \
php84-openssl \
git \
curl
RUN ln -sf /usr/bin/php84 /usr/bin/php
WORKDIR /var/www
# Copy the application files
COPY --chown=root:root . .
COPY --from=docker.io/composer:latest /usr/bin/composer /usr/bin/composer
RUN rm -rf .circleci .github 2>/dev/null || true && \
chmod -R 775 storage bootstrap/cache 2>/dev/null || true
EXPOSE 8000
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]

21
Makefile Normal file
View File

@@ -0,0 +1,21 @@
IMG := mkm-admin
build:
@echo "Building image from project root..."
podman build -t $(IMG) .
deploy: build
@echo "Cleaning up existing deployment if any..."
-podman kube down k8s/deployment.yaml >/dev/null 2>&1
@echo "Generating merged deployment with ConfigMap..."
@sudo kubectl create configmap mkm-admin-config --from-env-file=.env --dry-run=client -o yaml > /tmp/merged_deployment.yaml
@echo "---" >> /tmp/merged_deployment.yaml
@cat k8s/deployment.yaml >> /tmp/merged_deployment.yaml
@echo "Deploying via podman kube play..."
podman kube play /tmp/merged_deployment.yaml
@rm -f /tmp/merged_deployment.yaml
stop:
-podman kube down k8s/deployment.yaml >/dev/null 2>&1
.PHONY: build deploy stop

View File

@@ -2,9 +2,10 @@
namespace App\Actions;
use Closure;
use Spatie\LaravelData\Data;
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;
use Closure;
use Exception;
use LogicException;
use App\Actions\BaseAction;
use App\Commands\Series\CreateSeriesCommand;
use App\DataObjects\CreateBranchDTO;
@@ -15,7 +18,7 @@ class StoreBranchSeries extends BaseAction
private CreateSeriesCommand $createSeriesCommand
) {}
public function __invoke(CreateBranchDTO|Data $payload, \Closure $next)
public function __invoke(CreateBranchDTO|Data $payload, Closure $next)
{
try {
$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());
return $next($payload);
} catch (\Exception $exception) {
throw new \LogicException('Failed to create branch series', $exception->getMessage());
} catch (Exception $exception) {
throw new LogicException('Failed to create branch series', $exception->getMessage());
}
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Sales;
use Exception;
use App\Actions\Transactions\CreateRecordTransactionsAction;
use App\Commands\Sales\CreateSaleCommand;
use App\Commands\Series\CreateSeriesCommand;
@@ -24,7 +25,6 @@ class CreateSaleAction
try {
DB::beginTransaction();
//create transactions for the sale
app(CreateRecordTransactionsAction::class)($record, $transactions);
@@ -45,9 +45,9 @@ class CreateSaleAction
]);
DB::commit();
} catch (\Exception $exception) {
} catch (Exception $exception) {
DB::rollBack();
throw new \Exception('Failed to save transactions : '.$exception->getMessage());
throw new Exception('Failed to save transactions : '.$exception->getMessage());
}
return $record;

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Transactions;
use App\Models\Expense;
use App\Actions\Balances\CreateBalanceAction;
use App\Actions\BaseAction;
use App\Actions\Ledgers\CreateLedgerAction;
@@ -24,20 +25,26 @@ class CreateTransactionAction extends BaseAction
$this->cashAccountLedger($payload);
if ($payload->transaction->discount !== 0) {
$this->discountAccountLedger($payload);
}
return $next($payload);
}
public function transactionAccountLedger($payload): void
{
$branch = $payload->transaction->branch;
$isExpense = $payload->transactionable instanceof \App\Models\Expense;
$isExpense = $payload->transactionable instanceof Expense;
$type = $isExpense ? 'debit' : 'credit';
$discount = $payload->transaction->discount ?? 0.00;
if ($branch->isClientVatable) {
//create transaction account ledger
$ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id,
amount: $payload->transaction->net_amount ?? 0.00,
amount: $payload->transaction->net_amount + $discount ?? 0.00,
transaction: $payload->transaction,
account: $payload->transaction->account,
type: $type,
@@ -96,7 +103,7 @@ class CreateTransactionAction extends BaseAction
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';
$type = $isExpense ? 'credit' : 'debit';
$clientId = $payload->transactionable->branch->client_id;
@@ -122,7 +129,7 @@ class CreateTransactionAction extends BaseAction
public function cashAccountLedger($payload): void
{
$isExpense = $payload->transactionable instanceof \App\Models\Expense;
$isExpense = $payload->transactionable instanceof Expense;
$type = $isExpense ? 'credit' : 'debit';
$wht = $isExpense ? ($payload->transaction->payable_withholding_tax ?? 0) : ($payload->transaction->creditable_withholding_tax ?? 0);
$amount = ($payload->transaction->gross_amount ?? 0) - $wht;
@@ -144,4 +151,28 @@ class CreateTransactionAction extends BaseAction
$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;
use Closure;
use Exception;
use LogicException;
use App\Actions\BaseAction;
use App\Commands\Transmittal\StoreTransmittalCommand;
use App\DataObjects\CreateTransmittalDTO;
@@ -15,15 +18,15 @@ class CreateTransmittal extends BaseAction
private readonly StoreTransmittalCommand $storeTransmittalCommand
) {}
public function __invoke(CreateTransmittalDTO | Data $payload, \Closure $next)
public function __invoke(CreateTransmittalDTO | Data $payload, Closure $next)
{
try {
$payload->transmittal = $this->storeTransmittalCommand->execute(Arr::except($payload->data, ['files']));
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;
use Closure;
use Exception;
use LogicException;
use App\Actions\BaseAction;
use App\Commands\Transmittal\StoreCommentCommand;
use App\Commands\Transmittal\StoreFileCommand;
@@ -22,7 +25,7 @@ class CreateTransmittalFiles extends BaseAction
private readonly StoreRemarkCommand $storeRemarkCommand
) {}
public function __invoke(CreateTransmittalDTO | Data $payload, \Closure $next)
public function __invoke(CreateTransmittalDTO | Data $payload, Closure $next)
{
try {
$files = Arr::only($payload->data, 'files');
@@ -55,9 +58,9 @@ class CreateTransmittalFiles extends BaseAction
$payload->files = $filesCreated;
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

@@ -2,6 +2,7 @@
namespace App\Commands\Sales;
use Carbon\Carbon;
use App\Commands\Command;
use App\DataObjects\CreateSaleDTO;
use App\Models\Sale;
@@ -15,7 +16,7 @@ class CreateSaleCommand implements Command
return DB::transaction(function () use ($data, &$sale) {
$tData = new CreateSaleDTO(
reference_number: $data['current_series'],
happened_on: \Carbon\Carbon::parse($data['happened_on']),
happened_on: Carbon::parse($data['happened_on']),
branch_id: $data['branch_id'],
user_id: Auth::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
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\RelationManagers\BalancesRelationManager;
use App\Filament\Resources\BranchResource\RelationManagers\ExpenseRelationManager;
use App\Filament\Resources\Branches\RelationManagers\BalancesRelationManager;
use App\Filament\Resources\Branches\RelationManagers\ExpenseRelationManager;
use App\Models\Branch;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Support\RawJs;
use Filament\Tables;
@@ -19,16 +25,16 @@ class BranchResource extends Resource
{
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 ?string $recordTitleAttribute = 'code';
public static function form(Form $form): Form
public static function form(Schema $schema): Schema
{
return $form
->schema([
return $schema
->components([
Select::make('client_id')->relationship('client', 'id')
->getOptionLabelFromRecordUsing(fn ($record) => $record->company)
->disabled()
@@ -63,12 +69,12 @@ class BranchResource extends Resource
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
->recordActions([
EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
@@ -85,9 +91,9 @@ class BranchResource extends Resource
public static function getPages(): array
{
return [
'index' => Pages\ListBranches::route('/'),
'create' => Pages\CreateBranch::route('/create'),
'edit' => Pages\EditBranch::route('/{record}/edit'),
'index' => ListBranches::route('/'),
'create' => CreateBranch::route('/create'),
'edit' => EditBranch::route('/{record}/edit'),
];
}
}

View File

@@ -1,8 +1,8 @@
<?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\Resources\Pages\CreateRecord;

View File

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

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListBranches extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
CreateAction::make(),
];
}
}

View File

@@ -1,10 +1,17 @@
<?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 Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
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
->schema([
Forms\Components\TextInput::make('branch_id')
return $schema
->components([
TextInput::make('branch_id')
->required()
->maxLength(255),
]);
@@ -37,25 +44,25 @@ class AccountsRelationManager extends RelationManager
return $table
->recordTitleAttribute('branch_id')
->columns([
Tables\Columns\TextColumn::make('account'),
Tables\Columns\TextColumn::make('branch_id'),
Tables\Columns\TextColumn::make('normal_balance'),
Tables\Columns\TextColumn::make('starting_balance'),
Tables\Columns\TextColumn::make('current_balance'),
TextColumn::make('account'),
TextColumn::make('branch_id'),
TextColumn::make('normal_balance'),
TextColumn::make('starting_balance'),
TextColumn::make('current_balance'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}

View File

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

View File

@@ -1,21 +1,22 @@
<?php
namespace App\Filament\Resources\BranchResource\RelationManagers;
namespace App\Filament\Resources\Branches\RelationManagers;
use App\Filament\Resources\ExpenseResource;
use Filament\Forms\Form;
use Filament\Schemas\Schema;
use Filament\Actions\CreateAction;
use App\Filament\Resources\Expenses\Pages\CreateExpense;
use App\Filament\Resources\Expenses\ExpenseResource;
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
public function form(Schema $schema): Schema
{
return $form
->schema(ExpenseResource::getExpenseFormFields());
return $schema
->components(ExpenseResource::getExpenseFormFields());
}
public function table(Table $table): Table
@@ -25,8 +26,8 @@ class ExpenseRelationManager extends RelationManager
->columns(ExpenseResource::getTableColumns())
->headerActions([
CreateAction::make()
->mutateFormDataUsing(
fn (array $data): array => app(ExpenseResource\Pages\CreateExpense::class)
->mutateDataUsing(
fn (array $data): array => app(CreateExpense::class)
->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,74 +0,0 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
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\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\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);
}),
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

@@ -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
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;
class CreateClient extends CreateRecord

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\EditRecord;
@@ -13,7 +14,7 @@ class EditClient extends EditRecord
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make()->icon('heroicon-s-trash')->requiresConfirmation(),
DeleteAction::make()->icon('heroicon-s-trash')->requiresConfirmation(),
];
}

View File

@@ -1,8 +1,8 @@
<?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 Filament\Resources\Pages\Page;
use Filament\Tables\Concerns\InteractsWithTable;
@@ -14,7 +14,7 @@ use Filament\Tables\Filters\Filter;
use Filament\Forms\Components\DatePicker;
use Illuminate\Database\Eloquent\Builder;
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\Columns\Column;
@@ -25,9 +25,9 @@ class GeneralLedger extends Page implements HasTable
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
{
@@ -79,7 +79,7 @@ class GeneralLedger extends Page implements HasTable
->searchable()
->preload(),
Filter::make('date_range')
->form([
->schema([
DatePicker::make('from'),
DatePicker::make('to'),
])
@@ -108,7 +108,7 @@ class GeneralLedger extends Page implements HasTable
'account.account',
])
->headerActions([
ExportAction::make()
ExcelExportAction::make()
->label('Export General Ledger')
->exports([
ExcelExport::make()

View File

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

View File

@@ -1,8 +1,8 @@
<?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 Filament\Resources\Pages\Page;
use Filament\Tables\Concerns\InteractsWithTable;
@@ -21,9 +21,9 @@ class TrialBalance extends Page implements HasTable
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
{

View File

@@ -1,21 +1,21 @@
<?php
namespace App\Filament\Resources\ClientResource\Pages;
namespace App\Filament\Resources\Clients\Pages;
use App\Filament\Resources\ClientResource;
use Filament\Infolists\Components\Grid;
use Filament\Infolists\Components\Section;
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Grid;
use App\Filament\Resources\Clients\ClientResource;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Infolist;
use Filament\Resources\Pages\ViewRecord;
class ViewClient extends ViewRecord
{
protected static string $resource = ClientResource::class;
public function infolist(Infolist $infolist): Infolist
public function infolist(Schema $schema): Schema
{
return $infolist
return $schema
->schema([
Section::make()->schema([
Grid::make()->schema([
@@ -25,7 +25,7 @@ class ViewClient extends ViewRecord
TextEntry::make('company')->label('Company'),
TextEntry::make('type.type')->label('Type'),
])->columns(3),
]),
])->columnSpanFull(),
// Section::make('Branches')->schema([
// 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
namespace App\Filament\Resources\ClientResource\RelationManagers;
namespace App\Filament\Resources\Clients\RelationManagers;
use App\Filament\Resources\BranchResource\Pages\EditBranch;
use App\Filament\Resources\ClientResource;
use Filament\Schemas\Schema;
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\Processes\Branch\CreateBranchProcess;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\RawJs;
use Filament\Tables;
@@ -27,19 +35,19 @@ class BranchesRelationManager extends RelationManager
$this->createBranchProcess = new CreateBranchProcess;
}
public function form(Form $form): Form
public function form(Schema $schema): Schema
{
return $form
->schema([
Forms\Components\Hidden::make('id'),
Forms\Components\TextInput::make('code')->required()
return $schema
->components([
Hidden::make('id'),
TextInput::make('code')->required()
->unique(
'branches',
'code',
ignoreRecord: true,
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()
->numeric()
->integer()
@@ -56,29 +64,29 @@ class BranchesRelationManager extends RelationManager
return $table
->recordTitleAttribute('client_id')
->columns([
Tables\Columns\TextColumn::make('code')->label('Branch Code'),
Tables\Columns\TextColumn::make('current_series')->label('Current Series'),
TextColumn::make('code')->label('Branch Code'),
TextColumn::make('current_series')->label('Current Series'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->mutateFormDataUsing(fn ($data) => $this->appendCientId($data))
CreateAction::make()
->mutateDataUsing(fn ($data) => $this->appendCientId($data))
->using(fn ($data) => $this->saveBranch($data)),
])
->actions([
->recordActions([
// 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])
->mutateFormDataUsing(fn ($data) => $this->appendCientId($data))
->mutateDataUsing(fn ($data) => $this->appendCientId($data))
->using(fn ($data) => $this->saveBranch($data))
->url(fn ($record) => EditBranch::getUrl(['record' => $record->id])),
Tables\Actions\DeleteAction::make(),
DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
->toolbarActions([
BulkActionGroup::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

@@ -1,10 +1,11 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
namespace App\Filament\Resources\Clients\RelationManagers;
use App\Filament\Resources\ExpenseResource;
use Filament\Schemas\Schema;
use Filament\Actions\Action;
use App\Filament\Resources\Expenses\ExpenseResource;
use App\Models\Expense;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
@@ -16,16 +17,16 @@ class ExpensesRelationManager extends RelationManager
protected static ?string $title = 'Expenses';
public function form(Form $form): Form
public function form(Schema $schema): Schema
{
return $form
->schema([]);
return $schema
->components([]);
}
public function table(Table $table): Table
{
return ExpenseResource::table($table)->headerActions([
Tables\Actions\Action::make('New Expense')->action('openCreateForm'),
Action::make('New Expense')->action('openCreateForm'),
]);
}

View File

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

View File

@@ -1,11 +1,12 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
namespace App\Filament\Resources\Clients\RelationManagers;
use App\Filament\Resources\SaleResource;
use Filament\Schemas\Schema;
use Filament\Actions\Action;
use App\Filament\Resources\Sales\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;
@@ -17,15 +18,15 @@ class SalesRelationManager extends RelationManager
protected static ?string $title = 'Sales';
public function form(Form $form): Form
public function form(Schema $schema): Schema
{
return $form->schema([]);
return $schema->components([]);
}
public function table(Table $table): Table
{
return SaleResource::table($table)->headerActions([
Tables\Actions\Action::make('New Sale')->action('openCreateForm'),
Action::make('New Sale')->action('openCreateForm'),
]);
}

View File

@@ -1,9 +1,10 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
namespace App\Filament\Resources\Clients\RelationManagers;
use App\Filament\Resources\TransmittalResource;
use Filament\Forms\Form;
use Filament\Schemas\Schema;
use Filament\Actions\Action;
use App\Filament\Resources\Transmittals\TransmittalResource;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
@@ -18,9 +19,9 @@ class TransmittalsRelationManager extends RelationManager
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(
['client_id' => $this->getOwnerRecord()->id]
);
@@ -29,7 +30,7 @@ class TransmittalsRelationManager extends RelationManager
public function table(Table $table): Table
{
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,7 +1,18 @@
<?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\Filament\Resources\ExpenseResource\Pages;
use App\Models\Account;
@@ -14,9 +25,6 @@ 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\Table;
@@ -29,14 +37,14 @@ class ExpenseResource extends Resource
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;
public static function form(Form $form): Form
public static function form(Schema $schema): Schema
{
return $form
->schema(static::getExpenseFormFields());
return $schema
->components(static::getExpenseFormFields());
}
public static function getExpenseFormFields(): array
@@ -195,11 +203,11 @@ class ExpenseResource extends Resource
'client_id' => $get('../../client'),
]);
if ($get('../../branch_id')) {
$query->whereHas('balances', function ($query) use ($get) {
return $query->where('branch_id', $get('../../branch_id'));
});
}
// 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');
@@ -208,7 +216,6 @@ class ExpenseResource extends Resource
return $query->get()->pluck('account', 'id');
}
#[NoReturn]
public static function setDefaultFormValues(Get $get, Set $set, ?string $old, ?string $state): void
{
@@ -253,13 +260,13 @@ class ExpenseResource extends Resource
->filters([
//
])
->actions([
Tables\Actions\DeleteAction::make(),
Tables\Actions\EditAction::make(),
->recordActions([
DeleteAction::make(),
EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
@@ -267,13 +274,13 @@ 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'),
Tables\Columns\TextColumn::make('accounts_list')->label('Accounts'),
TextColumn::make('supplier'),
TextColumn::make('reference_number'),
TextColumn::make('voucher_number'),
TextColumn::make('branch.client.company'),
TextColumn::make('branch.code'),
TextColumn::make('happened_on'),
TextColumn::make('accounts_list')->label('Accounts'),
];
}
@@ -287,9 +294,9 @@ class ExpenseResource extends Resource
public static function getPages(): array
{
return [
'index' => Pages\ListExpenses::route('/'),
'create' => Pages\CreateExpense::route('/create'),
'edit' => Pages\EditExpense::route('/{record}/edit'),
'index' => ListExpenses::route('/'),
'create' => CreateExpense::route('/create'),
'edit' => EditExpense::route('/{record}/edit'),
];
}
}

View File

@@ -1,10 +1,10 @@
<?php
namespace App\Filament\Resources\ExpenseResource\Pages;
namespace App\Filament\Resources\Expenses\Pages;
use App\Actions\Transactions\CreateRecordTransactionsAction;
use App\Filament\Resources\ClientResource;
use App\Filament\Resources\ExpenseResource;
use App\Filament\Resources\Clients\ClientResource;
use App\Filament\Resources\Expenses\ExpenseResource;
use App\Models\Client;
use Exception;
use Filament\Resources\Pages\CreateRecord;

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\EditRecord;
@@ -13,7 +14,7 @@ class EditExpense extends EditRecord
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
DeleteAction::make(),
];
}

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListExpenses extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
CreateAction::make(),
];
}
}

View File

@@ -1,8 +1,10 @@
<?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 Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr;
@@ -10,29 +12,30 @@ use Illuminate\Support\Collection;
class CreateRole extends CreateRecord
{
protected static string $resource = RoleResource::class;
public Collection $permissions;
protected static string $resource = RoleResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
$this->permissions = collect($data)
->filter(function ($permission, $key) {
return ! in_array($key, ['name', 'guard_name', 'select_all']);
})
->filter(fn (mixed $permission, string $key): bool => ! in_array($key, ['name', 'guard_name', 'select_all', Utils::getTenantModelForeignKey()]))
->values()
->flatten()
->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']);
}
protected function afterCreate(): void
{
$permissionModels = collect();
$this->permissions->each(function ($permission) use ($permissionModels) {
$this->permissions->each(function (string $permission) use ($permissionModels): void {
$permissionModels->push(Utils::getPermissionModel()::firstOrCreate([
/** @phpstan-ignore-next-line */
'name' => $permission,
'guard_name' => $this->data['guard_name'],
]));

View File

@@ -1,50 +1,55 @@
<?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 Filament\Actions;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
class EditRole extends EditRecord
{
protected static string $resource = RoleResource::class;
public Collection $permissions;
protected static string $resource = RoleResource::class;
protected function getActions(): array
{
return [
Actions\DeleteAction::make(),
DeleteAction::make(),
];
}
protected function mutateFormDataBeforeSave(array $data): array
{
$this->permissions = collect($data)
->filter(function ($permission, $key) {
return ! in_array($key, ['name', 'guard_name', 'select_all']);
})
->filter(fn (mixed $permission, string $key): bool => ! in_array($key, ['name', 'guard_name', 'select_all', Utils::getTenantModelForeignKey()]))
->values()
->flatten()
->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']);
}
protected function afterSave(): void
{
$permissionModels = collect();
$this->permissions->each(function ($permission) use ($permissionModels) {
$this->permissions->each(function (string $permission) use ($permissionModels): void {
$permissionModels->push(Utils::getPermissionModel()::firstOrCreate([
'name' => $permission,
'guard_name' => $this->data['guard_name'],
]));
});
// @phpstan-ignore-next-line
$this->record->syncPermissions($permissionModels);
}
}

View File

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

View File

@@ -1,9 +1,11 @@
<?php
namespace App\Filament\Resources\Shield\RoleResource\Pages;
declare(strict_types=1);
use App\Filament\Resources\Shield\RoleResource;
use Filament\Actions;
namespace App\Filament\Resources\Roles\Pages;
use App\Filament\Resources\Roles\Roles\RoleResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewRole extends ViewRecord
@@ -13,7 +15,7 @@ class ViewRole extends ViewRecord
protected function getActions(): array
{
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,256 +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', '');
})
->disabled()
->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')
->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')
->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('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([
//
])
->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,13 +1,13 @@
<?php
namespace App\Filament\Resources\SaleResource\Pages;
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\ClientResource;
use App\Filament\Resources\SaleResource;
use App\Filament\Resources\Clients\ClientResource;
use App\Filament\Resources\Sales\SaleResource;
use App\Models\Branch;
use App\Models\Client;
use App\Models\Sale;
@@ -76,10 +76,11 @@ class CreateSale extends CreateRecord
$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));
$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', 'with_discount']);
return Arr::except($data, ['client', 'transactions']);
}
public function processCreate(array $data, array $transactions): Model

View File

@@ -1,10 +1,12 @@
<?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\DataObjects\CreateTransactionDTO;
use App\Filament\Resources\SaleResource;
use App\Filament\Resources\Sales\SaleResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Database\Eloquent\Model;
@@ -19,7 +21,7 @@ class EditSale extends EditRecord
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
DeleteAction::make(),
];
}
@@ -92,9 +94,9 @@ class EditSale extends EditRecord
$record->accounts()->sync($accountIds);
DB::commit();
} catch (\Exception $exception) {
} catch (Exception $exception) {
DB::rollBack();
throw new \Exception('Failed to save transactions : '.$exception->getMessage());
throw new Exception('Failed to save transactions : '.$exception->getMessage());
}
return $record;

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListSales extends ListRecords
protected function getHeaderActions(): array
{
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
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\Resources\Pages\EditRecord;
@@ -13,8 +15,8 @@ class EditTransmittal extends EditRecord
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
ViewAction::make(),
DeleteAction::make(),
];
}
}

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListTransmittals extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
CreateAction::make(),
];
}
}

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\ViewRecord;
@@ -13,7 +14,7 @@ class ViewTransmittal extends ViewRecord
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
EditAction::make(),
];
}
}

View File

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

View File

@@ -1,8 +1,8 @@
<?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;
class CreateUser extends CreateRecord

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\EditRecord;
@@ -13,7 +14,7 @@ class EditUser extends EditRecord
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
DeleteAction::make(),
];
}
}

View File

@@ -1,8 +1,9 @@
<?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\Resources\Pages\ListRecords;
@@ -13,7 +14,7 @@ class ListUsers extends ListRecords
protected function getHeaderActions(): array
{
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
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\RelationManagers;
use App\Models\User;
use Filament\Forms;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
@@ -16,21 +23,21 @@ class UserResource extends Resource
{
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([
'default' => 2,
])
->schema([
->components([
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'),
Forms\Components\CheckboxList::make('roles')
CheckboxList::make('roles')
->relationship('roles', 'name')
->searchable()
]);
@@ -40,20 +47,20 @@ class UserResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')->searchable(),
Tables\Columns\TextColumn::make('email')->searchable(),
TextColumn::make('name')->searchable(),
TextColumn::make('email')->searchable(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make()->slideOver(),
Tables\Actions\DeleteAction::make()->requiresConfirmation()
->recordActions([
EditAction::make()->slideOver(),
DeleteAction::make()->requiresConfirmation()
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
@@ -68,7 +75,7 @@ class UserResource extends Resource
public static function getPages(): array
{
return [
'index' => \App\Filament\Resources\UserResource\Pages\ListUsers::route('/'),
'index' => ListUsers::route('/'),
];
}
}

View File

@@ -2,7 +2,10 @@
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 Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -13,30 +16,41 @@ use Illuminate\Support\Facades\Storage;
class ExportCompleteJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(private $user)
public function __construct(private User $user, private array $ids)
{
}
/**
* Execute the job.
*/
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')
->actions([
NotificationAction::make('download_transmittal-export.xlsx')
->label('Download File')
->url(url: Storage::url('public/transmittal-export.xlsx') ,shouldOpenInNewTab: true)
->markAsRead(),
]
)
Action::make('download_transmittal_export')
->label('Download PDF File')
->url(Storage::url($filename), true)
->markAsRead(),
])
->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.
*
* @throws \Illuminate\Validation\ValidationException
* @throws ValidationException
*/
public function authenticate(): void
{

View File

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

View File

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

View File

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

View File

@@ -78,4 +78,9 @@ class Client extends Model
{
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

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

View File

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

View File

@@ -12,7 +12,14 @@ class Sale extends Model
{
use HasFactory;
protected $guarded = [];
protected $fillable = [
'branch_id',
'user_id',
'client_id',
'happened_on',
'reference_number',
'buyer'
];
protected $casts = [
'happened_on' => 'date:Y-m-d',

View File

@@ -1,108 +1,70 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
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
public function viewAny(AuthUser $authUser): bool
{
return $user->can('view_any_branch');
return $authUser->can('ViewAny:Branch');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Branch $branch): bool
public function view(AuthUser $authUser, Branch $branch): bool
{
return $user->can('view_branch');
return $authUser->can('View:Branch');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
public function create(AuthUser $authUser): bool
{
return $user->can('create_branch');
return $authUser->can('Create:Branch');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Branch $branch): bool
public function update(AuthUser $authUser, Branch $branch): bool
{
return $user->can('update_branch');
return $authUser->can('Update:Branch');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Branch $branch): bool
public function delete(AuthUser $authUser, Branch $branch): bool
{
return $user->can('delete_branch');
return $authUser->can('Delete:Branch');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
public function restore(AuthUser $authUser, Branch $branch): bool
{
return $user->can('delete_any_branch');
return $authUser->can('Restore:Branch');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Branch $branch): bool
public function forceDelete(AuthUser $authUser, Branch $branch): bool
{
return $user->can('force_delete_branch');
return $authUser->can('ForceDelete:Branch');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
public function forceDeleteAny(AuthUser $authUser): bool
{
return $user->can('force_delete_any_branch');
return $authUser->can('ForceDeleteAny:Branch');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Branch $branch): bool
public function restoreAny(AuthUser $authUser): bool
{
return $user->can('restore_branch');
return $authUser->can('RestoreAny:Branch');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
public function replicate(AuthUser $authUser, Branch $branch): bool
{
return $user->can('restore_any_branch');
return $authUser->can('Replicate:Branch');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Branch $branch): bool
public function reorder(AuthUser $authUser): 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
declare(strict_types=1);
namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Client;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ClientPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
public function viewAny(AuthUser $authUser): bool
{
return $user->can('view_any_client');
return $authUser->can('ViewAny:Client');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Client $client): bool
public function view(AuthUser $authUser, Client $client): bool
{
return $user->can('view_client');
return $authUser->can('View:Client');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
public function create(AuthUser $authUser): bool
{
return $user->can('create_client');
return $authUser->can('Create:Client');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Client $client): bool
public function update(AuthUser $authUser, Client $client): bool
{
return $user->can('update_client');
return $authUser->can('Update:Client');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Client $client): bool
public function delete(AuthUser $authUser, Client $client): bool
{
return $user->can('delete_client');
return $authUser->can('Delete:Client');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
public function restore(AuthUser $authUser, Client $client): bool
{
return $user->can('delete_any_client');
return $authUser->can('Restore:Client');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Client $client): bool
public function forceDelete(AuthUser $authUser, Client $client): bool
{
return $user->can('force_delete_client');
return $authUser->can('ForceDelete:Client');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
public function forceDeleteAny(AuthUser $authUser): bool
{
return $user->can('force_delete_any_client');
return $authUser->can('ForceDeleteAny:Client');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Client $client): bool
public function restoreAny(AuthUser $authUser): bool
{
return $user->can('restore_client');
return $authUser->can('RestoreAny:Client');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
public function replicate(AuthUser $authUser, Client $client): bool
{
return $user->can('restore_any_client');
return $authUser->can('Replicate:Client');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Client $client): bool
public function reorder(AuthUser $authUser): 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
declare(strict_types=1);
namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
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
public function viewAny(AuthUser $authUser): bool
{
return $user->can('view_any_expense');
return $authUser->can('ViewAny:Expense');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Expense $expense): bool
public function view(AuthUser $authUser, Expense $expense): bool
{
return $user->can('view_expense');
return $authUser->can('View:Expense');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
public function create(AuthUser $authUser): bool
{
return $user->can('create_expense');
return $authUser->can('Create:Expense');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Expense $expense): bool
public function update(AuthUser $authUser, Expense $expense): bool
{
return $user->can('update_expense');
return $authUser->can('Update:Expense');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Expense $expense): bool
public function delete(AuthUser $authUser, Expense $expense): bool
{
return $user->can('delete_expense');
return $authUser->can('Delete:Expense');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
public function restore(AuthUser $authUser, Expense $expense): bool
{
return $user->can('delete_any_expense');
return $authUser->can('Restore:Expense');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Expense $expense): bool
public function forceDelete(AuthUser $authUser, Expense $expense): bool
{
return $user->can('force_delete_expense');
return $authUser->can('ForceDelete:Expense');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
public function forceDeleteAny(AuthUser $authUser): bool
{
return $user->can('force_delete_any_expense');
return $authUser->can('ForceDeleteAny:Expense');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Expense $expense): bool
public function restoreAny(AuthUser $authUser): bool
{
return $user->can('restore_expense');
return $authUser->can('RestoreAny:Expense');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
public function replicate(AuthUser $authUser, Expense $expense): bool
{
return $user->can('restore_any_expense');
return $authUser->can('Replicate:Expense');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Expense $expense): bool
public function reorder(AuthUser $authUser): 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
declare(strict_types=1);
namespace App\Policies;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Foundation\Auth\User as AuthUser;
use Spatie\Permission\Models\Role;
use Illuminate\Auth\Access\HandlesAuthorization;
class RolePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
public function viewAny(AuthUser $authUser): bool
{
return $user->can('view_any_shield::role');
return $authUser->can('ViewAny:Role');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Role $role): bool
public function view(AuthUser $authUser, Role $role): bool
{
return $user->can('view_shield::role');
return $authUser->can('View:Role');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
public function create(AuthUser $authUser): bool
{
return $user->can('create_shield::role');
return $authUser->can('Create:Role');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Role $role): bool
public function update(AuthUser $authUser, Role $role): bool
{
return $user->can('update_shield::role');
return $authUser->can('Update:Role');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Role $role): bool
public function delete(AuthUser $authUser, Role $role): bool
{
return $user->can('delete_shield::role');
return $authUser->can('Delete:Role');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
public function restore(AuthUser $authUser, Role $role): bool
{
return $user->can('delete_any_shield::role');
return $authUser->can('Restore:Role');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Role $role): bool
public function forceDelete(AuthUser $authUser, Role $role): bool
{
return $user->can('{{ ForceDelete }}');
return $authUser->can('ForceDelete:Role');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
public function forceDeleteAny(AuthUser $authUser): bool
{
return $user->can('{{ ForceDeleteAny }}');
return $authUser->can('ForceDeleteAny:Role');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Role $role): bool
public function restoreAny(AuthUser $authUser): bool
{
return $user->can('{{ Restore }}');
return $authUser->can('RestoreAny:Role');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
public function replicate(AuthUser $authUser, Role $role): bool
{
return $user->can('{{ RestoreAny }}');
return $authUser->can('Replicate:Role');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Role $role): bool
public function reorder(AuthUser $authUser): 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
declare(strict_types=1);
namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
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
public function viewAny(AuthUser $authUser): bool
{
return $user->can('view_any_sale');
return $authUser->can('ViewAny:Sale');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Sale $sale): bool
public function view(AuthUser $authUser, Sale $sale): bool
{
return $user->can('view_sale');
return $authUser->can('View:Sale');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
public function create(AuthUser $authUser): bool
{
return $user->can('create_sale');
return $authUser->can('Create:Sale');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Sale $sale): bool
public function update(AuthUser $authUser, Sale $sale): bool
{
return $user->can('update_sale');
return $authUser->can('Update:Sale');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Sale $sale): bool
public function delete(AuthUser $authUser, Sale $sale): bool
{
return $user->can('delete_sale');
return $authUser->can('Delete:Sale');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
public function restore(AuthUser $authUser, Sale $sale): bool
{
return $user->can('delete_any_sale');
return $authUser->can('Restore:Sale');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Sale $sale): bool
public function forceDelete(AuthUser $authUser, Sale $sale): bool
{
return $user->can('force_delete_sale');
return $authUser->can('ForceDelete:Sale');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
public function forceDeleteAny(AuthUser $authUser): bool
{
return $user->can('force_delete_any_sale');
return $authUser->can('ForceDeleteAny:Sale');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Sale $sale): bool
public function restoreAny(AuthUser $authUser): bool
{
return $user->can('restore_sale');
return $authUser->can('RestoreAny:Sale');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
public function replicate(AuthUser $authUser, Sale $sale): bool
{
return $user->can('restore_any_sale');
return $authUser->can('Replicate:Sale');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Sale $sale): bool
public function reorder(AuthUser $authUser): 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
declare(strict_types=1);
namespace App\Policies;
use Illuminate\Foundation\Auth\User as AuthUser;
use App\Models\Transmittal;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class TransmittalPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
public function viewAny(AuthUser $authUser): bool
{
return $user->can('view_any_transmittal');
return $authUser->can('ViewAny:Transmittal');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Transmittal $transmittal): bool
public function view(AuthUser $authUser, Transmittal $transmittal): bool
{
return $user->can('view_transmittal');
return $authUser->can('View:Transmittal');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
public function create(AuthUser $authUser): bool
{
return $user->can('create_transmittal');
return $authUser->can('Create:Transmittal');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Transmittal $transmittal): bool
public function update(AuthUser $authUser, Transmittal $transmittal): bool
{
return $user->can('update_transmittal');
return $authUser->can('Update:Transmittal');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Transmittal $transmittal): bool
public function delete(AuthUser $authUser, Transmittal $transmittal): bool
{
return $user->can('delete_transmittal');
return $authUser->can('Delete:Transmittal');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
public function restore(AuthUser $authUser, Transmittal $transmittal): bool
{
return $user->can('delete_any_transmittal');
return $authUser->can('Restore:Transmittal');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Transmittal $transmittal): bool
public function forceDelete(AuthUser $authUser, Transmittal $transmittal): bool
{
return $user->can('force_delete_transmittal');
return $authUser->can('ForceDelete:Transmittal');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
public function forceDeleteAny(AuthUser $authUser): bool
{
return $user->can('force_delete_any_transmittal');
return $authUser->can('ForceDeleteAny:Transmittal');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Transmittal $transmittal): bool
public function restoreAny(AuthUser $authUser): bool
{
return $user->can('restore_transmittal');
return $authUser->can('RestoreAny:Transmittal');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
public function replicate(AuthUser $authUser, Transmittal $transmittal): bool
{
return $user->can('restore_any_transmittal');
return $authUser->can('Replicate:Transmittal');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Transmittal $transmittal): bool
public function reorder(AuthUser $authUser): 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;
use App\Models\User;
use Illuminate\Foundation\Auth\User as AuthUser;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
public function viewAny(AuthUser $authUser): bool
{
return $user->can('view_any_user');
return $authUser->can('ViewAny:User');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user): bool
public function view(AuthUser $authUser): bool
{
return $user->can('view_user');
return $authUser->can('View:User');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
public function create(AuthUser $authUser): bool
{
return $user->can('create_user');
return $authUser->can('Create:User');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user): bool
public function update(AuthUser $authUser): bool
{
return $user->can('update_user');
return $authUser->can('Update:User');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user): bool
public function delete(AuthUser $authUser): bool
{
return $user->can('delete_user');
return $authUser->can('Delete:User');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
public function restore(AuthUser $authUser): bool
{
return $user->can('delete_any_user');
return $authUser->can('Restore:User');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user): bool
public function forceDelete(AuthUser $authUser): bool
{
return $user->can('force_delete_user');
return $authUser->can('ForceDelete:User');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
public function forceDeleteAny(AuthUser $authUser): bool
{
return $user->can('force_delete_any_user');
return $authUser->can('ForceDeleteAny:User');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user): bool
public function restoreAny(AuthUser $authUser): bool
{
return $user->can('restore_user');
return $authUser->can('RestoreAny:User');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
public function replicate(AuthUser $authUser): bool
{
return $user->can('restore_any_user');
return $authUser->can('Replicate:User');
}
/**
* Determine whether the user can bulk restore.
*/
public function replicate(User $user): bool
public function reorder(AuthUser $authUser): 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,6 +2,8 @@
namespace App\Providers\Filament;
use Filament\Pages\Dashboard;
use Filament\Widgets\AccountWidget;
use BezhanSalleh\FilamentShield\FilamentShieldPlugin;
use Filament\Navigation\NavigationItem;
use Filament\Http\Middleware\Authenticate;
@@ -18,6 +20,7 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class AdminPanelProvider extends PanelProvider
@@ -38,19 +41,19 @@ class AdminPanelProvider extends PanelProvider
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\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),
->visible(fn (): bool => Auth::user()?->hasRole('super_admin') ?? false),
])
->databaseNotifications()
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([
Widgets\AccountWidget::class,
AccountWidget::class,
])
->middleware([
EncryptCookies::class,

View File

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

1991
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,89 +1,263 @@
<?php
declare(strict_types=1);
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' => [
'should_register_navigation' => true,
'slug' => 'shield/roles',
'navigation_sort' => -1,
'navigation_badge' => true,
'navigation_group' => true,
'is_globally_searchable' => true,
'show_model_path' => true,
'is_scoped_to_tenant' => true,
'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' => [
'enabled' => true,
'name' => 'super_admin',
'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' => [
'enabled' => true,
'name' => 'panel_user',
],
'permission_prefixes' => [
'resource' => [
'view',
'view_any',
/*
|--------------------------------------------------------------------------
| Permission Builder
|--------------------------------------------------------------------------
|
| 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',
'update',
'restore',
'restore_any',
'replicate',
'deleteAny',
'forceDeleteAny',
'restoreAny',
'reorder',
'delete',
'delete_any',
'force_delete',
'force_delete_any',
],
'page' => 'page',
'widget' => 'widget',
],
'entities' => [
'pages' => true,
'widgets' => true,
'resources' => true,
'custom_permissions' => true,
/*
|--------------------------------------------------------------------------
| Localization
|--------------------------------------------------------------------------
|
| 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',
'policy_namespace' => 'Policies',
],
/*
|--------------------------------------------------------------------------
| Resources
|--------------------------------------------------------------------------
|
| 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' => [
'enabled' => true,
'pages' => [
'Dashboard',
'resources' => [
'subject' => 'model',
'manage' => [
\BezhanSalleh\FilamentShield\Resources\Roles\RoleResource::class => [
'viewAny',
'view',
'create',
'update',
'delete',
],
],
'widgets' => [
'AccountWidget', 'FilamentInfoWidget',
'exclude' => [
//
],
'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' => [
'discover_all_resources' => true,
'discover_all_widgets' => true,
'discover_all_pages' => true,
'discover_all_resources' => false,
'discover_all_widgets' => false,
'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,
];

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@@ -17,15 +17,11 @@ return new class extends Migration
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
if (empty($tableNames)) {
throw new \Exception('Error: 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.');
}
throw_if(empty($tableNames), Exception::class, '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.');
Schema::create($tableNames['permissions'], function (Blueprint $table) {
//$table->engine('InnoDB');
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
// $table->engine('InnoDB');
$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('guard_name'); // For MyISAM use string('guard_name', 25);
@@ -34,8 +30,8 @@ return new class extends Migration
$table->unique(['name', 'guard_name']);
});
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) {
//$table->engine('InnoDB');
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$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->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->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($pivotRole);
@@ -127,9 +123,7 @@ return new class extends Migration
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
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.');
}
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.');
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);

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('discounts', function (Blueprint $table) {
$table->id();
$table->text('discount');
$table->foreignId('client_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('discounts');
}
};

View File

@@ -0,0 +1,28 @@
<?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::table('transactions', function (Blueprint $table) {
$table->string('discount_type')->after('with_discount')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('transactions', function (Blueprint $table) {
$table->dropColumn('discount_type');
});
}
};

View File

@@ -1,58 +0,0 @@
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

80
k8s/deployment.yaml Normal file
View File

@@ -0,0 +1,80 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mkm-admin
labels:
app: mkm-admin
spec:
replicas: 1
selector:
matchLabels:
app: mkm-admin
template:
metadata:
labels:
app: mkm-admin
spec:
containers:
- name: mkm-admin
image: localhost/mkm-admin:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: mkm-admin-config
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mkm-admin-worker
labels:
app: mkm-admin
component: worker
spec:
replicas: 1
selector:
matchLabels:
app: mkm-admin
component: worker
template:
metadata:
labels:
app: mkm-admin
component: worker
spec:
containers:
- name: worker
image: localhost/mkm-admin:latest
imagePullPolicy: Never
command: ["php", "artisan", "horizon"]
envFrom:
- configMapRef:
name: mkm-admin-config
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
---
apiVersion: v1
kind: Service
metadata:
name: mkm-admin-service
spec:
selector:
app: mkm-admin
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: ClusterIP

1834
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,13 @@
"devDependencies": {
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.14",
"@tailwindcss/postcss": "^4.2.0",
"autoprefixer": "^10.4.20",
"axios": "^1.6.4",
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.41",
"postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.9",
"tailwindcss": "^4.0.0",
"vite": "^5.0"
}
}

View File

@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': {},
},
};

File diff suppressed because one or more lines are too long

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