Merge pull request #8205 from turbo124/v5-develop

v5.5.62
This commit is contained in:
David Bomba 2023-01-25 09:32:20 +11:00 committed by GitHub
commit 725f8f7747
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 8871 additions and 138 deletions

View File

@ -1 +1 @@
5.5.61
5.5.62

View File

@ -54,6 +54,7 @@ use Database\Factories\BankTransactionRuleFactory;
use Faker\Factory;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use stdClass;
@ -203,6 +204,22 @@ class CreateSingleAccount extends Command
'applies_to' => (bool)rand(0,1) ? 'CREDIT' : 'DEBIT',
]);
$client = Client::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'name' => 'cypress'
]);
ClientContact::factory()->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id,
'is_primary' => 1,
'email' => 'cypress@example.com',
'password' => Hash::make('password'),
]);
$this->info('Creating '.$this->count.' clients');
for ($x = 0; $x < $this->count; $x++) {
@ -356,7 +373,7 @@ class CreateSingleAccount extends Command
'client_id' => $client->id,
'company_id' => $company->id,
'is_primary' => 1,
'email' => 'user@example.com'
'email' => 'user@example.com',
]);
ClientContact::factory()->count(rand(1, 2))->create([

View File

@ -149,6 +149,7 @@ class NinjaPlanController extends Controller
$account->plan_started = now();
$account->plan_expires = now()->addDays(14);
$account->is_trial=true;
$account->hosted_company_count = 10;
$account->save();
}

View File

@ -148,21 +148,21 @@ class AdjustProductInventory implements ShouldQueue
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
// $product->company_users->each(function ($cu) use($product, $nmo){
$this->company->company_users->each(function ($cu) use($product, $nmo){
// if($this->checkNotificationExists($cu, $product, ['inventory_all', 'inventory_user']))
// {
// $nmo->to_user = $cu->user;
// NinjaMailerJob::dispatch($nmo);
// }
// });
$nmo->to_user = $this->company->owner();
if($this->checkNotificationExists($cu, $product, ['inventory_all', 'inventory_user']))
{
$nmo->to_user = $cu->user;
NinjaMailerJob::dispatch($nmo);
}
});
// $nmo->to_user = $this->company->owner();
// NinjaMailerJob::dispatch($nmo);
}
}

View File

@ -117,7 +117,7 @@ class QuoteCheckExpired implements ShouldQueue
}
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_expired', 'quote_expired_all']);
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_expired', 'quote_expired_all', 'quote_expired_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -57,7 +57,7 @@ class CreditCreatedNotification implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'credit');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($credit->invitations()->first(), $company_user, 'credit', ['all_notifications', 'credit_created', 'credit_created_all']);
$methods = $this->findUserNotificationTypes($credit->invitations()->first(), $company_user, 'credit', ['all_notifications', 'credit_created', 'credit_created_all', 'credit_created_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -54,7 +54,7 @@ class CreditEmailedNotification implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'credit');
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'credit', ['all_notifications', 'credit_sent', 'credit_sent_all']);
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'credit', ['all_notifications', 'credit_sent', 'credit_sent_all', 'credit_sent_user']);
if (($key = array_search('mail', $methods)) !== false) {
// if (($key = array_search('mail', $methods))) {

View File

@ -63,7 +63,7 @@ class InvoiceCreatedNotification implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'invoice');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($invoice->invitations()->first(), $company_user, 'invoice', ['all_notifications', 'invoice_created', 'invoice_created_all']);
$methods = $this->findUserNotificationTypes($invoice->invitations()->first(), $company_user, 'invoice', ['all_notifications', 'invoice_created', 'invoice_created_all', 'invoice_created_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -61,7 +61,7 @@ class InvoiceEmailedNotification implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'invoice');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent', 'invoice_sent_all']);
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent', 'invoice_sent_all', 'invoice_sent_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -56,7 +56,7 @@ class InvoiceFailedEmailNotification
foreach ($event->invitation->company->company_users as $company_user) {
$user = $company_user->user;
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent', 'invoice_sent_all']);
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent', 'invoice_sent_all', 'invoice_sent_user']);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);

View File

@ -68,8 +68,9 @@ class InvitationViewedListener implements ShouldQueue
foreach ($invitation->company->company_users as $company_user) {
$entity_viewed = "{$entity_name}_viewed";
$entity_viewed_all = "{$entity_name}_viewed_all";
$entity_viewed_user = "{$entity_name}_viewed_user";
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed, $entity_viewed_all]);
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed, $entity_viewed_all, $entity_viewed_user]);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);

View File

@ -59,7 +59,7 @@ class PurchaseOrderAcceptedListener implements ShouldQueue
}
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_accepted', 'purchase_order_accepted_all']);
$methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_accepted', 'purchase_order_accepted_all', 'purchase_order_accepted_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -63,7 +63,7 @@ class PurchaseOrderCreatedListener implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'purchase_order');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_created', 'purchase_order_created_all']);
$methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_created', 'purchase_order_created_all', 'purchase_order_created_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -61,7 +61,7 @@ class PurchaseOrderEmailedNotification implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'purchase_order');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'purchase_order', ['all_notifications', 'purchase_order_sent', 'purchase_order_sent_all']);
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'purchase_order', ['all_notifications', 'purchase_order_sent', 'purchase_order_sent_all', 'purchase_order_sent_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -61,7 +61,7 @@ class QuoteApprovedNotification implements ShouldQueue
}
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_approved', 'quote_approved_all']);
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_approved', 'quote_approved_all', 'quote_approved_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -63,7 +63,7 @@ class QuoteCreatedNotification implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'quote');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_created', 'quote_created_all']);
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_created', 'quote_created_all', 'quote_created_user']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {

View File

@ -56,7 +56,7 @@ class QuoteEmailedNotification implements ShouldQueue
// $notification = new EntitySentNotification($event->invitation, 'quote');
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'quote', ['all_notifications', 'quote_sent', 'quote_sent_all']);
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'quote', ['all_notifications', 'quote_sent', 'quote_sent_all', 'quote_sent_user']);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);

View File

@ -27,7 +27,6 @@ class BankIntegration extends BaseModel
'bank_account_type',
'balance',
'currency',
'nickname',
'from_date',
'auto_sync',
];

View File

@ -358,7 +358,7 @@ class User extends Authenticatable implements MustVerifyEmail
public function hasPermission($permission) : bool
{
$parts = explode('_', $permission);
$all_permission = false;
$all_permission = '____';
if (count($parts) > 1) {
$all_permission = $parts[0].'_all';
@ -366,8 +366,8 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->isOwner() ||
$this->isAdmin() ||
(stripos($all_permission, $this->token()->cu->permissions) !== false) ||
(stripos($permission, $this->token()->cu->permissions) !== false);
(stripos($this->token()->cu->permissions, $all_permission) !== false) ||
(stripos($this->token()->cu->permissions, $permission) !== false);
// return $this->isOwner() ||
// $this->isAdmin() ||

View File

@ -111,6 +111,7 @@ class CreditCard
'value' => $amount,
],
'description' => $description,
'idempotencyKey' => uniqid("st",true),
'redirectUrl' => route('mollie.3ds_redirect', [
'company_key' => $this->mollie->client->company->company_key,
'company_gateway_id' => $this->mollie->company_gateway->hashed_id,

View File

@ -102,6 +102,9 @@ class ClientRepository extends BaseRepository
$data['name'] = $client->present()->name();
}
//24-01-2023 when a logo is uploaded, no other data is set, so we need to catch here and not update
//the contacts array UNLESS there are no contacts and we need to maintain state.
if(array_key_exists('contacts', $contact_data) || $client->contacts()->count() == 0)
$this->contact_repo->save($contact_data, $client);
return $client;

View File

@ -43,7 +43,7 @@ trait UserNotifies
//if a user owns this record or is assigned to it, they are attached the permission for notification.
if ($invitation->{$entity_name}->user_id == $company_user->user_id || $invitation->{$entity_name}->assigned_user_id == $company_user->user_id) {
$required_permissions = $this->addSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
// $required_permissions = $this->addSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
} else {
$required_permissions = $this->removeSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
}
@ -83,24 +83,24 @@ trait UserNotifies
private function addSpecialUserPermissionForEntity($entity, array $required_permissions) :array
{
array_merge($required_permissions, ['all_notifications']);
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications']);
switch ($entity) {
case $entity instanceof Payment || $entity instanceof Client: //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'payment_failure_user', 'payment_success_user']);
case $entity instanceof Invoice:
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user']);
case $entity instanceof Quote:
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'quote_created_user', 'quote_sent_user', 'quote_viewed_user', 'quote_approved_user', 'quote_expired_user']);
case $entity instanceof Credit:
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'credit_created_user', 'credit_sent_user', 'credit_viewed_user']);
case $entity instanceof PurchaseOrder:
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'purchase_order_created_user', 'purchase_order_sent_user', 'purchase_order_viewed_user']);
case $entity instanceof Product:
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'inventory_user', 'inventory_all']);
default:
return [];
}
// switch ($entity) {
// case $entity instanceof Payment || $entity instanceof Client: //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'payment_failure_user', 'payment_success_user']);
// case $entity instanceof Invoice:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user']);
// case $entity instanceof Quote:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'quote_created_user', 'quote_sent_user', 'quote_viewed_user', 'quote_approved_user', 'quote_expired_user']);
// case $entity instanceof Credit:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'credit_created_user', 'credit_sent_user', 'credit_viewed_user']);
// case $entity instanceof PurchaseOrder:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'purchase_order_created_user', 'purchase_order_sent_user', 'purchase_order_viewed_user']);
// case $entity instanceof Product:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'inventory_user', 'inventory_all']);
// default:
// return [];
// }
}
private function removeSpecialUserPermissionForEntity($entity, $required_permissions)

View File

@ -101,6 +101,7 @@
"darkaonline/l5-swagger": "8.1.0",
"fakerphp/faker": "^1.14",
"filp/whoops": "^2.7",
"laracasts/cypress": "^3.0",
"laravel/dusk": "^6.15",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^6.1",

61
composer.lock generated
View File

@ -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": "451d3dbdd5b0a87940e0f8fffadab4ae",
"content-hash": "fee0057b8444e2a245cea87dab6c1b3a",
"packages": [
{
"name": "afosto/yaac",
@ -13859,6 +13859,65 @@
},
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "laracasts/cypress",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/laracasts/cypress.git",
"reference": "9a9e5d25a51d2cbb410393e6a0d9883aa3304bf5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laracasts/cypress/zipball/9a9e5d25a51d2cbb410393e6a0d9883aa3304bf5",
"reference": "9a9e5d25a51d2cbb410393e6a0d9883aa3304bf5",
"shasum": ""
},
"require": {
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
"php": "^8.0"
},
"require-dev": {
"orchestra/testbench": "^6.0|^7.0",
"phpunit/phpunit": "^8.0|^9.5.10",
"spatie/laravel-ray": "^1.29"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laracasts\\Cypress\\CypressServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laracasts\\Cypress\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jeffrey Way",
"email": "jeffrey@laracasts.com",
"role": "Developer"
}
],
"description": "Laravel Cypress Boilerplate",
"homepage": "https://github.com/laracasts/cypress",
"keywords": [
"cypress",
"laracasts"
],
"support": {
"issues": "https://github.com/laracasts/cypress/issues",
"source": "https://github.com/laracasts/cypress/tree/3.0.0"
},
"time": "2022-06-27T13:49:35+00:00"
},
{
"name": "laravel/dusk",
"version": "v6.25.2",

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.61',
'app_tag' => '5.5.61',
'app_version' => '5.5.62',
'app_tag' => '5.5.62',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

19
cypress.config.js vendored Normal file
View File

@ -0,0 +1,19 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
chromeWebSecurity: false,
retries: 2,
defaultCommandTimeout: 5000,
watchForFileChanges: false,
videosFolder: 'tests/cypress/videos',
screenshotsFolder: 'tests/cypress/screenshots',
fixturesFolder: 'tests/cypress/fixture',
e2e: {
setupNodeEvents(on, config) {
return require('./tests/cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://ninja.test:8000/',
specPattern: 'tests/cypress/integration/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'tests/cypress/support/index.js',
},
})

View File

@ -118,68 +118,24 @@ class RandomDataSeeder extends Seeder
'settings' => null,
]);
$u2 = User::where('email', 'demo@invoiceninja.com')->first();
if (! $u2) {
$u2 = User::factory()->create([
'email' => 'demo@invoiceninja.com',
'password' => Hash::make('demo'),
'account_id' => $account->id,
'confirmation_code' => $this->createDbHash(config('database.default')),
]);
$company_token = CompanyToken::create([
'user_id' => $u2->id,
'company_id' => $company->id,
'account_id' => $account->id,
'name' => 'test token',
'token' => 'TOKEN',
]);
$u2->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'notifications' => CompanySettings::notificationDefaults(),
'permissions' => '',
'settings' => null,
]);
}
$client = Client::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'name' => 'cypress'
]);
ClientContact::create([
'first_name' => $faker->firstName(),
'last_name' => $faker->lastName(),
'email' => config('ninja.testvars.username'),
'company_id' => $company->id,
'password' => Hash::make(config('ninja.testvars.password')),
'email_verified_at' => now(),
'client_id' =>$client->id,
'user_id' => $user->id,
'is_primary' => true,
'contact_key' => \Illuminate\Support\Str::random(40),
]);
$client->number = $client->getNextClientNumber($client);
$client->save();
Client::factory()->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company) {
ClientContact::factory()->create([
'user_id' => $user->id,
'client_id' => $c->id,
'client_id' => $client->id,
'company_id' => $company->id,
'is_primary' => 1,
'email' => 'cypress@example.com',
'password' => Hash::make('password'),
]);
ClientContact::factory()->count(5)->create([
'user_id' => $user->id,
'client_id' => $c->id,
'company_id' => $company->id,
]);
});
/* Product Factory */
Product::factory()->count(2)->create(['user_id' => $user->id, 'company_id' => $company->id]);
@ -200,8 +156,6 @@ class RandomDataSeeder extends Seeder
$invoice = $invoice_calc->build()->getInvoice();
$invoice->save();
$invoice->service()->createInvitations()->markSent()->save();
$invoice->ledger()->updateInvoiceBalance($invoice->balance);
@ -220,16 +174,16 @@ class RandomDataSeeder extends Seeder
$payment->invoices()->save($invoice);
$payment_hash = new PaymentHash;
$payment_hash->hash = Str::random(128);
$payment_hash->data = [['invoice_id' => $invoice->hashed_id, 'amount' => $invoice->balance]];
$payment_hash->fee_total = 0;
$payment_hash->fee_invoice_id = $invoice->id;
$payment_hash->save();
// $payment_hash = new PaymentHash;
// $payment_hash->hash = Str::random(128);
// $payment_hash->data = [['invoice_id' => $invoice->hashed_id, 'amount' => $invoice->balance]];
// $payment_hash->fee_total = 0;
// $payment_hash->fee_invoice_id = $invoice->id;
// $payment_hash->save();
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
$payment->service()->updateInvoicePayment($payment_hash);
// $payment->service()->updateInvoicePayment($payment_hash);
// UpdateInvoicePayment::dispatchNow($payment, $payment->company);
}
@ -256,7 +210,6 @@ class RandomDataSeeder extends Seeder
$credit->service()->createInvitations()->markSent()->save();
//$invoice->markSent()->save();
});
/* Recurring Invoice Factory */
@ -286,14 +239,6 @@ class RandomDataSeeder extends Seeder
//$invoice->markSent()->save();
});
$clients = Client::all();
foreach ($clients as $client) {
//$client->getNextClientNumber($client);
$client->number = $client->getNextClientNumber($client);
$client->save();
}
GroupSetting::create([
'company_id' => $company->id,
'user_id' => $user->id,

2478
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,12 +12,13 @@
"@babel/compat-data": "7.15.0",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@tailwindcss/aspect-ratio": "^0.4.2",
"cypress": "^12.3.0",
"laravel-mix-purgecss": "^6.0.0",
"vue-template-compiler": "^2.6.14"
},
"dependencies": {
"@tailwindcss/line-clamp": "^0.3.1",
"@tailwindcss/forms": "^0.3.4",
"@tailwindcss/line-clamp": "^0.3.1",
"autoprefixer": "^10.3.7",
"axios": "^0.25",
"card-js": "^1.0.13",

View File

@ -15,6 +15,8 @@ namespace Tests\Feature\Notify;
use App\DataMapper\CompanySettings;
use App\Models\CompanyToken;
use App\Models\CompanyUser;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Product;
use App\Models\User;
use App\Utils\Traits\Notifications\UserNotifies;
@ -42,6 +44,65 @@ class NotificationTest extends TestCase
$this->makeTestData();
}
public function testEntityViewedNotificationWithEntityLate()
{
// ['all_notifications', 'all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user'];
$u = User::factory()->create([
'account_id' => $this->account->id,
'email' => $this->faker->safeEmail(),
'confirmation_code' => uniqid("st",true),
]);
$company_token = new CompanyToken;
$company_token->user_id = $u->id;
$company_token->company_id = $this->company->id;
$company_token->account_id = $this->account->id;
$company_token->name = 'test token';
$company_token->token = Str::random(64);
$company_token->is_system = true;
$company_token->save();
$u->companies()->attach($this->company->id, [
'account_id' => $this->account->id,
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'notifications' => CompanySettings::notificationDefaults(),
'settings' => null,
]);
$company_user = CompanyUser::where('user_id', $u->id)->where('company_id', $this->company->id)->first();
$notifications = new \stdClass;
$notifications->email = ["invoice_late_user","quote_approved_user"];
$company_user->update(['notifications' => (array)$notifications]);
$i = Invoice::factory()->create([
'user_id' => $u->id,
'company_id' => $this->company->id,
'number' => uniqid("st",true),
'client_id' => $this->client->id,
]);
$invitation = InvoiceInvitation::factory()->create([
'user_id' => $u->id,
'company_id' => $this->company->id,
'invoice_id' => $i->id,
'client_contact_id' => $this->client->contacts->first()->id,
]);
$methods = $this->findUserNotificationTypes($invitation, $company_user, 'invoice', ['all_notifications', 'invoice_late_user']);
$this->assertCount(1, $methods);
$methods = $this->findUserNotificationTypes($invitation, $company_user, 'invoice', ['all_notifications', 'invoice_viewed', 'invoice_viewed_all']);
$this->assertCount(0, $methods);
}
public function testNotificationFound()
{
$notifications = new \stdClass;
@ -101,7 +162,6 @@ class NotificationTest extends TestCase
}
public function testAllNotificationsDoesNotFiresForUser()
{
$u = User::factory()->create([
@ -170,12 +230,13 @@ class NotificationTest extends TestCase
'company_id' => $this->company->id
]);
$methods = $this->findUserEntityNotificationType($p, $cu, []);
$methods = $this->findUserEntityNotificationType($p, $cu, ['inventory_user']);
nlog($methods);
$this->assertCount(1, $methods);
$this->assertTrue($this->checkNotificationExists($cu, $p, ['inventory_all', 'inventory_user']));
}

View File

@ -79,6 +79,20 @@ class PermissionsTest extends TestCase
}
public function testViewClientPermission()
{
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
$low_cu->permissions = '["view_client"]';
$low_cu->save();
$this->assertFalse($this->user->hasPermission("viewclient"));
// this is aberrant
$this->assertFalse($this->user->hasPermission("view____client"));
}
public function testPermissionResolution()
{
$class = 'view'.lcfirst(class_basename(\Illuminate\Support\Str::snake(Invoice::class)));
@ -162,7 +176,6 @@ class PermissionsTest extends TestCase
public function testReturnTypesOfStripos()
{
$this->assertEquals(0, stripos("view_client", ''));
$all_permission = '[]';
@ -193,17 +206,7 @@ class PermissionsTest extends TestCase
}
public function testViewClientPermission()
{
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
$low_cu->permissions = '["view_client"]';
$low_cu->save();
// this is aberrant
$this->assertFalse($this->user->hasPermission("view____client"));
}
}

43
tests/cypress/integration/login.cy.js vendored Normal file
View File

@ -0,0 +1,43 @@
describe('Test Login Page', () => {
it('Shows the Password Reset Pasge.', () => {
cy.visit('/client/password/reset');
cy.contains('Password Recovery');
cy.get('input[name=email]').type('cypress@example.com{enter}');
cy.contains('We have e-mailed your password reset link!');
cy.visit('/client/password/reset');
cy.contains('Password Recovery');
cy.get('input[name=email]').type('nono@example.com{enter}');
cy.contains("We can't find a user with that e-mail address.");
});
it('Shows the login page.', () => {
cy.visit('/client/login');
cy.contains('Client Portal');
cy.get('input[name=email]').type('cypress@example.com');
cy.get('input[name=password]').type('password{enter}');
cy.url().should('include', '/invoices');
cy.visit('/client/recurring_invoices').contains('Recurring Invoices');
cy.visit('/client/payments').contains('Payments');
cy.visit('/client/quotes').contains('Quotes');
cy.visit('/client/credits').contains('Credits');
cy.visit('/client/payment_methods').contains('Payment Methods');
cy.visit('/client/documents').contains('Documents');
cy.visit('/client/statement').contains('Statement');
cy.visit('/client/subscriptions').contains('Subscriptions');
cy.get('[data-ref="client-profile-dropdown"]').click();
cy.get('[data-ref="client-profile-dropdown-settings"]').click();
cy.contains('Client Information');
});
});

23
tests/cypress/plugins/index.js vendored Normal file
View File

@ -0,0 +1,23 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('task', require('./swap-env'));
};

21
tests/cypress/plugins/swap-env.js vendored Normal file
View File

@ -0,0 +1,21 @@
let fs = require('fs');
module.exports = {
activateCypressEnvFile() {
if (fs.existsSync('.env.cypress')) {
fs.renameSync('.env', '.env.backup');
fs.renameSync('.env.cypress', '.env');
}
return null;
},
activateLocalEnvFile() {
if (fs.existsSync('.env.backup')) {
fs.renameSync('.env', '.env.cypress');
fs.renameSync('.env.backup', '.env');
}
return null;
}
};

3
tests/cypress/support/assertions.js vendored Normal file
View File

@ -0,0 +1,3 @@
Cypress.Commands.add('assertRedirect', path => {
cy.location('pathname').should('eq', `/${path}`.replace(/^\/\//, '/'));
});

92
tests/cypress/support/index.d.ts vendored Normal file
View File

@ -0,0 +1,92 @@
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable<Subject> {
/**
* Log in the user with the given attributes, or create a new user and then log them in.
*
* @example
* cy.login()
* cy.login({ id: 1 })
*/
login(attributes?: object): Chainable<any>;
/**
* Log out the current user.
*
* @example
* cy.logout()
*/
logout(): Chainable<any>;
/**
* Fetch the currently authenticated user.
*
* @example
* cy.currentUser()
*/
currentUser(): Chainable<any>;
/**
* Fetch a CSRF token from the server.
*
* @example
* cy.logout()
*/
csrfToken(): Chainable<any>;
/**
* Fetch a fresh list of URI routes from the server.
*
* @example
* cy.logout()
*/
refreshRoutes(): Chainable<any>;
/**
* Create and persist a new Eloquent record using Laravel model factories.
*
* @example
* cy.create('App\\User');
* cy.create('App\\User', 2);
* cy.create('App\\User', 2, { active: false });
* cy.create({ model: 'App\\User', state: ['guest'], relations: ['profile'], count: 2 }
*/
create(): Chainable<any>;
/**
* Refresh the database state using Laravel's migrate:fresh command.
*
* @example
* cy.refreshDatabase()
* cy.refreshDatabase({ '--drop-views': true }
*/
refreshDatabase(options?: object): Chainable<any>;
/**
* Run Artisan's db:seed command.
*
* @example
* cy.seed()
* cy.seed('PlansTableSeeder')
*/
seed(seederClass?: string): Chainable<any>;
/**
* Run an Artisan command.
*
* @example
* cy.artisan()
*/
artisan(command: string, parameters?: object, options?: object): Chainable<any>;
/**
* Execute arbitrary PHP on the server.
*
* @example
* cy.php('2 + 2')
* cy.php('App\\User::count()')
*/
php(command: string): Chainable<any>;
}
}

32
tests/cypress/support/index.js vendored Normal file
View File

@ -0,0 +1,32 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
/// <reference types="./" />
import './laravel-commands';
import './laravel-routes';
import './assertions';
before(() => {
cy.task('activateCypressEnvFile', {}, { log: false });
cy.artisan('config:clear', {}, { log: false });
cy.refreshRoutes();
cy.seed("RandomDataSeeder");
});
after(() => {
cy.task('activateLocalEnvFile', {}, { log: false });
cy.artisan('config:clear', {}, { log: false });
});

View File

@ -0,0 +1,301 @@
/**
* Create a new user and log them in.
*
* @param {Object} attributes
*
* @example cy.login();
* cy.login({ name: 'JohnDoe' });
* cy.login({ attributes: { name: 'JohnDoe' }, state: 'guest', load: ['comments] });
*/
Cypress.Commands.add('login', (attributes = {}) => {
// Are we using the new object system.
let requestBody = attributes.attributes || attributes.state || attributes.load ? attributes : { attributes };
return cy
.csrfToken()
.then((token) => {
return cy.request({
method: 'POST',
url: '/__cypress__/login',
body: { ...requestBody, _token: token },
log: false,
});
})
.then(({ body }) => {
Cypress.Laravel.currentUser = body;
Cypress.log({
name: 'login',
message: JSON.stringify(body),
consoleProps: () => ({ user: body }),
});
})
.its('body', { log: false });
});
/**
* Fetch the currently authenticated user object.
*
* @example cy.currentUser();
*/
Cypress.Commands.add('currentUser', () => {
return cy.csrfToken().then((token) => {
return cy
.request({
method: 'POST',
url: '/__cypress__/current-user',
body: { _token: token },
log: false,
})
.then((response) => {
if (!response.body) {
cy.log('No authenticated user found.');
}
Cypress.Laravel.currentUser = response?.body;
return response?.body;
});
});
});
/**
* Logout the current user.
*
* @example cy.logout();
*/
Cypress.Commands.add('logout', () => {
return cy
.csrfToken()
.then((token) => {
return cy.request({
method: 'POST',
url: '/__cypress__/logout',
body: { _token: token },
log: false,
});
})
.then(() => {
Cypress.log({ name: 'logout', message: '' });
});
});
/**
* Fetch a CSRF token.
*
* @example cy.csrfToken();
*/
Cypress.Commands.add('csrfToken', () => {
return cy
.request({
method: 'GET',
url: '/__cypress__/csrf_token',
log: false,
})
.its('body', { log: false });
});
/**
* Fetch and store all named routes.
*
* @example cy.refreshRoutes();
*/
Cypress.Commands.add('refreshRoutes', () => {
return cy.csrfToken().then((token) => {
return cy
.request({
method: 'POST',
url: '/__cypress__/routes',
body: { _token: token },
log: false,
})
.its('body', { log: false })
.then((routes) => {
cy.writeFile(Cypress.config().supportFolder + '/routes.json', routes, {
log: false,
});
Cypress.Laravel.routes = routes;
});
});
});
/**
* Visit the given URL or route.
*
* @example cy.visit('foo/path');
* cy.visit({ route: 'home' });
* cy.visit({ route: 'team', parameters: { team: 1 } });
*/
Cypress.Commands.overwrite('visit', (originalFn, subject, options) => {
if (subject.route) {
return originalFn({
url: Cypress.Laravel.route(subject.route, subject.parameters || {}),
method: Cypress.Laravel.routes[subject.route].method[0],
...options
});
}
return originalFn(subject, options);
});
/**
* Create a new Eloquent factory.
*
* @param {String} model
* @param {Number|null} times
* @param {Object} attributes
*
* @example cy.create('App\\User');
* cy.create('App\\User', 2);
* cy.create('App\\User', 2, { active: false });
* cy.create('App\\User', { active: false });
* cy.create('App\\User', 2, { active: false });
* cy.create('App\\User', 2, { active: false }, ['profile']);
* cy.create('App\\User', 2, { active: false }, ['profile'], ['guest']);
* cy.create('App\\User', { active: false }, ['profile']);
* cy.create('App\\User', { active: false }, ['profile'], ['guest']);
* cy.create('App\\User', ['profile']);
* cy.create('App\\User', ['profile'], ['guest']);
* cy.create({ model: 'App\\User', state: ['guest'], relations: ['profile'], count: 2 }
*/
Cypress.Commands.add('create', (model, count = 1, attributes = {}, load = [], state = []) => {
let requestBody = {};
if (typeof model !== 'object') {
if (Array.isArray(count)) {
state = attributes;
attributes = {};
load = count;
count = 1;
}
if (typeof count === 'object') {
state = load;
load = attributes;
attributes = count;
count = 1;
}
requestBody = { model, state, attributes, load, count };
} else {
requestBody = model;
}
return cy
.csrfToken()
.then((token) => {
return cy.request({
method: 'POST',
url: '/__cypress__/factory',
body: { ...requestBody, _token: token },
log: false,
});
})
.then((response) => {
Cypress.log({
name: 'create',
message: requestBody.model + (requestBody.count > 1 ? ` (${requestBody.count} times)` : ''),
consoleProps: () => ({ [model]: response.body }),
});
})
.its('body', { log: false });
});
/**
* Refresh the database state.
*
* @param {Object} options
*
* @example cy.refreshDatabase();
* cy.refreshDatabase({ '--drop-views': true });
*/
Cypress.Commands.add('refreshDatabase', (options = {}) => {
return cy.artisan('migrate:fresh', options);
});
/**
* Seed the database.
*
* @param {String} seederClass
*
* @example cy.seed();
* cy.seed('PlansTableSeeder');
*/
Cypress.Commands.add('seed', (seederClass = '') => {
let options = {};
if (seederClass) {
options['--class'] = seederClass;
}
return cy.artisan('db:seed', options);
});
/**
* Trigger an Artisan command.
*
* @param {String} command
* @param {Object} parameters
* @param {Object} options
*
* @example cy.artisan('cache:clear');
*/
Cypress.Commands.add('artisan', (command, parameters = {}, options = {}) => {
options = Object.assign({}, { log: true }, options);
if (options.log) {
Cypress.log({
name: 'artisan',
message: (() => {
let message = command;
for (let key in parameters) {
message += ` ${key}="${parameters[key]}"`;
}
return message;
})(),
consoleProps: () => ({ command, parameters }),
});
}
return cy.csrfToken().then((token) => {
return cy.request({
method: 'POST',
url: '/__cypress__/artisan',
body: { command: command, parameters: parameters, _token: token },
log: false,
});
});
});
/**
* Execute arbitrary PHP.
*
* @param {String} command
*
* @example cy.php('2 + 2');
* cy.php('App\\User::count()');
*/
Cypress.Commands.add('php', (command) => {
return cy
.csrfToken()
.then((token) => {
return cy.request({
method: 'POST',
url: '/__cypress__/run-php',
body: { command: command, _token: token },
log: false,
});
})
.then((response) => {
Cypress.log({
name: 'php',
message: command,
consoleProps: () => ({ result: response.body.result }),
});
})
.its('body.result', { log: false });
});

21
tests/cypress/support/laravel-routes.js vendored Normal file
View File

@ -0,0 +1,21 @@
Cypress.Laravel = {
routes: {},
route: (name, parameters = {}) => {
assert(
Cypress.Laravel.routes.hasOwnProperty(name),
`Laravel route "${name}" does not exist.`
);
return ((uri) => {
Object.keys(parameters).forEach((parameter) => {
uri = uri.replace(
new RegExp(`{${parameter}}`),
parameters[parameter]
);
});
return uri;
})(Cypress.Laravel.routes[name].uri);
},
};

File diff suppressed because it is too large Load Diff

Binary file not shown.