diff --git a/app/Console/Commands/GenerateBillingCycles.php b/app/Console/Commands/GenerateBillingCycles.php new file mode 100644 index 00000000..01082145 --- /dev/null +++ b/app/Console/Commands/GenerateBillingCycles.php @@ -0,0 +1,51 @@ +argument('teamId'); + $date = $this->argument('date'); + + $monthsWithTransactions = $this->getFirstTransaction($teamId); + return $service->generateBillingCycles($teamId, $date ?? $monthsWithTransactions->date); + } + + private function getFirstTransaction(int $teamId) { + return DB::table('transaction_lines') + ->where([ + "team_id" => $teamId + ]) + ->selectRaw("date_format(transaction_lines.date, '%Y-%m') AS date") + ->groupBy(DB::raw("date_format(transaction_lines.date, '%Y-%m')")) + ->orderBy('date') + ->first(); + } +} diff --git a/app/Domains/AppCore/Policies/FinanceAccountPolicy.php b/app/Domains/AppCore/Policies/FinanceAccountPolicy.php index 6e6b0d2c..60fe7eca 100644 --- a/app/Domains/AppCore/Policies/FinanceAccountPolicy.php +++ b/app/Domains/AppCore/Policies/FinanceAccountPolicy.php @@ -31,6 +31,6 @@ public function create(User $user) public function delete(User $user, Account $account) { - return $user->id == $account->user_id; + return $user->current_team_id == $account->team_id; } } diff --git a/app/Domains/Budget/Services/BudgetRolloverService.php b/app/Domains/Budget/Services/BudgetRolloverService.php index 03d5697a..1a968c65 100644 --- a/app/Domains/Budget/Services/BudgetRolloverService.php +++ b/app/Domains/Budget/Services/BudgetRolloverService.php @@ -9,7 +9,6 @@ use Insane\Journal\Models\Core\Account; use Insane\Journal\Models\Core\Category; use App\Domains\Budget\Models\BudgetMonth; -use App\Domains\Budget\Data\BudgetAssignData; use App\Domains\Budget\Data\BudgetReservedNames; use Insane\Journal\Models\Core\AccountDetailType; diff --git a/app/Domains/Journal/Actions/AccountDelete.php b/app/Domains/Journal/Actions/AccountDelete.php index c6fdda41..825ccc25 100644 --- a/app/Domains/Journal/Actions/AccountDelete.php +++ b/app/Domains/Journal/Actions/AccountDelete.php @@ -4,9 +4,9 @@ use Illuminate\Foundation\Auth\User; use Illuminate\Support\Facades\Gate; -use Illuminate\Validation\ValidationException; -use Insane\Journal\Contracts\AccountDeletes; use Insane\Journal\Models\Core\Account; +use Insane\Journal\Contracts\AccountDeletes; +use Illuminate\Validation\ValidationException; class AccountDelete implements AccountDeletes { @@ -18,7 +18,7 @@ public function delete(User $user, Account $account) public function validate(User $user, mixed $account) { - Gate::forUser($user)->authorize('delete-account', $account); + Gate::forUser($user)->authorize('delete', $account); if (count($account->transactions)) { throw ValidationException::withMessages([ 'account' => __('You may not delete account with transactions.'), diff --git a/app/Domains/Journal/Policies/AccountPolicy.php b/app/Domains/Journal/Policies/AccountPolicy.php index eb689157..6b044d62 100644 --- a/app/Domains/Journal/Policies/AccountPolicy.php +++ b/app/Domains/Journal/Policies/AccountPolicy.php @@ -3,8 +3,8 @@ namespace App\Domains\Journal\Policies; use App\Models\User; -use Illuminate\Auth\Access\HandlesAuthorization; use Insane\Journal\Models\Core\Account; +use Illuminate\Auth\Access\HandlesAuthorization; class AccountPolicy { @@ -32,6 +32,7 @@ public function update(User $user, Account $account) public function delete(User $user, Account $account) { + dd($account); return $user->team_id == $account->team_id; } } diff --git a/app/Domains/Transaction/Models/BillingCycle.php b/app/Domains/Transaction/Models/BillingCycle.php new file mode 100644 index 00000000..953ba40b --- /dev/null +++ b/app/Domains/Transaction/Models/BillingCycle.php @@ -0,0 +1,103 @@ +hasMany(TransactionLine::class, 'account_id') + ->whereBetween("date", [$this->from, $this->until]); + } + + // Transactionable config + public function getTransactionItems() { + return []; + } + + public static function getCategoryName($payable): string { + return "credit_cards"; + } + + public function getTransactionDescription() { + return "Balance de credito"; + } + + public function getTransactionDirection(): string { + return Transaction::DIRECTION_CREDIT; + } + + public function getAccountId() { + return $this->account_id; + } + + public function getCounterAccountId(): int { + return $this->client_account_id; + } + + // payable config + public function getStatusField(): string { + return 'payment_status'; + } + + public static function calculateTotal($payable) { + $payable->total = $payable->transactions()->sum('amount'); + $payable->due = $payable->total - $payable->paid; + } + + public static function checkStatus($payable) { + $debt = $payable->total - $payable->paid; + if ($debt <= 0) { + $status = self::STATUS_PAID; + } elseif ($debt > 0 && $debt < $payable->amount) { + $status = self::STATUS_PARTIALLY_PAID; + } elseif ($debt && $payable->hasLateInstallments()) { + $status = self::STATUS_LATE; + } elseif ($debt && !$payable->cancelled_at) { + $status = self::STATUS_PENDING; + } elseif ($payable->cancelled_at) { + $status = self::STATUS_CANCELLED; + } else { + $status = $payable->status; + } + $payable->status = $status; + } + + public function updateStatus() { + self::checkPayments($this); + $this->save(); + self::calculateTotal($this); + self::checkStatus($this); + } + + public function getConceptLine(): string { + return ""; + } +} diff --git a/app/Domains/Transaction/Services/CreditCardReportService.php b/app/Domains/Transaction/Services/CreditCardReportService.php index cdde5be0..70c3174f 100644 --- a/app/Domains/Transaction/Services/CreditCardReportService.php +++ b/app/Domains/Transaction/Services/CreditCardReportService.php @@ -2,21 +2,37 @@ namespace App\Domains\Transaction\Services; +use Exception; use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use App\Domains\AppCore\Models\Category; +use App\Domains\Budget\Data\BudgetReservedNames; +use App\Domains\Transaction\Models\BillingCycle; class CreditCardReportService { - public function creditCardCycleByAccount($teamId, $date, $creditCardId = null) { - $endCycleDate = Carbon::createFromFormat('Y-m-d', $date)->startOfMonth()->subMonth(1)->format('Y-m-d'); - $startCycleDate = Carbon::createFromFormat('Y-m-d', $date)->startOfMonth()->subMonth(2)->format('Y-m-d'); + public function creditCardCycleByAccount($teamId, $date, $monthsBack = 1, $creditCardId = null, $asToday = null) { + $endCycleDate = Carbon::createFromFormat('Y-m-d', $date)->startOfMonth()->subMonth($monthsBack)->format('Y-m-d'); + $startCycleDate = Carbon::createFromFormat('Y-m-d', $date)->startOfMonth()->subMonth($monthsBack+1)->format('Y-m-d'); + + $readyToAssign = Category::where([ + 'team_id' => $teamId, + ]) + ->where('name', BudgetReservedNames::READY_TO_ASSIGN->value) + ->first(); return DB::table(DB::raw('accounts a')) ->where("a.team_id", $teamId) ->whereNotNull('a.credit_closing_day') + ->when($asToday, fn ($q) => $q->whereRaw("now() >= DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0')))", [ + $endCycleDate + ])) ->selectRaw(" + ABS(COALESCE(SUM(CASE WHEN tl.type = -1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS subtotal, ABS(COALESCE(SUM(tl.amount * tl.type), 0)) AS total, + ABS(COALESCE(SUM(CASE WHEN tl.type = 1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS discount, a.name, + a.id, a.credit_limit, DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) AS `from`, DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) AS `until`", [ @@ -24,28 +40,171 @@ public function creditCardCycleByAccount($teamId, $date, $creditCardId = null) { $endCycleDate ]) - ->leftJoin(DB::raw('transaction_lines tl'), fn ($q) => $q->on('a.id', 'tl.account_id')->where('tl.type', -1)) + ->leftJoin(DB::raw('transaction_lines tl'), fn ($q) => $q->on('a.id', 'tl.account_id') + ->whereRaw('(tl.type = ? or tl.category_id = ?)', [-1, $readyToAssign->id]) + ) // ->where('account_id', $accountId) - ->whereRaw("tl.date >= DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) - AND tl.date < DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0')))", [ + ->whereRaw("(tl.date >= DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) and tl.type = -1 + AND + tl.date < DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) + ) + OR tl.date = DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) AND tl.type = 1", [ $startCycleDate, + $endCycleDate, $endCycleDate ]) ->groupBy('a.id') ->get(); } + public function getTopCategoriesByAccount($teamId, $startDate = 1, $endDate, $creditCardId = null) { + return DB::table(DB::raw('accounts a')) + ->where("a.team_id", $teamId) + ->whereNotNull('a.credit_closing_day') + ->selectRaw(" + ABS(COALESCE(SUM(CASE WHEN tl.type = -1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS subtotal, + ABS(COALESCE(SUM(tl.amount * tl.type), 0)) AS total, + ABS(COALESCE(SUM(CASE WHEN tl.type = 1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS discount, + a.name, + a.id, + c.name as cat_name + ") + ->leftJoin(DB::raw('transaction_lines tl'), fn ($q) => $q->on('a.id', 'tl.account_id') + ->whereNotNull('tl.category_id') + ) + ->join(DB::raw('categories c'),'tl.category_id', 'c.id') + ->whereBetween("tl.date", [$startDate, $endDate]) + ->groupBy(DB::raw('a.id, c.name')) + ->orderBy('total', 'desc') + ->get(); + } + + public function getTopCategoriesByCreditCard($teamId, $startDate = 1, $endDate, $creditCardId = null) { + $readyToAssign = Category::where([ + 'team_id' => $teamId, + ]) + ->where('name', BudgetReservedNames::READY_TO_ASSIGN->value) + ->first(); + + return DB::select(" + WITH TopCategories as ( + SELECT + ABS(COALESCE(SUM(CASE WHEN tl.type = -1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS subtotal, + ABS(COALESCE(SUM(tl.amount * tl.type), 0)) AS total, + ABS(COALESCE(SUM(CASE WHEN tl.type = 1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS discount, + a.name, + a.id, + c.name as cat_name, + ROW_NUMBER() OVER (PARTITION BY tl.account_id ORDER BY SUM(tl.amount * tl.type)) AS rank + FROM transaction_lines tl + INNER JOIN transactions t on tl.transaction_id = t.id AND t.status = 'verified' AND tl.category_id <> :readyToAssign + INNER JOIN accounts a on tl.account_id = a.id AND a.credit_closing_day IS NOT NULL + INNER JOIN categories c on c.id = tl.category_id + WHERE tl.date >= :startDate AND tl.date <= :endDate + AND tl.team_id = :teamId + GROUP BY tl.account_id, tl.category_id + ORDER BY a.id, ABS(COALESCE(SUM(tl.amount * tl.type), 0)) + ) + SELECT + tc.name, + tc.cat_name, + tc.total + FROM TopCategories tc + WHERE tc.rank <= 4 + GROUP BY tc.id, tc.cat_name + ORDER BY tc.id, tc.rank", [ + 'teamId' => $teamId, + 'startDate' => $startDate, + 'endDate' => $endDate, + 'readyToAssign' => $readyToAssign->id + ]); + } + + public function getTopPayeesByAccount($teamId, $date, $monthsBack = 1, $creditCardId = null, $asToday = null) { + $endCycleDate = Carbon::createFromFormat('Y-m-d', $date)->startOfMonth()->subMonth($monthsBack)->format('Y-m-d'); + $startCycleDate = Carbon::createFromFormat('Y-m-d', $date)->startOfMonth()->subMonth($monthsBack+1)->format('Y-m-d'); + + $readyToAssign = Category::where([ + 'team_id' => $teamId, + ]) + ->where('name', BudgetReservedNames::READY_TO_ASSIGN->value) + ->first(); + + return DB::table(DB::raw('accounts a')) + ->where("a.team_id", $teamId) + ->whereNotNull('a.credit_closing_day') + ->when($asToday, fn ($q) => $q->whereRaw("now() >= DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0')))", [ + $endCycleDate + ])) + ->selectRaw(" + ABS(COALESCE(SUM(CASE WHEN tl.type = -1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS subtotal, + ABS(COALESCE(SUM(tl.amount * tl.type), 0)) AS total, + ABS(COALESCE(SUM(CASE WHEN tl.type = 1 THEN tl.amount * tl.type ELSE 0 END), 0)) AS discount, + a.name, + a.id, + a.credit_limit, + DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) AS `from`, + DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) AS `until`", [ + $startCycleDate, + $endCycleDate + ]) + + ->leftJoin(DB::raw('transaction_lines tl'), fn ($q) => $q->on('a.id', 'tl.account_id') + ->whereRaw('(tl.type = ? or tl.category_id = ?)', [-1, $readyToAssign->id]) + ) + // ->where('account_id', $accountId) + ->whereRaw("(tl.date >= DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) and tl.type = -1 + AND + tl.date < DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) + ) + OR tl.date = DATE_FORMAT(?, CONCAT('%Y-%m-', LPAD(a.credit_closing_day, 2, '0'))) AND tl.type = 1", [ + $startCycleDate, + $endCycleDate, + $endCycleDate + ]) + ->groupBy('a.id') + ->get(); + } + + public function getBillingCyclesByCardInPeriod($teamId, $startDate = 1, $endDate, $creditCardId = null) { + $billingData = DB::table(DB::raw('accounts a')) + ->where("a.team_id", $teamId) + ->whereNotNull('a.credit_closing_day') + ->selectRaw(" + ABS(COALESCE(SUM(bc.subtotal), 0)) AS subtotal, + ABS(COALESCE(SUM(bc.total), 0)) AS total, + ABS(COALESCE(SUM(bc.discounts), 0)) AS discounts, + a.name, + a.id + ") + ->leftJoin(DB::raw('billing_cycles bc'), 'a.id', 'bc.account_id') + ->whereBetween("bc.end_at", [$startDate, $endDate]) + ->groupBy('a.id') + ->get(); + + return [ + "data" => $billingData, + "discountTotal" => $billingData->sum('discount'), + "total" => $billingData->sum('total'), + "subtotal" => $billingData->sum('subtotal') + ]; + } + public function creditCards($teamId, $date) { $lastCycleBalances = $this->creditCardCycleByAccount($teamId, $date); $creditTotal = $lastCycleBalances->sum('total'); + $startPeriodDate = Carbon::createFromFormat('Y-m-d', $date)->startOfMonth()->subMonths(12); + + // dd($this->getTopCategoriesByCreditCard($teamId, $startPeriodDate, $date)); + $data = [ 'lastCycleBalances' => $lastCycleBalances, "creditTotal" => $creditTotal, "creditLineUsage" => round($creditTotal / $lastCycleBalances->sum('credit_limit') * 100, 2), - 'topCategoriesByCard' => [], - 'rewardsByCardInPeriod' => [], + 'topCategoriesByCard' => $this->getTopCategoriesByCreditCard($teamId, $startPeriodDate, $date), + 'billingCyclesByCard' => $this->getBillingCyclesByCardInPeriod($teamId, $startPeriodDate, $date), 'topPayeesByCard' => [], 'topTransaction' => [], 'billingCyclePayments' => [], @@ -54,5 +213,42 @@ public function creditCards($teamId, $date) return $data; } + public function generateBillingCycles($teamId, $yearMonth) { + $monthsWithTransactions = DB::table('transaction_lines') + ->selectRaw("date_format(transaction_lines.date, '%Y-%m') AS date") + ->groupBy(DB::raw("date_format(transaction_lines.date, '%Y-%m')")) + ->whereRaw("date_format(transaction_lines.date, '%Y-%m') >= ?", [$yearMonth]) + ->get() + ->pluck('date'); + + $monthsWithTransactions = [ + ...$monthsWithTransactions, + ]; + + $total = count($monthsWithTransactions); + $count = 0; + + foreach ($monthsWithTransactions as $month) { + $lastCycleBalances = $this->creditCardCycleByAccount($teamId, $month."-01", 0, null, true)->sortBy('from'); + $count++; + foreach ($lastCycleBalances as $creditCardAccount) { + BillingCycle::updateOrCreate([ + "account_id" => $creditCardAccount->id, + "team_id" => $teamId, + "user_id" => 0, + "start_at" => $creditCardAccount->from, + "end_at" => $creditCardAccount->until, + ], [ + "minimum_payment" => $creditCardAccount->total, + "subtotal" => $creditCardAccount->subtotal, + "discounts" =>$creditCardAccount->discount, + "total" => $creditCardAccount->total, + "due_at" => $creditCardAccount->until + ]); + } + echo "updated month {$month}".PHP_EOL; + echo "{$count} of {$total}".PHP_EOL.PHP_EOL; + } + } } diff --git a/database/migrations/2024_06_23_183947_credit_card.php b/database/migrations/2024_06_23_183947_credit_card.php new file mode 100644 index 00000000..6bdad430 --- /dev/null +++ b/database/migrations/2024_06_23_183947_credit_card.php @@ -0,0 +1,39 @@ +id(); + $table->foreignId('team_id'); + $table->foreignId('user_id'); + $table->foreignId('account_id'); + $table->date('start_at'); + $table->date('end_at'); + $table->date('due_at'); + $table->decimal('minimum_payment', 11, 4)->default(0);; + $table->decimal('due', 11, 4)->default(0);; + $table->decimal('subtotal', 11, 4)->default(0);; + $table->decimal('discount', 11, 4)->default(0);; + $table->decimal('total', 11, 4)->default(0);; + $table->string('status')->default('pending'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/resources/js/Components/ChartTopCreditCard.vue b/resources/js/Components/ChartTopCreditCard.vue new file mode 100644 index 00000000..0baebe0a --- /dev/null +++ b/resources/js/Components/ChartTopCreditCard.vue @@ -0,0 +1,116 @@ + + + + + + diff --git a/resources/js/Components/molecules/WidgetTitleCard.vue b/resources/js/Components/molecules/WidgetTitleCard.vue index d27f5ad2..1f07634a 100644 --- a/resources/js/Components/molecules/WidgetTitleCard.vue +++ b/resources/js/Components/molecules/WidgetTitleCard.vue @@ -4,13 +4,13 @@ import { AtButton } from "atmosphere-ui"; withDefaults(defineProps<{ title: string; - action: { + action?: { label: string, iconClass?: string, }, - hideDivider: boolean; - withPadding: boolean; - border: boolean + hideDivider?: boolean; + withPadding?: boolean; + border?: boolean }>(), { withPadding: true, border: true, diff --git a/resources/js/Components/organisms/DonutChart.vue b/resources/js/Components/organisms/DonutChart.vue index f1714b25..0c16668d 100644 --- a/resources/js/Components/organisms/DonutChart.vue +++ b/resources/js/Components/organisms/DonutChart.vue @@ -1,6 +1,6 @@ + + + + diff --git a/resources/js/utils/index.ts b/resources/js/utils/index.ts index 93632ad4..1640391b 100644 --- a/resources/js/utils/index.ts +++ b/resources/js/utils/index.ts @@ -163,3 +163,17 @@ export const getRangeData = (range: RangeValue[]|null, direction = 'back') => { } return rangeData; } + +export const nameToColor = (name: string) => { + let hash = 0; + for (let i = 0; i < name?.length; i++) { + hash = name.charCodeAt(i) + ((hash << 5) - hash); + } + const color = Math.abs(hash).toString(16).substring(0, 6); + return "#" + "0".repeat(6 - color.length) + color; // pad with zeros if necessary + } + + export const getOrGenerateColor = (index: number, name: string) => { + const colors = ["#00C4B3", "#5B5293", "#FFB4AA"] + return colors[index] ?? nameToColor(name); + } diff --git a/tests/Feature/CreditCard/CreditCardCronsTest.php b/tests/Feature/CreditCard/CreditCardCronsTest.php new file mode 100644 index 00000000..5d2d359d --- /dev/null +++ b/tests/Feature/CreditCard/CreditCardCronsTest.php @@ -0,0 +1,23 @@ +createCreditCard([ + "name" => "Visa Gold", + "category_id" => null, + "account_detail_type_id" => null, + "description" => 0, + "opening_balance" => 0, + "credit_closing_day" => 15, + "credit_limit" => 10000, + ]); + + $this->artisan('background:generate-billing-cycles')->assertSuccessful(); + $this->assertGreaterThan(0, BillingCycle::all()); + } +} diff --git a/tests/Feature/CreditCard/CreditCardTest.php b/tests/Feature/CreditCard/CreditCardTest.php new file mode 100644 index 00000000..188dc7d9 --- /dev/null +++ b/tests/Feature/CreditCard/CreditCardTest.php @@ -0,0 +1,73 @@ +actingAs($this->user); + + $response = $this->get('/loans'); + + $response->assertStatus(200); + } + + public function testItShouldNotCreateLoanWithoutSourceAccountId() { + $this->actingAs($this->user); + + $response = $this->post('/loans', array_merge( + $this->loanData, [ + 'source_account_id' => null + ])); + + $response->assertSessionHasErrors(['source_account_id']); + + } + + public function testItShouldNotCreateLoanWithoutFunds() { + $this->actingAs($this->user); + + $response = $this->withoutExceptionHandling()->post('/loans', $this->loanData); + $response->assertSessionHasErrors(); + } + + public function testItShouldCreateLoan() { + $this->actingAs($this->user); + $this->fundAccount('daily_box', $this->loanData['amount'], $this->lender->team_id); + $response = $this->post('/loans', $this->loanData); + $response->assertStatus(302); + $this->assertCount(1, Loan::all()); + } + + public function testLoanShouldHaveStatusPending() { + $this->actingAs($this->user); + $this->fundAccount('daily_box', $this->loanData['amount'], $this->lender->team_id); + + $response = $this->post('/loans', $this->loanData); + $response->assertStatus(302); + $loan = Loan::query()->first(); + + $this->assertEquals($loan->payment_status, Loan::STATUS_PENDING); + } + + public function testClientShouldHaveStatusActive() { + $this->actingAs($this->user); + $this->fundAccount('daily_box', $this->loanData['amount'], $this->lender->team_id); + + $response = $this->post('/loans', $this->loanData); + $response->assertStatus(302); + $loan = Loan::query()->first(); + + $this->assertEquals($loan->client->status, ClientStatus::Active); + } +} diff --git a/tests/Feature/CreditCard/Helpers/CreditCardBase.php b/tests/Feature/CreditCard/Helpers/CreditCardBase.php new file mode 100644 index 00000000..bf715ad3 --- /dev/null +++ b/tests/Feature/CreditCard/Helpers/CreditCardBase.php @@ -0,0 +1,63 @@ +seed(); + $user = User::factory()->withPersonalTeam()->create(); + $user->current_team_id = $user->fresh()->ownedTeams()->latest('id')->first()->id; + $user->save(); + $this->user = $user; + + $this->creditCardData = self::getData($this->user, []); + } + + public function fundAccount(string $accountDisplayId, int $amount, $teamId) { + Account::findByDisplayId($accountDisplayId, $teamId)->openBalance($amount); + } + + public function createCreditCard(mixed $formData = []) { + $this->actingAs($this->user); + + $this->post('/accounts?json=true', self::getData($this->user, [ + ...$formData, + "" + ])); + + return Account::latest()->first(); + } + + public static function getData(User $user, $formData = []) { + return [ + ...$formData, + 'user_id' => $user->id, + 'team_id' => $user->current_team_id, + 'display_id' => $formData['display_id'] ?? null, + "account_detail_type_id" => AccountDetailType::where([ + 'name' => AccountDetailType::CREDIT_CARD + ])->select('id')->get()->id, + 'name' => $formData['name'] ?? null, + 'description' => $formData['description'] ?? "", + 'currency_code' => $formData['currency_code'] ?? "DOP", + ]; + } +}