Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

RecurringInvoiceResource.php 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. namespace App\Filament\Company\Resources\Sales;
  3. use App\Enums\Accounting\DocumentDiscountMethod;
  4. use App\Enums\Accounting\DocumentType;
  5. use App\Enums\Accounting\RecurringInvoiceStatus;
  6. use App\Enums\Setting\PaymentTerms;
  7. use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\RecurringInvoicesRelationManager;
  8. use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
  9. use App\Filament\Forms\Components\CreateCurrencySelect;
  10. use App\Filament\Forms\Components\DocumentFooterSection;
  11. use App\Filament\Forms\Components\DocumentHeaderSection;
  12. use App\Filament\Forms\Components\DocumentTotals;
  13. use App\Filament\Tables\Columns;
  14. use App\Models\Accounting\Adjustment;
  15. use App\Models\Accounting\RecurringInvoice;
  16. use App\Models\Common\Client;
  17. use App\Models\Common\Offering;
  18. use App\Utilities\Currency\CurrencyAccessor;
  19. use App\Utilities\Currency\CurrencyConverter;
  20. use App\Utilities\RateCalculator;
  21. use Awcodes\TableRepeater\Components\TableRepeater;
  22. use Awcodes\TableRepeater\Header;
  23. use Filament\Forms;
  24. use Filament\Forms\Form;
  25. use Filament\Resources\Resource;
  26. use Filament\Tables;
  27. use Filament\Tables\Table;
  28. use Illuminate\Support\Facades\Auth;
  29. class RecurringInvoiceResource extends Resource
  30. {
  31. protected static ?string $model = RecurringInvoice::class;
  32. public static function form(Form $form): Form
  33. {
  34. $company = Auth::user()->currentCompany;
  35. $settings = $company->defaultInvoice;
  36. return $form
  37. ->schema([
  38. DocumentHeaderSection::make('Invoice Header')
  39. ->defaultHeader($settings->header)
  40. ->defaultSubheader($settings->subheader),
  41. Forms\Components\Section::make('Invoice Details')
  42. ->schema([
  43. Forms\Components\Split::make([
  44. Forms\Components\Group::make([
  45. Forms\Components\Select::make('client_id')
  46. ->relationship('client', 'name')
  47. ->preload()
  48. ->searchable()
  49. ->required()
  50. ->live()
  51. ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
  52. if (! $state) {
  53. return;
  54. }
  55. $currencyCode = Client::find($state)?->currency_code;
  56. if ($currencyCode) {
  57. $set('currency_code', $currencyCode);
  58. }
  59. }),
  60. CreateCurrencySelect::make('currency_code'),
  61. ]),
  62. Forms\Components\Group::make([
  63. Forms\Components\Placeholder::make('invoice_number')
  64. ->label('Invoice number')
  65. ->content('Auto-generated'),
  66. Forms\Components\TextInput::make('order_number')
  67. ->label('P.O/S.O Number'),
  68. Forms\Components\Placeholder::make('date')
  69. ->label('Invoice date')
  70. ->content('Auto-generated'),
  71. Forms\Components\Select::make('payment_terms')
  72. ->label('Payment due')
  73. ->options(PaymentTerms::class)
  74. ->softRequired()
  75. ->default($settings->payment_terms)
  76. ->live(),
  77. Forms\Components\Select::make('discount_method')
  78. ->label('Discount method')
  79. ->options(DocumentDiscountMethod::class)
  80. ->selectablePlaceholder(false)
  81. ->default(DocumentDiscountMethod::PerLineItem)
  82. ->afterStateUpdated(function ($state, Forms\Set $set) {
  83. $discountMethod = DocumentDiscountMethod::parse($state);
  84. if ($discountMethod->isPerDocument()) {
  85. $set('lineItems.*.salesDiscounts', []);
  86. }
  87. })
  88. ->live(),
  89. ])->grow(true),
  90. ])->from('md'),
  91. TableRepeater::make('lineItems')
  92. ->relationship()
  93. ->saveRelationshipsUsing(null)
  94. ->dehydrated(true)
  95. ->headers(function (Forms\Get $get) use ($settings) {
  96. $hasDiscounts = DocumentDiscountMethod::parse($get('discount_method'))->isPerLineItem();
  97. $headers = [
  98. Header::make($settings->resolveColumnLabel('item_name', 'Items'))
  99. ->width($hasDiscounts ? '15%' : '20%'),
  100. Header::make('Description')
  101. ->width($hasDiscounts ? '25%' : '30%'),
  102. Header::make($settings->resolveColumnLabel('unit_name', 'Quantity'))
  103. ->width('10%'),
  104. Header::make($settings->resolveColumnLabel('price_name', 'Price'))
  105. ->width('10%'),
  106. Header::make('Taxes')
  107. ->width($hasDiscounts ? '15%' : '20%'),
  108. ];
  109. if ($hasDiscounts) {
  110. $headers[] = Header::make('Discounts')->width('15%');
  111. }
  112. $headers[] = Header::make($settings->resolveColumnLabel('amount_name', 'Amount'))
  113. ->width('10%')
  114. ->align('right');
  115. return $headers;
  116. })
  117. ->schema([
  118. Forms\Components\Select::make('offering_id')
  119. ->relationship('sellableOffering', 'name')
  120. ->preload()
  121. ->searchable()
  122. ->required()
  123. ->live()
  124. ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
  125. $offeringId = $state;
  126. $offeringRecord = Offering::with(['salesTaxes', 'salesDiscounts'])->find($offeringId);
  127. if (! $offeringRecord) {
  128. return;
  129. }
  130. $unitPrice = CurrencyConverter::convertToFloat($offeringRecord->price, $get('../../currency_code') ?? CurrencyAccessor::getDefaultCurrency());
  131. $set('description', $offeringRecord->description);
  132. $set('unit_price', $unitPrice);
  133. $set('salesTaxes', $offeringRecord->salesTaxes->pluck('id')->toArray());
  134. $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
  135. if ($discountMethod->isPerLineItem()) {
  136. $set('salesDiscounts', $offeringRecord->salesDiscounts->pluck('id')->toArray());
  137. }
  138. }),
  139. Forms\Components\TextInput::make('description'),
  140. Forms\Components\TextInput::make('quantity')
  141. ->required()
  142. ->numeric()
  143. ->live()
  144. ->maxValue(9999999999.99)
  145. ->default(1),
  146. Forms\Components\TextInput::make('unit_price')
  147. ->hiddenLabel()
  148. ->numeric()
  149. ->live()
  150. ->maxValue(9999999999.99)
  151. ->default(0),
  152. Forms\Components\Select::make('salesTaxes')
  153. ->relationship('salesTaxes', 'name')
  154. ->saveRelationshipsUsing(null)
  155. ->dehydrated(true)
  156. ->preload()
  157. ->multiple()
  158. ->live()
  159. ->searchable(),
  160. Forms\Components\Select::make('salesDiscounts')
  161. ->relationship('salesDiscounts', 'name')
  162. ->saveRelationshipsUsing(null)
  163. ->dehydrated(true)
  164. ->preload()
  165. ->multiple()
  166. ->live()
  167. ->hidden(function (Forms\Get $get) {
  168. $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
  169. return $discountMethod->isPerDocument();
  170. })
  171. ->searchable(),
  172. Forms\Components\Placeholder::make('total')
  173. ->hiddenLabel()
  174. ->extraAttributes(['class' => 'text-left sm:text-right'])
  175. ->content(function (Forms\Get $get) {
  176. $quantity = max((float) ($get('quantity') ?? 0), 0);
  177. $unitPrice = max((float) ($get('unit_price') ?? 0), 0);
  178. $salesTaxes = $get('salesTaxes') ?? [];
  179. $salesDiscounts = $get('salesDiscounts') ?? [];
  180. $currencyCode = $get('../../currency_code') ?? CurrencyAccessor::getDefaultCurrency();
  181. $subtotal = $quantity * $unitPrice;
  182. $subtotalInCents = CurrencyConverter::convertToCents($subtotal, $currencyCode);
  183. $taxAmountInCents = Adjustment::whereIn('id', $salesTaxes)
  184. ->get()
  185. ->sum(function (Adjustment $adjustment) use ($subtotalInCents) {
  186. if ($adjustment->computation->isPercentage()) {
  187. return RateCalculator::calculatePercentage($subtotalInCents, $adjustment->getRawOriginal('rate'));
  188. } else {
  189. return $adjustment->getRawOriginal('rate');
  190. }
  191. });
  192. $discountAmountInCents = Adjustment::whereIn('id', $salesDiscounts)
  193. ->get()
  194. ->sum(function (Adjustment $adjustment) use ($subtotalInCents) {
  195. if ($adjustment->computation->isPercentage()) {
  196. return RateCalculator::calculatePercentage($subtotalInCents, $adjustment->getRawOriginal('rate'));
  197. } else {
  198. return $adjustment->getRawOriginal('rate');
  199. }
  200. });
  201. // Final total
  202. $totalInCents = $subtotalInCents + ($taxAmountInCents - $discountAmountInCents);
  203. return CurrencyConverter::formatCentsToMoney($totalInCents, $currencyCode);
  204. }),
  205. ]),
  206. DocumentTotals::make()
  207. ->type(DocumentType::Invoice),
  208. Forms\Components\Textarea::make('terms')
  209. ->default($settings->terms)
  210. ->columnSpanFull(),
  211. ]),
  212. DocumentFooterSection::make('Invoice Footer')
  213. ->defaultFooter($settings->footer),
  214. ]);
  215. }
  216. public static function table(Table $table): Table
  217. {
  218. return $table
  219. ->defaultSort('next_date')
  220. ->columns([
  221. Columns::id(),
  222. Tables\Columns\TextColumn::make('status')
  223. ->badge()
  224. ->searchable(),
  225. Tables\Columns\TextColumn::make('client.name')
  226. ->sortable()
  227. ->searchable()
  228. ->hiddenOn(RecurringInvoicesRelationManager::class),
  229. Tables\Columns\TextColumn::make('schedule')
  230. ->label('Schedule')
  231. ->getStateUsing(function (RecurringInvoice $record) {
  232. return $record->getScheduleDescription();
  233. })
  234. ->description(function (RecurringInvoice $record) {
  235. return $record->getTimelineDescription();
  236. }),
  237. Tables\Columns\TextColumn::make('created_at')
  238. ->label('Created')
  239. ->date()
  240. ->sortable()
  241. ->showOnTabs(['draft']),
  242. Tables\Columns\TextColumn::make('start_date')
  243. ->label('First invoice')
  244. ->date()
  245. ->sortable()
  246. ->showOnTabs(['draft']),
  247. Tables\Columns\TextColumn::make('last_date')
  248. ->label('Last invoice')
  249. ->date()
  250. ->sortable()
  251. ->hideOnTabs(['draft']),
  252. Tables\Columns\TextColumn::make('next_date')
  253. ->label('Next invoice')
  254. ->date()
  255. ->sortable()
  256. ->hideOnTabs(['draft']),
  257. Tables\Columns\TextColumn::make('total')
  258. ->currencyWithConversion(static fn (RecurringInvoice $record) => $record->currency_code)
  259. ->sortable()
  260. ->toggleable()
  261. ->alignEnd(),
  262. ])
  263. ->filters([
  264. Tables\Filters\SelectFilter::make('client')
  265. ->relationship('client', 'name')
  266. ->searchable()
  267. ->preload()
  268. ->hiddenOn(RecurringInvoicesRelationManager::class),
  269. Tables\Filters\SelectFilter::make('status')
  270. ->options(RecurringInvoiceStatus::class)
  271. ->native(false),
  272. ])
  273. ->actions([
  274. Tables\Actions\ActionGroup::make([
  275. Tables\Actions\ActionGroup::make([
  276. Tables\Actions\EditAction::make(),
  277. Tables\Actions\ViewAction::make(),
  278. RecurringInvoice::getManageScheduleAction(Tables\Actions\Action::class),
  279. ])->dropdown(false),
  280. Tables\Actions\DeleteAction::make(),
  281. ]),
  282. ])
  283. ->bulkActions([
  284. Tables\Actions\BulkActionGroup::make([
  285. Tables\Actions\DeleteBulkAction::make(),
  286. ]),
  287. ]);
  288. }
  289. public static function getRelations(): array
  290. {
  291. return [
  292. //
  293. ];
  294. }
  295. public static function getPages(): array
  296. {
  297. return [
  298. 'index' => Pages\ListRecurringInvoices::route('/'),
  299. 'create' => Pages\CreateRecurringInvoice::route('/create'),
  300. 'view' => Pages\ViewRecurringInvoice::route('/{record}'),
  301. 'edit' => Pages\EditRecurringInvoice::route('/{record}/edit'),
  302. ];
  303. }
  304. }