diff --git a/app/Console/Commands/AutomationCheck.php b/app/Console/Commands/AutomationCheck.php index 10958792..f027bb60 100644 --- a/app/Console/Commands/AutomationCheck.php +++ b/app/Console/Commands/AutomationCheck.php @@ -2,8 +2,8 @@ namespace App\Console\Commands; -use App\Domains\Integration\Models\Automation; -use App\Domains\Integration\Services\LogerAutomationService; +use App\Domains\Automation\Models\Automation; +use App\Domains\Automation\Services\LogerAutomationService; use Illuminate\Console\Command; class AutomationCheck extends Command diff --git a/app/Domains/AppCore/Models/Planner.php b/app/Domains/AppCore/Models/Planner.php index 1d438faa..89ed3a2d 100644 --- a/app/Domains/AppCore/Models/Planner.php +++ b/app/Domains/AppCore/Models/Planner.php @@ -3,7 +3,7 @@ namespace App\Domains\AppCore\Models; use App\Concerns\SupportsDateFrame; -use App\Domains\Integration\Services\LogerAutomationService; +use App\Domains\Automation\Services\LogerAutomationService; use App\Events\AutomationEvent; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; diff --git a/app/Domains/Automation/Concerns/AutomationActionContract.php b/app/Domains/Automation/Concerns/AutomationActionContract.php new file mode 100644 index 00000000..c24fa949 --- /dev/null +++ b/app/Domains/Automation/Concerns/AutomationActionContract.php @@ -0,0 +1,28 @@ +tasks[] = [ + "name" => $action->getName(), + "entity" => get_class($action), + "action" => $action, + "order" => count($this->tasks), + "task_type" => $type, + "values" => $config['values'] ?? [] + ]; + + return $this; + } + + public function getSchema() { + return [ + ... $this->data->toArray(), + "description" => implode(" ", array_map(fn ($task) => $task['action']?->label(), $this->tasks)) , + "integration_id" => $this->data->integration_id, + "service_id" => $this->data->service_id, + "sentence" => implode(" ", array_map(fn ($task) => $task['action']?->label(), $this->tasks)), + "name" => implode(" ", array_map(fn ($task) => $task['action']?->getName(), $this->tasks)), + "sentence" => "", + "tasks" => $this->tasks + ]; + } +} diff --git a/app/Http/Controllers/Api/AutomationController.php b/app/Domains/Automation/Http/Controllers/AutomationController.php similarity index 78% rename from app/Http/Controllers/Api/AutomationController.php rename to app/Domains/Automation/Http/Controllers/AutomationController.php index 554ea4c8..4e4c0f6d 100644 --- a/app/Http/Controllers/Api/AutomationController.php +++ b/app/Domains/Automation/Http/Controllers/AutomationController.php @@ -1,12 +1,12 @@ belongsTo('App\Models\Daily\AutomationRecipe', 'automation_recipe_id', 'id'); } @@ -25,15 +37,20 @@ public function saveTasks($tasks) { AutomationTaskAction::query()->where('automation_id', $this->id)->delete(); foreach ($tasks as $index => $task) { if (!$task['name']) continue; - $taskSource = AutomationTask::find($task['automation_task_id']); + if (empty($task['entity'])) { + $taskSource = AutomationTask::find($task['automation_task_id']); + $task['entity'] = $taskSource->entity; + $task['task_type'] = $taskSource->task_type; + } + $this->tasks()->create([ "team_id" => $this->team_id, "user_id" => $this->user_id, - "entity" => $taskSource->entity, - "task_type" => $taskSource->task_type, + "entity" => $task['entity'], + "task_type" => $task['task_type'], 'order' => $index, "automation_id" => $this->id, - "automation_task_id" => $task['automation_task_id'], + "automation_task_id" => $task['automation_task_id'] ?? null, "name" => $task['name'], "values" => json_encode($task['values']), ]); diff --git a/app/Domains/Integration/Models/AutomationRecipe.php b/app/Domains/Automation/Models/AutomationRecipe.php similarity index 87% rename from app/Domains/Integration/Models/AutomationRecipe.php rename to app/Domains/Automation/Models/AutomationRecipe.php index 6851e7c3..d86bcaec 100644 --- a/app/Domains/Integration/Models/AutomationRecipe.php +++ b/app/Domains/Automation/Models/AutomationRecipe.php @@ -1,6 +1,6 @@ entity; - $action = $task->name; - $lastData = $entity::$action($automation, $lastData, $task, $previousTask, $trigger); + $lastData = $entity::handle($automation, $lastData, $task, $previousTask, $trigger); if (!$lastData) { break; } @@ -145,20 +140,12 @@ public static function services() { 'components' => [ [ 'label' => 'Parse Alert', - 'name' => 'parseAlert', - 'entity' => BHDAlert::class, + 'name' => 'parseMessage', + 'entity' => BHD::class, 'description' => 'Parse an email alert', 'config' => json_encode(BHDAlert::getSchema()), ], - [ - 'label' => 'Parse Notification', - 'name' => 'parseNotification', - 'entity' => BHDNotification::class, - 'description' => 'Parse an email alert', - 'config' => json_encode(BHDNotification::getSchema()), - - ] ], "type" => "internal" @@ -167,9 +154,9 @@ public static function services() { 'name' => 'transactions', 'label' => 'Transactions', 'logo' => '/images/transactions.png', - 'entity' => Entry::class, + 'entity' => TransactionCreateEntry::class, 'description' => 'BHD bank', - 'fields' => json_encode(Entry::fieldConfig()), + 'fields' => json_encode(TransactionCreateEntry::fieldConfig()), "triggers" => [ [ 'name' => 'transactionCreated', @@ -180,7 +167,7 @@ public static function services() { 'field' => [ 'title' => 'field', 'type' => 'select', - 'options' => [array_keys(Entry::fieldConfig())] + 'options' => [array_keys(TransactionCreateEntry::fieldConfig())] ], 'conditionType' => [ 'title' => 'Condition', @@ -204,9 +191,9 @@ public static function services() { [ 'name' => 'createTransaction', 'label' => 'Create transaction', - 'entity' => Entry::class, + 'entity' => TransactionCreateEntry::class, 'description' => 'Create a new transaction', - 'config' => json_encode(Entry::fieldConfig()), + 'config' => json_encode(TransactionCreateEntry::fieldConfig()), "accepts_config" => true, ], ], diff --git a/app/Domains/Automation/routes.php b/app/Domains/Automation/routes.php new file mode 100644 index 00000000..5061d7bb --- /dev/null +++ b/app/Domains/Automation/routes.php @@ -0,0 +1,22 @@ +prefix('/api')->name('api.')->group(function () { + Route::controller(AutomationController::class)->group(function () { + Route::apiResource('automation', AutomationController::class); + Route::post('/automation/{id}/run', 'run'); + Route::post('/automation/run-all', 'runAll'); + }); + + Route::apiResource('/automation-services', AutomationServiceController::class); + Route::apiResource('/automation-recipes', AutomationRecipeController::class); +}); diff --git a/app/Domains/Integration/Actions/BHD.php b/app/Domains/Integration/Actions/BHD.php index 0071277a..4d56fa95 100644 --- a/app/Domains/Integration/Actions/BHD.php +++ b/app/Domains/Integration/Actions/BHD.php @@ -2,11 +2,50 @@ namespace App\Domains\Integration\Actions; -use App\Domains\Integration\Models\Automation; -use App\Domains\Integration\Models\AutomationTaskAction; +use App\Domains\Automation\Concerns\AutomationActionContract; +use App\Domains\Automation\Models\Automation; +use App\Domains\Automation\Models\AutomationTaskAction; +use Exception; +use Symfony\Component\DomCrawler\Crawler; -class BHD +class BHD implements AutomationActionContract { + public static function handle( + Automation $automation, + mixed $payload, + AutomationTaskAction $task, + AutomationTaskAction $previousTask, + AutomationTaskAction $trigger + ) + { + $type = self::getMessageType($payload); + try { + $transaction = match ($type) { + 'alert'=> self::parseAlert($automation, $payload, $task, $previousTask, $trigger), + 'notification'=> self::parseNotification($automation, $payload, $task, $previousTask, $trigger), + }; + return $transaction?->toArray(); + } catch (Exception $e) { + dd("hola", $e); + } + + } + + public function getName(): string + { + return "BHDMessage"; + } + + public function label(): string + { + return "BHD Message"; + } + + public function getDescription(): string + { + return "Parse an email alert or notification"; + } + /** * Validate and create a new team for the given user. * @@ -23,10 +62,10 @@ public static function parseAlert( ) { - return (new BHDAlert())->handle($automation, $payload); + return (new BHDAlert())->handle($automation, $payload); } - /** + /** * Validate and create a new team for the given user. * * @param Automation $automation @@ -45,4 +84,18 @@ public static function parseNotification( return (new BHDNotification())->handle($automation, $payload); } + + public static function getMessageType($mail) { + try { + $body = new Crawler($mail['message']); + $tdValues = $body->filter("[class*=table_trans_body] td")->each(function (Crawler $node) { + return $node->text(); + }); + + return empty($tdValues) ? 'notification' : 'alert'; + } catch (Exception) { + return 'notification'; + } + } + } diff --git a/app/Domains/Integration/Actions/BHDAction.php b/app/Domains/Integration/Actions/BHDAction.php new file mode 100644 index 00000000..cf703641 --- /dev/null +++ b/app/Domains/Integration/Actions/BHDAction.php @@ -0,0 +1,46 @@ + [ + 'type' => 'string', + 'required' => true + ], + 'date' => [ + 'type' => 'date', + 'label' => 'Date', + + ], + 'currencyCode' => [ + 'type' => 'string', + 'label' => 'currencyCode', + 'required' => true + ], + 'amount' => [ + 'type' => 'money', + 'label' => 'Amount', + 'required' => true + ], + 'payee' => [ + 'type' => 'string', + 'label' => 'Payee', + 'required' => true + ], + 'categoryGroup' => [ + 'type' => 'string', + 'label' => 'categoryGroup', + 'required' => true + ], + 'category' => [ + 'type' => 'string', + 'label' => 'Category', + 'required' => true + ] + ]; + } +} diff --git a/app/Domains/Integration/Actions/BHDAlert.php b/app/Domains/Integration/Actions/BHDAlert.php index 3ea3093c..45ad194f 100644 --- a/app/Domains/Integration/Actions/BHDAlert.php +++ b/app/Domains/Integration/Actions/BHDAlert.php @@ -2,14 +2,16 @@ namespace App\Domains\Integration\Actions; +use App\Domains\Automation\Models\Automation; use App\Domains\Integration\Concerns\MailToTransaction; use App\Domains\Integration\Concerns\TransactionDataDTO; -use App\Domains\Integration\Models\Automation; use App\Domains\Transaction\Services\BHDService; use Symfony\Component\DomCrawler\Crawler; class BHDAlert implements MailToTransaction { + use BHDAction; + public function handle(Automation $automation, mixed $mail, int $index = 0): TransactionDataDTO { @@ -24,55 +26,15 @@ public function handle(Automation $automation, mixed $mail, int $index = 0): Tra return new TransactionDataDTO([ - "id" => $mail['id'], + "id" => (int) $mail['id'], "date" => Date('Y-m-d', strtotime($mail['date'])), "payee" => $tdValues[3], 'category' => '', 'categoryGroup' => '', 'description' => $product, "amount" => $total * $type, - "currencyCode" => BHDService::parseCurrency([$tdValues[1]]), + "currencyCode" => BHDService::parseCurrency($tdValues[1]), ]); } - public static function getSchema(): mixed - { - return [ - 'description'=> [ - 'type' => 'string', - 'required' => true - ], - 'date' => [ - 'type' => 'date', - 'label' => 'Date', - - ], - 'currencyCode' => [ - 'type' => 'string', - 'label' => 'currencyCode', - 'required' => true - ], - 'amount' => [ - 'type' => 'money', - 'label' => 'Amount', - 'required' => true - ], - 'payee' => [ - 'type' => 'string', - 'label' => 'Payee', - 'required' => true - ], - 'categoryGroup' => [ - 'type' => 'string', - 'label' => 'categoryGroup', - 'required' => true - ], - 'category' => [ - 'type' => 'string', - 'label' => 'Category', - 'required' => true - ] - ]; - } - } diff --git a/app/Domains/Integration/Actions/BHDNotification.php b/app/Domains/Integration/Actions/BHDNotification.php index d51d0f84..942b7c78 100644 --- a/app/Domains/Integration/Actions/BHDNotification.php +++ b/app/Domains/Integration/Actions/BHDNotification.php @@ -2,15 +2,17 @@ namespace App\Domains\Integration\Actions; +use App\Domains\Automation\Models\Automation; use App\Domains\Integration\Concerns\MailToTransaction; use App\Domains\Integration\Concerns\TransactionDataDTO; -use App\Domains\Integration\Models\Automation; -use App\Domains\Transaction\Services\BHDService; use App\Domains\Transaction\Services\YNABService; +use Illuminate\Support\Carbon; use Symfony\Component\DomCrawler\Crawler; class BHDNotification implements MailToTransaction { + use BHDAction; + public function handle(Automation $automation, mixed $mail, int $index = 0): TransactionDataDTO { @@ -52,61 +54,21 @@ public function handle(Automation $automation, mixed $mail, int $index = 0): Tra ]; foreach ($tableIds as $fieldName => $field) { - $bhdOutput[$field['name']] = $this->{$field['processor']}($body->filter("[id*=${fieldName}")->first()->text()); + $bhdOutput[$field['name']] = $this->{$field['processor']}($body->filter("[id*=${fieldName}]")->first()->text()); } + return new TransactionDataDTO([ - "id" => $mail['id'], + "id" => (int) $mail['id'], "date" => $bhdOutput['date'], "payee" => $bhdOutput['seller'], 'category' => '', 'categoryGroup' => '', - 'description' => $bhdOutput['product'], + 'description' => $bhdOutput['product'].":".$bhdOutput['description'], "amount" => $bhdOutput['amount']->amount * 1, "currencyCode" => $bhdOutput['amount']->currencyCode, ]); } - public static function getSchema(): mixed - { - return [ - 'description'=> [ - 'type' => 'string', - 'required' => true - ], - 'date' => [ - 'type' => 'date', - 'label' => 'Date', - - ], - 'currencyCode' => [ - 'type' => 'string', - 'label' => 'currencyCode', - 'required' => true - ], - 'amount' => [ - 'type' => 'money', - 'label' => 'Amount', - 'required' => true - ], - 'payee' => [ - 'type' => 'string', - 'label' => 'Payee', - 'required' => true - ], - 'categoryGroup' => [ - 'type' => 'string', - 'label' => 'categoryGroup', - 'required' => true - ], - 'category' => [ - 'type' => 'string', - 'label' => 'Category', - 'required' => true - ] - ]; - } - - public function processResult($value) { return $value; @@ -119,7 +81,8 @@ public function processMoney($value) public function processDate($value) { - return Date('Y-m-d', strtotime($value)); + $date = substr($value, 0, 10); + return Carbon::createFromFormat("d/m/Y", $date)->format("Y-m-d"); } public function processType($value) diff --git a/app/Domains/Integration/Actions/Entry.php b/app/Domains/Integration/Actions/Entry.php deleted file mode 100644 index 5b896c34..00000000 --- a/app/Domains/Integration/Actions/Entry.php +++ /dev/null @@ -1,155 +0,0 @@ -values); - - $accountName = FormulaHelper::parseFormula($taskData->account_id, $payload); - $categoryName = FormulaHelper::parseFormula($taskData->category_id, $payload); - $accountId = Account::guessAccount($automation, [$accountName ?? "General"]); - $categoryId = Account::guessAccount($automation, [$categoryName ?? "Unknown"]); - - $transaction = Transaction::createTransaction([ - 'team_id' => $automation->team_id, - 'user_id' => $automation->user_id, - 'account_id' => $accountId, - 'date' => FormulaHelper::parseFormula($taskData->date, $payload), - 'currency_code' => FormulaHelper::parseFormula($taskData->currency_code, $payload), - 'category_id' => $categoryId, - 'description' => FormulaHelper::parseFormula($taskData->description, $payload), - 'direction' => FormulaHelper::parseFormula($taskData->direction, $payload), - 'total' => FormulaHelper::parseFormula($taskData->total, $payload), - 'items' => [], - 'metaData' => [ - "resource_id" => $payload['id'], - "resource_origin" => $previousTask->name, - "resource_type" => $trigger->name, - ] - ]); - User::find($automation->user_id)->notify(new EntryGenerated($transaction)); - return $transaction; - } - - public static function transactionCreated( - Automation $automation, - mixed $payload, - AutomationTaskAction $task, - AutomationTaskAction $previousTask, - AutomationTaskAction $trigger - ) { - $config = json_decode($trigger->values); - if (Str::contains($payload['description'], $config->value)) { - return $payload; - } - } - - /** - * Validate and create a new team for the given user. - * - * @param Automation $automation - * @return void - */ - public static function transactionsFetch( - Automation $automation, - mixed $payload, - AutomationTaskAction $task, - mixed $previousTask = null, - AutomationTaskAction $trigger = null - ) - { - $config = json_decode($trigger->values); - $track = json_decode($automation->track, true); - $trackId = $track['lastId'] ?? 0; - $transactions = ModelsTransaction::where('description', 'like' ,"%$config->value%")->orderBy('date')->get(); - foreach ($transactions as $transaction) { - $tasks = $automation->tasks; - $previousTask = $tasks->first(); - $payload = $transaction->toArray(); - foreach ($tasks as $taskIndex => $task) { - if ($taskIndex !== 0) { - $action = $task->name; - $actionEntity = $task->entity; - try { - $payload = $actionEntity::$action($automation, $payload, $task, $previousTask, $trigger); - $previousTask = $task; - } catch (\Exception $e) { - print_r($e->getMessage()); - Log::error($e->getMessage(), $transaction->toArray()); - continue; - } - } - } - $track['lastId'] = $transaction->id; - } - $automation->track = json_encode($track); - $automation->save(); - } - - public static function fieldConfig() { - return [ - 'account_id' => [ - 'type' => 'id', - 'required' => true - ], - 'date' => [ - 'type' => 'date', - 'required' => true - ], - 'currency_code' => [ - 'type' => 'string' - ], - 'category_id' => [ - 'type' => 'string', - 'required' => true - ], - 'description' => [ - 'type' => 'string', - 'required' => false - ], - 'direction' => [ - 'type' => 'labels', - 'options' => [Transaction::DIRECTION_CREDIT, Transaction::DIRECTION_DEBIT], - ], - 'total' => [ - 'type' => 'money', - 'required' => true - ], - 'items' => [ - 'type' => 'array', - 'required' => false - ], - 'metaData' => [ - 'type' => 'json', - 'required' => false - ] - ]; - } -} diff --git a/app/Domains/Integration/Actions/Gmail.php b/app/Domains/Integration/Actions/GmailReceived.php similarity index 82% rename from app/Domains/Integration/Actions/Gmail.php rename to app/Domains/Integration/Actions/GmailReceived.php index 6dbdcbc3..9532bc3c 100644 --- a/app/Domains/Integration/Actions/Gmail.php +++ b/app/Domains/Integration/Actions/GmailReceived.php @@ -2,13 +2,14 @@ namespace App\Domains\Integration\Actions; -use App\Domains\Integration\Models\Automation; +use App\Domains\Automation\Concerns\AutomationActionContract; +use App\Domains\Automation\Models\Automation; use App\Domains\Integration\Services\GoogleService; use Google\Service\Gmail as ServiceGmail; use Illuminate\Support\Facades\Log; use PhpMimeMailParser\Parser as EmailParser; -class Gmail +class GmailReceived implements AutomationActionContract { /** * Validate and create a new team for the given user. @@ -16,9 +17,9 @@ class Gmail * @param Automation $automation * @return void */ - public static function received(Automation $automation, $lastData = null, $task = null, $previousTask = null, $trigger = null) + public static function handle(Automation $automation, $lastData = null, $task = null, $previousTask = null, $trigger = null) { - $maxResults = 50; + $maxResults = 15; $track = json_decode($automation->track, true); $trackId = $track['historyId'] ?? 0; $config = json_decode($trigger->values); @@ -30,6 +31,7 @@ public static function received(Automation $automation, $lastData = null, $task } $results = $service->users_threads->listUsersThreads("me", ['maxResults' => $maxResults, 'q' => "$condition"]); + foreach ($results->getThreads() as $index => $thread) { echo "reading thread $index \n"; $theadResponse = $service->users_threads->get("me", $thread->id, ['format' => 'MINIMAL']); @@ -62,10 +64,9 @@ public static function received(Automation $automation, $lastData = null, $task $previousTask = $tasks->first(); foreach ($tasks as $taskIndex => $task) { if ($taskIndex !== 0) { - $action = $task->name; $actionEntity = $task->entity; try { - $payload = $actionEntity::$action($automation, $payload, $task, $previousTask, $tasks[0]); + $payload = $actionEntity::handle($automation, $payload, $task, $previousTask, $tasks[0]); $previousTask = $task; } catch (\Exception $e) { print_r($e->getMessage()); @@ -79,6 +80,21 @@ public static function received(Automation $automation, $lastData = null, $task }; } + public function getName(): string + { + return "gmailReceived"; + } + + public function label(): string + { + return "New Gmail"; + } + + public function getDescription(): string + { + return "When a new email is received"; + } + public static function parseEmail($raw) { $switched = str_replace(['-', '_'], ['+', '/'], $raw['raw']); diff --git a/app/Domains/Integration/Actions/MealPlanAutomation.php b/app/Domains/Integration/Actions/MealPlanAutomation.php index 1be1bb2f..35da62a7 100644 --- a/app/Domains/Integration/Actions/MealPlanAutomation.php +++ b/app/Domains/Integration/Actions/MealPlanAutomation.php @@ -2,9 +2,9 @@ namespace App\Domains\Integration\Actions; -use App\Domains\Integration\Models\Automation; -use App\Domains\Integration\Models\AutomationTask; -use App\Domains\Integration\Models\AutomationTaskAction; +use App\Domains\Automation\Models\Automation; +use App\Domains\Automation\Models\AutomationTask; +use App\Domains\Automation\Models\AutomationTaskAction; use App\Domains\Meal\Models\MealPlan; use App\Domains\Meal\Services\MealService; use App\Helpers\FormulaHelper; diff --git a/app/Domains/Integration/Actions/OccurrenceCheckAutomation.php b/app/Domains/Integration/Actions/OccurrenceCheckAutomation.php index 7b632801..4e33a11a 100644 --- a/app/Domains/Integration/Actions/OccurrenceCheckAutomation.php +++ b/app/Domains/Integration/Actions/OccurrenceCheckAutomation.php @@ -4,9 +4,9 @@ use App\Domains\Housing\Actions\RegisterOccurrence; use App\Domains\Housing\Models\OccurrenceCheck; -use App\Domains\Integration\Models\Automation; -use App\Domains\Integration\Models\AutomationTask; -use App\Domains\Integration\Models\AutomationTaskAction; +use App\Domains\Automation\Models\Automation; +use App\Domains\Automation\Models\AutomationTask; +use App\Domains\Automation\Models\AutomationTaskAction; class OccurrenceCheckAutomation { diff --git a/app/Domains/Integration/Actions/TransactionAction.php b/app/Domains/Integration/Actions/TransactionAction.php new file mode 100644 index 00000000..81a34a98 --- /dev/null +++ b/app/Domains/Integration/Actions/TransactionAction.php @@ -0,0 +1,48 @@ + [ + 'type' => 'id', + 'required' => true + ], + 'date' => [ + 'type' => 'date', + 'required' => true + ], + 'currency_code' => [ + 'type' => 'string' + ], + 'category_id' => [ + 'type' => 'string', + 'required' => true + ], + 'description' => [ + 'type' => 'string', + 'required' => false + ], + 'direction' => [ + 'type' => 'labels', + 'options' => [Transaction::DIRECTION_CREDIT, Transaction::DIRECTION_DEBIT], + ], + 'total' => [ + 'type' => 'money', + 'required' => true + ], + 'items' => [ + 'type' => 'array', + 'required' => false + ], + 'metaData' => [ + 'type' => 'json', + 'required' => false + ] + ]; + } +} diff --git a/app/Domains/Integration/Actions/TransactionCreateEntry.php b/app/Domains/Integration/Actions/TransactionCreateEntry.php new file mode 100644 index 00000000..9e3baa91 --- /dev/null +++ b/app/Domains/Integration/Actions/TransactionCreateEntry.php @@ -0,0 +1,81 @@ +values); + + $accountId = FormulaHelper::parseFormula($taskData->account_id, $payload); + + if ($payeeName = $payload["payee"]) { + $payee = Payee::findOrCreateByName($automation, $payeeName ?? 'General Provider'); + $lastTransaction = TransactionLine::where([ + "payee_id" => $payee->id, + "team_id" => $automation->team_id + ])->first(); + $transactionCategoryId = $lastTransaction?->category_id; + $payeeId = $payee->id; + $counterAccountId = $payee->account_id; + } + + $transactionData = [ + 'team_id' => $automation->team_id, + 'user_id' => $automation->user_id, + 'account_id' => $accountId, + 'payee_id' => $payeeId, + 'counter_account_id' => $counterAccountId ?? null, + 'date' => FormulaHelper::parseFormula($taskData->date, $payload), + 'currency_code' => FormulaHelper::parseFormula($taskData->currency_code, $payload), + 'category_id' => $transactionCategoryId ?? null, + 'description' => FormulaHelper::parseFormula($taskData->description, $payload), + 'direction' => FormulaHelper::parseFormula($taskData->direction, $payload), + 'total' => FormulaHelper::parseFormula($taskData->total, $payload), + 'items' => [], + 'metaData' => [ + "resource_id" => $payload['id'], + "resource_origin" => $previousTask->name, + "resource_type" => $trigger->name, + ] + ]; + $transaction = Transaction::createTransaction($transactionData); + User::find($automation->user_id)->notify(new EntryGenerated($transaction)); + return $transaction; + } + + public function getName(): string + { + return "createTransaction"; + } + + public function label(): string + { + return "Create transaction"; + } + + public function getDescription(): string + { + return "Create a new transaction"; + } + +} diff --git a/app/Domains/Integration/Actions/TransactionCreated.php b/app/Domains/Integration/Actions/TransactionCreated.php new file mode 100644 index 00000000..e2d61f93 --- /dev/null +++ b/app/Domains/Integration/Actions/TransactionCreated.php @@ -0,0 +1,26 @@ +values); + if (Str::contains($payload['description'], $config->value)) { + return $payload; + } + } +} diff --git a/app/Domains/Integration/Actions/TransactionFetched.php b/app/Domains/Integration/Actions/TransactionFetched.php new file mode 100644 index 00000000..5dc6efc9 --- /dev/null +++ b/app/Domains/Integration/Actions/TransactionFetched.php @@ -0,0 +1,51 @@ +values); + $track = json_decode($automation->track, true); + $trackId = $track['lastId'] ?? 0; + $transactions = ModelsTransaction::where('description', 'like' ,"%$config->value%")->orderBy('date')->get(); + foreach ($transactions as $transaction) { + $tasks = $automation->tasks; + $previousTask = $tasks->first(); + $payload = $transaction->toArray(); + foreach ($tasks as $taskIndex => $task) { + if ($taskIndex !== 0) { + $action = $task->name; + $actionEntity = $task->entity; + try { + $payload = $actionEntity::$action($automation, $payload, $task, $previousTask, $trigger); + $previousTask = $task; + } catch (\Exception $e) { + print_r($e->getMessage()); + Log::error($e->getMessage(), $transaction->toArray()); + continue; + } + } + } + $track['lastId'] = $transaction->id; + } + $automation->track = json_encode($track); + $automation->save(); + } +} diff --git a/app/Domains/Integration/Concerns/MailToTransaction.php b/app/Domains/Integration/Concerns/MailToTransaction.php index fbd619e9..36960d31 100644 --- a/app/Domains/Integration/Concerns/MailToTransaction.php +++ b/app/Domains/Integration/Concerns/MailToTransaction.php @@ -2,7 +2,7 @@ namespace App\Domains\Integration\Concerns; -use App\Domains\Integration\Models\Automation; +use App\Domains\Automation\Models\Automation; interface MailToTransaction { public function handle(Automation $automation, mixed $mail, int $index): TransactionDataDTO; diff --git a/app/Domains/Integration/Concerns/TransactionDataDTO.php b/app/Domains/Integration/Concerns/TransactionDataDTO.php index 00490d29..b784f6f5 100644 --- a/app/Domains/Integration/Concerns/TransactionDataDTO.php +++ b/app/Domains/Integration/Concerns/TransactionDataDTO.php @@ -1,7 +1,9 @@ model = new Integration(); + $this->searchable = ['name']; + $this->validationRules = []; + } +} diff --git a/app/Domains/Integration/Models/Integration.php b/app/Domains/Integration/Models/Integration.php index 78c4967b..16b99c31 100644 --- a/app/Domains/Integration/Models/Integration.php +++ b/app/Domains/Integration/Models/Integration.php @@ -2,6 +2,7 @@ namespace App\Domains\Integration\Models; +use App\Domains\Automation\Models\Automation; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; diff --git a/app/Domains/Integration/Services/GoogleService.php b/app/Domains/Integration/Services/GoogleService.php index 5e87163c..50ecdcbe 100644 --- a/app/Domains/Integration/Services/GoogleService.php +++ b/app/Domains/Integration/Services/GoogleService.php @@ -4,34 +4,12 @@ use App\Domains\Integration\Models\Integration; use Exception; use Google\Client as GoogleClient; -use Google\Service\Calendar; use Google\Service\Gmail; +use Google\Service\Oauth2; +use Google\Oauth; class GoogleService { - - public static function requestAccessToken($data, $user) { - $client = new GoogleClient([ - "client_id" => config('integrations.google.client_id') - ]); - $client->addScope([ - Gmail::GMAIL_READONLY, - Calendar::CALENDAR_READONLY, - Calendar::CALENDAR_EVENTS_READONLY - ]); - $client->setRedirectUri(config('app.url') . "/services/accept-oauth"); - $client->setAccessType('offline'); - $client->setLoginHint($user->email); - $client->setApprovalPrompt('force'); - $client->setIncludeGrantedScopes(true); - - $authUrl = $client->createAuthUrl(); - if ($authUrl) { - self::storeIntegration($data, $user); - } - return $authUrl; - } - public static function getConfigPath() { return base_path(config("integrations.google.credentials_path")); } @@ -39,22 +17,35 @@ public static function getConfigPath() { public static function setTokens($data, $user, $integrationId = null) { if (!$integrationId && $_GET['code']) { $client = new GoogleClient([ 'client_id' => config('integrations.google.client_id')]); + $client->setApplicationName(config('app.name')); $client->setAuthConfig(self::getConfigPath()); $client->setAccessType('offline'); $userIdToken = $_GET['code']; $tokenResponse = $client->fetchAccessTokenWithAuthCode($userIdToken); + $integration = Integration::where([ 'user_id' => $user->id, 'team_id' => $user->current_team_id, 'name' => 'Google' ])->first(); - $integration->token = json_encode($tokenResponse); - $integration->save(); - return; + $oauth2 = new Oauth2($client); + $userInfo = $oauth2->userinfo->get(); + + if ($userInfo->email == $user->email) { + $integration->token = json_encode($tokenResponse); + if ($tokenResponse["refresh_token"]) { + $integration->meta_data = json_encode($tokenResponse["refresh_token"]); + } + $integration->save(); + session(['g_token', json_encode($tokenResponse)]); + return; + } + throw new Exception("Error obtaining the token" . $userInfo->email); } else if ($integrationId) { $integration = Integration::find($integrationId); $integration->token = json_encode($data->access_token); + session(['g_token', json_encode($data->access_token)]); return; }; } @@ -64,17 +55,18 @@ public static function getClient($integrationId) { $client = new GoogleClient(); $client->setAuthConfig(self::getConfigPath()); - if ($integration->token) { - $accessToken = json_decode($integration->token, true); + if (!$accessToken = session('g_token') && $integration->token) { + $accessToken = $integration->token; } $client->setAccessToken($accessToken); if ($client->isAccessTokenExpired()) { - if ($refreshToken = $client->getRefreshToken()) { + if ($refreshToken = $integration->meta_data) { $tokenResponse = $client->fetchAccessTokenWithRefreshToken($refreshToken); self::setTokens((object) [ 'access_token' => $tokenResponse, + "refresh_token" => $refreshToken ], $integration->user, $integrationId); @@ -95,4 +87,26 @@ public static function storeIntegration($data, $user) { "hash" => $user->email ]); } + + public static function requestAccessToken($data, $user) { + $client = new GoogleClient([ + "client_id" => config('integrations.google.client_id') + ]); + $client->addScope([ + Gmail::GMAIL_READONLY, + Oauth2::USERINFO_PROFILE, + Oauth2::USERINFO_EMAIL, + ]); + $client->setRedirectUri(config('app.url') . "/services/accept-oauth"); + $client->setAccessType('offline'); + $client->setLoginHint($user->email); + $client->setApprovalPrompt('auto'); + $client->setIncludeGrantedScopes(true); + + $authUrl = $client->createAuthUrl(); + if ($authUrl) { + self::storeIntegration($data, $user); + } + return $authUrl; + } } diff --git a/app/Domains/Transaction/Actions/MapDTOToLoger.php b/app/Domains/Transaction/Actions/MapDTOToLoger.php new file mode 100644 index 00000000..1cf511c2 --- /dev/null +++ b/app/Domains/Transaction/Actions/MapDTOToLoger.php @@ -0,0 +1,61 @@ + FormulaHelper::parseFormula($taskData->date, $payload), + 'currency_code' => FormulaHelper::parseFormula($taskData->currency_code, $payload), + 'category_id' => $categoryId, + 'description' => FormulaHelper::parseFormula($taskData->description, $payload), + 'direction' => FormulaHelper::parseFormula($taskData->direction, $payload), + 'total' => FormulaHelper::parseFormula($taskData->total, $payload), + 'items' => [], + 'metaData' => [ + "resource_id" => $payload['id'], + "resource_origin" => $previousTask->name, + "resource_type" => $trigger->name, + ] + ]; + + $money = YNABService::getMoney($row); + $accountId = + $isTransfer = str_contains($row['payee'], 'Transfer :'); + $direction = $money->direction; + if (!$isTransfer && $row['category_group']) { + $categoryGroupId = Category::findOrCreateByName($session, $row['category_group']); + $transactionCategoryId = Category::findOrCreateByName($session, $row['category'], $categoryGroupId); + $payee = Payee::findOrCreateByName($session, $row['payee'] ?? 'General Provider'); + $payeeId = $payee->id; + $counterAccountId = $payee->account_id; + } + $row['team_id'] = $session['team_id']; + $row['user_id'] = $session['user_id']; + $row['account_id'] = $accountId; + $row['payee_id'] = $payeeId; + $row['date'] = Carbon::parse($row['date'])->format('Y-m-d'); + $row['currency_code'] = $money->currencyCode; + $row['transaction_category_group_id'] = $categoryGroupId; + $row['category_id'] = $transactionCategoryId; + $row['counter_account_id'] = $counterAccountId; + $row['description'] = $row['memo'] ?? ''; + $row['direction'] = $direction; + $row['total'] = $money->amount; + $row['items'] = []; + $row['metaData'] = [ + "resource_id" => 'YNAB', + "resource_origin" => 'YNAB', + "resource_type" => 'transaction', + ]; + $row['is_transfer'] = $isTransfer; + + return $row; + } +} diff --git a/app/Domains/Transaction/Services/BHDService.php b/app/Domains/Transaction/Services/BHDService.php index a07247ba..0df43d36 100644 --- a/app/Domains/Transaction/Services/BHDService.php +++ b/app/Domains/Transaction/Services/BHDService.php @@ -1,20 +1,85 @@ 'USD', - 'RD' => 'DOP', - ]; - return $BHD_CURRENCY_CODES[$bhdCurrencyCode]; + try { + $BHD_CURRENCY_CODES = [ + 'USD' => 'USD', + 'RD' => 'DOP', + ]; + + return $BHD_CURRENCY_CODES[$bhdCurrencyCode]; + } catch (Exception) { + dd($bhdCurrencyCode, "The code"); + } } public static function parseTypes($type) { $types = [ - 'Compra' => 1 + 'Compra' => 1, + "compra" => 1 ]; return $types[$type]; } + + public function linkAccount(Account $account, $serviceId, $integrationId) { + $alertAutomation = AutomationBuilder::make(new AutomationData( + $account->team_id, + $account->user_id, + $serviceId, + $integrationId + + )) + ->addAction(new GmailReceived(), AutomationTaskType::TRIGGER, [ + "values" => [ + "conditionType" => BHDService::CONDITION_FROM, + // "value" => implode(",", [BHDService::EMAIL_ALERT, BHDService::EMAIL_NOTIFICATION]) + "value" => BHDService::EMAIL_ALERT + ] + ]) + ->addAction(new BHD(), AutomationTaskType::COMPONENT, []) + ->addAction(new TransactionCreateEntry(), AutomationTaskType::ACTION, [ + "values" => [ + 'account_id' => $account->id, + 'date' => '${date}', + 'currency_code' => '${currencyCode}', + 'category_id' => '', + 'description' => '${description}', + 'direction' => Transaction::DIRECTION_CREDIT, + 'total' => '${amount}', + 'items' => '', + 'payee' => '${payee}', + 'metaData' => '' + ] + ]); + + $builderData = $alertAutomation->getSchema(); + $automation = Automation::create([ + ...$builderData, + "automatable_id" => $account->id, + "automatable_type" => get_class($account) + ]); + $automation->saveTasks($builderData['tasks']); + } } diff --git a/app/Domains/Transaction/routes.php b/app/Domains/Transaction/routes.php index f394e502..bfba3d88 100644 --- a/app/Domains/Transaction/routes.php +++ b/app/Domains/Transaction/routes.php @@ -1,10 +1,13 @@ group(function () { + Route::post('/finance/accounts/{account}/link', [FinanceAccountController::class, 'linkAccount']); + Route::get('/finance/reconciliation/accounts/{account}', [ReconciliationController::class, 'create']); Route::post('/finance/reconciliation/accounts/{account}', [ReconciliationController::class, 'store']); Route::get('/finance/accounts/{account}/reconciliations', [ReconciliationController::class, 'accountReconciliations']); diff --git a/app/Helpers/FormulaHelper.php b/app/Helpers/FormulaHelper.php index 78c0e92d..69f85117 100644 --- a/app/Helpers/FormulaHelper.php +++ b/app/Helpers/FormulaHelper.php @@ -1,31 +1,33 @@ $match) { - $variableToken = $match; - $variableName = $matches[1][$key]; - if ($variableSet && $variableSet[$variableName]) { - $value = $variableSet[$variableName]; - } else { - $value = 0; + try { + // here we take all the vars that are in ${variable} + preg_match_all('/\$\{(.*?)}/', $formula, $matches); + // here we set the values to the variables + if ($matches) { + foreach ($matches[0] as $key => $match) { + $variableToken = $match; + $variableName = $matches[1][$key]; + if ($variableSet && $variableSet[$variableName]) { + $value = $variableSet[$variableName]; + } else { + $value = 0; + } + $formula = str_replace($variableToken, $value, $formula); } - $formula = str_replace($variableToken, $value, $formula); } + + return $formula; + } catch (Exception $e) { + print_r([$e, "el error"]); + return null; } - // Here we get the value of the formula - return $formula; - // $spreadsheet = new Spreadsheet(); - // $sheet = $spreadsheet->getActiveSheet(); - // $sheet->setCellValue('A1', '=' . $formula); - // return $sheet->getCell('A1')->getCalculatedValue(); } } diff --git a/app/Http/Controllers/Api/BaseController.php b/app/Http/Controllers/Api/BaseController.php index 84d45c47..a6501a27 100644 --- a/app/Http/Controllers/Api/BaseController.php +++ b/app/Http/Controllers/Api/BaseController.php @@ -112,7 +112,7 @@ public function validateLocal(Request $request) { return $request->validate($this->validationRules); } - /** + /** * Remove the specified resource from storage. * * @param int $id diff --git a/app/Http/Controllers/Finance/FinanceAccountController.php b/app/Http/Controllers/Finance/FinanceAccountController.php index a5436202..b8022eae 100644 --- a/app/Http/Controllers/Finance/FinanceAccountController.php +++ b/app/Http/Controllers/Finance/FinanceAccountController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers\Finance; +use App\Domains\Automation\Models\AutomationService; +use App\Domains\Transaction\Services\BHDService; use App\Domains\Transaction\Services\ReportService; use App\Models\Setting; use Freesgen\Atmosphere\Http\InertiaController; @@ -51,4 +53,10 @@ public function show(Account $account) { "serverSearchOptions" => $this->getServerParams(), ]); } + + public function linkAccount(Account $account, BHDService $service) { + $data = $this->getPostData(request()); + $bankBHD = AutomationService::where('name', 'BHD')->first(); + $service->linkAccount($account, $bankBHD->id, $data["integration_id"]); + } } diff --git a/app/Http/Controllers/System/IntegrationController.php b/app/Http/Controllers/System/IntegrationController.php index 0eb5aceb..11b4f68b 100644 --- a/app/Http/Controllers/System/IntegrationController.php +++ b/app/Http/Controllers/System/IntegrationController.php @@ -2,9 +2,9 @@ namespace App\Http\Controllers\System; -use App\Domains\Integration\Models\AutomationRecipe; -use App\Domains\Integration\Models\AutomationService; -use App\Domains\Integration\Models\AutomationTask; +use App\Domains\Automation\Models\AutomationRecipe; +use App\Domains\Automation\Models\AutomationService; +use App\Domains\Automation\Models\AutomationTask; use App\Domains\Integration\Models\Integration; use App\Domains\Integration\Services\GoogleService; use Illuminate\Http\Request; diff --git a/app/Http/Controllers/System/ServiceController.php b/app/Http/Controllers/System/ServiceController.php index 2b797e18..31c24db9 100644 --- a/app/Http/Controllers/System/ServiceController.php +++ b/app/Http/Controllers/System/ServiceController.php @@ -19,7 +19,6 @@ public function acceptOauth(Request $request) GoogleService::setTokens((object) $request->post('credentials'), $request->user()); return redirect('/integrations'); } catch (Exception $e) { - dd($e->getMessage()); return redirect('/integrations')->with('flash', [ 'banner' => $e->getMessage(), ]); diff --git a/app/Jobs/RunAutomation.php b/app/Jobs/RunAutomation.php index 415c77e3..6e682312 100644 --- a/app/Jobs/RunAutomation.php +++ b/app/Jobs/RunAutomation.php @@ -2,7 +2,7 @@ namespace App\Jobs; -use App\Domains\Integration\Models\Automation; +use App\Domains\Automation\Models\Automation; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; diff --git a/app/Listeners/AutomationListener.php b/app/Listeners/AutomationListener.php index 4c0d7e29..702e93ba 100644 --- a/app/Listeners/AutomationListener.php +++ b/app/Listeners/AutomationListener.php @@ -2,8 +2,8 @@ namespace App\Listeners; -use App\Domains\Integration\Models\Automation; -use App\Domains\Integration\Services\LogerAutomationService; +use App\Domains\Automation\Models\Automation; +use App\Domains\Automation\Services\LogerAutomationService; use App\Events\AutomationEvent; use Illuminate\Contracts\Queue\ShouldQueue; diff --git a/app/Listeners/HandleTransactionCreated.php b/app/Listeners/HandleTransactionCreated.php index 5c702a03..a5416a56 100644 --- a/app/Listeners/HandleTransactionCreated.php +++ b/app/Listeners/HandleTransactionCreated.php @@ -2,13 +2,13 @@ namespace App\Listeners; -use App\Domains\Integration\Services\LogerAutomationService; +use App\Domains\Automation\Services\LogerAutomationService; use App\Events\AutomationEvent; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use Insane\Journal\Events\TransactionCreated; -class HandleTransactionCreated +class HandleTransactionCreated implements ShouldQueue { /** * Create the event listener. diff --git a/components.d.ts b/components.d.ts index b1e2199e..be7d4c64 100644 --- a/components.d.ts +++ b/components.d.ts @@ -19,9 +19,11 @@ declare module 'vue' { IMdiChevronLeft: typeof import('~icons/mdi/chevron-left')['default'] IMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] IMdiClose: typeof import('~icons/mdi/close')['default'] + IMdiEdit: typeof import('~icons/mdi/edit')['default'] IMdiExport: typeof import('~icons/mdi/export')['default'] IMdiFile: typeof import('~icons/mdi/file')['default'] IMdiFilter: typeof import('~icons/mdi/filter')['default'] + IMdiHistory: typeof import('~icons/mdi/history')['default'] IMdiLink: typeof import('~icons/mdi/link')['default'] IMdiMinus: typeof import('~icons/mdi/minus')['default'] IMdiPencil: typeof import('~icons/mdi/pencil')['default'] @@ -32,5 +34,6 @@ declare module 'vue' { IMdiStarOutline: typeof import('~icons/mdi/star-outline')['default'] IMdiSync: typeof import('~icons/mdi/sync')['default'] IMdiTrash: typeof import('~icons/mdi/trash')['default'] + IMdiWallet: typeof import('~icons/mdi/wallet')['default'] } } diff --git a/database/migrations/2023_09_05_185532_add_integration_meta_data.php b/database/migrations/2023_09_05_185532_add_integration_meta_data.php new file mode 100644 index 00000000..f6eb64ab --- /dev/null +++ b/database/migrations/2023_09_05_185532_add_integration_meta_data.php @@ -0,0 +1,28 @@ +text('meta_data')->nullable()->after('token'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('integrations', function (Blueprint $table) { + $table->dropColumn('meta_data'); + }); + } +}; diff --git a/database/seeders/AutomationSeeder.php b/database/seeders/AutomationSeeder.php index def89c38..c26bee2c 100644 --- a/database/seeders/AutomationSeeder.php +++ b/database/seeders/AutomationSeeder.php @@ -2,11 +2,11 @@ namespace Database\Seeders; -use App\Domains\Integration\Models\AutomationRecipe; -use App\Domains\Integration\Models\AutomationService; -use App\Domains\Integration\Models\AutomationTask; -use App\Domains\Integration\Models\AutomationTaskAction; -use App\Domains\Integration\Services\LogerAutomationService; +use App\Domains\Automation\Models\AutomationRecipe; +use App\Domains\Automation\Models\AutomationService; +use App\Domains\Automation\Models\AutomationTask; +use App\Domains\Automation\Models\AutomationTaskAction; +use App\Domains\Automation\Services\LogerAutomationService; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; diff --git a/resources/js/Components/AutomationModal.vue b/resources/js/Components/AutomationModal.vue index f8114e1a..0ade6419 100644 --- a/resources/js/Components/AutomationModal.vue +++ b/resources/js/Components/AutomationModal.vue @@ -12,7 +12,7 @@
- {{ service.description }} -
-+ {{ service.description }} +
+