diff --git a/.env.example b/.env.example index 7493df5b5..4f6e2bddc 100644 --- a/.env.example +++ b/.env.example @@ -134,4 +134,5 @@ TEST_ZOHO_CLIENT_REFRESH_TOKEN= TEST_SHOPIFY_API_KEY= TEST_SHOPIFY_API_SECRET= -TEST_SHOPIFY_SHOP_URL= \ No newline at end of file +TEST_SHOPIFY_SHOP_URL= +TEST_STRIPE_SECRET_KEY= diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 73797611a..7e80caf17 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -58,6 +58,8 @@ jobs: TEST_SHOPIFY_SHOP_URL: ${{ secrets.TEST_SHOPIFY_SHOP_URL }} TEST_APPLE_LOGIN_TOKEN: ${{ secrets.TEST_APPLE_LOGIN_TOKEN }} TEST_APOLLO_KEY: ${{ secrets.TEST_APOLLO_KEY }} + TEST_STRIPE_SECRET_KEY: ${{ secrets.TEST_STRIPE_SECRET_KEY }} + strategy: fail-fast: false matrix: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 381222f0e..d101e0e8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,6 +57,7 @@ jobs: MEILISEARCH_KEY: masterKey SCOUT_QUEUE: false #APP_DEBUG: true + #third party integration TEST_ZOHO_CLIENT_ID: ${{ secrets.TEST_ZOHO_CLIENT_ID }} TEST_ZOHO_CLIENT_SECRET: ${{ secrets.TEST_ZOHO_CLIENT_SECRET }} @@ -66,6 +67,7 @@ jobs: TEST_SHOPIFY_SHOP_URL: ${{ secrets.TEST_SHOPIFY_SHOP_URL }} TEST_APPLE_LOGIN_TOKEN: ${{ secrets.TEST_APPLE_LOGIN_TOKEN }} TEST_APOLLO_KEY: ${{ secrets.TEST_APOLLO_KEY }} + TEST_STRIPE_SECRET_KEY: ${{ secrets.TEST_STRIPE_SECRET_KEY }} strategy: fail-fast: false matrix: diff --git a/app/Console/Commands/Connectors/Shopify/ShopifyInventoryDownloadCommand.php b/app/Console/Commands/Connectors/Shopify/ShopifyInventoryDownloadCommand.php index ab96cdca4..397662757 100644 --- a/app/Console/Commands/Connectors/Shopify/ShopifyInventoryDownloadCommand.php +++ b/app/Console/Commands/Connectors/Shopify/ShopifyInventoryDownloadCommand.php @@ -17,7 +17,7 @@ class ShopifyInventoryDownloadCommand extends Command * * @var string */ - protected $signature = '343 {app_id} {branch_id} {warehouse_id}'; + protected $signature = 'kanvas:inventory-shopify-sync {app_id} {branch_id} {warehouse_id}'; /** * The console command description. diff --git a/app/Console/Commands/CreateEntityWorkflowCommand.php b/app/Console/Commands/Workflows/CreateEntityWorkflowCommand.php similarity index 99% rename from app/Console/Commands/CreateEntityWorkflowCommand.php rename to app/Console/Commands/Workflows/CreateEntityWorkflowCommand.php index d7caa7740..bebd584c2 100644 --- a/app/Console/Commands/CreateEntityWorkflowCommand.php +++ b/app/Console/Commands/Workflows/CreateEntityWorkflowCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Console\Commands; +namespace App\Console\Commands\Workflows; use Illuminate\Console\Command; use Kanvas\Apps\Models\Apps; diff --git a/app/Console/Commands/Workflows/KanvasCreateReceiverCommand.php b/app/Console/Commands/Workflows/KanvasCreateReceiverCommand.php new file mode 100644 index 000000000..0a02df291 --- /dev/null +++ b/app/Console/Commands/Workflows/KanvasCreateReceiverCommand.php @@ -0,0 +1,62 @@ +info('Creating Receiver...'); + $app = select( + label: 'Select the app for the receiver: ', + options: Apps::pluck('name', 'id'), + ); + + $action = select( + label: 'Select the action for the receiver: ', + options: WorkflowAction::pluck('name', 'id'), + ); + + $userId = $this->ask('Enter the user ID for the receiver: '); + $companyId = $this->ask('Enter the company ID for the receiver: '); + $name = $this->ask('Enter the name for the receiver: '); + $description = $this->ask('Enter the description for the receiver: '); + + $company = Companies::getById($companyId); + + $user = UsersRepository::getUserOfCompanyById($company, (int)$userId); + + $receiver = ReceiverWebhook::create([ + 'apps_id' => $app, + 'action_id' => $action, + 'companies_id' => $company->getId(), + 'users_id' => $user->getId(), + 'name' => $name, + 'description' => $description, + 'is_active' => true, + 'is_deleted' => false, + ]); + + $this->info('Receiver created successfully!'); + $url = config('app.url') . '/receiver/' . $receiver->uuid; + $this->info('Webhook URL: ' . $url); + } +} diff --git a/app/GraphQL/Guild/Queries/PeopleManagementQueries.php b/app/GraphQL/Guild/Queries/PeopleManagementQueries.php new file mode 100644 index 000000000..f141039f9 --- /dev/null +++ b/app/GraphQL/Guild/Queries/PeopleManagementQueries.php @@ -0,0 +1,42 @@ +where('tags.name', $request['tag']); + }); + + $builder->where('is_deleted', StateEnums::NO->getValue()); + $builder = $this->scopeFromCompany($builder); + $builder = $this->scopeFromApp($builder); + + return $builder->count(); + } + + public function getBySubscriptionType(mixed $root, array $request, GraphQLContext $context): int + { + $builder = People::whereHas('subscriptions', function ($query) use ($request) { + $query->where('peoples_subscriptions.subscription_type', $request['type']); + }); + + $builder->where('is_deleted', StateEnums::NO->getValue()); + $builder = $this->scopeFromCompany($builder); + $builder = $this->scopeFromApp($builder); + + return $builder->count(); + } +} diff --git a/app/GraphQL/Social/Builders/Interactions/EntityInteractionsBuilder.php b/app/GraphQL/Social/Builders/Interactions/EntityInteractionsBuilder.php new file mode 100644 index 000000000..977cf8b05 --- /dev/null +++ b/app/GraphQL/Social/Builders/Interactions/EntityInteractionsBuilder.php @@ -0,0 +1,25 @@ +uuid) + ->where('entity_namespace', '=', $root::class) + ->where('is_deleted', '=', StateEnums::NO->getValue()); + } +} diff --git a/app/GraphQL/Social/Mutations/Channels/ChannelsManagementMutation.php b/app/GraphQL/Social/Mutations/Channels/ChannelsManagementMutation.php index cb7bda3d6..b2c9d062a 100644 --- a/app/GraphQL/Social/Mutations/Channels/ChannelsManagementMutation.php +++ b/app/GraphQL/Social/Mutations/Channels/ChannelsManagementMutation.php @@ -4,6 +4,9 @@ namespace App\GraphQL\Social\Mutations\Channels; +use Baka\Support\Str; +use Exception; +use Kanvas\AccessControlList\Enums\RolesEnums; use Kanvas\AccessControlList\Repositories\RolesRepository; use Kanvas\Apps\Models\Apps; use Kanvas\Social\Channels\Actions\CreateChannelAction; @@ -12,7 +15,6 @@ use Kanvas\Social\Channels\Repositories\ChannelRepository; use Kanvas\SystemModules\Repositories\SystemModulesRepository; use Kanvas\Users\Models\Users; -use Baka\Support\Str; class ChannelsManagementMutation { @@ -26,7 +28,7 @@ public function createChannel(mixed $rootValue, array $request): Channel name: $request['input']['name'], description: $request['input']['description'], entity_id: $request['input']['entity_id'], - entity_namespace: $systemModule->uuid, + entity_namespace: $systemModule->model_name, slug: $request['input']['slug'] ?? Str::slug($request['input']['name']) ); @@ -65,7 +67,12 @@ public function attachUserToChannel(mixed $rootValue, array $request): Channel { $channel = ChannelRepository::getById((int)$request['input']['channel_id'], auth()->user()); $user = Users::getByIdFromCompany($request['input']['user_id'], auth()->user()->getCurrentCompany()); - $roles = RolesRepository::getByIdFromCompany($request['input']['roles_id'], auth()->user()->getCurrentCompany()); + + try { + $roles = RolesRepository::getByMixedParamFromCompany($request['input']['roles_id'], auth()->user()->getCurrentCompany()); + } catch (Exception $e) { + $roles = RolesRepository::getByMixedParamFromCompany(RolesEnums::USER->value, auth()->user()->getCurrentCompany()); + } $channel->users()->attach($user->id, ['roles_id' => $roles->id]); return $channel; diff --git a/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php b/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php index 8f3f0c666..0cdefc2fe 100644 --- a/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php +++ b/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php @@ -10,17 +10,12 @@ use Kanvas\Apps\Models\Apps; use Kanvas\Auth\Exceptions\AuthenticationException; use Kanvas\Exceptions\ValidationException; -use Kanvas\Social\Enums\InteractionEnum; -use Kanvas\Social\Interactions\Actions\CreateInteraction; -use Kanvas\Social\Interactions\DataTransferObject\Interaction; use Kanvas\Social\Messages\Actions\CreateMessageAction; use Kanvas\Social\Messages\Actions\DistributeChannelAction; use Kanvas\Social\Messages\Actions\DistributeToUsers; use Kanvas\Social\Messages\DataTransferObject\MessageInput; -use Kanvas\Social\Messages\Enums\ActivityTypeEnum; use Kanvas\Social\Messages\Enums\DistributionTypeEnum; use Kanvas\Social\Messages\Models\Message; -use Kanvas\Social\Messages\Services\MessageInteractionService; use Kanvas\Social\Messages\Validations\ValidParentMessage; use Kanvas\Social\MessagesTypes\Actions\CreateMessageTypeAction; use Kanvas\Social\MessagesTypes\DataTransferObject\MessageTypeInput; diff --git a/app/Http/Controllers/ReceiverController.php b/app/Http/Controllers/ReceiverController.php index 0f1537c8c..c5640a51b 100644 --- a/app/Http/Controllers/ReceiverController.php +++ b/app/Http/Controllers/ReceiverController.php @@ -10,6 +10,7 @@ use Illuminate\Routing\Controller as BaseController; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Kanvas\Apps\Models\Apps; use Kanvas\Connectors\Zoho\Actions\SyncZohoAgentAction; use Kanvas\Connectors\Zoho\Actions\SyncZohoLeadAction; @@ -30,7 +31,6 @@ public function store(string $uuid, Request $request): JsonResponse { $app = app(Apps::class); $receiver = ReceiverWebhook::where('uuid', $uuid)->notDeleted()->first(); - if ($receiver) { // return response()->json(['message' => 'Receiver not found'], 404); if ($app->getId() != $receiver->apps_id) { diff --git a/composer.json b/composer.json index 9ffe4913c..ffe5a67be 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "spatie/laravel-health": "^1.27", "spatie/laravel-queueable-action": "^2.15", "spatie/laravel-webhook-server": "^3.8", + "stripe/stripe-php": "^15.0", "symfony/expression-language": "^7.0", "symfony/http-client": "^7.0", "symfony/mailgun-mailer": "^7.0", diff --git a/composer.lock b/composer.lock index 959fc8b30..fa72f837d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "66a75e57f8f8feba0b4e9acf4ac16f7c", + "content-hash": "9fe1b13ba14b05cd9bf48c1443242686", "packages": [ { "name": "algolia/algoliasearch-client-php", @@ -1195,16 +1195,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.315.5", + "version": "3.316.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3e6d619d45d8e1a8681dd58de61ddfe90e8341e6" + "reference": "4d8caae512c3be4d59ee6d583b3f82872dde5071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3e6d619d45d8e1a8681dd58de61ddfe90e8341e6", - "reference": "3e6d619d45d8e1a8681dd58de61ddfe90e8341e6", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4d8caae512c3be4d59ee6d583b3f82872dde5071", + "reference": "4d8caae512c3be4d59ee6d583b3f82872dde5071", "shasum": "" }, "require": { @@ -1284,9 +1284,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.315.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.316.2" }, - "time": "2024-07-03T18:12:51+00:00" + "time": "2024-07-10T19:16:28+00:00" }, { "name": "berkayk/onesignal-laravel", @@ -2434,34 +2434,34 @@ }, { "name": "google/apiclient", - "version": "v2.16.0", + "version": "v2.17.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client.git", - "reference": "017400f609c1fb71ab5ad824c50eabd4c3eaf779" + "reference": "b1f63d72c44307ec8ef7bf18f1012de35d8944ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/017400f609c1fb71ab5ad824c50eabd4c3eaf779", - "reference": "017400f609c1fb71ab5ad824c50eabd4c3eaf779", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/b1f63d72c44307ec8ef7bf18f1012de35d8944ed", + "reference": "b1f63d72c44307ec8ef7bf18f1012de35d8944ed", "shasum": "" }, "require": { - "firebase/php-jwt": "~6.0", + "firebase/php-jwt": "^6.0", "google/apiclient-services": "~0.350", "google/auth": "^1.37", - "guzzlehttp/guzzle": "^6.5.8||^7.4.5", - "guzzlehttp/psr7": "^1.9.1||^2.2.1", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.6", "monolog/monolog": "^2.9||^3.0", - "php": "^7.4|^8.0", + "php": "^8.0", "phpseclib/phpseclib": "^3.0.36" }, "require-dev": { "cache/filesystem-adapter": "^1.1", "composer/composer": "^1.10.23", "phpcompatibility/php-compatibility": "^9.2", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", + "phpspec/prophecy-phpunit": "^2.1", + "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "^3.8", "symfony/css-selector": "~2.1", "symfony/dom-crawler": "~2.1" @@ -2497,22 +2497,22 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client/issues", - "source": "https://github.com/googleapis/google-api-php-client/tree/v2.16.0" + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.17.0" }, - "time": "2024-04-24T00:59:47+00:00" + "time": "2024-07-10T14:57:54+00:00" }, { "name": "google/apiclient-services", - "version": "v0.362.0", + "version": "v0.363.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "c5016217c8aba823a8ebeed6ccd75d93522e3311" + "reference": "5a0943e498e98e23dccdd98c5a3f74bc1b442053" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/c5016217c8aba823a8ebeed6ccd75d93522e3311", - "reference": "c5016217c8aba823a8ebeed6ccd75d93522e3311", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/5a0943e498e98e23dccdd98c5a3f74bc1b442053", + "reference": "5a0943e498e98e23dccdd98c5a3f74bc1b442053", "shasum": "" }, "require": { @@ -2541,22 +2541,22 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.362.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.363.0" }, - "time": "2024-06-28T01:06:13+00:00" + "time": "2024-07-02T01:04:18+00:00" }, { "name": "google/auth", - "version": "v1.40.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "bff9f2d01677e71a98394b5ac981b99523df5178" + "reference": "1043ea18fe7f5dfbf5b208ce3ee6d6b6ab8cb038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/bff9f2d01677e71a98394b5ac981b99523df5178", - "reference": "bff9f2d01677e71a98394b5ac981b99523df5178", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/1043ea18fe7f5dfbf5b208ce3ee6d6b6ab8cb038", + "reference": "1043ea18fe7f5dfbf5b208ce3ee6d6b6ab8cb038", "shasum": "" }, "require": { @@ -2601,9 +2601,9 @@ "support": { "docs": "https://googleapis.github.io/google-auth-library-php/main/", "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.40.0" + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.41.0" }, - "time": "2024-05-31T19:16:15+00:00" + "time": "2024-07-10T15:21:07+00:00" }, { "name": "google/cloud-core", @@ -2673,16 +2673,16 @@ }, { "name": "google/cloud-storage", - "version": "v1.42.0", + "version": "v1.42.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-cloud-php-storage.git", - "reference": "1c77f5882c30bec95ab2837b9534a946325d1c57" + "reference": "2a418cad887e44d08a86de19a878ea3607212edb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-cloud-php-storage/zipball/1c77f5882c30bec95ab2837b9534a946325d1c57", - "reference": "1c77f5882c30bec95ab2837b9534a946325d1c57", + "url": "https://api.github.com/repos/googleapis/google-cloud-php-storage/zipball/2a418cad887e44d08a86de19a878ea3607212edb", + "reference": "2a418cad887e44d08a86de19a878ea3607212edb", "shasum": "" }, "require": { @@ -2724,9 +2724,9 @@ ], "description": "Cloud Storage Client for PHP", "support": { - "source": "https://github.com/googleapis/google-cloud-php-storage/tree/v1.42.0" + "source": "https://github.com/googleapis/google-cloud-php-storage/tree/v1.42.1" }, - "time": "2024-05-19T17:27:42+00:00" + "time": "2024-07-08T23:14:13+00:00" }, { "name": "google/common-protos", @@ -3906,16 +3906,16 @@ }, { "name": "laravel/framework", - "version": "v11.14.0", + "version": "v11.15.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "657e8464e13147d56bc3a399115c8c26f38d4821" + "reference": "ba85f1c019bed59b3c736c9c4502805efd0ba84b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/657e8464e13147d56bc3a399115c8c26f38d4821", - "reference": "657e8464e13147d56bc3a399115c8c26f38d4821", + "url": "https://api.github.com/repos/laravel/framework/zipball/ba85f1c019bed59b3c736c9c4502805efd0ba84b", + "reference": "ba85f1c019bed59b3c736c9c4502805efd0ba84b", "shasum": "" }, "require": { @@ -4021,7 +4021,7 @@ "nyholm/psr7": "^1.2", "orchestra/testbench-core": "^9.1.5", "pda/pheanstalk": "^5.0", - "phpstan/phpstan": "^1.4.7", + "phpstan/phpstan": "^1.11.5", "phpunit/phpunit": "^10.5|^11.0", "predis/predis": "^2.0.2", "resend/resend-php": "^0.10.0", @@ -4108,20 +4108,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-07-02T17:23:58+00:00" + "time": "2024-07-09T15:38:12+00:00" }, { "name": "laravel/octane", - "version": "v2.5.1", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/laravel/octane.git", - "reference": "21b319dd699c20821554a8f806c2487c6cb2cf59" + "reference": "ba9b7fffc3551eb8ba6d9e708866463e42f27511" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/octane/zipball/21b319dd699c20821554a8f806c2487c6cb2cf59", - "reference": "21b319dd699c20821554a8f806c2487c6cb2cf59", + "url": "https://api.github.com/repos/laravel/octane/zipball/ba9b7fffc3551eb8ba6d9e708866463e42f27511", + "reference": "ba9b7fffc3551eb8ba6d9e708866463e42f27511", "shasum": "" }, "require": { @@ -4198,7 +4198,7 @@ "issues": "https://github.com/laravel/octane/issues", "source": "https://github.com/laravel/octane" }, - "time": "2024-07-01T20:50:27+00:00" + "time": "2024-07-08T14:44:19+00:00" }, { "name": "laravel/prompts", @@ -6712,16 +6712,16 @@ }, { "name": "php-amqplib/php-amqplib", - "version": "v3.6.2", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/php-amqplib/php-amqplib.git", - "reference": "cb514530ce45a6d2f636be5196010c47c3bcf6e0" + "reference": "91fd00e74cd2eea624fd50a321d926b1c124bb99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/cb514530ce45a6d2f636be5196010c47c3bcf6e0", - "reference": "cb514530ce45a6d2f636be5196010c47c3bcf6e0", + "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/91fd00e74cd2eea624fd50a321d926b1c124bb99", + "reference": "91fd00e74cd2eea624fd50a321d926b1c124bb99", "shasum": "" }, "require": { @@ -6787,9 +6787,9 @@ ], "support": { "issues": "https://github.com/php-amqplib/php-amqplib/issues", - "source": "https://github.com/php-amqplib/php-amqplib/tree/v3.6.2" + "source": "https://github.com/php-amqplib/php-amqplib/tree/v3.7.0" }, - "time": "2024-04-15T18:31:22+00:00" + "time": "2024-07-09T21:10:28+00:00" }, { "name": "php-http/client-common", @@ -8669,16 +8669,16 @@ }, { "name": "sentry/sentry-laravel", - "version": "4.6.1", + "version": "4.7.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "7f5fd9f362e440c4c0c492f386b93095321f9101" + "reference": "44151798253145c1abc7fe7e38210b98f926d794" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/7f5fd9f362e440c4c0c492f386b93095321f9101", - "reference": "7f5fd9f362e440c4c0c492f386b93095321f9101", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/44151798253145c1abc7fe7e38210b98f926d794", + "reference": "44151798253145c1abc7fe7e38210b98f926d794", "shasum": "" }, "require": { @@ -8742,7 +8742,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.6.1" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.7.0" }, "funding": [ { @@ -8754,7 +8754,7 @@ "type": "custom" } ], - "time": "2024-06-18T15:06:09+00:00" + "time": "2024-07-10T21:55:04+00:00" }, { "name": "shopify/shopify-api", @@ -9731,6 +9731,65 @@ ], "time": "2023-12-25T11:46:58+00:00" }, + { + "name": "stripe/stripe-php", + "version": "v15.2.0", + "source": { + "type": "git", + "url": "https://github.com/stripe/stripe-php.git", + "reference": "29a28251d35dba236ad6e860e1927b3f35cc1b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/29a28251d35dba236ad6e860e1927b3f35cc1b2e", + "reference": "29a28251d35dba236ad6e860e1927b3f35cc1b2e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^5.7 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Stripe\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "description": "Stripe PHP Library", + "homepage": "https://stripe.com/", + "keywords": [ + "api", + "payment processing", + "stripe" + ], + "support": { + "issues": "https://github.com/stripe/stripe-php/issues", + "source": "https://github.com/stripe/stripe-php/tree/v15.2.0" + }, + "time": "2024-07-11T18:46:58+00:00" + }, { "name": "symfony/cache", "version": "v7.1.2", @@ -14461,16 +14520,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.2.6", + "version": "11.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1dc0fedac703199e8704de085e47dd46bac0dde4" + "reference": "15c7e69dec4a8f246840859e6b430bd2abeb5039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1dc0fedac703199e8704de085e47dd46bac0dde4", - "reference": "1dc0fedac703199e8704de085e47dd46bac0dde4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/15c7e69dec4a8f246840859e6b430bd2abeb5039", + "reference": "15c7e69dec4a8f246840859e6b430bd2abeb5039", "shasum": "" }, "require": { @@ -14480,25 +14539,25 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0", - "phpunit/php-file-iterator": "^5.0", - "phpunit/php-invoker": "^5.0", - "phpunit/php-text-template": "^4.0", - "phpunit/php-timer": "^7.0", - "sebastian/cli-parser": "^3.0", - "sebastian/code-unit": "^3.0", - "sebastian/comparator": "^6.0", - "sebastian/diff": "^6.0", - "sebastian/environment": "^7.0", - "sebastian/exporter": "^6.1.2", - "sebastian/global-state": "^7.0", - "sebastian/object-enumerator": "^6.0", - "sebastian/type": "^5.0", - "sebastian/version": "^5.0" + "phpunit/php-code-coverage": "^11.0.5", + "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.1", + "sebastian/comparator": "^6.0.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.1.3", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.0.1", + "sebastian/version": "^5.0.1" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -14541,7 +14600,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.7" }, "funding": [ { @@ -14557,7 +14616,7 @@ "type": "tidelift" } ], - "time": "2024-07-03T05:51:49+00:00" + "time": "2024-07-10T11:50:09+00:00" }, { "name": "sebastian/cli-parser", diff --git a/database/migrations/Guild/2024_06_25_032857_peoples_subscriptions.php b/database/migrations/Guild/2024_06_25_032857_peoples_subscriptions.php new file mode 100644 index 000000000..e31967662 --- /dev/null +++ b/database/migrations/Guild/2024_06_25_032857_peoples_subscriptions.php @@ -0,0 +1,41 @@ +id(); + $table->bigInteger('apps_id')->unsigned()->index(); + $table->bigInteger('peoples_id')->unsigned()->index(); + $table->string('subscription_type')->index(); + $table->string('status')->index(); + $table->date('first_date'); + $table->date('start_date'); + $table->date('end_date')->nullable()->index(); + $table->date('next_renewal')->nullable()->index(); + $table->json('metadata')->nullable(); + $table->boolean('is_deleted')->default(0)->index(); + $table->dateTime('created_at')->index('created_at'); + $table->dateTime('updated_at')->nullable()->index('updated_at'); + + //index apps people + $table->index(['apps_id', 'peoples_id']); + $table->index(['peoples_id', 'subscription_type']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('peoples_subscriptions'); + } +}; diff --git a/database/migrations/Inventory/2024_07_13_052119_update_product_slug_index.php b/database/migrations/Inventory/2024_07_13_052119_update_product_slug_index.php new file mode 100644 index 000000000..d5fccb9f9 --- /dev/null +++ b/database/migrations/Inventory/2024_07_13_052119_update_product_slug_index.php @@ -0,0 +1,31 @@ +dropUnique(['companies_id', 'slug']); + + $table->unique(['companies_id', 'slug', 'is_deleted']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('products', function (Blueprint $table) { + $table->dropUnique(['companies_id', 'slug', 'is_deleted']); + + $table->unique(['companies_id', 'slug']); + }); + } +}; diff --git a/database/migrations/Social/2024_07_13_045959_add_app_id_to_user_interactions.php b/database/migrations/Social/2024_07_13_045959_add_app_id_to_user_interactions.php new file mode 100644 index 000000000..acad3492b --- /dev/null +++ b/database/migrations/Social/2024_07_13_045959_add_app_id_to_user_interactions.php @@ -0,0 +1,27 @@ +bigInteger('apps_id')->after('users_id')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users_interactions', function (Blueprint $table) { + $table->dropColumn('apps_id'); + }); + } +}; diff --git a/database/migrations/Souk/2024_05_06_015856_add_order_table.php b/database/migrations/Souk/2024_05_06_015856_add_order_table.php index ed905b3d0..383fa58f2 100644 --- a/database/migrations/Souk/2024_05_06_015856_add_order_table.php +++ b/database/migrations/Souk/2024_05_06_015856_add_order_table.php @@ -33,7 +33,7 @@ public function up() $table->string('discount_name', 255)->nullable(); $table->unsignedBigInteger('voucher_id')->nullable()->index(); $table->string('language_code', 10)->nullable()->index(); - $table->enum('status', ['draft', 'completed', 'canceled'])->nullable()->index(); + $table->enum('status', ['draft', 'completed', 'canceled', 'cancelled'])->nullable()->index(); $table->string('shipping_method_name', 255)->nullable(); $table->unsignedBigInteger('shipping_method_id')->nullable()->index(); $table->boolean('display_gross_prices')->default(false); diff --git a/docker-compose.development.yml b/docker-compose.development.yml index c08250deb..489bb435f 100644 --- a/docker-compose.development.yml +++ b/docker-compose.development.yml @@ -1,11 +1,16 @@ version: '3.8' -x-common-settings: &common-settings +x-common-queue-settings: &common-queue-settings + restart: always build: context: . dockerfile: development.Dockerfile extra_hosts: - "host.docker.internal:host-gateway" + command: + - "sh" + - "-c" + - "php artisan config:cache && php artisan queue:work --tries=3 --timeout=3750" environment: WWWUSER: "${WWWUSER}" LARAVEL_SAIL: 1 @@ -18,62 +23,89 @@ x-common-settings: &common-settings networks: - sail -x-common-queue-command: &common-queue-command - [ - "sh", - "-c", - "php artisan config:cache && php artisan queue:work --tries=3 --timeout=3750", - ] - services: php: container_name: php${APP_CONTAINER_NAME} - <<: *common-settings + build: + context: . + dockerfile: development.Dockerfile + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + WWWUSER: "${WWWUSER}" + LARAVEL_SAIL: 1 + XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}" + XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}" + volumes: + - ".:/var/www/html" + - ./docker/docker-php-ext-opcache.ini:/usr/local/etc/php/conf.d/docker-php-ext-opcache.ini + - ./docker/php.ini:/usr/local/etc/php/conf.d/xz-custom.ini + networks: + - sail queue: + <<: *common-queue-settings container_name: queue - <<: *common-settings - command: *common-queue-command queue2: + <<: *common-queue-settings container_name: queue2 - <<: *common-settings - command: *common-queue-command queue3: + <<: *common-queue-settings container_name: queue3 - <<: *common-settings - command: *common-queue-command + + queue4: + <<: *common-queue-settings + container_name: queue4 + + queue5: + <<: *common-queue-settings + container_name: queue5 + + queue6: + <<: *common-queue-settings + container_name: queue6 queue-social: + <<: *common-queue-settings container_name: queue-social - <<: *common-settings - command: - [ - "sh", - "-c", - "php artisan config:cache && php artisan queue:work --queue kanvas-social --tries=3 --timeout=3750", - ] + command: + - "sh" + - "-c" + - "php artisan config:cache && php artisan queue:work --queue kanvas-social --tries=3 --timeout=3750" queue-notifications: + <<: *common-queue-settings container_name: queue-notifications - <<: *common-settings - command: - [ - "sh", - "-c", - "php artisan config:cache && php artisan queue:work --queue notifications --tries=3 --timeout=3750", - ] + command: + - "sh" + - "-c" + - "php artisan config:cache && php artisan queue:work --queue notifications --tries=3 --timeout=3750" laravel-scheduler: container_name: laravel-scheduler - <<: *common-settings - command: - [ - "sh", - "-c", - "php artisan config:cache && php artisan schedule:work", - ] + restart: always + build: + context: . + dockerfile: development.Dockerfile + extra_hosts: + - "host.docker.internal:host-gateway" + command: + - "sh" + - "-c" + - "php artisan config:cache && php artisan schedule:work" + environment: + WWWUSER: "${WWWUSER}" + LARAVEL_SAIL: 1 + XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}" + XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}" + volumes: + - ".:/var/www/html" + - ./docker/docker-php-ext-opcache.ini:/usr/local/etc/php/conf.d/docker-php-ext-opcache.ini + - ./docker/php.ini:/usr/local/etc/php/conf.d/xz-custom.ini + networks: + - sail nginx: image: nginx:latest diff --git a/graphql/schemas/Guild/people.graphql b/graphql/schemas/Guild/people.graphql index 29ba17bb6..88c2d1c13 100644 --- a/graphql/schemas/Guild/people.graphql +++ b/graphql/schemas/Guild/people.graphql @@ -7,10 +7,12 @@ type People { firstname: String! middlename: String lastname: String + subscriptions: [PeopleSubscription!]! @hasMany dob: Date organizations: [Organization!] @belongsToMany contacts: [Contact!]! @hasMany address: [Address!]! @hasMany + employment_history: [PeopleEmploymentHistory!] @hasMany(relation: "employmentHistory") files: [Filesystem!]! @@ -23,12 +25,27 @@ type People { defaultCount: 25 builder: "App\\GraphQL\\Ecosystem\\Queries\\CustomFields\\CustomFieldQuery@getAllByGraphType" ) + interactions: [EntityInteraction!]! + @paginate( + defaultCount: 25 + builder: "App\\GraphQL\\Social\\Builders\\Interactions\\EntityInteractionsBuilder@getAll" + ) tags: [Tag!] @paginate( defaultCount: 25 builder: "App\\GraphQL\\Social\\Queries\\Tags\\TagsQueries@getTagsBuilder" ) } +type PeopleSubscription { + id: ID! + subscription_type: String! + status: String! + start_date: Date! + end_date: Date + first_date: Date + next_renewal: Date +} + type PeopleEmploymentHistory { id: ID! organization: Organization! @belongsTo @@ -133,7 +150,14 @@ extend type Query @guard { model: "Kanvas\\Guild\\Customers\\Models\\People" scopes: ["fromApp", "fromCompany", "notDeleted"] ) - + peopleCountByTag(tag: String!): Int + @field( + resolver: "App\\GraphQL\\Guild\\Queries\\PeopleManagementQueries@countByTag" + ) + peopleCountBySubscriptionType(type: String!): Int + @field( + resolver: "App\\GraphQL\\Guild\\Queries\\PeopleManagementQueries@getBySubscriptionType" + ) } extend type Mutation @guard { diff --git a/graphql/schemas/Social/interactions.graphql b/graphql/schemas/Social/interactions.graphql index a9f3d985d..a0fd0403d 100644 --- a/graphql/schemas/Social/interactions.graphql +++ b/graphql/schemas/Social/interactions.graphql @@ -11,7 +11,7 @@ type Interactions { dislike: Boolean } -type InteractionType { +type SocialInteractionType { name: String! title: String! description: String @@ -26,6 +26,15 @@ type EntityInteractions { entity: Mixed @method(name: "interactedEntityData") } +type EntityInteraction { + interacted_entity_id: Mixed! + interacted_entity_namespace: String! + entity: Mixed! @method(name: "interactedEntityData") + interaction: SocialInteractionType! + notes: Mixed + created_at: Date! +} + extend type Mutation @guardByAuthOrCompany { likeEntity(input: LikeEntityInput!): Boolean! @field( @@ -39,6 +48,7 @@ extend type Mutation @guardByAuthOrCompany { @field( resolver: "App\\GraphQL\\Social\\Mutations\\Interactions\\EntityInteractionMutation@disLikeEntity" ) + @deprecated(reason: "Use likeEntity instead") getInteractionByEntity(input: LikeEntityInput!): Interactions! @field( resolver: "App\\GraphQL\\Social\\Mutations\\Interactions\\EntityInteractionMutation@getInteractionByEntity" diff --git a/graphql/schemas/Social/usersInteractions.graphql b/graphql/schemas/Social/usersInteractions.graphql index 4bd4c0df6..8db0bc57e 100644 --- a/graphql/schemas/Social/usersInteractions.graphql +++ b/graphql/schemas/Social/usersInteractions.graphql @@ -18,10 +18,10 @@ type Interaction { count: Int } -type EntityInteraction { +type UserEntityInteraction { entity_id: ID! entity_namespace: String! - interactions: JSON + interactions: Mixed } input UserInteractionInput { @@ -54,14 +54,15 @@ extend type Query @guard { @paginate( model: "Kanvas\\Social\\Interactions\\Models\\UsersInteractions" defaultCount: 25 + scopes: ["fromApp", "fromUser"] ) } -extend type Query { +extend type Query @guard { getUserInteraction( entity_id: ID! entity_namespace: String! - ): EntityInteraction + ): UserEntityInteraction @field( resolver: "App\\GraphQL\\Social\\Queries\\UsersInteractions\\GetUserInteraction" ) diff --git a/src/Domains/Connectors/Ghost/Jobs/UpdatePeopleGhostSubscriptionJob.php b/src/Domains/Connectors/Ghost/Jobs/UpdatePeopleGhostSubscriptionJob.php new file mode 100644 index 000000000..984913e2b --- /dev/null +++ b/src/Domains/Connectors/Ghost/Jobs/UpdatePeopleGhostSubscriptionJob.php @@ -0,0 +1,42 @@ +webhookRequest->payload; + $app = $this->webhookRequest->receiverWebhook->app; + $company = $this->webhookRequest->receiverWebhook->company; + $people = PeoplesRepository::getByEmail($member['email'], $company); + if (! $people) { + throw new Exception('People not found'); + } + $dto = new PeopleSubscriptionDTO( + app: $app, + people: $people, + subscription_type: 'Free', + status: '1', + first_date: date('Y-m-d H:i:s', $member['created_at']), + start_date: date('Y-m-d H:i:s', $member['created_at']), + metadata: $this->webhookRequest->payload + ); + $action = new CreateOrUpdatePeopleSubscriptionAction($dto); + $peopleSub = $action->handle(); + + return [ + 'success' => true, + 'data' => $peopleSub, + ]; + } +} diff --git a/src/Domains/Connectors/Shopify/Actions/SyncShopifyOrderAction.php b/src/Domains/Connectors/Shopify/Actions/SyncShopifyOrderAction.php index de1bae7fa..df942e8df 100644 --- a/src/Domains/Connectors/Shopify/Actions/SyncShopifyOrderAction.php +++ b/src/Domains/Connectors/Shopify/Actions/SyncShopifyOrderAction.php @@ -76,10 +76,11 @@ public function execute(): ModelsOrder taxes: (float) $this->orderData['current_total_tax'], totalDiscount: (float) $this->orderData['total_discounts'], totalShipping: (float) $this->orderData['total_shipping_price_set']['shop_money']['amount'], - status: 'completed', + status: ! empty($this->orderData['cancelled_at']) ? 'cancelled' : 'completed', orderNumber: (string) $this->orderData['order_number'], shippingMethod: $this->orderData['shipping_lines'][0]['title'] ?? null, currency: Currencies::getByCode($this->orderData['currency']), + fulfillmentStatus: $this->orderData['fulfillment_status'] ?? null, items: $this->getOrderItems(), metadata: json_encode($this->orderData), weight: $this->orderData['total_weight'], @@ -95,6 +96,12 @@ public function execute(): ModelsOrder ); if ($orderExist) { + match (true) { + $order->fulfill() => $orderExist->fulfill(), + $order->isCancelled() => $orderExist->cancel(), + default => null, + }; + return $orderExist; } diff --git a/src/Domains/Connectors/Shopify/Client.php b/src/Domains/Connectors/Shopify/Client.php index 6a88d153f..9d0a7ef8c 100644 --- a/src/Domains/Connectors/Shopify/Client.php +++ b/src/Domains/Connectors/Shopify/Client.php @@ -59,7 +59,7 @@ public static function getKeys(CompanyInterface $company, AppInterface $app, Reg $credential = $company->get($clientCredentialNaming); // its no supposed to explode with this - if (empty($credential)) { + if (empty($credential) || ! is_array($credential)) { throw new ValidationException( 'Shopify keys are not set for company ' . $company->name . ' ' . $company->id . ' ' . 'on region ' . $region->name ); diff --git a/src/Domains/Connectors/Stripe/Enums/ConfigurationEnum.php b/src/Domains/Connectors/Stripe/Enums/ConfigurationEnum.php new file mode 100644 index 000000000..33e68de7f --- /dev/null +++ b/src/Domains/Connectors/Stripe/Enums/ConfigurationEnum.php @@ -0,0 +1,10 @@ +webhookRequest->payload['type'], ['customer.subscription.updated', 'customer.subscription.created', 'customer.subscription.deleted'])) { + Log::error('Webhook type not found', ['type' => $this->webhookRequest->payload['type']]); + + return []; + } + + $this->data = $this->webhookRequest->payload; + $webhookSub = $this->data['data']['object']; + $app = $this->webhookRequest->receiverWebhook->app; + $company = $this->webhookRequest->receiverWebhook->company; + $user = $this->webhookRequest->receiverWebhook->user; + + $stripe = new StripeClient($app->get(ConfigurationEnum::STRIPE_SECRET_KEY->value)); + $customer = $stripe->customers->retrieve( + $webhookSub['customer'], + ['expand' => ['subscriptions']] + ); + + if (! $customer->email) { + Log::error('Customer email not found'); + + return ['error' => 'Customer email not found ' . $customer->id]; + } + $people = PeoplesRepository::getByEmail($customer->email, $company); + if (! $people) { + Log::error('People not found'); + + return ['error' => 'People not found' . $customer->email]; + + return []; + } + $subscriptions = $customer->subscriptions->data[0]; + + $dto = new PeopleSubscriptionDTO( + app: $app, + people: $people, + subscription_type: $subscriptions['plan']['nickname'], + status: '1', + first_date: date('Y-m-d H:i:s', $subscriptions['created']), + start_date: date('Y-m-d H:i:s', $subscriptions['current_period_start']), + end_date: date('Y-m-d H:i:s', $subscriptions['ended_at']), + next_renewal: date('Y-m-d H:i:s', $subscriptions['current_period_end']), + metadata: $this->data ?? [], + ); + $action = new CreateOrUpdatePeopleSubscriptionAction($dto); + $peopleSub = $action->handle(); + + return [ + 'message' => 'People Subscription updated', + 'data' => $peopleSub, + ]; + } +} diff --git a/src/Domains/Connectors/Zoho/Actions/SyncZohoLeadAction.php b/src/Domains/Connectors/Zoho/Actions/SyncZohoLeadAction.php index b7609cac1..12460f3e2 100644 --- a/src/Domains/Connectors/Zoho/Actions/SyncZohoLeadAction.php +++ b/src/Domains/Connectors/Zoho/Actions/SyncZohoLeadAction.php @@ -68,6 +68,7 @@ public function execute(): ?Lead $leadStatus = match (true) { Str::contains($status, 'close') => LeadStatus::getByName('bad'), Str::contains($status, 'won') => LeadStatus::getByName('complete'), + Str::contains($status, 'duplicate') => LeadStatus::getByName('complete'), default => LeadStatus::getByName('active'), }; @@ -95,13 +96,16 @@ public function execute(): ?Lead ]; } + /** + * @todo assign owner and user and member # if exist + */ $lead = new DataTransferObjectLead( - $this->app, - $this->company->defaultBranch, - $user ?? $this->company->user, - $zohoLead->Full_Name, - $pipelineStage->getId(), - new People( + app: $this->app, + branch: $this->company->defaultBranch, + user: $user ?? $this->company->user, + title: $zohoLead->Full_Name, + pipeline_stage_id: $pipelineStage->getId(), + people: new People( $this->app, $this->company->defaultBranch, $user ?? $this->company->user, @@ -110,19 +114,13 @@ public function execute(): ?Lead Address::collect([], DataCollection::class), $zohoLead->Last_Name ), - $user ? $user->getId() : 0, - 0, - $leadStatus->getId(), - 0, - $this->receiver->getId(), - null, - null, - null, - [ + leads_owner_id: $user ? $user->getId() : 0, + status_id: $leadStatus->getId(), + receiver_id: $this->receiver->getId(), + custom_fields: [ CustomFieldEnum::ZOHO_LEAD_ID->value => $this->zohoLeadId, ], - [], - true + runWorkflow: false ); return (new CreateLeadAction($lead))->execute(); diff --git a/src/Domains/Connectors/Zoho/DataTransferObject/ZohoLead.php b/src/Domains/Connectors/Zoho/DataTransferObject/ZohoLead.php index f66681f9a..c34deaf49 100644 --- a/src/Domains/Connectors/Zoho/DataTransferObject/ZohoLead.php +++ b/src/Domains/Connectors/Zoho/DataTransferObject/ZohoLead.php @@ -41,6 +41,8 @@ public static function fromLead(Lead $lead): self $people = $lead->people()->first(); $leadStatus = $lead->status()->first(); $owner = (string) ($lead->owner()->first() ? $company->get(CustomFieldEnum::DEFAULT_OWNER->value) : null); + + //@todo get the lead status from zoho $newLead = 'New Lead'; $status = $leadStatus ? ($leadStatus->get(CustomFieldEnum::ZOHO_STATUS_NAME->value) ?? $newLead) : $newLead; @@ -58,10 +60,11 @@ public static function fromLead(Lead $lead): self public function toArray(): array { - $data = parent::toArray(); - //unset($data['additionalFields']); + $data = array_merge(parent::toArray(), $this->additionalFields); + unset($data['additionalFields']); - return $data; + // Remove empty values + return array_filter($data, fn ($value) => ! empty($value)); } /** diff --git a/src/Domains/Connectors/Zoho/Workflows/ZohoLeadActivity.php b/src/Domains/Connectors/Zoho/Workflows/ZohoLeadActivity.php index 41641c370..29285b7bb 100644 --- a/src/Domains/Connectors/Zoho/Workflows/ZohoLeadActivity.php +++ b/src/Domains/Connectors/Zoho/Workflows/ZohoLeadActivity.php @@ -39,12 +39,13 @@ public function execute(Model $lead, AppInterface $app, array $params): array $usesAgentsModule = $company->get(CustomFieldEnum::ZOHO_HAS_AGENTS_MODULE->value); $zohoCrm = Client::getInstance($app, $company); - - if ($usesAgentsModule) { - $this->assignAgent($app, $zohoLead, $lead, $company, $zohoData); - } + $status = 'created'; if (! $zohoLeadId = $lead->get(CustomFieldEnum::ZOHO_LEAD_ID->value)) { + if ($usesAgentsModule) { + $this->assignAgent($app, $zohoLead, $lead, $company, $zohoData); + } + $zohoLead = $zohoCrm->leads->create($zohoData); $zohoLeadId = $zohoLead->getId(); @@ -55,10 +56,23 @@ public function execute(Model $lead, AppInterface $app, array $params): array $zohoLeadId ); } else { - $zohoLead = $zohoCrm->leads->update( - (string) $zohoLeadId, - $zohoData - ); + $zohoLeadInfo = $zohoCrm->leads->get((string) $zohoLeadId)->getData(); + if (! empty($zohoLeadInfo)) { + $status = 'updated'; + $zohoLead = $zohoCrm->leads->update( + (string) $zohoLeadId, + $zohoData + ); + } else { + $lead->close(); + + return [ + 'zohoLeadId' => $zohoLeadId, + 'zohoRequest' => 'Lead not found in Zoho', + 'leadId' => $lead->getId(), + 'status' => 'closed', + ]; + } } $this->uploadAttachments($zohoCrm->leads, $lead); @@ -67,6 +81,7 @@ public function execute(Model $lead, AppInterface $app, array $params): array 'zohoLeadId' => $zohoLeadId, 'zohoRequest' => $zohoData, 'leadId' => $lead->getId(), + 'status' => $status, ]; } @@ -108,7 +123,15 @@ protected function assignAgent( } if (is_object($agent)) { - $zohoData['Owner'] = (int) $agent->Owner['id']; + try { + ///lead owner should match lead routing + $leadRoutingEmailCleanUp = preg_replace('/[^a-zA-Z0-9@._-]/', '', $agent->Lead_Routing); + $zohoData['Owner'] = $zohoService->getAgentByEmail($leadRoutingEmailCleanUp)->Owner['id']; + } catch (Throwable $e) { + //send fail notification and assign to default lead routing email + $zohoData['Owner'] = (int) ($app->get(CustomFieldEnum::DEFAULT_OWNER->value) ?? $agent->Owner['id']); + } + if ($agent->Sponsor) { $zohoData['Sponsor'] = (string) $agent->Sponsor; } @@ -121,6 +144,7 @@ protected function assignAgent( if ($agentInfo && $agentInfo->get('over_write_owner')) { $zohoData['Owner'] = (int) $agentInfo->get('over_write_owner'); } + $zohoData['Lead_Source'] = $agent->name ?? $agent->Name; } elseif ($agentInfo instanceof Agent) { $zohoData['Owner'] = (int) $agentInfo->owner_linked_source_id; if (empty($defaultLeadSource)) { diff --git a/src/Domains/Guild/Customers/Actions/CreateOrUpdatePeopleSubscriptionAction.php b/src/Domains/Guild/Customers/Actions/CreateOrUpdatePeopleSubscriptionAction.php new file mode 100644 index 000000000..f937b0080 --- /dev/null +++ b/src/Domains/Guild/Customers/Actions/CreateOrUpdatePeopleSubscriptionAction.php @@ -0,0 +1,38 @@ + $this->peopleSubscriptionDTO->subscription_type, + 'status' => '1', + 'first_date' => $this->peopleSubscriptionDTO->first_date, + 'start_date' => $this->peopleSubscriptionDTO->start_date, + 'end_date' => $this->peopleSubscriptionDTO->end_date, + 'next_renewal' => $this->peopleSubscriptionDTO->next_renewal, + 'metadata' => $this->peopleSubscriptionDTO->metadata, + 'apps_id' => $this->peopleSubscriptionDTO->app->getId(), + ]; + + return PeopleSubscription::updateOrCreate( + $dataPeopleSub, + [ + 'peoples_id' => $this->peopleSubscriptionDTO->people->getId(), + 'subscription_type' => $this->peopleSubscriptionDTO->subscription_type, + ] + ); + } +} diff --git a/src/Domains/Guild/Customers/DataTransferObject/PeopleSubscription.php b/src/Domains/Guild/Customers/DataTransferObject/PeopleSubscription.php new file mode 100644 index 000000000..2cc66e3ac --- /dev/null +++ b/src/Domains/Guild/Customers/DataTransferObject/PeopleSubscription.php @@ -0,0 +1,25 @@ + ContactTypeEnum::EMAIL->value, + 'value' => fake()->email, + 'weight' => 1, + ]; + } +} diff --git a/src/Domains/Guild/Customers/Models/Contact.php b/src/Domains/Guild/Customers/Models/Contact.php index 4f45fff14..29d7ddc92 100644 --- a/src/Domains/Guild/Customers/Models/Contact.php +++ b/src/Domains/Guild/Customers/Models/Contact.php @@ -7,6 +7,7 @@ use Baka\Traits\NoAppRelationshipTrait; use Baka\Traits\NoCompanyRelationshipTrait; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Kanvas\Guild\Customers\Factories\ContactFactory; use Kanvas\Guild\Models\BaseModel; /** @@ -17,7 +18,6 @@ * @property int $peoples_id * @property string $value * @property int $weight - * */ class Contact extends BaseModel { @@ -44,4 +44,9 @@ public function type(): BelongsTo 'id' ); } + + protected static function newFactory() + { + return new ContactFactory(); + } } diff --git a/src/Domains/Guild/Customers/Models/People.php b/src/Domains/Guild/Customers/Models/People.php index 5b814669e..0f37e7fbf 100644 --- a/src/Domains/Guild/Customers/Models/People.php +++ b/src/Domains/Guild/Customers/Models/People.php @@ -13,6 +13,7 @@ use Kanvas\Guild\Customers\Factories\PeopleFactory; use Kanvas\Guild\Models\BaseModel; use Kanvas\Guild\Organizations\Models\Organization; +use Kanvas\Social\Interactions\Traits\SocialInteractionsTrait; use Kanvas\Locations\Models\Countries; use Kanvas\Social\Tags\Traits\HasTagsTrait; use Kanvas\Workflow\Traits\CanUseWorkflow; @@ -44,6 +45,7 @@ class People extends BaseModel use Searchable; use HasTagsTrait; use CanUseWorkflow; + use SocialInteractionsTrait; use Notifiable; protected $table = 'peoples'; @@ -115,6 +117,15 @@ public function phones(): HasMany ); } + public function subscriptions(): HasMany + { + return $this->hasMany( + PeopleSubscription::class, + 'peoples_id', + 'id' + ); + } + /** * @psalm-suppress MixedReturnStatement */ diff --git a/src/Domains/Guild/Customers/Models/PeopleSubscription.php b/src/Domains/Guild/Customers/Models/PeopleSubscription.php new file mode 100644 index 000000000..7b213801a --- /dev/null +++ b/src/Domains/Guild/Customers/Models/PeopleSubscription.php @@ -0,0 +1,29 @@ + Json::class, + ]; +} diff --git a/src/Domains/Guild/Leads/Actions/CreateLeadAction.php b/src/Domains/Guild/Leads/Actions/CreateLeadAction.php index bd4270dda..b50613eeb 100644 --- a/src/Domains/Guild/Leads/Actions/CreateLeadAction.php +++ b/src/Domains/Guild/Leads/Actions/CreateLeadAction.php @@ -61,12 +61,12 @@ public function execute(): Lead $newLead->people_id = $people->getId(); $newLead->email = $people->getEmails()->isNotEmpty() ? $people->getEmails()->first()?->value : null; $newLead->phone = $people->getPhones()->isNotEmpty() ? $people->getPhones()->first()?->value : null; - $newLead->saveOrFail(); - - $newLead->setCustomFields($this->leadData->custom_fields); if (! $this->leadData->runWorkflow) { $newLead->disableWorkflows(); } + $newLead->saveOrFail(); + + $newLead->setCustomFields($this->leadData->custom_fields); $newLead->saveCustomFields(); if ($this->leadData->files) { diff --git a/src/Domains/Guild/Leads/Jobs/CreateLeadsFromReceiverJob.php b/src/Domains/Guild/Leads/Jobs/CreateLeadsFromReceiverJob.php index b401530ca..f10eaa8e9 100644 --- a/src/Domains/Guild/Leads/Jobs/CreateLeadsFromReceiverJob.php +++ b/src/Domains/Guild/Leads/Jobs/CreateLeadsFromReceiverJob.php @@ -40,6 +40,7 @@ public function execute(): array $payload = $parseTemplate->execute(); } + $payload['receiver_id'] = $leadReceiver->getId(); $createLead = new CreateLeadAction( Lead::viaRequest( $leadReceiver->user, diff --git a/src/Domains/Inventory/Channels/DataTransferObject/Channels.php b/src/Domains/Inventory/Channels/DataTransferObject/Channels.php index 542626f7d..937aa6be0 100644 --- a/src/Domains/Inventory/Channels/DataTransferObject/Channels.php +++ b/src/Domains/Inventory/Channels/DataTransferObject/Channels.php @@ -27,6 +27,7 @@ public function __construct( public ?string $description = null, public bool $is_default = false, public bool $is_published = true, + public ?string $slug = null ) { } diff --git a/src/Domains/Social/Channels/Models/Channel.php b/src/Domains/Social/Channels/Models/Channel.php index cd2e1fbc2..0a662c50e 100644 --- a/src/Domains/Social/Channels/Models/Channel.php +++ b/src/Domains/Social/Channels/Models/Channel.php @@ -20,6 +20,8 @@ * @property string $slug * @property string $description * @property int $last_message_id + * @property int $apps_id + * @property int $companies_id * @property int $entity_id * @property int $entity_namespace */ @@ -42,7 +44,7 @@ public function users(): BelongsToMany public function systemModule(): BelongsTo { - return $this->belongsTo(SystemModules::class, 'entity_namespace', 'model_name'); + return $this->belongsTo(SystemModules::class, 'entity_namespace', 'model_name')->where('apps_id', $this->apps_id); } public function messages(): BelongsToMany diff --git a/src/Domains/Social/Channels/Repositories/ChannelRepository.php b/src/Domains/Social/Channels/Repositories/ChannelRepository.php index 9b1ee3dd7..049f14104 100644 --- a/src/Domains/Social/Channels/Repositories/ChannelRepository.php +++ b/src/Domains/Social/Channels/Repositories/ChannelRepository.php @@ -19,7 +19,7 @@ public static function getByIdBuilder(Users $user): Builder { $databaseSocial = config('database.connections.social.database', 'social'); $builder = Channel::join($databaseSocial . '.channel_users', 'channel_users.channel_id', '=', 'channels.id') - ->where('users_id', auth()->user()->id); + ->where('users_id', $user->getId()); return $builder; } diff --git a/src/Domains/Social/Interactions/Actions/CreateEntityInteraction.php b/src/Domains/Social/Interactions/Actions/CreateEntityInteraction.php index f5e843b6d..addf387c5 100644 --- a/src/Domains/Social/Interactions/Actions/CreateEntityInteraction.php +++ b/src/Domains/Social/Interactions/Actions/CreateEntityInteraction.php @@ -10,6 +10,9 @@ use Kanvas\Social\Interactions\DataTransferObject\LikeEntityInput; use Kanvas\Social\Interactions\Models\EntityInteractions; +/** + * @deprecated v1.0 + */ class CreateEntityInteraction { public function __construct( diff --git a/src/Domains/Social/Interactions/Actions/CreateEntityInteractionAction.php b/src/Domains/Social/Interactions/Actions/CreateEntityInteractionAction.php new file mode 100644 index 000000000..9400ae413 --- /dev/null +++ b/src/Domains/Social/Interactions/Actions/CreateEntityInteractionAction.php @@ -0,0 +1,47 @@ +entityInteractionData->interaction, + $this->app, + ucfirst($this->entityInteractionData->interaction), + ) + ); + + $interaction = $createInteractions->execute(); + + return EntityInteractions::updateOrCreate( + [ + 'entity_id' => $this->entityInteractionData->entity->uuid, + 'entity_namespace' => get_class($this->entityInteractionData->entity), + 'interactions_id' => $interaction->getId(), + 'interacted_entity_id' => $this->entityInteractionData->interactedEntity->uuid, + 'interacted_entity_namespace' => get_class($this->entityInteractionData->interactedEntity), + ], + [ + 'notes' => $this->entityInteractionData->note, + 'is_deleted' => StateEnums::NO->getValue(), + ] + ); + } +} diff --git a/src/Domains/Social/Interactions/Actions/CreateInteraction.php b/src/Domains/Social/Interactions/Actions/CreateInteraction.php index 8d14f0cb0..59bdfb011 100644 --- a/src/Domains/Social/Interactions/Actions/CreateInteraction.php +++ b/src/Domains/Social/Interactions/Actions/CreateInteraction.php @@ -16,8 +16,6 @@ public function __construct( /** * execute. - * - * @return Interactions */ public function execute(): Interactions { diff --git a/src/Domains/Social/Interactions/Actions/CreateUserInteractionAction.php b/src/Domains/Social/Interactions/Actions/CreateUserInteractionAction.php index d09c61353..b295a8c99 100644 --- a/src/Domains/Social/Interactions/Actions/CreateUserInteractionAction.php +++ b/src/Domains/Social/Interactions/Actions/CreateUserInteractionAction.php @@ -19,6 +19,7 @@ public function execute(): UsersInteractions { $userInteraction = UsersInteractions::firstOrCreate([ 'users_id' => $this->userInteractionData->user->getId(), + 'apps_id' => $this->userInteractionData->interaction->apps_id, 'entity_id' => $this->userInteractionData->entity_id, 'entity_namespace' => $this->userInteractionData->entity_namespace, 'interactions_id' => $this->userInteractionData->interaction->getId(), diff --git a/src/Domains/Social/Interactions/DataTransferObject/EntityInteraction.php b/src/Domains/Social/Interactions/DataTransferObject/EntityInteraction.php new file mode 100644 index 000000000..4893a1d42 --- /dev/null +++ b/src/Domains/Social/Interactions/DataTransferObject/EntityInteraction.php @@ -0,0 +1,19 @@ +interacted_entity_namespace::notDeleted() - ->where('uuid', $this->interacted_entity_id)->first(); + ->where('uuid', $this->interacted_entity_id) + ->first(); } /** diff --git a/src/Domains/Social/Interactions/Traits/SocialInteractionsTrait.php b/src/Domains/Social/Interactions/Traits/SocialInteractionsTrait.php index 43a3801b5..6c9abe24b 100644 --- a/src/Domains/Social/Interactions/Traits/SocialInteractionsTrait.php +++ b/src/Domains/Social/Interactions/Traits/SocialInteractionsTrait.php @@ -4,13 +4,58 @@ namespace Kanvas\Social\Interactions\Traits; +use Illuminate\Database\Eloquent\Model; +use Kanvas\Social\Interactions\Actions\CreateEntityInteractionAction; +use Kanvas\Social\Interactions\Actions\CreateInteraction; +use Kanvas\Social\Interactions\DataTransferObject\EntityInteraction; +use Kanvas\Social\Interactions\DataTransferObject\Interaction; use Kanvas\Social\Interactions\DataTransferObject\LikeEntityInput; +use Kanvas\Social\Interactions\Models\EntityInteractions; use Kanvas\Social\Interactions\Models\Interactions; +use Kanvas\Social\Interactions\Models\UsersInteractions; use Kanvas\Social\Interactions\Repositories\EntityInteractionsRepository; use Kanvas\Users\Enums\UserConfigEnum; +use Kanvas\Users\Models\Users; trait SocialInteractionsTrait { + use LikableTrait; + + public function addInteraction(Model $entity, string $interaction, ?string $note = null): UsersInteractions|EntityInteractions + { + if ($this instanceof Users) { + $interaction = ( + new CreateInteraction( + new Interaction( + $interaction, + $this->app, + $interaction + ) + ))->execute(); + + return UsersInteractions::firstOrCreate([ + 'users_id' => $this->getId(), + 'interactions_id' => $interaction->getId(), + 'entity_id' => $entity->getId(), + 'entity_namespace' => $entity::class, + 'is_deleted' => 0, + ], [ + 'notes' => $note, + ]); + } + + return ( + new CreateEntityInteractionAction( + (new EntityInteraction( + $this, + $entity, + $interaction, + $note + )), + $this->app + ))->execute(); + } + /** * Given a visitorInput get the social interactions for the entity. * diff --git a/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php b/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php index 6f60e3dc3..a8afb90b1 100644 --- a/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php +++ b/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php @@ -7,7 +7,10 @@ use Exception; use Illuminate\Database\Eloquent\ModelNotFoundException; use Kanvas\Social\Messages\Actions\CreateMessageAction; +use Kanvas\Social\Messages\Actions\DistributeChannelAction; +use Kanvas\Social\Messages\Actions\DistributeToUsers; use Kanvas\Social\Messages\DataTransferObject\MessageInput; +use Kanvas\Social\Messages\Enums\DistributionTypeEnum; use Kanvas\Social\MessagesTypes\Actions\CreateMessageTypeAction; use Kanvas\Social\MessagesTypes\DataTransferObject\MessageTypeInput; use Kanvas\Social\MessagesTypes\Repositories\MessagesTypesRepository; @@ -37,11 +40,12 @@ public function execute(): array $messageType = (new CreateMessageTypeAction($messageTypeDto))->execute(); } + $user = $this->receiver->user; $createMessage = new CreateMessageAction( new MessageInput( app: $this->receiver->app, company: $this->receiver->company, - user: $this->receiver->user, + user: $user, type: $messageType, message: $payload['message'] ) @@ -49,10 +53,29 @@ public function execute(): array $message = $createMessage->execute(); + /** + * @todo refactor this logic here and in message mutation to avoid duplication + */ + if (key_exists('distribution', $payload)) { + $distributionType = DistributionTypeEnum::from($payload['distribution']['distributionType']); + + if ($distributionType->value == DistributionTypeEnum::ALL->value) { + $channels = key_exists('channels', $payload['distribution']) ? $payload['distribution']['channels'] : []; + (new DistributeChannelAction($channels, $message, $user))->execute(); + (new DistributeToUsers($message))->execute(); + } elseif ($distributionType->value == DistributionTypeEnum::Channels->value) { + $channels = key_exists('channels', $payload['distribution']) ? $payload['distribution']['channels'] : []; + (new DistributeChannelAction($channels, $message, $user))->execute(); + } elseif ($distributionType->value == DistributionTypeEnum::Followers->value) { + (new DistributeToUsers($message))->execute(); + } + } + return [ 'message' => 'Message created successfully from receiver with id ' . $message->getId(), 'message_id' => $message->getId(), 'message_verb' => $messageType->verb, + 'distribution' => $payload['distribution'] ?? null, ]; } } diff --git a/src/Domains/Souk/Orders/Actions/CreateOrderAction.php b/src/Domains/Souk/Orders/Actions/CreateOrderAction.php index d1e746631..72683e614 100644 --- a/src/Domains/Souk/Orders/Actions/CreateOrderAction.php +++ b/src/Domains/Souk/Orders/Actions/CreateOrderAction.php @@ -50,6 +50,7 @@ public function execute(): ModelsOrder $order->discount_amount = $this->orderData->totalDiscount; $order->status = $this->orderData->status; $order->shipping_method_name = $this->orderData->shippingMethod; + $order->fulfillment_status = $this->orderData->fulfillmentStatus; $order->weight = $this->orderData->weight; $order->checkout_token = $this->orderData->checkoutToken; $order->currency = $this->orderData->currency->code; diff --git a/src/Domains/Souk/Orders/DataTransferObject/Order.php b/src/Domains/Souk/Orders/DataTransferObject/Order.php index 5ce0a2695..3b681d423 100644 --- a/src/Domains/Souk/Orders/DataTransferObject/Order.php +++ b/src/Domains/Souk/Orders/DataTransferObject/Order.php @@ -49,4 +49,14 @@ public function __construct( public readonly array $paymentGatewayName = [], ) { } + + public function fulfill(): bool + { + return $this->fulfillmentStatus === 'fulfilled'; + } + + public function isCancelled(): bool + { + return $this->status === 'cancelled'; + } } diff --git a/src/Domains/Souk/Orders/Models/Order.php b/src/Domains/Souk/Orders/Models/Order.php index 4f9e6b88f..3c6d84723 100644 --- a/src/Domains/Souk/Orders/Models/Order.php +++ b/src/Domains/Souk/Orders/Models/Order.php @@ -48,6 +48,7 @@ * @property string $status * @property string|null $fulfillment_status * @property string|null $shipping_method_name + * @property string|null $fulfillment_status * @property int|null $shipping_method_id * @property bool $display_gross_prices * @property string|null $translated_discount_name @@ -147,4 +148,28 @@ public function addItem(OrderItemDto $item): OrderItem return $orderItem; } + + public function fulfill(): void + { + $this->fulfillment_status = 'fulfilled'; + $this->saveOrFail(); + } + + public function fulfillCancelled(): void + { + $this->fulfillment_status = 'cancelled'; + $this->saveOrFail(); + } + + public function completed(): void + { + $this->status = 'completed'; + $this->saveOrFail(); + } + + public function cancel(): void + { + $this->status = 'cancelled'; + $this->saveOrFail(); + } } diff --git a/src/Domains/Workflow/Models/WorkflowAction.php b/src/Domains/Workflow/Models/WorkflowAction.php index c721f452d..82d4dc762 100644 --- a/src/Domains/Workflow/Models/WorkflowAction.php +++ b/src/Domains/Workflow/Models/WorkflowAction.php @@ -14,6 +14,8 @@ class WorkflowAction extends BaseModel protected $table = 'actions'; + protected $guarded = []; + protected static function newFactory(): Factory { return ActionFactory::new(); diff --git a/src/Kanvas/Apps/Support/SmtpRuntimeConfiguration.php b/src/Kanvas/Apps/Support/SmtpRuntimeConfiguration.php index 784050541..a828d3d84 100644 --- a/src/Kanvas/Apps/Support/SmtpRuntimeConfiguration.php +++ b/src/Kanvas/Apps/Support/SmtpRuntimeConfiguration.php @@ -7,27 +7,28 @@ use Baka\Contracts\AppInterface; use Baka\Contracts\CompanyInterface; use Baka\Contracts\HashTableInterface; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Config; class SmtpRuntimeConfiguration { protected string $appSmtp = 'appSmtp'; protected string $companySmtp = 'companySmtp'; - protected string $defaultSmtp; + protected array $defaultSmtp; public function __construct( protected AppInterface $app, protected ?CompanyInterface $company = null ) { - $this->defaultSmtp = config('mail.default'); + $this->defaultSmtp = config('mail.mailers.smtp'); } /** * Load SMTP settings from the given source. */ - protected function loadSmtpSettingsFromSource(string $provider, HashTableInterface $source): string + protected function loadSmtpSettingsFromSource(string $provider, HashTableInterface $source): array { - $config = [ + return [ 'transport' => 'smtp', 'host' => $source->get('smtp_host'), 'port' => $source->get('smtp_port'), @@ -37,16 +38,16 @@ protected function loadSmtpSettingsFromSource(string $provider, HashTableInterfa 'timeout' => null, ]; - Config::set('mail.mailers.' . $provider, $config); + //Config::set('mail.mailers.' . $provider, $config); - return $provider; + //return $provider; } /** * Load SMTP settings from the app. */ - protected function loadAppSettings(): string + protected function loadAppSettings(): array { return $this->loadSmtpSettingsFromSource($this->appSmtp, $this->app); } @@ -54,7 +55,7 @@ protected function loadAppSettings(): string /** * Load SMTP settings from the company config. */ - protected function loadCompanySettings(): string + protected function loadCompanySettings(): array { return $this->loadSmtpSettingsFromSource($this->companySmtp, $this->company); } @@ -63,7 +64,7 @@ protected function loadCompanySettings(): string * Determine the source of SMTP settings and load them. * Returns the SMTP settings source used. */ - public function loadSmtpSettings(): string + public function loadSmtpSettings(): array { if ($this->company !== null && $this->company->get('smtp_host')) { return $this->loadCompanySettings(); diff --git a/src/Kanvas/Notifications/KanvasMailable.php b/src/Kanvas/Notifications/KanvasMailable.php new file mode 100644 index 000000000..6cfdef076 --- /dev/null +++ b/src/Kanvas/Notifications/KanvasMailable.php @@ -0,0 +1,47 @@ + $this->emailContent, + ], + ); + } + + public function build(): self + { + if (app()->environment('testing')) { + // Skip setting the custom mailer configuration in testing environment + return $this; + } + + //thanks to https://github.com/laravel/framework/issues/42602#issuecomment-1143637921 + $customConfig = Mail::createSymfonyTransport($this->mailerConfig); + Mail::setSymfonyTransport($customConfig); + + return $this; + } +} diff --git a/src/Kanvas/Notifications/Notification.php b/src/Kanvas/Notifications/Notification.php index d436a70ad..a79803e43 100644 --- a/src/Kanvas/Notifications/Notification.php +++ b/src/Kanvas/Notifications/Notification.php @@ -11,8 +11,11 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Database\Eloquent\Model; +use Illuminate\Mail\Mailable; +use Illuminate\Notifications\AnonymousNotifiable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification as LaravelNotification; +use Illuminate\Support\Facades\Config; use Kanvas\Apps\Models\Apps; use Kanvas\Apps\Support\SmtpRuntimeConfiguration; use Kanvas\Exceptions\ValidationException; @@ -142,20 +145,19 @@ public function via(object $notifiable): array * * @param mixed $notifiable */ - public function toMail($notifiable): ?MailMessage + public function toMail($notifiable): Mailable { $smtpConfiguration = new SmtpRuntimeConfiguration($this->app, $this->company); - $mailer = $smtpConfiguration->loadSmtpSettings(); + $mailConfig = $smtpConfiguration->loadSmtpSettings(); $fromMail = $smtpConfiguration->getFromEmail(); $fromEmail = $fromMail['address']; $fromName = $fromMail['name']; - $mailMessage = (new MailMessage()) - ->mailer($mailer) + $toEmail = $notifiable instanceof AnonymousNotifiable ? $notifiable->routes['mail'] : $notifiable->email; + $mailMessage = (new KanvasMailable($mailConfig, $this->getEmailContent())) ->from($fromEmail, $fromName) - //->subject($this->app->get('name') . ' - ' . $this->getTitle() - ->view('emails.layout', ['html' => $this->getEmailContent()]); + ->to($toEmail); $this->subject = $this->subject ?? $this->getNotificationTitle(); diff --git a/tests/Connectors/Integration/Ghosts/CreatePeopleSubscriptionTest.php b/tests/Connectors/Integration/Ghosts/CreatePeopleSubscriptionTest.php new file mode 100644 index 000000000..9c603b91d --- /dev/null +++ b/tests/Connectors/Integration/Ghosts/CreatePeopleSubscriptionTest.php @@ -0,0 +1,64 @@ +user(); + $company = $user->getCurrentCompany(); + + $people = People::factory() + ->withAppId($app->getId()) + ->withCompanyId($company->getId()) + ->has(Contact::factory()->count(1), 'contacts') + ->create(); + + $payload = [ + 'email' => $people->getEmails()[0]->value, + 'created_at' => time(), + 'current_period_start' => time(), + ]; + + $workflowAction = WorkflowAction::firstOrCreate([ + 'name' => 'Update People Subscription', + 'model_name' => UpdatePeopleGhostSubscriptionJob::class, + ]); + + $receiverWebhook = ReceiverWebhook::factory() + ->app($app->getId()) + ->user($user->getId()) + ->company($company->getId()) + ->create([ + 'action_id' => $workflowAction->getId(), + ]); + + $request = Request::create('https://localhost/ghosttest', 'POST', $payload); + + // Execute the action and get the webhook request + $webhookRequest = (new ProcessWebhookAttemptAction($receiverWebhook, $request))->execute(); + + // Fake the queue + Queue::fake(); + $job = new UpdatePeopleGhostSubscriptionJob($webhookRequest); + $result = $job->handle(); + $this->assertArrayHasKey('success', $result); + $this->assertArrayHasKey('data', $result); + $this->assertTrue($result['success']); + } +} diff --git a/tests/Connectors/Integration/Stripe/UpdateSubscriptionTest.php b/tests/Connectors/Integration/Stripe/UpdateSubscriptionTest.php new file mode 100644 index 000000000..db3f8cd8d --- /dev/null +++ b/tests/Connectors/Integration/Stripe/UpdateSubscriptionTest.php @@ -0,0 +1,101 @@ +user(); + $company = $user->getCurrentCompany(); + + $people = People::factory() + ->withAppId($app->getId()) + ->withCompanyId($company->getId()) + ->has(Contact::factory()->count(1), 'contacts') + ->create(); + + $app->set(ConfigurationEnum::STRIPE_SECRET_KEY->value, getenv('TEST_STRIPE_SECRET_KEY')); + $stripe = new StripeClient($app->get(ConfigurationEnum::STRIPE_SECRET_KEY->value)); + $customer = $stripe->customers->create([ + 'email' => $people->getEmails()[0]->value, + 'name' => $people->getName(), + ]); + $paymentMethod = $stripe->paymentMethods->create([ + 'type' => 'card', + 'card' => [ + 'number' => '4242424242424242', + 'exp_month' => 8, + 'exp_year' => 2026, + 'cvc' => '314', + ], + ]); + + $stripe->paymentMethods->attach( + $paymentMethod->id, + ['customer' => $customer->id] + ); + $stripe->customers->update( + $customer->id, + ['invoice_settings' => ['default_payment_method' => $paymentMethod->id]] + ); + + $prices = $stripe->prices->all(); + $stripe->subscriptions->create([ + 'customer' => $customer->id, + 'items' => [ + ['price' => $prices->data[0]->id], + ], + ]); + $payload = [ + 'type' => 'customer.subscription.updated', + 'data' => [ + 'object' => [ + 'customer' => $customer->id, + ], + ], + ]; + + $workflowAction = WorkflowAction::firstOrCreate([ + 'name' => 'Update People Subscription', + 'model_name' => UpdatePeopleStripeSubscriptionJob::class, + ]); + + $receiverWebhook = ReceiverWebhook::factory() + ->app($app->getId()) + ->user($user->getId()) + ->company($company->getId()) + ->create([ + 'action_id' => $workflowAction->getId(), + ]); + + $request = Request::create('https://localhost/shopifytest', 'POST', $payload); + + // Execute the action and get the webhook request + $webhookRequest = (new ProcessWebhookAttemptAction($receiverWebhook, $request))->execute(); + + // Fake the queue + Queue::fake(); + $job = new UpdatePeopleStripeSubscriptionJob($webhookRequest); + $result = $job->handle(); + + $this->assertArrayHasKey('message', $result); + $this->assertEquals('People Subscription updated', $result['message']); + } +} diff --git a/tests/GraphQL/Guild/PeopleTest.php b/tests/GraphQL/Guild/PeopleTest.php index b54434259..2650c70e2 100644 --- a/tests/GraphQL/Guild/PeopleTest.php +++ b/tests/GraphQL/Guild/PeopleTest.php @@ -457,4 +457,56 @@ public function testCountPeople() ]); $this->assertTrue(is_int($response['data']['peopleCount'])); } + + public function testPeopleCountBySubscriptionType() + { + $user = auth()->user(); + $branch = $user->getCurrentBranch(); + $firstname = fake()->firstName(); + $lastname = fake()->lastName(); + + $input = [ + 'firstname' => $firstname, + 'lastname' => $lastname, + 'contacts' => [ + [ + 'value' => fake()->email(), + 'contacts_types_id' => 1, + 'weight' => 0, + ], + [ + 'value' => fake()->phoneNumber(), + 'contacts_types_id' => 2, + 'weight' => 0, + ], + ], + 'address' => [ + [ + 'address' => fake()->address(), + 'city' => fake()->city(), + 'county' => fake()->city(), + 'state' => fake()->state(), + 'country' => fake()->country(), + 'zip' => fake()->postcode(), + ], + ], + 'custom_fields' => [], + ]; + + $this->createPeopleAndResponse($input); + + $response = $this->graphQL(' + query { + peopleCountBySubscriptionType( + type: "Free" + ) + } + '); + $response->assertJsonStructure([ + 'data' => [ + 'peopleCountBySubscriptionType', + ], + ]); + $this->assertTrue(is_int($response['data']['peopleCountBySubscriptionType'])); + } } diff --git a/tests/GraphQL/Social/ChannelsTest.php b/tests/GraphQL/Social/ChannelsTest.php index f0e6362ff..be6c30e36 100644 --- a/tests/GraphQL/Social/ChannelsTest.php +++ b/tests/GraphQL/Social/ChannelsTest.php @@ -40,7 +40,7 @@ public function testCreateChannel() 'name' => $data['name'], 'description' => $data['description'], 'entity_id' => $data['entity_id'], - 'entity_namespace' => $data['entity_namespace_uuid'], + 'entity_namespace' => $systemModule->model_name ], ], ]); @@ -184,6 +184,7 @@ public function testAttachUserToSocialChannel() ], ] ); + $response->assertJsonStructure([ 'data' => [ 'attachUserToSocialChannel' => [ diff --git a/tests/Social/Integration/InteractionsTest.php b/tests/Social/Integration/InteractionsTest.php index 5e7617b2b..89ed29a53 100644 --- a/tests/Social/Integration/InteractionsTest.php +++ b/tests/Social/Integration/InteractionsTest.php @@ -5,6 +5,7 @@ namespace Tests\Social\Integration; use Kanvas\Apps\Models\Apps; +use Kanvas\Guild\Customers\Models\People; use Kanvas\Inventory\Products\Models\Products; use Kanvas\Inventory\Warehouses\Models\Warehouses; use Kanvas\Social\Interactions\Models\EntityInteractions; @@ -64,4 +65,15 @@ public function testEntityUnLikeOtherEntity(): void $product->unLike($warehouse) ); } + + public function testEntityNewInteraction() + { + $people = People::firstOrFail(); + $product = Products::firstOrFail(); + + $this->assertInstanceOf( + EntityInteractions::class, + $people->addInteraction($product, 'view', 'This is a test note') + ); + } }