Skip to content

Commit

Permalink
Merge pull request #87 from andrewdwallo/development-3.x
Browse files Browse the repository at this point in the history
Development 3.x
  • Loading branch information
andrewdwallo authored Dec 15, 2024
2 parents 54be059 + 6cec455 commit 755a322
Show file tree
Hide file tree
Showing 41 changed files with 1,330 additions and 1,000 deletions.
119 changes: 119 additions & 0 deletions app/Concerns/ManagesLineItems.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace App\Concerns;

use App\Enums\Accounting\AdjustmentComputation;
use App\Enums\Accounting\DocumentDiscountMethod;
use App\Models\Accounting\Bill;
use App\Models\Accounting\DocumentLineItem;
use App\Utilities\Currency\CurrencyConverter;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

trait ManagesLineItems
{
protected function handleLineItems(Model $record, Collection $lineItems): void
{
foreach ($lineItems as $itemData) {
$lineItem = isset($itemData['id'])
? $record->lineItems->find($itemData['id'])
: $record->lineItems()->make();

$lineItem->fill([
'offering_id' => $itemData['offering_id'],
'description' => $itemData['description'],
'quantity' => $itemData['quantity'],
'unit_price' => $itemData['unit_price'],
]);

if (! $lineItem->exists) {
$lineItem->documentable()->associate($record);
}

$lineItem->save();

$this->handleLineItemAdjustments($lineItem, $itemData, $record->discount_method);
$this->updateLineItemTotals($lineItem, $record->discount_method);
}
}

protected function deleteRemovedLineItems(Model $record, Collection $lineItems): void
{
$existingLineItemIds = $record->lineItems->pluck('id');
$updatedLineItemIds = $lineItems->pluck('id')->filter();
$lineItemsToDelete = $existingLineItemIds->diff($updatedLineItemIds);

if ($lineItemsToDelete->isNotEmpty()) {
$record
->lineItems()
->whereIn('id', $lineItemsToDelete)
->each(fn (DocumentLineItem $lineItem) => $lineItem->delete());
}
}

protected function handleLineItemAdjustments(DocumentLineItem $lineItem, array $itemData, DocumentDiscountMethod $discountMethod): void
{
$isBill = $lineItem->documentable instanceof Bill;

$taxType = $isBill ? 'purchaseTaxes' : 'salesTaxes';
$discountType = $isBill ? 'purchaseDiscounts' : 'salesDiscounts';

$adjustmentIds = collect($itemData[$taxType] ?? [])
->merge($discountMethod->isPerLineItem() ? ($itemData[$discountType] ?? []) : [])
->filter()
->unique();

$lineItem->adjustments()->sync($adjustmentIds);
$lineItem->refresh();
}

protected function updateLineItemTotals(DocumentLineItem $lineItem, DocumentDiscountMethod $discountMethod): void
{
$lineItem->updateQuietly([
'tax_total' => $lineItem->calculateTaxTotal()->getAmount(),
'discount_total' => $discountMethod->isPerLineItem()
? $lineItem->calculateDiscountTotal()->getAmount()
: 0,
]);
}

protected function updateDocumentTotals(Model $record, array $data): array
{
$subtotalCents = $record->lineItems()->sum('subtotal');
$taxTotalCents = $record->lineItems()->sum('tax_total');
$discountTotalCents = $this->calculateDiscountTotal(
DocumentDiscountMethod::parse($data['discount_method']),
AdjustmentComputation::parse($data['discount_computation']),
$data['discount_rate'] ?? null,
$subtotalCents,
$record
);

$grandTotalCents = $subtotalCents + $taxTotalCents - $discountTotalCents;

return [
'subtotal' => CurrencyConverter::convertCentsToFormatSimple($subtotalCents),
'tax_total' => CurrencyConverter::convertCentsToFormatSimple($taxTotalCents),
'discount_total' => CurrencyConverter::convertCentsToFormatSimple($discountTotalCents),
'total' => CurrencyConverter::convertCentsToFormatSimple($grandTotalCents),
];
}

protected function calculateDiscountTotal(
DocumentDiscountMethod $discountMethod,
?AdjustmentComputation $discountComputation,
?string $discountRate,
int $subtotalCents,
Model $record
): int {
if ($discountMethod->isPerLineItem()) {
return $record->lineItems()->sum('discount_total');
}

if ($discountComputation?->isPercentage()) {
return (int) ($subtotalCents * ((float) $discountRate / 100));
}

return CurrencyConverter::convertToCents($discountRate);
}
}
13 changes: 13 additions & 0 deletions app/Enums/Accounting/AdjustmentComputation.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@

namespace App\Enums\Accounting;

use App\Enums\Concerns\ParsesEnum;
use Filament\Support\Contracts\HasLabel;

enum AdjustmentComputation: string implements HasLabel
{
use ParsesEnum;

case Percentage = 'percentage';
case Fixed = 'fixed';

public function getLabel(): ?string
{
return translate($this->name);
}

public function isPercentage(): bool
{
return $this == self::Percentage;
}

public function isFixed(): bool
{
return $this == self::Fixed;
}
}
32 changes: 32 additions & 0 deletions app/Enums/Accounting/DocumentDiscountMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Enums\Accounting;

use App\Enums\Concerns\ParsesEnum;
use Filament\Support\Contracts\HasLabel;

enum DocumentDiscountMethod: string implements HasLabel
{
use ParsesEnum;

case PerLineItem = 'per_line_item';
case PerDocument = 'per_document';

public function getLabel(): string
{
return match ($this) {
self::PerLineItem => 'Per Line Item',
self::PerDocument => 'Per Document',
};
}

public function isPerLineItem(): bool
{
return $this == self::PerLineItem;
}

public function isPerDocument(): bool
{
return $this == self::PerDocument;
}
}
73 changes: 0 additions & 73 deletions app/Filament/Company/Clusters/Settings/Pages/CompanyDefault.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
use App\Filament\Company\Clusters\Settings;
use App\Models\Banking\BankAccount;
use App\Models\Setting\CompanyDefault as CompanyDefaultModel;
use App\Models\Setting\Discount;
use App\Models\Setting\Tax;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\Component;
Expand Down Expand Up @@ -108,7 +106,6 @@ public function form(Form $form): Form
return $form
->schema([
$this->getGeneralSection(),
// $this->getModifiersSection(),
])
->model($this->record)
->statePath('data')
Expand Down Expand Up @@ -140,76 +137,6 @@ protected function getGeneralSection(): Component
])->columns();
}

protected function getModifiersSection(): Component
{
return Section::make('Taxes & Discounts')
->schema([
Select::make('sales_tax_id')
->softRequired()
->localizeLabel()
->relationship('salesTax', 'name')
->getOptionLabelFromRecordUsing(function (Tax $record) {
$currencyCode = $this->record->currency_code;

$rate = rateFormat($record->rate, $record->computation->value, $currencyCode);

$rateBadge = $this->renderBadgeOptionLabel($rate);

return "{$record->name}{$rateBadge}";
})
->allowHtml()
->saveRelationshipsUsing(null)
->searchable(),
Select::make('purchase_tax_id')
->softRequired()
->localizeLabel()
->relationship('purchaseTax', 'name')
->getOptionLabelFromRecordUsing(function (Tax $record) {
$currencyCode = $this->record->currency_code;

$rate = rateFormat($record->rate, $record->computation->value, $currencyCode);

$rateBadge = $this->renderBadgeOptionLabel($rate);

return "{$record->name}{$rateBadge}";
})
->allowHtml()
->saveRelationshipsUsing(null)
->searchable(),
Select::make('sales_discount_id')
->softRequired()
->localizeLabel()
->relationship('salesDiscount', 'name')
->getOptionLabelFromRecordUsing(function (Discount $record) {
$currencyCode = $this->record->currency_code;

$rate = rateFormat($record->rate, $record->computation->value, $currencyCode);

$rateBadge = $this->renderBadgeOptionLabel($rate);

return "{$record->name}{$rateBadge}";
})
->saveRelationshipsUsing(null)
->allowHtml()
->searchable(),
Select::make('purchase_discount_id')
->softRequired()
->localizeLabel()
->relationship('purchaseDiscount', 'name')
->getOptionLabelFromRecordUsing(function (Discount $record) {
$currencyCode = $this->record->currency_code;
$rate = rateFormat($record->rate, $record->computation->value, $currencyCode);

$rateBadge = $this->renderBadgeOptionLabel($rate);

return "{$record->name}{$rateBadge}";
})
->allowHtml()
->saveRelationshipsUsing(null)
->searchable(),
])->columns();
}

public function renderBadgeOptionLabel(string $label): string
{
return Blade::render('filament::components.badge', [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static function form(Form $form): Form
ToggleButton::make('recoverable')
->label('Recoverable')
->default(false)
->visible(fn (Forms\Get $get) => AdjustmentCategory::parse($get('category')) === AdjustmentCategory::Tax && AdjustmentType::parse($get('type')) === AdjustmentType::Purchase),
->visible(fn (Forms\Get $get) => AdjustmentCategory::parse($get('category'))->isTax() && AdjustmentType::parse($get('type'))->isPurchase()),
])
->columns()
->visibleOn('create'),
Expand All @@ -80,7 +80,7 @@ public static function form(Form $form): Form
Forms\Components\DateTimePicker::make('end_date'),
])
->columns()
->visible(fn (Forms\Get $get) => AdjustmentCategory::parse($get('category')) === AdjustmentCategory::Discount),
->visible(fn (Forms\Get $get) => AdjustmentCategory::parse($get('category'))->isDiscount()),
]);
}

Expand Down
Loading

0 comments on commit 755a322

Please sign in to comment.