Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

RecurringInvoiceFactory.php 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <?php
  2. namespace Database\Factories\Accounting;
  3. use App\Enums\Accounting\DayOfMonth;
  4. use App\Enums\Accounting\DayOfWeek;
  5. use App\Enums\Accounting\EndType;
  6. use App\Enums\Accounting\Frequency;
  7. use App\Enums\Accounting\IntervalType;
  8. use App\Enums\Accounting\Month;
  9. use App\Enums\Accounting\RecurringInvoiceStatus;
  10. use App\Enums\Setting\PaymentTerms;
  11. use App\Models\Accounting\DocumentLineItem;
  12. use App\Models\Accounting\RecurringInvoice;
  13. use App\Models\Common\Client;
  14. use Illuminate\Database\Eloquent\Factories\Factory;
  15. use Illuminate\Support\Carbon;
  16. /**
  17. * @extends Factory<RecurringInvoice>
  18. */
  19. class RecurringInvoiceFactory extends Factory
  20. {
  21. /**
  22. * The name of the factory's corresponding model.
  23. */
  24. protected $model = RecurringInvoice::class;
  25. /**
  26. * Define the model's default state.
  27. *
  28. * @return array<string, mixed>
  29. */
  30. public function definition(): array
  31. {
  32. return [
  33. 'company_id' => 1,
  34. 'client_id' => Client::inRandomOrder()->value('id'),
  35. 'header' => 'Invoice',
  36. 'subheader' => 'Invoice',
  37. 'order_number' => $this->faker->unique()->numerify('ORD-#####'),
  38. 'payment_terms' => PaymentTerms::Net30,
  39. 'status' => RecurringInvoiceStatus::Draft,
  40. 'currency_code' => 'USD',
  41. 'terms' => $this->faker->sentence,
  42. 'footer' => $this->faker->sentence,
  43. 'created_by' => 1,
  44. 'updated_by' => 1,
  45. ];
  46. }
  47. public function withLineItems(int $count = 3): static
  48. {
  49. return $this->afterCreating(function (RecurringInvoice $recurringInvoice) use ($count) {
  50. DocumentLineItem::factory()
  51. ->count($count)
  52. ->forInvoice($recurringInvoice)
  53. ->create();
  54. $this->recalculateTotals($recurringInvoice);
  55. });
  56. }
  57. public function withSchedule(
  58. ?Frequency $frequency = null,
  59. ?Carbon $startDate = null,
  60. ?EndType $endType = null
  61. ): static {
  62. $frequency ??= $this->faker->randomElement(Frequency::class);
  63. $endType ??= EndType::Never;
  64. // Adjust the start date range based on frequency
  65. $startDate = match ($frequency) {
  66. Frequency::Daily => Carbon::parse($this->faker->dateTimeBetween('-30 days')), // At most 30 days back
  67. default => $startDate ?? Carbon::parse($this->faker->dateTimeBetween('-1 year')),
  68. };
  69. return match ($frequency) {
  70. Frequency::Daily => $this->withDailySchedule($startDate, $endType),
  71. Frequency::Weekly => $this->withWeeklySchedule($startDate, $endType),
  72. Frequency::Monthly => $this->withMonthlySchedule($startDate, $endType),
  73. Frequency::Yearly => $this->withYearlySchedule($startDate, $endType),
  74. Frequency::Custom => $this->withCustomSchedule($startDate, $endType),
  75. };
  76. }
  77. protected function withDailySchedule(Carbon $startDate, EndType $endType): static
  78. {
  79. return $this->state([
  80. 'frequency' => Frequency::Daily,
  81. 'start_date' => $startDate,
  82. 'end_type' => $endType,
  83. ]);
  84. }
  85. protected function withWeeklySchedule(Carbon $startDate, EndType $endType): static
  86. {
  87. return $this->state([
  88. 'frequency' => Frequency::Weekly,
  89. 'day_of_week' => DayOfWeek::from($startDate->dayOfWeek),
  90. 'start_date' => $startDate,
  91. 'end_type' => $endType,
  92. ]);
  93. }
  94. protected function withMonthlySchedule(Carbon $startDate, EndType $endType): static
  95. {
  96. return $this->state([
  97. 'frequency' => Frequency::Monthly,
  98. 'day_of_month' => DayOfMonth::from($startDate->day),
  99. 'start_date' => $startDate,
  100. 'end_type' => $endType,
  101. ]);
  102. }
  103. protected function withYearlySchedule(Carbon $startDate, EndType $endType): static
  104. {
  105. return $this->state([
  106. 'frequency' => Frequency::Yearly,
  107. 'month' => Month::from($startDate->month),
  108. 'day_of_month' => DayOfMonth::from($startDate->day),
  109. 'start_date' => $startDate,
  110. 'end_type' => $endType,
  111. ]);
  112. }
  113. protected function withCustomSchedule(
  114. Carbon $startDate,
  115. EndType $endType,
  116. ?IntervalType $intervalType = null,
  117. ?int $intervalValue = null
  118. ): static {
  119. $intervalType ??= $this->faker->randomElement(IntervalType::class);
  120. $intervalValue ??= match ($intervalType) {
  121. IntervalType::Day => $this->faker->numberBetween(1, 7),
  122. IntervalType::Week => $this->faker->numberBetween(1, 4),
  123. IntervalType::Month => $this->faker->numberBetween(1, 3),
  124. IntervalType::Year => 1,
  125. };
  126. $state = [
  127. 'frequency' => Frequency::Custom,
  128. 'interval_type' => $intervalType,
  129. 'interval_value' => $intervalValue,
  130. 'start_date' => $startDate,
  131. 'end_type' => $endType,
  132. ];
  133. // Add interval-specific attributes
  134. switch ($intervalType) {
  135. case IntervalType::Day:
  136. // No additional attributes needed
  137. break;
  138. case IntervalType::Week:
  139. $state['day_of_week'] = DayOfWeek::from($startDate->dayOfWeek);
  140. break;
  141. case IntervalType::Month:
  142. $state['day_of_month'] = DayOfMonth::from($startDate->day);
  143. break;
  144. case IntervalType::Year:
  145. $state['month'] = Month::from($startDate->month);
  146. $state['day_of_month'] = DayOfMonth::from($startDate->day);
  147. break;
  148. }
  149. return $this->state($state);
  150. }
  151. public function endAfter(int $occurrences = 12): static
  152. {
  153. return $this->state([
  154. 'end_type' => EndType::After,
  155. 'max_occurrences' => $occurrences,
  156. ]);
  157. }
  158. public function endOn(?Carbon $endDate = null): static
  159. {
  160. $endDate ??= now()->addMonths($this->faker->numberBetween(1, 12));
  161. return $this->state([
  162. 'end_type' => EndType::On,
  163. 'end_date' => $endDate,
  164. ]);
  165. }
  166. public function autoSend(string $sendTime = '09:00'): static
  167. {
  168. return $this->state([
  169. 'auto_send' => true,
  170. 'send_time' => $sendTime,
  171. ]);
  172. }
  173. public function approved(): static
  174. {
  175. return $this->afterCreating(function (RecurringInvoice $recurringInvoice) {
  176. $this->ensureLineItems($recurringInvoice);
  177. if (! $recurringInvoice->hasSchedule()) {
  178. $this->withSchedule()->callAfterCreating(collect([$recurringInvoice]));
  179. $recurringInvoice->refresh();
  180. }
  181. if (! $recurringInvoice->canBeApproved()) {
  182. return;
  183. }
  184. $approvedAt = $recurringInvoice->start_date
  185. ? $recurringInvoice->start_date->copy()->subDays($this->faker->numberBetween(1, 7))
  186. : now()->subDays($this->faker->numberBetween(1, 30));
  187. $recurringInvoice->approveDraft($approvedAt);
  188. });
  189. }
  190. public function active(): static
  191. {
  192. return $this->withLineItems()
  193. ->withSchedule()
  194. ->approved();
  195. }
  196. public function ended(): static
  197. {
  198. return $this->afterCreating(function (RecurringInvoice $recurringInvoice) {
  199. if (! $recurringInvoice->canBeEnded()) {
  200. $this->active()->callAfterCreating(collect([$recurringInvoice]));
  201. }
  202. $endedAt = now()->subDays($this->faker->numberBetween(1, 30));
  203. $recurringInvoice->update([
  204. 'ended_at' => $endedAt,
  205. 'status' => RecurringInvoiceStatus::Ended,
  206. ]);
  207. });
  208. }
  209. public function configure(): static
  210. {
  211. return $this->afterCreating(function (RecurringInvoice $recurringInvoice) {
  212. $this->ensureLineItems($recurringInvoice);
  213. $nextDate = $recurringInvoice->calculateNextDate();
  214. if ($nextDate) {
  215. $recurringInvoice->updateQuietly([
  216. 'next_date' => $nextDate,
  217. ]);
  218. }
  219. });
  220. }
  221. protected function ensureLineItems(RecurringInvoice $recurringInvoice): void
  222. {
  223. if (! $recurringInvoice->hasLineItems()) {
  224. $this->withLineItems()->callAfterCreating(collect([$recurringInvoice]));
  225. }
  226. }
  227. protected function recalculateTotals(RecurringInvoice $recurringInvoice): void
  228. {
  229. $recurringInvoice->refresh();
  230. if (! $recurringInvoice->hasLineItems()) {
  231. return;
  232. }
  233. $subtotal = $recurringInvoice->lineItems()->sum('subtotal') / 100;
  234. $taxTotal = $recurringInvoice->lineItems()->sum('tax_total') / 100;
  235. $discountTotal = $recurringInvoice->lineItems()->sum('discount_total') / 100;
  236. $grandTotal = $subtotal + $taxTotal - $discountTotal;
  237. $recurringInvoice->update([
  238. 'subtotal' => $subtotal,
  239. 'tax_total' => $taxTotal,
  240. 'discount_total' => $discountTotal,
  241. 'total' => $grandTotal,
  242. ]);
  243. }
  244. }