Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

DocumentDefault.php 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <?php
  2. namespace App\Models\Setting;
  3. use App\Casts\TrimLeadingZeroCast;
  4. use App\Concerns\Blamable;
  5. use App\Concerns\CompanyOwned;
  6. use App\Enums\Accounting\DocumentType;
  7. use App\Enums\Setting\Font;
  8. use App\Enums\Setting\PaymentTerms;
  9. use App\Enums\Setting\Template;
  10. use Database\Factories\Setting\DocumentDefaultFactory;
  11. use Illuminate\Database\Eloquent\Builder;
  12. use Illuminate\Database\Eloquent\Casts\AsArrayObject;
  13. use Illuminate\Database\Eloquent\Casts\Attribute;
  14. use Illuminate\Database\Eloquent\Factories\Factory;
  15. use Illuminate\Database\Eloquent\Factories\HasFactory;
  16. use Illuminate\Database\Eloquent\Model;
  17. use Illuminate\Support\Facades\Storage;
  18. class DocumentDefault extends Model
  19. {
  20. use Blamable;
  21. use CompanyOwned;
  22. use HasFactory;
  23. protected $table = 'document_defaults';
  24. protected $fillable = [
  25. 'company_id',
  26. 'type',
  27. 'logo',
  28. 'show_logo',
  29. 'number_prefix',
  30. 'number_digits',
  31. 'number_next',
  32. 'payment_terms',
  33. 'header',
  34. 'subheader',
  35. 'terms',
  36. 'footer',
  37. 'accent_color',
  38. 'font',
  39. 'template',
  40. 'item_name',
  41. 'unit_name',
  42. 'price_name',
  43. 'amount_name',
  44. 'created_by',
  45. 'updated_by',
  46. ];
  47. protected $casts = [
  48. 'type' => DocumentType::class,
  49. 'show_logo' => 'boolean',
  50. 'number_next' => TrimLeadingZeroCast::class,
  51. 'payment_terms' => PaymentTerms::class,
  52. 'font' => Font::class,
  53. 'template' => Template::class,
  54. 'item_name' => AsArrayObject::class,
  55. 'unit_name' => AsArrayObject::class,
  56. 'price_name' => AsArrayObject::class,
  57. 'amount_name' => AsArrayObject::class,
  58. ];
  59. protected $appends = [
  60. 'logo_url',
  61. ];
  62. protected function logoUrl(): Attribute
  63. {
  64. return Attribute::get(static function (mixed $value, array $attributes): ?string {
  65. return $attributes['logo'] ? Storage::disk('public')->url($attributes['logo']) : null;
  66. });
  67. }
  68. public function scopeType(Builder $query, string | DocumentType $type): Builder
  69. {
  70. return $query->where($this->qualifyColumn('type'), $type);
  71. }
  72. public function scopeInvoice(Builder $query): Builder
  73. {
  74. return $query->type(DocumentType::Invoice);
  75. }
  76. public function scopeRecurringInvoice(Builder $query): Builder
  77. {
  78. return $query->type(DocumentType::RecurringInvoice);
  79. }
  80. public function scopeBill(Builder $query): Builder
  81. {
  82. return $query->type(DocumentType::Bill);
  83. }
  84. public function scopeEstimate(Builder $query): Builder
  85. {
  86. return $query->type(DocumentType::Estimate);
  87. }
  88. public static function availableNumberDigits(): array
  89. {
  90. return array_combine(range(1, 20), range(1, 20));
  91. }
  92. public function getNumberNext(?bool $padded = null, ?bool $format = null, ?string $prefix = null, int | string | null $digits = null, int | string | null $next = null): string
  93. {
  94. [$number_prefix, $number_digits, $number_next] = $this->initializeAttributes($prefix, $digits, $next);
  95. return match (true) {
  96. $format && $padded => $number_prefix . $this->getPaddedNumberNext($number_next, $number_digits),
  97. $format => $number_prefix . $number_next,
  98. $padded => $this->getPaddedNumberNext($number_next, $number_digits),
  99. default => $number_next,
  100. };
  101. }
  102. public function initializeAttributes(?string $prefix, int | string | null $digits, int | string | null $next): array
  103. {
  104. $number_prefix = $prefix ?? $this->number_prefix;
  105. $number_digits = $digits ?? $this->number_digits;
  106. $number_next = $next ?? $this->number_next;
  107. return [$number_prefix, $number_digits, $number_next];
  108. }
  109. /**
  110. * Get the next number with padding for dynamic display purposes.
  111. * Even if number_next is a string, it will be cast to an integer.
  112. */
  113. public function getPaddedNumberNext(int | string | null $number_next, int | string | null $number_digits): string
  114. {
  115. return str_pad($number_next, $number_digits, '0', STR_PAD_LEFT);
  116. }
  117. public static function getAvailableItemNameOptions(): array
  118. {
  119. $options = [
  120. 'items' => 'Items',
  121. 'products' => 'Products',
  122. 'services' => 'Services',
  123. 'other' => 'Other',
  124. ];
  125. return array_map(translate(...), $options);
  126. }
  127. public static function getAvailableUnitNameOptions(): array
  128. {
  129. $options = [
  130. 'quantity' => 'Quantity',
  131. 'hours' => 'Hours',
  132. 'other' => 'Other',
  133. ];
  134. return array_map(translate(...), $options);
  135. }
  136. public static function getAvailablePriceNameOptions(): array
  137. {
  138. $options = [
  139. 'price' => 'Price',
  140. 'rate' => 'Rate',
  141. 'other' => 'Other',
  142. ];
  143. return array_map(translate(...), $options);
  144. }
  145. public static function getAvailableAmountNameOptions(): array
  146. {
  147. $options = [
  148. 'amount' => 'Amount',
  149. 'total' => 'Total',
  150. 'other' => 'Other',
  151. ];
  152. return array_map(translate(...), $options);
  153. }
  154. public function getLabelOptionFor(string $optionType, ?string $optionValue)
  155. {
  156. $optionValue = $optionValue ?? $this->{$optionType}['option'];
  157. if (! $optionValue) {
  158. return null;
  159. }
  160. $options = match ($optionType) {
  161. 'item_name' => static::getAvailableItemNameOptions(),
  162. 'unit_name' => static::getAvailableUnitNameOptions(),
  163. 'price_name' => static::getAvailablePriceNameOptions(),
  164. 'amount_name' => static::getAvailableAmountNameOptions(),
  165. default => [],
  166. };
  167. return $options[$optionValue] ?? null;
  168. }
  169. public function resolveColumnLabel(string $column, string $default, ?array $data = null): string
  170. {
  171. if ($data) {
  172. $custom = $data[$column]['custom'] ?? null;
  173. $option = $data[$column]['option'] ?? null;
  174. } else {
  175. $custom = $this->{$column}['custom'] ?? null;
  176. $option = $this->{$column}['option'] ?? null;
  177. }
  178. if ($custom) {
  179. return $custom;
  180. }
  181. return $this->getLabelOptionFor($column, $option) ?? $default;
  182. }
  183. protected static function newFactory(): Factory
  184. {
  185. return DocumentDefaultFactory::new();
  186. }
  187. }