From abb7c8900f879263f44e3eea38ece818af13cd8c Mon Sep 17 00:00:00 2001 From: Jesus Guerrero Date: Sat, 20 Jul 2024 20:49:10 -0400 Subject: [PATCH 1/4] fix: Improve duplication transaction finder --- .../Services/LogerAutomationService.php | 5 +- .../Integration/Actions/APAP/APAPAlert.php | 2 +- .../Actions/APAP/APAPNotification.php | 53 +++++++++++++++++++ .../Actions/TransactionCreateEntry.php | 17 ++---- .../Services/TransactionService.php | 39 +++++++++++--- .../Components/molecules/TransactionCard.vue | 6 +++ .../Dashboard/Partials/DashboardDrafts.vue | 1 + .../components/TransactionTable.vue | 2 +- .../components/TransactionsList.vue | 14 ++--- 9 files changed, 108 insertions(+), 31 deletions(-) create mode 100644 app/Domains/Integration/Actions/APAP/APAPNotification.php diff --git a/app/Domains/Automation/Services/LogerAutomationService.php b/app/Domains/Automation/Services/LogerAutomationService.php index d47b31c8..f555259b 100644 --- a/app/Domains/Automation/Services/LogerAutomationService.php +++ b/app/Domains/Automation/Services/LogerAutomationService.php @@ -22,7 +22,7 @@ class LogerAutomationService public static function run(Automation $automation, $eventData = null) { - echo "starting $automation->name with $automation->id \n"; + echo "\n starting workflow:$automation->id:$automation->name"; $tasks = $automation->tasks; $trigger = $automation->triggerTask; @@ -32,12 +32,15 @@ public static function run(Automation $automation, $eventData = null) $previousTask = null; } $entity = $task->entity; + echo "\n starting workflow-task:$task->id:$task->name"; $lastData = $entity::handle($automation, $lastData, $task, $previousTask, $trigger); if (!$lastData) { break; } $previousTask = $task; } + + echo "\n \n"; } public static function setupService($serviceId, $service) diff --git a/app/Domains/Integration/Actions/APAP/APAPAlert.php b/app/Domains/Integration/Actions/APAP/APAPAlert.php index dfe4d35e..08e00f3f 100644 --- a/app/Domains/Integration/Actions/APAP/APAPAlert.php +++ b/app/Domains/Integration/Actions/APAP/APAPAlert.php @@ -42,7 +42,7 @@ public function handle(Automation $automation, mixed $mail, int $index = 0): Tra 'categoryGroup' => '', 'description' => $product, 'amount' => $total * $type, - 'currencyCode' => APAP::parseCurrency($tdValues[11]), + 'currencyCode' => APAP::parseCurrency($tdValues[11]) ?? "DOP", ]); } catch (Exception $e) { Log::error($e->getMessage()); diff --git a/app/Domains/Integration/Actions/APAP/APAPNotification.php b/app/Domains/Integration/Actions/APAP/APAPNotification.php new file mode 100644 index 00000000..63a709ac --- /dev/null +++ b/app/Domains/Integration/Actions/APAP/APAPNotification.php @@ -0,0 +1,53 @@ +', "", $mail['message']); + $html= str_replace('', "", $html); + // dd($html); + $body = new Crawler($html, true, null , true); + try { + if (!$body) return null; + $productLine = $body->filter('table')->first()->text(); + $visaPos = strpos($productLine, 'Visa'); + $product = Str::substr($productLine, $visaPos - 1, strpos($productLine, 'terminada') - $visaPos - 1); + + + $tdValues = $body->filter('table td')->each(function (Crawler $node) { + return $node->text(); + }); + + $total = (int) str_replace(',', '', $tdValues[13]); + $type = 1; + + return new TransactionDataDTO([ + 'id' => (int) $mail['id'], + 'date' => date('Y-m-d', strtotime($mail['date'])), + 'payee' => $tdValues[15], + 'category' => '', + 'categoryGroup' => '', + 'description' => $product, + 'amount' => $total * $type, + 'currencyCode' => APAP::parseCurrency($tdValues[11]) ?? "DOP", + ]); + } catch (Exception $e) { + Log::error($e->getMessage()); + return null; + } + + } +} diff --git a/app/Domains/Integration/Actions/TransactionCreateEntry.php b/app/Domains/Integration/Actions/TransactionCreateEntry.php index 1b51e623..f36f536a 100644 --- a/app/Domains/Integration/Actions/TransactionCreateEntry.php +++ b/app/Domains/Integration/Actions/TransactionCreateEntry.php @@ -10,6 +10,7 @@ use App\Domains\Automation\Models\Automation; use App\Domains\Transaction\Models\TransactionLine; use App\Domains\Automation\Models\AutomationTaskAction; +use App\Domains\Transaction\Services\TransactionService; use App\Domains\Automation\Concerns\AutomationActionContract; class TransactionCreateEntry implements AutomationActionContract @@ -63,20 +64,10 @@ public static function handle( ]), ]; - $transaction = Transaction::where([ - "team_id" => $transactionData['team_id'], - 'date' => $transactionData['date'], - 'total' => $transactionData['total'], - 'currency_code' => $transactionData['currency_code'], - 'direction' => $transactionData['direction'], - 'payee_id' => $transactionData['payee_id'], - ]) - ->where( - fn($q) => $q->where('description', $transactionData['description']) - ->orWhere('reference', $transactionData['description']) - )->first(); + $transactionService = new TransactionService(); - if ($transaction) { + if ($transaction = $transactionService->findIfDuplicated($transactionData)) { + print_r($transaction); return $transaction; } diff --git a/app/Domains/Transaction/Services/TransactionService.php b/app/Domains/Transaction/Services/TransactionService.php index faf55a51..5c099479 100644 --- a/app/Domains/Transaction/Services/TransactionService.php +++ b/app/Domains/Transaction/Services/TransactionService.php @@ -193,8 +193,12 @@ public static function getCategoryExpenseDetails($teamId, $startDate, $endDate, ]; } - public static function getCategoryExpensesGroup($teamId, $startDate, $endDate, $limit = null) + public static function getCategoryExpensesGroup($teamId, $startDate, $endDate, $limit = null, $categories = []) { + $categories = collect($categories); + $excluded = $categories->filter( fn ($id) => $id < 0)->map(fn($item) => abs($item))->all(); + $included = $categories->filter( fn ($id) => $id > 0)->all(); + $totals = DB::table('transaction_lines')->where([ 'transaction_lines.team_id' => $teamId, 'transactions.status' => 'verified', @@ -203,12 +207,13 @@ public static function getCategoryExpensesGroup($teamId, $startDate, $endDate, $ ->whereNot('catGroup.name', BudgetReservedNames::INFLOW->value) ->whereBetween('transactions.date', [$startDate, $endDate]) ->select(DB::raw(" - ABS(sum(transaction_lines.amount * transaction_lines.type)) as total, - catGroup.name, - catGroup.id, - group_concat(concat(transaction_lines.id, '/',accounts.name, '/', transactions.date, '/', payees.name, '/', transaction_lines.concept, '/', amount * transaction_lines.type) SEPARATOR '|') as details - ", - )) + ABS(sum(transaction_lines.amount * transaction_lines.type)) as total, + catGroup.name, + catGroup.id, + group_concat(concat(transaction_lines.id, '/',accounts.name, '/', transactions.date, '/', payees.name, '/', transaction_lines.concept, '/', amount * transaction_lines.type) SEPARATOR '|') as details + ")) + ->when(count($excluded), fn($q) => $q->whereNotIn('transaction_lines.category_id', $excluded)) + ->when(count($included), fn($q) => $q->whereIn('transaction_lines.category_id', $included)) ->join('transactions', 'transactions.id', 'transaction_id') ->join('accounts', 'accounts.id', 'transaction_lines.account_id') ->join('categories', 'categories.id', 'transaction_lines.category_id') @@ -514,5 +519,23 @@ public function getCreditCardSpentTransactions(int $teamId) { 'g.display_id' => 'liabilities', ]) ->get(); - } + } + + public function findIfDuplicated($transactionData) { + print_r($transactionData); + return Transaction::where([ + "team_id" => $transactionData['team_id'], + 'date' => $transactionData['date'], + 'total' => $transactionData['total'], + 'currency_code' => $transactionData['currency_code'], + 'direction' => $transactionData['direction'], + 'payee_id' => $transactionData['payee_id'], + ]) + ->where( + fn($q) => $q->where('description', $transactionData['description']) + ->orWhere('reference', $transactionData['reference']) + ) + ->orWhere("reference", $transactionData['reference']) + ->first(); + } } diff --git a/resources/js/Components/molecules/TransactionCard.vue b/resources/js/Components/molecules/TransactionCard.vue index 4b835405..3b0ee8b8 100644 --- a/resources/js/Components/molecules/TransactionCard.vue +++ b/resources/js/Components/molecules/TransactionCard.vue @@ -21,6 +21,7 @@ const props = defineProps<{ allowEdit: boolean, allowRemove: boolean, allowSelect: boolean, + allowMatch: boolean, isSelected: boolean, }>(); @@ -64,6 +65,11 @@ const options = computed(() => { label: "Remove", hide: !props.allowRemove }, + { + name: "findLinked", + label: "Find Linked", + hide: !props.allowMatch, + }, ]; diff --git a/resources/js/Pages/Dashboard/Partials/DashboardDrafts.vue b/resources/js/Pages/Dashboard/Partials/DashboardDrafts.vue index 7a8f9989..f7e28103 100644 --- a/resources/js/Pages/Dashboard/Partials/DashboardDrafts.vue +++ b/resources/js/Pages/Dashboard/Partials/DashboardDrafts.vue @@ -81,6 +81,7 @@ :transactions="transactionsDraft" :parser="draftsDBToTransaction" :allow-remove="true" + allow-match :allow-mark-as-approved="true" :hide-accounts="true" @approved="handleEdit" diff --git a/resources/js/domains/transactions/components/TransactionTable.vue b/resources/js/domains/transactions/components/TransactionTable.vue index 12cc0690..ca8aeb64 100644 --- a/resources/js/domains/transactions/components/TransactionTable.vue +++ b/resources/js/domains/transactions/components/TransactionTable.vue @@ -50,7 +50,7 @@ const options = (row: Record) => { { name: "findLinked", label: "Find Linked", - hide: row.status != "draft", + hide: row.status !== "draft", }, ]; diff --git a/resources/js/domains/transactions/components/TransactionsList.vue b/resources/js/domains/transactions/components/TransactionsList.vue index 3fb3469e..8d71efdd 100644 --- a/resources/js/domains/transactions/components/TransactionsList.vue +++ b/resources/js/domains/transactions/components/TransactionsList.vue @@ -1,6 +1,5 @@ @@ -96,6 +95,7 @@ const calculateSum = (items: ITransaction[]) => { :mark-as-approved="allowMarkAsApproved" :allow-remove="allowRemove" :allow-select="allowSelect" + :allow-match="allowMatch" :allow-edit="allowEdit" :key="transaction.id" :isSelected="isSelected(transaction)" @@ -109,8 +109,8 @@ const calculateSum = (items: ITransaction[]) => {
Selected Items sum
-
- {{ formatMoney(currencySum, currency) }} +
+ {{ formatMoney(currencySum) }}
From 96d038f6d7871ea509ca027965ea75d03dbac6fc Mon Sep 17 00:00:00 2001 From: Jesus Guerrero Date: Sat, 20 Jul 2024 23:00:46 -0400 Subject: [PATCH 2/4] feat: add payment of billying cycle --- .../Transaction/Models/BillingCycle.php | 113 ++++++++++++++++- .../Services/CreditCardReportService.php | 7 ++ .../Api/BillingCycleApiController.php | 12 ++ .../js/Components/templates/AppGlobals.vue | 2 +- resources/js/Pages/Finance/Account.vue | 119 ++++++++++-------- resources/js/Pages/Trends/Overview.vue | 77 +++++++----- .../components/PaymentFormModal.vue | 10 +- routes/web.php | 1 + 8 files changed, 248 insertions(+), 93 deletions(-) diff --git a/app/Domains/Transaction/Models/BillingCycle.php b/app/Domains/Transaction/Models/BillingCycle.php index 20a848e4..d0033de3 100644 --- a/app/Domains/Transaction/Models/BillingCycle.php +++ b/app/Domains/Transaction/Models/BillingCycle.php @@ -2,6 +2,7 @@ namespace App\Domains\Transaction\Models; +use Exception; use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Model; use Insane\Journal\Models\Core\Payment; @@ -143,9 +144,9 @@ public static function checkPayments($payable) } public function linkPayment(Transaction $transaction, $formData) { - // if ($this->debt <= 0) { - // throw new Exception("The document {$this->concept} is already paid"); - // } + if ($this->debt <= 0) { + throw new Exception("The document {$this->concept} is already paid"); + } $payment = $this->payments()->create([ "amount" => $transaction->total, @@ -171,4 +172,110 @@ public function linkPayment(Transaction $transaction, $formData) { return $payment; } + + public function createPayment($formData) + { + $paid = $this->payments->sum('amount'); + if ($paid >= $this->total) { + throw new Exception("This invoice is already paid"); + } + + $debt = $this->total - $paid; + + $formData['amount'] = $formData['amount'] > $debt ? $debt : $formData['amount']; + $payment = $this->payments()->create([ + ...$formData, + 'user_id' => $formData['user_id'] ?? $this->user_id, + 'team_id' => $formData['team_id'] ?? $this->team_id, + 'client_id' => $formData['client_id'] ?? $this->user_id, + ]); + + $this->save(); + return $payment; + } + + public function createPaymentTransaction(Payment $payment) { + $direction = Transaction::DIRECTION_CREDIT; + $counterAccountId = $this->account_id; + + return [ + "team_id" => $payment->team_id, + "user_id" => $payment->user_id, + "date" => $payment->payment_date, + "description" => $payment->concept, + "direction" => $direction, + "total" => $payment->amount, + "account_id" => $payment->account_id, + "counter_account_id" => $counterAccountId, + "items" => [] + ]; + } + + protected function getBillPaymentItems($payment) + { + $isExpense = $this->isBill(); + $items = []; + + $mainAccount = $isExpense ? $this->invoice_account_id : Account::where([ + "team_id" => $this->team_id, + "display_id" => "products"])->first()->id; + + $lineCount = 0; + $taxAmount = 0; + + foreach ($this->lines as $line) { + // debits + $lineTaxAmount = $line->taxes->sum('amount'); + $items[] = [ + "index" => $lineCount, + "account_id" => $mainAccount, + "category_id" => null, + "type" => 1, + "concept" => $line->concept ?? $this->formData['concept'], + "amount" => ($line->amount ?? $payment->total) - $lineTaxAmount, + "anchor" => false, + ]; + echo $lineTaxAmount . PHP_EOL; + + // taxes and retentions + $lineCount+= 1; + foreach ($line->taxes as $index => $tax) { + $lineCount+=$index; + $items[] = [ + "index" => $lineCount, + "account_id" => $tax->tax->translate_account_id ?? Account::guessAccount($this, [$tax['name'], 'sales_taxes']), + "category_id" => null, + "type" => -1, + "concept" => $tax['name'], + "amount" => $tax['amount'], + "anchor" => false, + ]; + + $taxAmount += $tax['amount']; + + $items[] = [ + "index" => $lineCount + 1, + "account_id" => $tax->tax->account_id ?? Account::guessAccount($this, [$tax['name'], 'sales_taxes']), + "category_id" => null, + "type" => 1, + "concept" => $tax['name'], + "amount" => $tax['amount'], + "anchor" => false, + ]; + } + } + + // credits + $items[] = [ + "index" => count($items), + "account_id" => $payment->account_id, + "category_id" => null, + "type" => -1, + "concept" => $payment->concept, + "amount" => $payment->amount, + "anchor" => true, + ]; + + return $items; + } } diff --git a/app/Domains/Transaction/Services/CreditCardReportService.php b/app/Domains/Transaction/Services/CreditCardReportService.php index 15ceeb64..245384f0 100644 --- a/app/Domains/Transaction/Services/CreditCardReportService.php +++ b/app/Domains/Transaction/Services/CreditCardReportService.php @@ -307,4 +307,11 @@ public function linkCreditCardPayment(BillingCycle $billingCycle, Transaction $t $billingCycle->linkPayment($transaction, $postData); $billingCycle->save(); } + + public function addPayment(BillingCycle $billingCycle, $postData) + { + $payment = $billingCycle->createPayment($postData); + $billingCycle->save(); + return $payment; + } } diff --git a/app/Http/Controllers/Api/BillingCycleApiController.php b/app/Http/Controllers/Api/BillingCycleApiController.php index 147c8cee..f8e8bac7 100644 --- a/app/Http/Controllers/Api/BillingCycleApiController.php +++ b/app/Http/Controllers/Api/BillingCycleApiController.php @@ -29,4 +29,16 @@ public function linkPayments(CreditCardReportService $creditCardReportService, B } } + public function addPayment(CreditCardReportService $creditCardReportService, BillingCycle $billingCycle) { + try { + return $creditCardReportService->addPayment($billingCycle, request()->post()); + } catch (Exception $e) { + return response([ + 'status' => [ + 'message' => $e->getMessage() + ] + ], 400); + } + } + } diff --git a/resources/js/Components/templates/AppGlobals.vue b/resources/js/Components/templates/AppGlobals.vue index a1c3fe60..c0b291d1 100644 --- a/resources/js/Components/templates/AppGlobals.vue +++ b/resources/js/Components/templates/AppGlobals.vue @@ -62,7 +62,7 @@ const { isOpen: isPaymentModalOpen } = usePaymentModal(); v-if="paymentModalState" v-bind="paymentModalState?.data" v-model="isPaymentModalOpen" - @saved="onPaymentSaved" + @saved="onTransactionSaved" /> diff --git a/resources/js/Pages/Finance/Account.vue b/resources/js/Pages/Finance/Account.vue index 3fd0d7e0..c2b6eecb 100644 --- a/resources/js/Pages/Finance/Account.vue +++ b/resources/js/Pages/Finance/Account.vue @@ -27,6 +27,7 @@ import { formatMoney } from "@/utils"; import { IAccount, ICategory, ITransaction } from "@/domains/transactions/models"; import NextPaymentsWidget from "@/domains/transactions/components/NextPaymentsWidget.vue"; import { usePaymentModal } from "@/domains/transactions/usePaymentModal"; +import WidgetContainer from "@/Components/WidgetContainer.vue"; const { openTransactionModal } = useTransactionModal(); const { openModal } = usePaymentModal(); @@ -146,13 +147,13 @@ const payCreditCard = () => { counter_account_id: accountId ?? "", due: debt, description: `Payment of ${selectedAccount.value?.name}`, - account_id: props.accounts.find((account) => account.balance > debt)?.id, + account_id: props.accounts?.find?.((account) => account.balance > debt)?.id, documents: [transaction], resourceId: transaction?.id, title: `Payment of ${transaction?.name}`, defaultConcept: `Payment of ${transaction?.name}`, transaction: transaction, - endpoint: `/accounts/${transaction?.account_id}/payments/`, + endpoint: `/api/billing-cycles/${currentBillingCycle.value.id}/payments/`, paymentMethod: paymentMethods[0], }, }) @@ -167,7 +168,7 @@ const setPaymentBill = (transaction: ITransaction) => { defaultConcept: `Payment of ${transaction.name}`, due: transaction.total, transaction: transaction, - endpoint: `/accounts/${transaction.account_id}/payments/`, + endpoint: `/api/billing-cycles/${currentBillingCycle.value.id}/payments/`, paymentMethod: paymentMethods[0], } }) @@ -186,6 +187,23 @@ const currentBillingCycle = computed(() => { date: payment.due_at }))?.at(0) }) + +const financeTabs = [{ + name: "transactions", + label: "Transactions", + }, + // { + // name: "trends", + // label: "Trends", + // } +]; + +const selectedTabName = computed(() => { + return `All transactions ${monthName.value}`; +}) + +fetchBillingCycleDetails(); + -
+
-
- -
-
-
-

- All transactions in - {{ monthName }} +

+ + + + - + { :type="section" :series="data" :data="data" + :cols="1" @selected="handleSelection" v-bind="metaData.props" :title="metaData.title" @@ -143,35 +140,49 @@ const isFilterSelected = (filterValue: string) => { :title="metaData.title" class="mt-5" > -
- + +
+ + +
diff --git a/resources/js/domains/transactions/components/PaymentFormModal.vue b/resources/js/domains/transactions/components/PaymentFormModal.vue index ea6ac1b1..517af7a2 100644 --- a/resources/js/domains/transactions/components/PaymentFormModal.vue +++ b/resources/js/domains/transactions/components/PaymentFormModal.vue @@ -392,7 +392,7 @@ const dialogWidth = computed(() => {
@@ -405,7 +405,7 @@ const dialogWidth = computed(() => { @@ -416,7 +416,7 @@ const dialogWidth = computed(() => { placeholder="Selecciona una cuenta" /> - + { track-by="id" /> - +
- +