diff --git a/src/app/Tenant.php b/src/app/Tenant.php index 6a05c730..197c5daa 100644 --- a/src/app/Tenant.php +++ b/src/app/Tenant.php @@ -1,95 +1,94 @@ id != $tenantId) { $tenant = null; if ($tenantId) { $tenant = self::findOrFail($tenantId); } } // Supported options (TODO: document this somewhere): // - app.name (tenants.title will be returned) // - app.public_url and app.url // - app.support_url // - mail.from.address and mail.from.name // - mail.reply_to.address and mail.reply_to.name // - app.kb.account_delete and app.kb.account_suspended // - pgp.enable if ($key == 'app.name') { return $tenant ? $tenant->title : \config($key); } $value = $tenant ? $tenant->getSetting($key) : null; return $value !== null ? $value : \config($key); } /** * Discounts assigned to this tenant. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function discounts() { return $this->hasMany('App\Discount'); } /** * SignupInvitations assigned to this tenant. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function signupInvitations() { return $this->hasMany('App\SignupInvitation'); } /* * Returns the wallet of the tanant (reseller's wallet). * * @return ?\App\Wallet A wallet object */ public function wallet(): ?Wallet { $user = \App\User::where('role', 'reseller')->where('tenant_id', $this->id)->first(); return $user ? $user->wallets->first() : null; } } diff --git a/src/app/Traits/UuidIntKeyTrait.php b/src/app/Traits/UuidIntKeyTrait.php index 0bf0811d..df6c562d 100644 --- a/src/app/Traits/UuidIntKeyTrait.php +++ b/src/app/Traits/UuidIntKeyTrait.php @@ -1,51 +1,41 @@ {$model->getKeyName()})) { $allegedly_unique = \App\Utils::uuidInt(); // Verify if unique if (in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($model))) { while ($model->withTrashed()->find($allegedly_unique)) { $allegedly_unique = \App\Utils::uuidInt(); } } else { while ($model->find($allegedly_unique)) { $allegedly_unique = \App\Utils::uuidInt(); } } $model->{$model->getKeyName()} = $allegedly_unique; } }); } /** * Get if the key is incrementing. * * @return bool */ public function getIncrementing() { return false; } - - /** - * Get the key type. - * - * @return string - */ - public function getKeyType() - { - return 'bigint'; - } } diff --git a/src/resources/js/locale.js b/src/resources/js/locale.js index afe2b18e..fedff4c9 100644 --- a/src/resources/js/locale.js +++ b/src/resources/js/locale.js @@ -1,66 +1,66 @@ import Vue from 'vue' import VueI18n from 'vue-i18n' // We do pre-load English localization as this is possible // the only one that is complete and used as a fallback. import messages from '../build/js/en.json' Vue.use(VueI18n) export const i18n = new VueI18n({ locale: 'en', fallbackLocale: 'en', messages: { en: messages }, silentFallbackWarn: true }) let currentLanguage const loadedLanguages = ['en'] // our default language that is preloaded const setI18nLanguage = (lang) => { i18n.locale = lang document.querySelector('html').setAttribute('lang', lang) // Set language for API requests // Note, it's kinda redundant as we support the cookie window.axios.defaults.headers.common['Accept-Language'] = lang // Save the selected language in a cookie, so it can be used server-side // after page reload. Make the cookie valid for 10 years const age = 10 * 60 * 60 * 24 * 365 - document.cookie = 'language=' + lang + '; max-age=' + age + document.cookie = 'language=' + lang + '; max-age=' + age + '; path=/; secure' return lang } export const getLang = () => { if (!currentLanguage) { currentLanguage = document.querySelector('html').getAttribute('lang') || 'en' } return currentLanguage } export const setLang = lang => { currentLanguage = lang loadLangAsync() } export function loadLangAsync() { const lang = getLang() // If the language was already loaded if (loadedLanguages.includes(lang)) { return Promise.resolve(setI18nLanguage(lang)) } // If the language hasn't been loaded yet return import(/* webpackChunkName: "locale/[request]" */ `../build/js/${lang}.json`) .then(messages => { i18n.setLocaleMessage(lang, messages.default) loadedLanguages.push(lang) return setI18nLanguage(lang) }) } diff --git a/src/tests/Feature/Console/Wallet/ChargeTest.php b/src/tests/Feature/Console/Wallet/ChargeTest.php index 628114eb..28991135 100644 --- a/src/tests/Feature/Console/Wallet/ChargeTest.php +++ b/src/tests/Feature/Console/Wallet/ChargeTest.php @@ -1,147 +1,147 @@ deleteTestUser('wallet-charge@kolabnow.com'); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestUser('wallet-charge@kolabnow.com'); parent::tearDown(); } /** * Test command run for a specified wallet */ public function testHandleSingle(): void { $user = $this->getTestUser('wallet-charge@kolabnow.com'); $wallet = $user->wallets()->first(); $wallet->balance = 0; $wallet->save(); Queue::fake(); // Non-existing wallet ID $this->artisan('wallet:charge 123') ->assertExitCode(1) ->expectsOutput("Wallet not found."); Queue::assertNothingPushed(); // The wallet has no entitlements, expect no charge and no check $this->artisan('wallet:charge ' . $wallet->id) ->assertExitCode(0); Queue::assertNothingPushed(); // The wallet has no entitlements, but has negative balance $wallet->balance = -100; $wallet->save(); $this->artisan('wallet:charge ' . $wallet->id) ->assertExitCode(0); Queue::assertPushed(\App\Jobs\WalletCharge::class, 0); Queue::assertPushed(\App\Jobs\WalletCheck::class, 1); Queue::assertPushed(\App\Jobs\WalletCheck::class, function ($job) use ($wallet) { $job_wallet = TestCase::getObjectProperty($job, 'wallet'); return $job_wallet->id === $wallet->id; }); Queue::fake(); // The wallet has entitlements to charge, and negative balance - $sku = \App\Sku::where('title', 'mailbox')->first(); + $sku = \App\Sku::withObjectTenantContext($user)->where('title', 'mailbox')->first(); $entitlement = \App\Entitlement::create([ 'wallet_id' => $wallet->id, 'sku_id' => $sku->id, 'cost' => 100, 'entitleable_id' => $user->id, 'entitleable_type' => \App\User::class, ]); \App\Entitlement::where('id', $entitlement->id)->update([ - 'created_at' => \Carbon\Carbon::now()->subMonths(1), - 'updated_at' => \Carbon\Carbon::now()->subMonths(1), + 'created_at' => \Carbon\Carbon::now()->subMonthsNoOverflow(1), + 'updated_at' => \Carbon\Carbon::now()->subMonthsNoOverflow(1), ]); \App\User::where('id', $user->id)->update([ - 'created_at' => \Carbon\Carbon::now()->subMonths(1), - 'updated_at' => \Carbon\Carbon::now()->subMonths(1), + 'created_at' => \Carbon\Carbon::now()->subMonthsNoOverflow(1), + 'updated_at' => \Carbon\Carbon::now()->subMonthsNoOverflow(1), ]); $this->assertSame(100, $wallet->fresh()->chargeEntitlements(false)); $this->artisan('wallet:charge ' . $wallet->id) ->assertExitCode(0); Queue::assertPushed(\App\Jobs\WalletCharge::class, 1); Queue::assertPushed(\App\Jobs\WalletCharge::class, function ($job) use ($wallet) { $job_wallet = TestCase::getObjectProperty($job, 'wallet'); return $job_wallet->id === $wallet->id; }); Queue::assertPushed(\App\Jobs\WalletCheck::class, 1); Queue::assertPushed(\App\Jobs\WalletCheck::class, function ($job) use ($wallet) { $job_wallet = TestCase::getObjectProperty($job, 'wallet'); return $job_wallet->id === $wallet->id; }); } /** * Test command run for all wallets */ public function testHandleAll(): void { $user = $this->getTestUser('john@kolab.org'); $wallet = $user->wallets()->first(); $wallet->balance = 0; $wallet->save(); // backdate john's entitlements and set balance=0 for all wallets $this->backdateEntitlements($user->entitlements, \Carbon\Carbon::now()->subWeeks(5)); \App\Wallet::where('balance', '<', '0')->update(['balance' => 0]); $user2 = $this->getTestUser('wallet-charge@kolabnow.com'); $wallet2 = $user2->wallets()->first(); $wallet2->balance = -100; $wallet2->save(); Queue::fake(); // Non-existing wallet ID $this->artisan('wallet:charge')->assertExitCode(0); Queue::assertPushed(\App\Jobs\WalletCheck::class, 2); Queue::assertPushed(\App\Jobs\WalletCheck::class, function ($job) use ($wallet) { $job_wallet = TestCase::getObjectProperty($job, 'wallet'); return $job_wallet->id === $wallet->id; }); Queue::assertPushed(\App\Jobs\WalletCheck::class, function ($job) use ($wallet2) { $job_wallet = TestCase::getObjectProperty($job, 'wallet'); return $job_wallet->id === $wallet2->id; }); Queue::assertPushed(\App\Jobs\WalletCharge::class, 1); Queue::assertPushed(\App\Jobs\WalletCharge::class, function ($job) use ($wallet) { $job_wallet = TestCase::getObjectProperty($job, 'wallet'); return $job_wallet->id === $wallet->id; }); } }