You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RecurringInvoiceFactory.php 11KB

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