Compare commits
2 Commits
feat/dashb
...
feat/dba-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc715c6022 | ||
|
|
32a00493f8 |
144
app/Console/Commands/MigrateDbaData.php
Normal file
144
app/Console/Commands/MigrateDbaData.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Business;
|
||||
use App\Models\BusinessDba;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Migrate existing business DBA data to the new business_dbas table.
|
||||
*
|
||||
* This command creates DBA records from existing business fields:
|
||||
* - dba_name
|
||||
* - invoice_payable_company_name, invoice_payable_address, etc.
|
||||
* - ap_contact_* fields
|
||||
* - primary_contact_* fields
|
||||
*/
|
||||
class MigrateDbaData extends Command
|
||||
{
|
||||
protected $signature = 'dba:migrate
|
||||
{--dry-run : Show what would be created without actually creating records}
|
||||
{--business= : Migrate only a specific business by ID or slug}
|
||||
{--force : Skip confirmation prompt}';
|
||||
|
||||
protected $description = 'Migrate existing dba_name and invoice_payable_* fields to the business_dbas table';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('DBA Data Migration');
|
||||
$this->line('==================');
|
||||
|
||||
$dryRun = $this->option('dry-run');
|
||||
$specificBusiness = $this->option('business');
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('DRY RUN MODE - No records will be created');
|
||||
}
|
||||
|
||||
// Build query
|
||||
$query = Business::query()
|
||||
->whereNotNull('dba_name')
|
||||
->where('dba_name', '!=', '');
|
||||
|
||||
if ($specificBusiness) {
|
||||
$query->where(function ($q) use ($specificBusiness) {
|
||||
$q->where('id', $specificBusiness)
|
||||
->orWhere('slug', $specificBusiness);
|
||||
});
|
||||
}
|
||||
|
||||
$businesses = $query->get();
|
||||
$this->info("Found {$businesses->count()} businesses with dba_name set.");
|
||||
|
||||
if ($businesses->isEmpty()) {
|
||||
$this->info('No businesses to migrate.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
// Show preview
|
||||
$this->newLine();
|
||||
$this->table(
|
||||
['ID', 'Business Name', 'DBA Name', 'Has Invoice Address', 'Already Has DBAs'],
|
||||
$businesses->map(fn ($b) => [
|
||||
$b->id,
|
||||
\Illuminate\Support\Str::limit($b->name, 30),
|
||||
\Illuminate\Support\Str::limit($b->dba_name, 30),
|
||||
$b->invoice_payable_address ? 'Yes' : 'No',
|
||||
$b->dbas()->exists() ? 'Yes' : 'No',
|
||||
])
|
||||
);
|
||||
|
||||
if (! $dryRun && ! $this->option('force')) {
|
||||
if (! $this->confirm('Do you want to proceed with creating DBA records?')) {
|
||||
$this->info('Aborted.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$skipped = 0;
|
||||
|
||||
foreach ($businesses as $business) {
|
||||
// Skip if business already has DBAs
|
||||
if ($business->dbas()->exists()) {
|
||||
$this->line(" Skipping {$business->name} - already has DBAs");
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$this->line(" Would create DBA for: {$business->name} -> {$business->dba_name}");
|
||||
$created++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create DBA from existing business fields
|
||||
$dba = BusinessDba::create([
|
||||
'business_id' => $business->id,
|
||||
'trade_name' => $business->dba_name,
|
||||
|
||||
// Address - prefer invoice_payable fields, fall back to physical
|
||||
'address' => $business->invoice_payable_address ?: $business->physical_address,
|
||||
'city' => $business->invoice_payable_city ?: $business->physical_city,
|
||||
'state' => $business->invoice_payable_state ?: $business->physical_state,
|
||||
'zip' => $business->invoice_payable_zipcode ?: $business->physical_zipcode,
|
||||
|
||||
// License
|
||||
'license_number' => $business->license_number,
|
||||
'license_type' => $business->license_type,
|
||||
|
||||
// Contacts
|
||||
'primary_contact_name' => trim(($business->primary_contact_first_name ?? '').' '.($business->primary_contact_last_name ?? '')) ?: null,
|
||||
'primary_contact_email' => $business->primary_contact_email,
|
||||
'primary_contact_phone' => $business->primary_contact_phone,
|
||||
'ap_contact_name' => trim(($business->ap_contact_first_name ?? '').' '.($business->ap_contact_last_name ?? '')) ?: null,
|
||||
'ap_contact_email' => $business->ap_contact_email,
|
||||
'ap_contact_phone' => $business->ap_contact_phone,
|
||||
|
||||
// Invoice Settings
|
||||
'invoice_footer' => $business->order_invoice_footer,
|
||||
|
||||
// Status
|
||||
'is_default' => true,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->info(" Created DBA #{$dba->id} for {$business->name}: {$dba->trade_name}");
|
||||
$created++;
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info("Summary: {$created} created, {$skipped} skipped");
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('Run without --dry-run to actually create records.');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -2082,6 +2082,7 @@ class BusinessResource extends Resource
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
BusinessResource\RelationManagers\DbasRelationManager::class,
|
||||
\Tapp\FilamentAuditing\RelationManagers\AuditsRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\BusinessResource\RelationManagers;
|
||||
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class DbasRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'dbas';
|
||||
|
||||
protected static ?string $title = 'Trade Names (DBAs)';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'trade_name';
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Basic Information')
|
||||
->schema([
|
||||
TextInput::make('trade_name')
|
||||
->label('Trade Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('slug')
|
||||
->label('Slug')
|
||||
->disabled()
|
||||
->dehydrated(false)
|
||||
->helperText('Auto-generated from trade name'),
|
||||
Toggle::make('is_default')
|
||||
->label('Default DBA')
|
||||
->helperText('Use for new invoices by default'),
|
||||
Toggle::make('is_active')
|
||||
->label('Active')
|
||||
->default(true),
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Address')
|
||||
->schema([
|
||||
TextInput::make('address')
|
||||
->label('Street Address')
|
||||
->maxLength(255),
|
||||
TextInput::make('address_line_2')
|
||||
->label('Address Line 2')
|
||||
->maxLength(255),
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
TextInput::make('city')
|
||||
->maxLength(255),
|
||||
TextInput::make('state')
|
||||
->maxLength(2)
|
||||
->extraAttributes(['class' => 'uppercase']),
|
||||
TextInput::make('zip')
|
||||
->label('ZIP Code')
|
||||
->maxLength(10),
|
||||
]),
|
||||
])
|
||||
->collapsible(),
|
||||
|
||||
Section::make('License Information')
|
||||
->schema([
|
||||
TextInput::make('license_number')
|
||||
->maxLength(255),
|
||||
TextInput::make('license_type')
|
||||
->maxLength(255),
|
||||
DatePicker::make('license_expiration')
|
||||
->label('Expiration Date'),
|
||||
])
|
||||
->columns(3)
|
||||
->collapsible(),
|
||||
|
||||
Section::make('Banking Information')
|
||||
->description('Sensitive data is encrypted at rest.')
|
||||
->schema([
|
||||
TextInput::make('bank_name')
|
||||
->maxLength(255),
|
||||
TextInput::make('bank_account_name')
|
||||
->maxLength(255),
|
||||
TextInput::make('bank_routing_number')
|
||||
->maxLength(50)
|
||||
->password()
|
||||
->revealable(),
|
||||
TextInput::make('bank_account_number')
|
||||
->maxLength(50)
|
||||
->password()
|
||||
->revealable(),
|
||||
Select::make('bank_account_type')
|
||||
->options([
|
||||
'checking' => 'Checking',
|
||||
'savings' => 'Savings',
|
||||
]),
|
||||
])
|
||||
->columns(2)
|
||||
->collapsible()
|
||||
->collapsed(),
|
||||
|
||||
Section::make('Tax Information')
|
||||
->description('Sensitive data is encrypted at rest.')
|
||||
->schema([
|
||||
TextInput::make('tax_id')
|
||||
->label('Tax ID')
|
||||
->maxLength(50)
|
||||
->password()
|
||||
->revealable(),
|
||||
Select::make('tax_id_type')
|
||||
->label('Tax ID Type')
|
||||
->options([
|
||||
'ein' => 'EIN',
|
||||
'ssn' => 'SSN',
|
||||
]),
|
||||
])
|
||||
->columns(2)
|
||||
->collapsible()
|
||||
->collapsed(),
|
||||
|
||||
Section::make('Contacts')
|
||||
->schema([
|
||||
Grid::make(2)
|
||||
->schema([
|
||||
Section::make('Primary Contact')
|
||||
->schema([
|
||||
TextInput::make('primary_contact_name')
|
||||
->label('Name')
|
||||
->maxLength(255),
|
||||
TextInput::make('primary_contact_email')
|
||||
->label('Email')
|
||||
->email()
|
||||
->maxLength(255),
|
||||
TextInput::make('primary_contact_phone')
|
||||
->label('Phone')
|
||||
->tel()
|
||||
->maxLength(50),
|
||||
]),
|
||||
Section::make('AP Contact')
|
||||
->schema([
|
||||
TextInput::make('ap_contact_name')
|
||||
->label('Name')
|
||||
->maxLength(255),
|
||||
TextInput::make('ap_contact_email')
|
||||
->label('Email')
|
||||
->email()
|
||||
->maxLength(255),
|
||||
TextInput::make('ap_contact_phone')
|
||||
->label('Phone')
|
||||
->tel()
|
||||
->maxLength(50),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->collapsible()
|
||||
->collapsed(),
|
||||
|
||||
Section::make('Invoice Settings')
|
||||
->schema([
|
||||
TextInput::make('payment_terms')
|
||||
->maxLength(50)
|
||||
->placeholder('Net 30'),
|
||||
TextInput::make('invoice_prefix')
|
||||
->maxLength(10)
|
||||
->placeholder('INV-'),
|
||||
Textarea::make('payment_instructions')
|
||||
->rows(2)
|
||||
->columnSpanFull(),
|
||||
Textarea::make('invoice_footer')
|
||||
->rows(2)
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->collapsible()
|
||||
->collapsed(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('trade_name')
|
||||
->label('Trade Name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('city')
|
||||
->label('Location')
|
||||
->formatStateUsing(fn ($record) => $record->city && $record->state
|
||||
? "{$record->city}, {$record->state}"
|
||||
: ($record->city ?? $record->state ?? '-'))
|
||||
->sortable(),
|
||||
TextColumn::make('license_number')
|
||||
->label('License')
|
||||
->limit(15)
|
||||
->tooltip(fn ($state) => $state),
|
||||
IconColumn::make('is_default')
|
||||
->label('Default')
|
||||
->boolean()
|
||||
->trueIcon('heroicon-o-star')
|
||||
->falseIcon('heroicon-o-minus')
|
||||
->trueColor('warning'),
|
||||
IconColumn::make('is_active')
|
||||
->label('Active')
|
||||
->boolean(),
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->dateTime('M j, Y')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->defaultSort('is_default', 'desc')
|
||||
->headerActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make()
|
||||
->requiresConfirmation(),
|
||||
])
|
||||
->emptyStateHeading('No Trade Names')
|
||||
->emptyStateDescription('Add a DBA to manage different trade names for invoices and licenses.')
|
||||
->emptyStateIcon('heroicon-o-building-office-2');
|
||||
}
|
||||
}
|
||||
263
app/Http/Controllers/Seller/Settings/DbaController.php
Normal file
263
app/Http/Controllers/Seller/Settings/DbaController.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Seller\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Business;
|
||||
use App\Models\BusinessDba;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class DbaController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of all DBAs for the business.
|
||||
*/
|
||||
public function index(Business $business): View
|
||||
{
|
||||
$dbas = $business->dbas()
|
||||
->orderByDesc('is_default')
|
||||
->orderBy('trade_name')
|
||||
->get();
|
||||
|
||||
return view('seller.settings.dbas.index', compact('business', 'dbas'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new DBA.
|
||||
*/
|
||||
public function create(Business $business): View
|
||||
{
|
||||
return view('seller.settings.dbas.create', compact('business'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created DBA in storage.
|
||||
*/
|
||||
public function store(Request $request, Business $business): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
// Identity
|
||||
'trade_name' => 'required|string|max:255',
|
||||
|
||||
// Address
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address_line_2' => 'nullable|string|max:255',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'state' => 'nullable|string|max:2',
|
||||
'zip' => 'nullable|string|max:10',
|
||||
|
||||
// License
|
||||
'license_number' => 'nullable|string|max:255',
|
||||
'license_type' => 'nullable|string|max:255',
|
||||
'license_expiration' => 'nullable|date',
|
||||
|
||||
// Bank Info
|
||||
'bank_name' => 'nullable|string|max:255',
|
||||
'bank_account_name' => 'nullable|string|max:255',
|
||||
'bank_routing_number' => 'nullable|string|max:50',
|
||||
'bank_account_number' => 'nullable|string|max:50',
|
||||
'bank_account_type' => 'nullable|string|in:checking,savings',
|
||||
|
||||
// Tax
|
||||
'tax_id' => 'nullable|string|max:50',
|
||||
'tax_id_type' => 'nullable|string|in:ein,ssn',
|
||||
|
||||
// Contacts
|
||||
'primary_contact_name' => 'nullable|string|max:255',
|
||||
'primary_contact_email' => 'nullable|email|max:255',
|
||||
'primary_contact_phone' => 'nullable|string|max:50',
|
||||
'ap_contact_name' => 'nullable|string|max:255',
|
||||
'ap_contact_email' => 'nullable|email|max:255',
|
||||
'ap_contact_phone' => 'nullable|string|max:50',
|
||||
|
||||
// Invoice Settings
|
||||
'payment_terms' => 'nullable|string|max:50',
|
||||
'payment_instructions' => 'nullable|string|max:2000',
|
||||
'invoice_footer' => 'nullable|string|max:2000',
|
||||
'invoice_prefix' => 'nullable|string|max:10',
|
||||
|
||||
// Branding
|
||||
'logo_path' => 'nullable|string|max:255',
|
||||
'brand_colors' => 'nullable|array',
|
||||
|
||||
// Status
|
||||
'is_default' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
$validated['business_id'] = $business->id;
|
||||
$validated['is_default'] = $request->boolean('is_default');
|
||||
$validated['is_active'] = $request->boolean('is_active', true);
|
||||
|
||||
$dba = BusinessDba::create($validated);
|
||||
|
||||
return redirect()
|
||||
->route('seller.business.settings.dbas.index', $business)
|
||||
->with('success', "DBA \"{$dba->trade_name}\" created successfully.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified DBA.
|
||||
*/
|
||||
public function edit(Business $business, BusinessDba $dba): View
|
||||
{
|
||||
// Verify DBA belongs to this business
|
||||
if ($dba->business_id !== $business->id) {
|
||||
abort(403, 'This DBA does not belong to your business.');
|
||||
}
|
||||
|
||||
return view('seller.settings.dbas.edit', compact('business', 'dba'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified DBA in storage.
|
||||
*/
|
||||
public function update(Request $request, Business $business, BusinessDba $dba): RedirectResponse
|
||||
{
|
||||
// Verify DBA belongs to this business
|
||||
if ($dba->business_id !== $business->id) {
|
||||
abort(403, 'This DBA does not belong to your business.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
// Identity
|
||||
'trade_name' => 'required|string|max:255',
|
||||
|
||||
// Address
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address_line_2' => 'nullable|string|max:255',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'state' => 'nullable|string|max:2',
|
||||
'zip' => 'nullable|string|max:10',
|
||||
|
||||
// License
|
||||
'license_number' => 'nullable|string|max:255',
|
||||
'license_type' => 'nullable|string|max:255',
|
||||
'license_expiration' => 'nullable|date',
|
||||
|
||||
// Bank Info
|
||||
'bank_name' => 'nullable|string|max:255',
|
||||
'bank_account_name' => 'nullable|string|max:255',
|
||||
'bank_routing_number' => 'nullable|string|max:50',
|
||||
'bank_account_number' => 'nullable|string|max:50',
|
||||
'bank_account_type' => 'nullable|string|in:checking,savings',
|
||||
|
||||
// Tax
|
||||
'tax_id' => 'nullable|string|max:50',
|
||||
'tax_id_type' => 'nullable|string|in:ein,ssn',
|
||||
|
||||
// Contacts
|
||||
'primary_contact_name' => 'nullable|string|max:255',
|
||||
'primary_contact_email' => 'nullable|email|max:255',
|
||||
'primary_contact_phone' => 'nullable|string|max:50',
|
||||
'ap_contact_name' => 'nullable|string|max:255',
|
||||
'ap_contact_email' => 'nullable|email|max:255',
|
||||
'ap_contact_phone' => 'nullable|string|max:50',
|
||||
|
||||
// Invoice Settings
|
||||
'payment_terms' => 'nullable|string|max:50',
|
||||
'payment_instructions' => 'nullable|string|max:2000',
|
||||
'invoice_footer' => 'nullable|string|max:2000',
|
||||
'invoice_prefix' => 'nullable|string|max:10',
|
||||
|
||||
// Branding
|
||||
'logo_path' => 'nullable|string|max:255',
|
||||
'brand_colors' => 'nullable|array',
|
||||
|
||||
// Status
|
||||
'is_default' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
$validated['is_default'] = $request->boolean('is_default');
|
||||
$validated['is_active'] = $request->boolean('is_active', true);
|
||||
|
||||
// Don't overwrite encrypted fields if left blank (preserve existing values)
|
||||
$encryptedFields = ['bank_routing_number', 'bank_account_number', 'tax_id'];
|
||||
foreach ($encryptedFields as $field) {
|
||||
if (empty($validated[$field])) {
|
||||
unset($validated[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
$dba->update($validated);
|
||||
|
||||
return redirect()
|
||||
->route('seller.business.settings.dbas.index', $business)
|
||||
->with('success', "DBA \"{$dba->trade_name}\" updated successfully.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified DBA from storage.
|
||||
*/
|
||||
public function destroy(Business $business, BusinessDba $dba): RedirectResponse
|
||||
{
|
||||
// Verify DBA belongs to this business
|
||||
if ($dba->business_id !== $business->id) {
|
||||
abort(403, 'This DBA does not belong to your business.');
|
||||
}
|
||||
|
||||
// Check if this is the only active DBA
|
||||
$activeCount = $business->dbas()->where('is_active', true)->count();
|
||||
if ($activeCount <= 1 && $dba->is_active) {
|
||||
return redirect()
|
||||
->route('seller.business.settings.dbas.index', $business)
|
||||
->with('error', 'You cannot delete the only active DBA. Create another DBA first or deactivate this one.');
|
||||
}
|
||||
|
||||
$tradeName = $dba->trade_name;
|
||||
$dba->delete();
|
||||
|
||||
return redirect()
|
||||
->route('seller.business.settings.dbas.index', $business)
|
||||
->with('success', "DBA \"{$tradeName}\" deleted successfully.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specified DBA as the default for the business.
|
||||
*/
|
||||
public function setDefault(Business $business, BusinessDba $dba): RedirectResponse
|
||||
{
|
||||
// Verify DBA belongs to this business
|
||||
if ($dba->business_id !== $business->id) {
|
||||
abort(403, 'This DBA does not belong to your business.');
|
||||
}
|
||||
|
||||
$dba->markAsDefault();
|
||||
|
||||
return redirect()
|
||||
->route('seller.business.settings.dbas.index', $business)
|
||||
->with('success', "\"{$dba->trade_name}\" is now your default DBA for invoices.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the active status of a DBA.
|
||||
*/
|
||||
public function toggleActive(Business $business, BusinessDba $dba): RedirectResponse
|
||||
{
|
||||
// Verify DBA belongs to this business
|
||||
if ($dba->business_id !== $business->id) {
|
||||
abort(403, 'This DBA does not belong to your business.');
|
||||
}
|
||||
|
||||
// Prevent deactivating if it's the only active DBA
|
||||
if ($dba->is_active) {
|
||||
$activeCount = $business->dbas()->where('is_active', true)->count();
|
||||
if ($activeCount <= 1) {
|
||||
return redirect()
|
||||
->route('seller.business.settings.dbas.index', $business)
|
||||
->with('error', 'You cannot deactivate the only active DBA.');
|
||||
}
|
||||
}
|
||||
|
||||
$dba->update(['is_active' => ! $dba->is_active]);
|
||||
|
||||
$status = $dba->is_active ? 'activated' : 'deactivated';
|
||||
|
||||
return redirect()
|
||||
->route('seller.business.settings.dbas.index', $business)
|
||||
->with('success', "DBA \"{$dba->trade_name}\" has been {$status}.");
|
||||
}
|
||||
}
|
||||
@@ -531,6 +531,47 @@ class Business extends Model implements AuditableContract
|
||||
return $this->hasMany(Brand::class);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DBA (Doing Business As) Relationships
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get all DBAs for this business.
|
||||
*/
|
||||
public function dbas(): HasMany
|
||||
{
|
||||
return $this->hasMany(BusinessDba::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active DBAs for this business.
|
||||
*/
|
||||
public function activeDbas(): HasMany
|
||||
{
|
||||
return $this->hasMany(BusinessDba::class)->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default DBA for this business.
|
||||
*/
|
||||
public function defaultDba(): HasOne
|
||||
{
|
||||
return $this->hasOne(BusinessDba::class)->where('is_default', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DBA for invoice generation.
|
||||
* Priority: explicit dba_id > default DBA > first active DBA > null
|
||||
*/
|
||||
public function getDbaForInvoice(?int $dbaId = null): ?BusinessDba
|
||||
{
|
||||
if ($dbaId) {
|
||||
return $this->dbas()->find($dbaId);
|
||||
}
|
||||
|
||||
return $this->defaultDba ?? $this->activeDbas()->first();
|
||||
}
|
||||
|
||||
public function brandAiProfiles(): HasMany
|
||||
{
|
||||
return $this->hasMany(BrandAiProfile::class);
|
||||
|
||||
250
app/Models/BusinessDba.php
Normal file
250
app/Models/BusinessDba.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\BelongsToBusinessDirectly;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Str;
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
|
||||
class BusinessDba extends Model implements Auditable
|
||||
{
|
||||
use BelongsToBusinessDirectly;
|
||||
use HasFactory;
|
||||
use \OwenIt\Auditing\Auditable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'business_dbas';
|
||||
|
||||
protected $fillable = [
|
||||
'business_id',
|
||||
'trade_name',
|
||||
'slug',
|
||||
// Address
|
||||
'address',
|
||||
'address_line_2',
|
||||
'city',
|
||||
'state',
|
||||
'zip',
|
||||
// License
|
||||
'license_number',
|
||||
'license_type',
|
||||
'license_expiration',
|
||||
// Bank Info
|
||||
'bank_name',
|
||||
'bank_account_name',
|
||||
'bank_routing_number',
|
||||
'bank_account_number',
|
||||
'bank_account_type',
|
||||
// Tax
|
||||
'tax_id',
|
||||
'tax_id_type',
|
||||
// Contacts
|
||||
'primary_contact_name',
|
||||
'primary_contact_email',
|
||||
'primary_contact_phone',
|
||||
'ap_contact_name',
|
||||
'ap_contact_email',
|
||||
'ap_contact_phone',
|
||||
// Invoice Settings
|
||||
'payment_terms',
|
||||
'payment_instructions',
|
||||
'invoice_footer',
|
||||
'invoice_prefix',
|
||||
// Branding
|
||||
'logo_path',
|
||||
'brand_colors',
|
||||
// Status
|
||||
'is_default',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'brand_colors' => 'array',
|
||||
'is_default' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
'license_expiration' => 'date',
|
||||
// Encrypted fields
|
||||
'bank_routing_number' => 'encrypted',
|
||||
'bank_account_number' => 'encrypted',
|
||||
'tax_id' => 'encrypted',
|
||||
];
|
||||
|
||||
/**
|
||||
* Fields to exclude from audit logging (sensitive data)
|
||||
*/
|
||||
protected array $auditExclude = [
|
||||
'bank_routing_number',
|
||||
'bank_account_number',
|
||||
'tax_id',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// Relationships
|
||||
// =========================================================================
|
||||
|
||||
public function business(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Business::class);
|
||||
}
|
||||
|
||||
public function invoices(): HasMany
|
||||
{
|
||||
return $this->hasMany(\App\Models\Crm\CrmInvoice::class, 'dba_id');
|
||||
}
|
||||
|
||||
public function orders(): HasMany
|
||||
{
|
||||
return $this->hasMany(Order::class, 'seller_dba_id');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Scopes
|
||||
// =========================================================================
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeDefault($query)
|
||||
{
|
||||
return $query->where('is_default', true);
|
||||
}
|
||||
|
||||
public function scopeForBusiness($query, int $businessId)
|
||||
{
|
||||
return $query->where('business_id', $businessId);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Accessors
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get the full formatted address.
|
||||
*/
|
||||
public function getFullAddressAttribute(): string
|
||||
{
|
||||
$parts = array_filter([
|
||||
$this->address,
|
||||
$this->address_line_2,
|
||||
]);
|
||||
|
||||
$cityStateZip = trim(
|
||||
($this->city ?? '').
|
||||
($this->city && $this->state ? ', ' : '').
|
||||
($this->state ?? '').' '.
|
||||
($this->zip ?? '')
|
||||
);
|
||||
|
||||
if ($cityStateZip) {
|
||||
$parts[] = $cityStateZip;
|
||||
}
|
||||
|
||||
return implode("\n", $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get masked bank account number (last 4 digits).
|
||||
*/
|
||||
public function getMaskedAccountNumberAttribute(): ?string
|
||||
{
|
||||
if (! $this->bank_account_number) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '****'.substr($this->bank_account_number, -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get masked tax ID (last 4 digits).
|
||||
*/
|
||||
public function getMaskedTaxIdAttribute(): ?string
|
||||
{
|
||||
if (! $this->tax_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '***-**-'.substr($this->tax_id, -4);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Methods
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Mark this DBA as the default for the business.
|
||||
*/
|
||||
public function markAsDefault(): void
|
||||
{
|
||||
// Clear default from other DBAs for this business
|
||||
static::where('business_id', $this->business_id)
|
||||
->where('id', '!=', $this->id)
|
||||
->update(['is_default' => false]);
|
||||
|
||||
$this->update(['is_default' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display info for invoices/orders.
|
||||
*/
|
||||
public function getDisplayInfo(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->trade_name,
|
||||
'address' => $this->full_address,
|
||||
'license' => $this->license_number,
|
||||
'logo' => $this->logo_path,
|
||||
'payment_terms' => $this->payment_terms,
|
||||
'payment_instructions' => $this->payment_instructions,
|
||||
'invoice_footer' => $this->invoice_footer,
|
||||
'primary_contact' => [
|
||||
'name' => $this->primary_contact_name,
|
||||
'email' => $this->primary_contact_email,
|
||||
'phone' => $this->primary_contact_phone,
|
||||
],
|
||||
'ap_contact' => [
|
||||
'name' => $this->ap_contact_name,
|
||||
'email' => $this->ap_contact_email,
|
||||
'phone' => $this->ap_contact_phone,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Boot
|
||||
// =========================================================================
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Auto-generate slug on creation
|
||||
static::creating(function ($dba) {
|
||||
if (empty($dba->slug)) {
|
||||
$dba->slug = Str::slug($dba->trade_name);
|
||||
|
||||
// Ensure unique
|
||||
$original = $dba->slug;
|
||||
$counter = 1;
|
||||
while (static::withTrashed()->where('slug', $dba->slug)->exists()) {
|
||||
$dba->slug = $original.'-'.$counter++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure only one default per business
|
||||
static::saving(function ($dba) {
|
||||
if ($dba->is_default && $dba->isDirty('is_default')) {
|
||||
static::where('business_id', $dba->business_id)
|
||||
->where('id', '!=', $dba->id ?? 0)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Models\Crm;
|
||||
use App\Models\Accounting\ArInvoice;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Business;
|
||||
use App\Models\BusinessDba;
|
||||
use App\Models\BusinessLocation;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Order;
|
||||
@@ -55,6 +56,7 @@ class CrmInvoice extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'business_id',
|
||||
'dba_id',
|
||||
'account_id',
|
||||
'location_id',
|
||||
'contact_id',
|
||||
@@ -110,6 +112,14 @@ class CrmInvoice extends Model
|
||||
return $this->belongsTo(Business::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The DBA (trade name) used for this invoice.
|
||||
*/
|
||||
public function dba(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BusinessDba::class, 'dba_id');
|
||||
}
|
||||
|
||||
public function account(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Business::class, 'account_id');
|
||||
@@ -400,4 +410,45 @@ class CrmInvoice extends Model
|
||||
|
||||
return $prefix.str_pad($number, 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seller display information for the invoice.
|
||||
* Prioritizes DBA if set, otherwise falls back to business defaults.
|
||||
*/
|
||||
public function getSellerDisplayInfo(): array
|
||||
{
|
||||
if ($this->dba_id && $this->dba) {
|
||||
return $this->dba->getDisplayInfo();
|
||||
}
|
||||
|
||||
// Fall back to business info
|
||||
$business = $this->business;
|
||||
|
||||
return [
|
||||
'name' => $business->dba_name ?: $business->name,
|
||||
'address' => implode("\n", array_filter([
|
||||
$business->invoice_payable_address ?? $business->physical_address,
|
||||
trim(
|
||||
($business->invoice_payable_city ?? $business->physical_city ?? '').
|
||||
($business->invoice_payable_state ?? $business->physical_state ? ', '.($business->invoice_payable_state ?? $business->physical_state) : '').' '.
|
||||
($business->invoice_payable_zipcode ?? $business->physical_zipcode ?? '')
|
||||
),
|
||||
])),
|
||||
'license' => $business->license_number,
|
||||
'logo' => null,
|
||||
'payment_terms' => null,
|
||||
'payment_instructions' => $business->order_invoice_footer,
|
||||
'invoice_footer' => $business->order_invoice_footer,
|
||||
'primary_contact' => [
|
||||
'name' => trim(($business->primary_contact_first_name ?? '').' '.($business->primary_contact_last_name ?? '')),
|
||||
'email' => $business->primary_contact_email ?? $business->business_email,
|
||||
'phone' => $business->primary_contact_phone ?? $business->business_phone,
|
||||
],
|
||||
'ap_contact' => [
|
||||
'name' => trim(($business->ap_contact_first_name ?? '').' '.($business->ap_contact_last_name ?? '')),
|
||||
'email' => $business->ap_contact_email,
|
||||
'phone' => $business->ap_contact_phone,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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('business_dbas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('business_id')->constrained('businesses')->onDelete('cascade');
|
||||
|
||||
// Identity
|
||||
$table->string('trade_name');
|
||||
$table->string('slug')->unique();
|
||||
|
||||
// Address
|
||||
$table->string('address')->nullable();
|
||||
$table->string('address_line_2')->nullable();
|
||||
$table->string('city')->nullable();
|
||||
$table->string('state', 2)->nullable();
|
||||
$table->string('zip', 10)->nullable();
|
||||
|
||||
// License
|
||||
$table->string('license_number')->nullable();
|
||||
$table->string('license_type')->nullable();
|
||||
$table->date('license_expiration')->nullable();
|
||||
|
||||
// Bank Info (encrypted at model level)
|
||||
$table->string('bank_name')->nullable();
|
||||
$table->string('bank_account_name')->nullable();
|
||||
$table->text('bank_routing_number')->nullable();
|
||||
$table->text('bank_account_number')->nullable();
|
||||
$table->string('bank_account_type', 50)->nullable();
|
||||
|
||||
// Tax
|
||||
$table->text('tax_id')->nullable();
|
||||
$table->string('tax_id_type', 50)->nullable();
|
||||
|
||||
// Contacts
|
||||
$table->string('primary_contact_name')->nullable();
|
||||
$table->string('primary_contact_email')->nullable();
|
||||
$table->string('primary_contact_phone', 50)->nullable();
|
||||
$table->string('ap_contact_name')->nullable();
|
||||
$table->string('ap_contact_email')->nullable();
|
||||
$table->string('ap_contact_phone', 50)->nullable();
|
||||
|
||||
// Invoice Settings
|
||||
$table->string('payment_terms', 50)->nullable();
|
||||
$table->text('payment_instructions')->nullable();
|
||||
$table->text('invoice_footer')->nullable();
|
||||
$table->string('invoice_prefix', 10)->nullable();
|
||||
|
||||
// Branding
|
||||
$table->string('logo_path')->nullable();
|
||||
$table->jsonb('brand_colors')->nullable();
|
||||
|
||||
// Status
|
||||
$table->boolean('is_default')->default(false);
|
||||
$table->boolean('is_active')->default(true);
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// Indexes
|
||||
$table->index('business_id');
|
||||
$table->index(['business_id', 'is_default']);
|
||||
$table->index('is_active');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('business_dbas');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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('crm_invoices', function (Blueprint $table) {
|
||||
$table->foreignId('dba_id')
|
||||
->nullable()
|
||||
->after('business_id')
|
||||
->constrained('business_dbas')
|
||||
->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('crm_invoices', function (Blueprint $table) {
|
||||
$table->dropForeign(['dba_id']);
|
||||
$table->dropColumn('dba_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -23,6 +23,7 @@
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="BROADCAST_CONNECTION" value="null"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<env name="DB_CONNECTION" value="pgsql"/>
|
||||
<env name="DB_HOST" value="pgsql"/>
|
||||
<env name="DB_PORT" value="5432"/>
|
||||
<env name="DB_DATABASE" value="testing"/>
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
@else
|
||||
<span class="badge badge-ghost text-neutral border-base-300 badge-sm">Inactive</span>
|
||||
@endif
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="btn btn-primary btn-sm gap-1">
|
||||
<div class="dropdown dropdown-end" x-data>
|
||||
<div tabindex="0" role="button" class="btn btn-primary btn-sm gap-1">
|
||||
<span class="icon-[heroicons--bolt] size-4"></span>
|
||||
Actions
|
||||
<span class="icon-[heroicons--chevron-down] size-3"></span>
|
||||
</label>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-10 menu p-2 shadow-lg bg-base-100 rounded-xl w-48 border border-base-200">
|
||||
<li>
|
||||
<a href="{{ route('seller.business.crm.accounts.edit', [$business->slug, $account->slug]) }}" class="gap-2 text-sm">
|
||||
@@ -44,7 +44,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button @click="$dispatch('open-create-opportunity-modal', { account_id: {{ $account->id }}, account_locked: true })" class="gap-2 text-sm">
|
||||
<button type="button" @click="$dispatch('open-create-opportunity-modal', { account_id: {{ $account->id }}, account_locked: true })" class="gap-2 text-sm w-full text-left">
|
||||
<span class="icon-[heroicons--sparkles] size-4 text-warning"></span>
|
||||
New Opportunity
|
||||
</button>
|
||||
@@ -56,13 +56,13 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button @click="document.getElementById('note-section').scrollIntoView({behavior:'smooth'})" class="gap-2 text-sm">
|
||||
<button type="button" @click="document.getElementById('note-section').scrollIntoView({behavior:'smooth'})" class="gap-2 text-sm w-full text-left">
|
||||
<span class="icon-[heroicons--pencil] size-4 text-base-content/60"></span>
|
||||
Add Note
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button @click="$dispatch('open-send-menu-modal', { customer_id: {{ $account->id }}, customer_locked: true })" class="gap-2 text-sm">
|
||||
<button type="button" @click="$dispatch('open-send-menu-modal', { customer_id: {{ $account->id }}, customer_locked: true })" class="gap-2 text-sm w-full text-left">
|
||||
<span class="icon-[heroicons--paper-airplane] size-4 text-primary"></span>
|
||||
Send Menu
|
||||
</button>
|
||||
|
||||
346
resources/views/seller/settings/dbas/create.blade.php
Normal file
346
resources/views/seller/settings/dbas/create.blade.php
Normal file
@@ -0,0 +1,346 @@
|
||||
@extends('layouts.app-with-sidebar')
|
||||
|
||||
@section('content')
|
||||
<!-- Page Title and Breadcrumbs -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">Add Trade Name</h1>
|
||||
<p class="text-sm text-base-content/60 mt-1">Create a new DBA for your business.</p>
|
||||
</div>
|
||||
<div class="breadcrumbs hidden p-0 text-sm sm:inline">
|
||||
<ul>
|
||||
<li><a href="{{ route('seller.business.settings.index', $business->slug) }}">Settings</a></li>
|
||||
<li><a href="{{ route('seller.business.settings.dbas.index', $business->slug) }}">Trade Names</a></li>
|
||||
<li class="opacity-60">Add</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('seller.business.settings.dbas.store', $business->slug) }}" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<!-- Basic Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--building-office-2] size-5 text-primary"></span>
|
||||
Basic Information
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium">Trade Name <span class="text-error">*</span></span>
|
||||
</label>
|
||||
<input type="text" name="trade_name" value="{{ old('trade_name') }}" required
|
||||
class="input input-bordered w-full @error('trade_name') input-error @enderror"
|
||||
placeholder="e.g., Acme Cannabis LLC" />
|
||||
@error('trade_name')
|
||||
<label class="label"><span class="label-text-alt text-error">{{ $message }}</span></label>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" name="is_default" value="1" {{ old('is_default') ? 'checked' : '' }} class="checkbox checkbox-primary" />
|
||||
<div>
|
||||
<span class="label-text font-medium">Set as Default</span>
|
||||
<p class="text-xs text-base-content/60">Use this DBA for new invoices by default</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" name="is_active" value="1" {{ old('is_active', true) ? 'checked' : '' }} class="checkbox checkbox-primary" />
|
||||
<div>
|
||||
<span class="label-text font-medium">Active</span>
|
||||
<p class="text-xs text-base-content/60">Available for use on invoices and orders</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--map-pin] size-5 text-primary"></span>
|
||||
Business Address
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Street Address</span></label>
|
||||
<input type="text" name="address" value="{{ old('address') }}"
|
||||
class="input input-bordered w-full @error('address') input-error @enderror"
|
||||
placeholder="123 Main Street" />
|
||||
@error('address')
|
||||
<label class="label"><span class="label-text-alt text-error">{{ $message }}</span></label>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Address Line 2</span></label>
|
||||
<input type="text" name="address_line_2" value="{{ old('address_line_2') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Suite 100" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">City</span></label>
|
||||
<input type="text" name="city" value="{{ old('city') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Phoenix" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">State</span></label>
|
||||
<input type="text" name="state" value="{{ old('state') }}" maxlength="2"
|
||||
class="input input-bordered w-full uppercase"
|
||||
placeholder="AZ" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">ZIP Code</span></label>
|
||||
<input type="text" name="zip" value="{{ old('zip') }}" maxlength="10"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="85001" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- License Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--identification] size-5 text-primary"></span>
|
||||
License Information
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">License Number</span></label>
|
||||
<input type="text" name="license_number" value="{{ old('license_number') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="00000123DCUP00730088" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">License Type</span></label>
|
||||
<input type="text" name="license_type" value="{{ old('license_type') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Dual (Medical + Recreational)" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Expiration Date</span></label>
|
||||
<input type="date" name="license_expiration" value="{{ old('license_expiration') }}"
|
||||
class="input input-bordered w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Banking Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--banknotes] size-5 text-primary"></span>
|
||||
Banking Information
|
||||
<span class="badge badge-ghost badge-sm">Encrypted</span>
|
||||
</h2>
|
||||
<p class="text-sm text-base-content/60 mb-4">Bank account details for payment processing. This information is encrypted at rest.</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Bank Name</span></label>
|
||||
<input type="text" name="bank_name" value="{{ old('bank_name') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Partner Colorado Credit Union" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Account Name</span></label>
|
||||
<input type="text" name="bank_account_name" value="{{ old('bank_account_name') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Acme Cannabis LLC" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Routing Number</span></label>
|
||||
<input type="text" name="bank_routing_number" value="{{ old('bank_routing_number') }}"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="102001017" maxlength="9" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Account Number</span></label>
|
||||
<input type="text" name="bank_account_number" value="{{ old('bank_account_number') }}"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="1234567890" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Account Type</span></label>
|
||||
<select name="bank_account_type" class="select select-bordered w-full">
|
||||
<option value="">Select type...</option>
|
||||
<option value="checking" {{ old('bank_account_type') === 'checking' ? 'selected' : '' }}>Checking</option>
|
||||
<option value="savings" {{ old('bank_account_type') === 'savings' ? 'selected' : '' }}>Savings</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tax Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--document-text] size-5 text-primary"></span>
|
||||
Tax Information
|
||||
<span class="badge badge-ghost badge-sm">Encrypted</span>
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Tax ID (EIN/SSN)</span></label>
|
||||
<input type="text" name="tax_id" value="{{ old('tax_id') }}"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="XX-XXXXXXX" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Tax ID Type</span></label>
|
||||
<select name="tax_id_type" class="select select-bordered w-full">
|
||||
<option value="">Select type...</option>
|
||||
<option value="ein" {{ old('tax_id_type') === 'ein' ? 'selected' : '' }}>EIN (Employer Identification Number)</option>
|
||||
<option value="ssn" {{ old('tax_id_type') === 'ssn' ? 'selected' : '' }}>SSN (Social Security Number)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contacts -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--users] size-5 text-primary"></span>
|
||||
Contacts
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Primary Contact -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="font-medium text-sm text-base-content/70 border-b border-base-200 pb-2">Primary Contact</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Name</span></label>
|
||||
<input type="text" name="primary_contact_name" value="{{ old('primary_contact_name') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="John Smith" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Email</span></label>
|
||||
<input type="email" name="primary_contact_email" value="{{ old('primary_contact_email') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="john@example.com" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Phone</span></label>
|
||||
<input type="tel" name="primary_contact_phone" value="{{ old('primary_contact_phone') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="(555) 123-4567" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AP Contact -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="font-medium text-sm text-base-content/70 border-b border-base-200 pb-2">Accounts Payable Contact</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Name</span></label>
|
||||
<input type="text" name="ap_contact_name" value="{{ old('ap_contact_name') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Jane Doe" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Email</span></label>
|
||||
<input type="email" name="ap_contact_email" value="{{ old('ap_contact_email') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="ap@example.com" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Phone</span></label>
|
||||
<input type="tel" name="ap_contact_phone" value="{{ old('ap_contact_phone') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="(555) 123-4567" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invoice Settings -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--document-currency-dollar] size-5 text-primary"></span>
|
||||
Invoice Settings
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Payment Terms</span></label>
|
||||
<input type="text" name="payment_terms" value="{{ old('payment_terms') }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Net 30" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Invoice Prefix</span></label>
|
||||
<input type="text" name="invoice_prefix" value="{{ old('invoice_prefix') }}" maxlength="10"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="INV-" />
|
||||
<label class="label"><span class="label-text-alt">Prefix for invoice numbers (e.g., INV-, ACME-)</span></label>
|
||||
</div>
|
||||
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Payment Instructions</span></label>
|
||||
<textarea name="payment_instructions" rows="3"
|
||||
class="textarea textarea-bordered w-full"
|
||||
placeholder="Wire instructions, payment portal URL, or other payment details...">{{ old('payment_instructions') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Invoice Footer</span></label>
|
||||
<textarea name="invoice_footer" rows="2"
|
||||
class="textarea textarea-bordered w-full"
|
||||
placeholder="Thank you for your business!">{{ old('invoice_footer') }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<a href="{{ route('seller.business.settings.dbas.index', $business->slug) }}" class="btn btn-ghost">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary gap-2">
|
||||
<span class="icon-[heroicons--check] size-4"></span>
|
||||
Create Trade Name
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
407
resources/views/seller/settings/dbas/edit.blade.php
Normal file
407
resources/views/seller/settings/dbas/edit.blade.php
Normal file
@@ -0,0 +1,407 @@
|
||||
@extends('layouts.app-with-sidebar')
|
||||
|
||||
@section('content')
|
||||
<!-- Page Title and Breadcrumbs -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">Edit Trade Name</h1>
|
||||
<p class="text-sm text-base-content/60 mt-1">Update DBA information for {{ $dba->trade_name }}.</p>
|
||||
</div>
|
||||
<div class="breadcrumbs hidden p-0 text-sm sm:inline">
|
||||
<ul>
|
||||
<li><a href="{{ route('seller.business.settings.index', $business->slug) }}">Settings</a></li>
|
||||
<li><a href="{{ route('seller.business.settings.dbas.index', $business->slug) }}">Trade Names</a></li>
|
||||
<li class="opacity-60">Edit</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('seller.business.settings.dbas.update', [$business->slug, $dba->id]) }}" class="space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<!-- Basic Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--building-office-2] size-5 text-primary"></span>
|
||||
Basic Information
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium">Trade Name <span class="text-error">*</span></span>
|
||||
</label>
|
||||
<input type="text" name="trade_name" value="{{ old('trade_name', $dba->trade_name) }}" required
|
||||
class="input input-bordered w-full @error('trade_name') input-error @enderror"
|
||||
placeholder="e.g., Acme Cannabis LLC" />
|
||||
@error('trade_name')
|
||||
<label class="label"><span class="label-text-alt text-error">{{ $message }}</span></label>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" name="is_default" value="1" {{ old('is_default', $dba->is_default) ? 'checked' : '' }} class="checkbox checkbox-primary" />
|
||||
<div>
|
||||
<span class="label-text font-medium">Set as Default</span>
|
||||
<p class="text-xs text-base-content/60">Use this DBA for new invoices by default</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" name="is_active" value="1" {{ old('is_active', $dba->is_active) ? 'checked' : '' }} class="checkbox checkbox-primary" />
|
||||
<div>
|
||||
<span class="label-text font-medium">Active</span>
|
||||
<p class="text-xs text-base-content/60">Available for use on invoices and orders</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--map-pin] size-5 text-primary"></span>
|
||||
Business Address
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Street Address</span></label>
|
||||
<input type="text" name="address" value="{{ old('address', $dba->address) }}"
|
||||
class="input input-bordered w-full @error('address') input-error @enderror"
|
||||
placeholder="123 Main Street" />
|
||||
@error('address')
|
||||
<label class="label"><span class="label-text-alt text-error">{{ $message }}</span></label>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Address Line 2</span></label>
|
||||
<input type="text" name="address_line_2" value="{{ old('address_line_2', $dba->address_line_2) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Suite 100" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">City</span></label>
|
||||
<input type="text" name="city" value="{{ old('city', $dba->city) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Phoenix" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">State</span></label>
|
||||
<input type="text" name="state" value="{{ old('state', $dba->state) }}" maxlength="2"
|
||||
class="input input-bordered w-full uppercase"
|
||||
placeholder="AZ" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">ZIP Code</span></label>
|
||||
<input type="text" name="zip" value="{{ old('zip', $dba->zip) }}" maxlength="10"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="85001" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- License Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--identification] size-5 text-primary"></span>
|
||||
License Information
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">License Number</span></label>
|
||||
<input type="text" name="license_number" value="{{ old('license_number', $dba->license_number) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="00000123DCUP00730088" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">License Type</span></label>
|
||||
<input type="text" name="license_type" value="{{ old('license_type', $dba->license_type) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Dual (Medical + Recreational)" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Expiration Date</span></label>
|
||||
<input type="date" name="license_expiration" value="{{ old('license_expiration', $dba->license_expiration?->format('Y-m-d')) }}"
|
||||
class="input input-bordered w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Banking Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--banknotes] size-5 text-primary"></span>
|
||||
Banking Information
|
||||
<span class="badge badge-ghost badge-sm">Encrypted</span>
|
||||
</h2>
|
||||
<p class="text-sm text-base-content/60 mb-4">Bank account details for payment processing. This information is encrypted at rest.</p>
|
||||
|
||||
@if($dba->bank_account_number)
|
||||
<div class="alert bg-info/10 border-info/20 mb-4">
|
||||
<span class="icon-[heroicons--information-circle] size-5 text-info"></span>
|
||||
<span class="text-sm">Current account: <span class="font-mono">{{ $dba->masked_account_number }}</span> at {{ $dba->bank_name }}. Leave fields blank to keep current values.</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Bank Name</span></label>
|
||||
<input type="text" name="bank_name" value="{{ old('bank_name', $dba->bank_name) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Partner Colorado Credit Union" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Account Name</span></label>
|
||||
<input type="text" name="bank_account_name" value="{{ old('bank_account_name', $dba->bank_account_name) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Acme Cannabis LLC" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Routing Number</span></label>
|
||||
<input type="text" name="bank_routing_number" value="{{ old('bank_routing_number') }}"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="{{ $dba->bank_routing_number ? '(current value hidden)' : '102001017' }}" maxlength="9" />
|
||||
<label class="label"><span class="label-text-alt">Leave blank to keep current value</span></label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Account Number</span></label>
|
||||
<input type="text" name="bank_account_number" value="{{ old('bank_account_number') }}"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="{{ $dba->bank_account_number ? '(current value hidden)' : '1234567890' }}" />
|
||||
<label class="label"><span class="label-text-alt">Leave blank to keep current value</span></label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Account Type</span></label>
|
||||
<select name="bank_account_type" class="select select-bordered w-full">
|
||||
<option value="">Select type...</option>
|
||||
<option value="checking" {{ old('bank_account_type', $dba->bank_account_type) === 'checking' ? 'selected' : '' }}>Checking</option>
|
||||
<option value="savings" {{ old('bank_account_type', $dba->bank_account_type) === 'savings' ? 'selected' : '' }}>Savings</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tax Information -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--document-text] size-5 text-primary"></span>
|
||||
Tax Information
|
||||
<span class="badge badge-ghost badge-sm">Encrypted</span>
|
||||
</h2>
|
||||
|
||||
@if($dba->tax_id)
|
||||
<div class="alert bg-info/10 border-info/20 mb-4">
|
||||
<span class="icon-[heroicons--information-circle] size-5 text-info"></span>
|
||||
<span class="text-sm">Current Tax ID: <span class="font-mono">{{ $dba->masked_tax_id }}</span>. Leave blank to keep current value.</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Tax ID (EIN/SSN)</span></label>
|
||||
<input type="text" name="tax_id" value="{{ old('tax_id') }}"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="{{ $dba->tax_id ? '(current value hidden)' : 'XX-XXXXXXX' }}" />
|
||||
<label class="label"><span class="label-text-alt">Leave blank to keep current value</span></label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Tax ID Type</span></label>
|
||||
<select name="tax_id_type" class="select select-bordered w-full">
|
||||
<option value="">Select type...</option>
|
||||
<option value="ein" {{ old('tax_id_type', $dba->tax_id_type) === 'ein' ? 'selected' : '' }}>EIN (Employer Identification Number)</option>
|
||||
<option value="ssn" {{ old('tax_id_type', $dba->tax_id_type) === 'ssn' ? 'selected' : '' }}>SSN (Social Security Number)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contacts -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--users] size-5 text-primary"></span>
|
||||
Contacts
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Primary Contact -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="font-medium text-sm text-base-content/70 border-b border-base-200 pb-2">Primary Contact</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Name</span></label>
|
||||
<input type="text" name="primary_contact_name" value="{{ old('primary_contact_name', $dba->primary_contact_name) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="John Smith" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Email</span></label>
|
||||
<input type="email" name="primary_contact_email" value="{{ old('primary_contact_email', $dba->primary_contact_email) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="john@example.com" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Phone</span></label>
|
||||
<input type="tel" name="primary_contact_phone" value="{{ old('primary_contact_phone', $dba->primary_contact_phone) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="(555) 123-4567" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AP Contact -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="font-medium text-sm text-base-content/70 border-b border-base-200 pb-2">Accounts Payable Contact</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Name</span></label>
|
||||
<input type="text" name="ap_contact_name" value="{{ old('ap_contact_name', $dba->ap_contact_name) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Jane Doe" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Email</span></label>
|
||||
<input type="email" name="ap_contact_email" value="{{ old('ap_contact_email', $dba->ap_contact_email) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="ap@example.com" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Phone</span></label>
|
||||
<input type="tel" name="ap_contact_phone" value="{{ old('ap_contact_phone', $dba->ap_contact_phone) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="(555) 123-4567" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invoice Settings -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base flex items-center gap-2 mb-4">
|
||||
<span class="icon-[heroicons--document-currency-dollar] size-5 text-primary"></span>
|
||||
Invoice Settings
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Payment Terms</span></label>
|
||||
<input type="text" name="payment_terms" value="{{ old('payment_terms', $dba->payment_terms) }}"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Net 30" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-medium">Invoice Prefix</span></label>
|
||||
<input type="text" name="invoice_prefix" value="{{ old('invoice_prefix', $dba->invoice_prefix) }}" maxlength="10"
|
||||
class="input input-bordered w-full font-mono"
|
||||
placeholder="INV-" />
|
||||
<label class="label"><span class="label-text-alt">Prefix for invoice numbers (e.g., INV-, ACME-)</span></label>
|
||||
</div>
|
||||
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Payment Instructions</span></label>
|
||||
<textarea name="payment_instructions" rows="3"
|
||||
class="textarea textarea-bordered w-full"
|
||||
placeholder="Wire instructions, payment portal URL, or other payment details...">{{ old('payment_instructions', $dba->payment_instructions) }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text font-medium">Invoice Footer</span></label>
|
||||
<textarea name="invoice_footer" rows="2"
|
||||
class="textarea textarea-bordered w-full"
|
||||
placeholder="Thank you for your business!">{{ old('invoice_footer', $dba->invoice_footer) }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@if(!$dba->is_default)
|
||||
<button type="button" onclick="confirmDelete()" class="btn btn-ghost text-error gap-2">
|
||||
<span class="icon-[heroicons--trash] size-4"></span>
|
||||
Delete Trade Name
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="{{ route('seller.business.settings.dbas.index', $business->slug) }}" class="btn btn-ghost">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary gap-2">
|
||||
<span class="icon-[heroicons--check] size-4"></span>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
@if(!$dba->is_default)
|
||||
<dialog id="delete_modal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg mb-4">Delete Trade Name</h3>
|
||||
<p class="text-base-content/70">Are you sure you want to delete <strong>{{ $dba->trade_name }}</strong>?</p>
|
||||
<p class="text-sm text-warning mt-2">This action cannot be undone. Any invoices using this DBA will retain the historical information.</p>
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-ghost">Cancel</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('seller.business.settings.dbas.destroy', [$business->slug, $dba->id]) }}">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-error gap-2">
|
||||
<span class="icon-[heroicons--trash] size-4"></span>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
function confirmDelete() {
|
||||
delete_modal.showModal();
|
||||
}
|
||||
</script>
|
||||
@endif
|
||||
@endsection
|
||||
254
resources/views/seller/settings/dbas/index.blade.php
Normal file
254
resources/views/seller/settings/dbas/index.blade.php
Normal file
@@ -0,0 +1,254 @@
|
||||
@extends('layouts.app-with-sidebar')
|
||||
|
||||
@section('content')
|
||||
<!-- Page Title and Breadcrumbs -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">Trade Names (DBAs)</h1>
|
||||
<p class="text-sm text-base-content/60 mt-1">Manage "Doing Business As" names for invoicing, licensing, and customer-facing documents.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="breadcrumbs hidden p-0 text-sm sm:inline">
|
||||
<ul>
|
||||
<li><a href="{{ route('seller.business.settings.index', $business->slug) }}">Settings</a></li>
|
||||
<li class="opacity-60">Trade Names</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="{{ route('seller.business.settings.dbas.create', $business->slug) }}" class="btn btn-primary gap-2">
|
||||
<span class="icon-[heroicons--plus] size-4"></span>
|
||||
Add Trade Name
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Alert -->
|
||||
<div class="alert bg-info/10 border-info/20 mb-6">
|
||||
<span class="icon-[heroicons--information-circle] size-5 text-info"></span>
|
||||
<div class="text-sm">
|
||||
<p class="font-semibold">What are Trade Names (DBAs)?</p>
|
||||
<p class="text-base-content/70">
|
||||
A DBA (Doing Business As) allows your company to operate under different trade names.
|
||||
Each DBA can have its own address, license, bank account, and invoice branding.
|
||||
The <span class="badge badge-success badge-xs">Default</span> DBA is used automatically for new invoices.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DBAs List -->
|
||||
@if($dbas->count() > 0)
|
||||
<div class="space-y-4">
|
||||
@foreach($dbas as $dba)
|
||||
<div class="card bg-base-100 border border-base-300 {{ !$dba->is_active ? 'opacity-60' : '' }}">
|
||||
<div class="card-body">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- Logo or Placeholder -->
|
||||
@if($dba->logo_path)
|
||||
<div class="avatar">
|
||||
<div class="w-14 h-14 rounded-lg bg-base-200">
|
||||
<img src="{{ Storage::url($dba->logo_path) }}" alt="{{ $dba->trade_name }}" class="object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="p-3 bg-base-200 rounded-lg">
|
||||
<span class="icon-[heroicons--building-office-2] size-8 text-base-content/50"></span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<h3 class="font-semibold text-lg">{{ $dba->trade_name }}</h3>
|
||||
@if($dba->is_default)
|
||||
<span class="badge badge-success badge-sm gap-1">
|
||||
<span class="icon-[heroicons--check-circle] size-3"></span>
|
||||
Default
|
||||
</span>
|
||||
@endif
|
||||
@if(!$dba->is_active)
|
||||
<span class="badge badge-ghost badge-sm">Inactive</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- DBA Details Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-3 text-sm">
|
||||
<!-- Address -->
|
||||
<div>
|
||||
<div class="flex items-center gap-1 text-base-content/60 mb-1">
|
||||
<span class="icon-[heroicons--map-pin] size-4"></span>
|
||||
Address
|
||||
</div>
|
||||
@if($dba->address)
|
||||
<div class="text-base-content">
|
||||
{{ $dba->address }}<br>
|
||||
@if($dba->address_line_2){{ $dba->address_line_2 }}<br>@endif
|
||||
@if($dba->city){{ $dba->city }}, @endif{{ $dba->state }} {{ $dba->zip }}
|
||||
</div>
|
||||
@else
|
||||
<span class="text-base-content/40">Not set</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- License -->
|
||||
<div>
|
||||
<div class="flex items-center gap-1 text-base-content/60 mb-1">
|
||||
<span class="icon-[heroicons--identification] size-4"></span>
|
||||
License
|
||||
</div>
|
||||
@if($dba->license_number)
|
||||
<div class="text-base-content">
|
||||
{{ $dba->license_number }}
|
||||
@if($dba->license_type)
|
||||
<br><span class="text-base-content/60">{{ $dba->license_type }}</span>
|
||||
@endif
|
||||
@if($dba->license_expiration)
|
||||
<br><span class="text-xs {{ $dba->license_expiration->isPast() ? 'text-error' : ($dba->license_expiration->diffInDays(now()) <= 30 ? 'text-warning' : 'text-base-content/60') }}">
|
||||
Exp: {{ $dba->license_expiration->format('M j, Y') }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<span class="text-base-content/40">Not set</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Banking -->
|
||||
<div>
|
||||
<div class="flex items-center gap-1 text-base-content/60 mb-1">
|
||||
<span class="icon-[heroicons--banknotes] size-4"></span>
|
||||
Banking
|
||||
</div>
|
||||
@if($dba->bank_name)
|
||||
<div class="text-base-content">
|
||||
{{ $dba->bank_name }}
|
||||
@if($dba->masked_account_number)
|
||||
<br><span class="text-base-content/60 font-mono">{{ $dba->masked_account_number }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<span class="text-base-content/40">Not set</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contacts Row -->
|
||||
@if($dba->primary_contact_name || $dba->ap_contact_name)
|
||||
<div class="flex flex-wrap gap-4 mt-3 pt-3 border-t border-base-200 text-sm">
|
||||
@if($dba->primary_contact_name)
|
||||
<div class="flex items-center gap-2 text-base-content/70">
|
||||
<span class="icon-[heroicons--user] size-4"></span>
|
||||
<span>Primary: {{ $dba->primary_contact_name }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($dba->ap_contact_name)
|
||||
<div class="flex items-center gap-2 text-base-content/70">
|
||||
<span class="icon-[heroicons--currency-dollar] size-4"></span>
|
||||
<span>AP: {{ $dba->ap_contact_name }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-2">
|
||||
@if(!$dba->is_default && $dba->is_active)
|
||||
<form method="POST" action="{{ route('seller.business.settings.dbas.set-default', [$business->slug, $dba->id]) }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm btn-ghost gap-2 tooltip" data-tip="Set as Default">
|
||||
<span class="icon-[heroicons--star] size-4"></span>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
<a href="{{ route('seller.business.settings.dbas.edit', [$business->slug, $dba->id]) }}"
|
||||
class="btn btn-sm btn-ghost gap-2">
|
||||
<span class="icon-[heroicons--pencil] size-4"></span>
|
||||
Edit
|
||||
</a>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-sm btn-ghost">
|
||||
<span class="icon-[heroicons--ellipsis-vertical] size-4"></span>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow border border-base-300">
|
||||
<li>
|
||||
<form method="POST" action="{{ route('seller.business.settings.dbas.toggle-active', [$business->slug, $dba->id]) }}">
|
||||
@csrf
|
||||
<button type="submit" class="w-full flex items-center gap-2">
|
||||
@if($dba->is_active)
|
||||
<span class="icon-[heroicons--pause-circle] size-4"></span>
|
||||
Deactivate
|
||||
@else
|
||||
<span class="icon-[heroicons--play-circle] size-4"></span>
|
||||
Activate
|
||||
@endif
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
@if(!$dba->is_default)
|
||||
<li>
|
||||
<button type="button"
|
||||
onclick="confirmDelete({{ $dba->id }}, '{{ addslashes($dba->trade_name) }}')"
|
||||
class="text-error">
|
||||
<span class="icon-[heroicons--trash] size-4"></span>
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<!-- Empty State -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<div class="text-center py-12 text-base-content/60">
|
||||
<span class="icon-[heroicons--building-office-2] size-16 mx-auto mb-4 opacity-30"></span>
|
||||
<h3 class="font-semibold text-lg mb-2">No Trade Names Yet</h3>
|
||||
<p class="text-sm mb-4">Add a DBA to customize your invoices and operate under different trade names.</p>
|
||||
<a href="{{ route('seller.business.settings.dbas.create', $business->slug) }}" class="btn btn-primary gap-2">
|
||||
<span class="icon-[heroicons--plus] size-4"></span>
|
||||
Add First Trade Name
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<dialog id="delete_modal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg mb-4">Delete Trade Name</h3>
|
||||
<p class="text-base-content/70">Are you sure you want to delete <strong id="delete_dba_name"></strong>?</p>
|
||||
<p class="text-sm text-warning mt-2">This action cannot be undone. Any invoices using this DBA will retain the historical information.</p>
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-ghost">Cancel</button>
|
||||
</form>
|
||||
<form id="delete_form" method="POST">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-error gap-2">
|
||||
<span class="icon-[heroicons--trash] size-4"></span>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
function confirmDelete(dbaId, dbaName) {
|
||||
document.getElementById('delete_dba_name').textContent = dbaName;
|
||||
document.getElementById('delete_form').action = "{{ url('/s/' . $business->slug . '/settings/dbas') }}/" + dbaId;
|
||||
delete_modal.showModal();
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -98,6 +98,22 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Trade Names (DBAs) -->
|
||||
<a href="{{ route('seller.business.settings.dbas.index', $business->slug) }}"
|
||||
class="card bg-base-100 border border-base-300 hover:border-primary hover:shadow-md transition-all">
|
||||
<div class="card-body">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="p-2 bg-primary/10 rounded-lg">
|
||||
<span class="icon-[heroicons--building-office-2] size-6 text-primary"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="card-title text-base">Trade Names (DBAs)</h2>
|
||||
<p class="text-sm text-base-content/60">Manage "Doing Business As" names for invoices and licenses</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{-- ═══════════════════════════════════════════════════════════════
|
||||
BILLING & PAYMENTS
|
||||
═══════════════════════════════════════════════════════════════ --}}
|
||||
|
||||
@@ -1360,6 +1360,18 @@ Route::prefix('s')->name('seller.')->middleware('seller')->group(function () {
|
||||
Route::delete('/{user}', [\App\Http\Controllers\Seller\BrandManagerSettingsController::class, 'destroy'])->name('destroy');
|
||||
});
|
||||
|
||||
// DBA (Doing Business As) Management
|
||||
Route::prefix('dbas')->name('dbas.')->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'index'])->name('index');
|
||||
Route::get('/create', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'create'])->name('create');
|
||||
Route::post('/', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'store'])->name('store');
|
||||
Route::get('/{dba}/edit', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'edit'])->name('edit');
|
||||
Route::put('/{dba}', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'update'])->name('update');
|
||||
Route::delete('/{dba}', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'destroy'])->name('destroy');
|
||||
Route::post('/{dba}/set-default', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'setDefault'])->name('set-default');
|
||||
Route::post('/{dba}/toggle-active', [\App\Http\Controllers\Seller\Settings\DbaController::class, 'toggleActive'])->name('toggle-active');
|
||||
});
|
||||
|
||||
// Category Management (under settings)
|
||||
Route::prefix('categories')->name('categories.')->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Seller\CategoryController::class, 'index'])->name('index');
|
||||
|
||||
Reference in New Issue
Block a user