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
This commit is contained in:
Jp
2026-02-18 01:42:44 +08:00
parent 2644be0505
commit 5d427cdea4
15 changed files with 316 additions and 29 deletions

View File

@@ -10,6 +10,7 @@ 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\DiscountRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\ExpensesRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\JournalsRelationManager;
use App\Filament\Resources\ClientResource\RelationManagers\SalesRelationManager;
@@ -109,6 +110,7 @@ class ClientResource extends Resource
SalesRelationManager::class,
ExpensesRelationManager::class,
JournalsRelationManager::class,
DiscountRelationManager::class,
];
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Filament\Resources\ClientResource\RelationManagers;
use Filament\Forms;
use Filament\Forms\Form;
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(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('discount')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('discount')
->columns([
Tables\Columns\TextColumn::make('discount'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\DiscountResource\Pages;
use App\Filament\Resources\DiscountResource\RelationManagers;
use App\Models\Discount;
use Filament\Forms;
use Filament\Forms\Form;
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 $navigationIcon = 'heroicon-o-rectangle-stack';
protected static bool $shouldRegisterNavigation = false;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('discount')
->label('Discount')
->required(),
Forms\Components\Hidden::make('client_id')
->default(fn () => request()->client_id),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('discount')
->label('Discount')
->searchable(),
])
->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\ListDiscounts::route('/'),
'create' => Pages\CreateDiscount::route('/create'),
'edit' => Pages\EditDiscount::route('/{record}/edit'),
];
}
}

View File

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

View File

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

View File

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

View File

@@ -195,11 +195,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 +208,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
{

View File

@@ -6,6 +6,7 @@ use App\Filament\Resources\SaleResource\Pages;
use App\Models\Account;
use App\Models\Branch;
use App\Models\Client;
use App\Models\Discount;
use App\Models\Sale;
use Awcodes\TableRepeater\Components\TableRepeater;
use Awcodes\TableRepeater\Header;
@@ -30,17 +31,17 @@ class SaleResource extends Resource
protected static bool $shouldRegisterNavigation = false;
public static function form(Form $form): Form
{
return $form
->schema([
Select::make('client')
->default(request()->query('client_id'))
->default(fn () => request()->integer('client_id'))
->options(Client::query()->get()->pluck('company', 'id'))
->afterStateUpdated(function ($set, $get) {
$set('branch_id', '');
})
->disabled()
->required()
->live(),
Select::make('branch_id')
@@ -72,6 +73,8 @@ class SaleResource extends Resource
]);
}
public static function getSeries(Get $get): string
{
$branch = Branch::find($get('branch_id'));
@@ -98,6 +101,7 @@ class SaleResource extends Resource
Header::make('Output Tax'),
Header::make('Withholding Tax'),
Header::make('Discount'),
Header::make('Discount Type'),
Header::make('Net Amount'),
];
}
@@ -136,7 +140,7 @@ class SaleResource extends Resource
->numeric()
->nullable()
->live()
->readOnly()
->readOnly(fn (Get $get) => $get('exempt') == 0)
->default(0),
Hidden::make('happened_on')->default(fn (Get $get) => $get('../../happened_on')),
Hidden::make('with_discount')->default(fn (Get $get) => $get('../../with_discount')),
@@ -159,6 +163,9 @@ class SaleResource extends Resource
->readOnly()
->visible(fn (Get $get) => $get('../../with_discount'))
->live(),
Select::make('discount_type')
->options(fn (Get $get) => static::getDiscountOptions($get))
->visible(fn (Get $get) => $get('../../with_discount')),
TextInput::make('net_amount')->numeric()->default(0),
];
}
@@ -184,6 +191,13 @@ class SaleResource extends Resource
return $query->get()->pluck('account', 'id');
}
private static function getDiscountOptions(Get $get)
{
$query = Discount::query()->where('client_id', $get('../../client'));
return $query->pluck('discount', 'id');
}
private static function setDefaultFormValues(Get $get, Set $set, ?string $old, ?string $state)
{
$exempt = (float) $get('exempt');

View File

@@ -4,7 +4,6 @@ namespace App\Filament\Resources;
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\Models\Branch;
@@ -26,6 +25,7 @@ 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
@@ -147,13 +147,11 @@ class TransmittalResource extends Resource
public static function exportTransmittal(array $id): void
{
$recipient = auth()->user();
$recipient = Auth::user();
static::generateExportNotification();
(new TransmittalsExport([$id]))->store('public/transmittal-export.xlsx')->chain([
app(ExportCompleteJob::class, ['user' => $recipient]),
]);
ExportCompleteJob::dispatch($recipient, Arr::flatten($id));
}
public static function generateExportNotification(): Notification

View File

@@ -2,6 +2,8 @@
namespace App\Jobs;
use App\Models\Transmittal;
use Barryvdh\DomPDF\Facade\Pdf;
use Filament\Notifications\Actions\Action as NotificationAction;
use Filament\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -13,30 +15,37 @@ 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, private array $ids)
{
}
/**
* Execute the job.
*/
public function handle(): void
{
Notification::make()->success()
$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,
]);
Storage::disk('public')->put('transmittal-export.pdf', $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(),
]
)
NotificationAction::make('download_transmittal-export.pdf')
->label('Download File')
->url(Storage::url('transmittal-export.pdf'), true)
->markAsRead(),
])
->sendToDatabase($this->user);
}
}

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'];
}