feat(sales): add discount support with ledger accounting

- Add discount_type column to transactions table via migration
- Update Sale model to use fillable instead of guarded for better security
- Implement discount account ledger creation when discount is applied
- Fix net amount calculation to include discount in CreateSaleAction
- Remove unused "Exempt" column from sale transaction table
- Make discount_type required when discount is enabled in form
- Update form data mutation to properly handle discount calculations
This commit is contained in:
Jp
2026-02-18 21:40:39 +08:00
parent 5d427cdea4
commit 7899ed75ea
7 changed files with 88 additions and 11 deletions

11
.devdbrc Normal file
View File

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

View File

@@ -24,7 +24,6 @@ class CreateSaleAction
try { try {
DB::beginTransaction(); DB::beginTransaction();
//create transactions for the sale //create transactions for the sale
app(CreateRecordTransactionsAction::class)($record, $transactions); app(CreateRecordTransactionsAction::class)($record, $transactions);

View File

@@ -24,6 +24,10 @@ class CreateTransactionAction extends BaseAction
$this->cashAccountLedger($payload); $this->cashAccountLedger($payload);
if ($payload->transaction->discount !== 0) {
$this->discountAccountLedger($payload);
}
return $next($payload); return $next($payload);
} }
@@ -33,11 +37,13 @@ class CreateTransactionAction extends BaseAction
$isExpense = $payload->transactionable instanceof \App\Models\Expense; $isExpense = $payload->transactionable instanceof \App\Models\Expense;
$type = $isExpense ? 'debit' : 'credit'; $type = $isExpense ? 'debit' : 'credit';
$discount = $payload->transaction->discount ?? 0.00;
if ($branch->isClientVatable) { if ($branch->isClientVatable) {
//create transaction account ledger //create transaction account ledger
$ledgerPayload = new CreateLedgerDTO( $ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id, branch_id: $payload->transactionable->branch_id,
amount: $payload->transaction->net_amount ?? 0.00, amount: $payload->transaction->net_amount + $discount ?? 0.00,
transaction: $payload->transaction, transaction: $payload->transaction,
account: $payload->transaction->account, account: $payload->transaction->account,
type: $type, type: $type,
@@ -144,4 +150,28 @@ class CreateTransactionAction extends BaseAction
$this->ledgerPipe($ledgerPayload); $this->ledgerPipe($ledgerPayload);
} }
} }
public function discountAccountLedger($payload): void
{
$isExpense = $payload->transactionable instanceof \App\Models\Expense;
$type = $isExpense ? 'credit' : 'debit';
$amount = $payload->transaction->discount ?? 0.00;
$clientId = $payload->transactionable->branch->client_id;
$discountAccount = Account::query()
->where('account', 'Sales Discount')
->where('client_id', $clientId)
->first();
if ($discountAccount && $amount > 0) {
$ledgerPayload = new CreateLedgerDTO(
branch_id: $payload->transactionable->branch_id,
amount: $amount,
transaction: $payload->transaction,
account: $discountAccount,
type: $type,
);
$this->ledgerPipe($ledgerPayload);
}
}
} }

View File

@@ -96,7 +96,7 @@ class SaleResource extends Resource
Header::make('Charge Account'), Header::make('Charge Account'),
Header::make('Description'), Header::make('Description'),
Header::make('Gross Amount'), Header::make('Gross Amount'),
Header::make('Exempt'), // Header::make('Exempt'),
Header::make('Vatable Amount'), Header::make('Vatable Amount'),
Header::make('Output Tax'), Header::make('Output Tax'),
Header::make('Withholding Tax'), Header::make('Withholding Tax'),
@@ -110,7 +110,7 @@ class SaleResource extends Resource
Header::make('Charge Account'), Header::make('Charge Account'),
Header::make('Description'), Header::make('Description'),
Header::make('Gross Amount'), Header::make('Gross Amount'),
Header::make('Exempt'), // Header::make('Exempt'),
Header::make('Vatable Amount'), Header::make('Vatable Amount'),
Header::make('Output Tax'), Header::make('Output Tax'),
Header::make('Withholding Tax'), Header::make('Withholding Tax'),
@@ -133,6 +133,7 @@ class SaleResource extends Resource
TextInput::make('exempt') TextInput::make('exempt')
->numeric() ->numeric()
->live() ->live()
->hidden()
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
static::setDefaultFormValues($get, $set, $old, $state); static::setDefaultFormValues($get, $set, $old, $state);
})->default(0), })->default(0),
@@ -155,16 +156,16 @@ class SaleResource extends Resource
->numeric() ->numeric()
->live() ->live()
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
static::setDefaultFormValues($get, $set, $old, $state); static::setDefaultFormValues($get, $set, $old, $state);
})->default(0), })->default(0),
TextInput::make('discount') TextInput::make('discount')
->numeric() ->numeric()
->readOnly() // ->readOnly()
->visible(fn (Get $get) => $get('../../with_discount')) ->visible(fn (Get $get) => $get('../../with_discount'))
->live(), ->live(),
Select::make('discount_type') Select::make('discount_type')
->options(fn (Get $get) => static::getDiscountOptions($get)) ->options(fn (Get $get) => static::getDiscountOptions($get))
->required(fn (Get $get) => $get('../../with_discount'))
->visible(fn (Get $get) => $get('../../with_discount')), ->visible(fn (Get $get) => $get('../../with_discount')),
TextInput::make('net_amount')->numeric()->default(0), TextInput::make('net_amount')->numeric()->default(0),
]; ];
@@ -225,7 +226,7 @@ class SaleResource extends Resource
} }
$set('output_tax', number_format($outputTax, 2, '.', '')); $set('output_tax', number_format($outputTax, 2, '.', ''));
$set('discount', number_format($discount, 2, '.', '')); // $set('discount', number_format($discount, 2, '.', ''));
$set('vatable_amount', number_format($vatableAmount, 2, '.', '')); $set('vatable_amount', number_format($vatableAmount, 2, '.', ''));
$set('net_amount', number_format($netAmount, 2, '.', '')); $set('net_amount', number_format($netAmount, 2, '.', ''));
} }

View File

@@ -76,10 +76,11 @@ class CreateSale extends CreateRecord
$data['vatable_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['vatable_amount'] ?? 0)); $data['vatable_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['vatable_amount'] ?? 0));
$data['output_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['output_tax'] ?? 0)); $data['output_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['output_tax'] ?? 0));
$data['payable_withholding_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['payable_withholding_tax'] ?? 0)); $data['payable_withholding_tax'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['payable_withholding_tax'] ?? 0));
$data['discount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['discount'] ?? 0)); $discount = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['discount'] ?? 0));
$data['net_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['net_amount'] ?? 0)); $data['discount'] = $discount;
$data['net_amount'] = collect($transactions)->sum(fn (array $transaction) => (float) ($transaction['net_amount'] ?? 0)) + $discount;
return Arr::except($data, ['client', 'transactions', 'with_discount']); return Arr::except($data, ['client', 'transactions']);
} }
public function processCreate(array $data, array $transactions): Model public function processCreate(array $data, array $transactions): Model

View File

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

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('transactions', function (Blueprint $table) {
$table->string('discount_type')->after('with_discount')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('transactions', function (Blueprint $table) {
$table->dropColumn('discount_type');
});
}
};