Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2023-01-25 09:47:20 +11:00
commit 6d90f697a4
58 changed files with 256398 additions and 246677 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

@ -822,6 +822,7 @@ class CompanySettings extends BaseSettings
'$client.address1',
'$client.address2',
'$client.city_state_postal',
'$client.postal_city',
'$client.country',
'$client.phone',
'$contact.email',
@ -833,6 +834,7 @@ class CompanySettings extends BaseSettings
'$vendor.address1',
'$vendor.address2',
'$vendor.city_state_postal',
'$vendor.postal_city',
'$vendor.country',
'$vendor.phone',
'$contact.email',
@ -857,6 +859,7 @@ class CompanySettings extends BaseSettings
'$company.address1',
'$company.address2',
'$company.city_state_postal',
'$company.postal_city',
'$company.country',
],
'invoice_details' => [

View file

@ -15,6 +15,7 @@ use App\Models\Client;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Utils\Helpers;
use Carbon\Carbon;
class RecurringInvoiceToInvoiceFactory
{
@ -70,11 +71,16 @@ class RecurringInvoiceToInvoiceFactory
private static function transformItems($recurring_invoice, $client)
{
$currentDateTime = null;
$line_items = $recurring_invoice->line_items;
if (isset($recurring_invoice->next_send_date)) {
$currentDateTime = Carbon::parse($recurring_invoice->next_send_date)->timezone($client->timezone()->name);
}
foreach ($line_items as $key => $item) {
if (property_exists($line_items[$key], 'notes')) {
$line_items[$key]->notes = Helpers::processReservedKeywords($item->notes, $client);
$line_items[$key]->notes = Helpers::processReservedKeywords($item->notes, $client, $currentDateTime);
}
}

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']))
// {
if($this->checkNotificationExists($cu, $product, ['inventory_all', 'inventory_user']))
{
// $nmo->to_user = $cu->user;
// NinjaMailerJob::dispatch($nmo);
$nmo->to_user = $cu->user;
NinjaMailerJob::dispatch($nmo);
// }
}
// });
});
$nmo->to_user = $this->company->owner();
// $nmo->to_user = $this->company->owner();
NinjaMailerJob::dispatch($nmo);
// 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

@ -190,6 +190,8 @@ class PaymentEmailEngine extends BaseEmailEngine
$data['$client.city_state_postal'] = &$data['$city_state_postal'];
$data['$postal_city_state'] = ['value' => $this->client->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
$data['$postal_city'] = ['value' => $this->client->present()->cityStateZip($this->client->city, null, $this->client->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city')];
$data['$client.postal_city'] = &$data['$postal_city'];
$data['$client.country'] = &$data['$country'];
$data['$client.email'] = &$data['$email'];
@ -213,6 +215,7 @@ class PaymentEmailEngine extends BaseEmailEngine
$data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: '&nbsp;', 'label' => ctrans('texts.city_state_postal')];
$data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$company.postal_city'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, null, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city')];
$data['$company.name'] = ['value' => $this->company->present()->name() ?: '&nbsp;', 'label' => ctrans('texts.company_name')];
$data['$company.address1'] = ['value' => $this->settings->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$company.address2'] = ['value' => $this->settings->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];

View file

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

View file

@ -84,14 +84,14 @@ class EntityPresenter extends Presenter
}
}
public function getShippingCityState()
public function getShippingCityState($printCity = true, $printState = true, $printPostalCode = true)
{
$client = $this->entity;
$swap = $client->shipping_country && $client->shipping_country->swap_postal_code;
$city = e($client->shipping_city);
$state = e($client->shipping_state);
$postalCode = e($client->shipping_postal_code);
$city = ($printCity) ? e($client->shipping_city) : null;
$state = ($printState) ? e($client->shipping_state) : null;
$postalCode = ($printPostalCode) ? e($client->shipping_postal_code) : null;
if ($city || $state || $postalCode) {
return $this->cityStateZip($city, $state, $postalCode, $swap);

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,7 +102,10 @@ class ClientRepository extends BaseRepository
$data['name'] = $client->present()->name();
}
$this->contact_repo->save($contact_data, $client);
//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

@ -413,6 +413,8 @@ class HtmlEngine
$data['$client.city_state_postal'] = &$data['$city_state_postal'];
$data['$postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
$data['$postal_city'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, null, $this->client->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city')];
$data['$client.postal_city'] = &$data['$postal_city'];
$data['$client.country'] = &$data['$country'];
$data['$client.email'] = &$data['$email'];
@ -463,6 +465,7 @@ class HtmlEngine
$data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: '&nbsp;', 'label' => ctrans('texts.city_state_postal')];
$data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$company.postal_city'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, null, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city')];
$data['$company.name'] = ['value' => $this->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')];
$data['$account'] = &$data['$company.name'];

View file

@ -149,6 +149,8 @@ trait MakesTemplateData
$data['$client.city_state_postal'] = &$data['$city_state_postal'];
$data['$postal_city_state'] = ['value' => '90210, Los Angeles, CA', 'label' => ctrans('texts.postal_city_state')];
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
$data['$postal_city'] = ['value' => '10178 Berlin', 'label' => ctrans('texts.postal_city')];
$data['$client.postal_city'] = &$data['$postal_city'];
$data['$client.country'] = &$data['$country'];
$data['$client.email'] = &$data['$email'];
$data['$contact_name'] = ['value' => 'Jimmy Nadel', 'label' => ctrans('texts.contact_name')];
@ -159,6 +161,7 @@ trait MakesTemplateData
$data['$contact4'] = ['value' => 'Custom Contact Values', 'label' => 'contact 4'];
$data['$company.city_state_postal'] = ['value' => 'Los Angeles, CA, 90210', 'label' => ctrans('texts.city_state_postal')];
$data['$company.postal_city_state'] = ['value' => '90210, Los Angeles, CA', 'label' => ctrans('texts.postal_city_state')];
$data['$company.postal_city'] = ['value' => '10178 Berlin', 'label' => ctrans('texts.postal_city')];
$data['$company.name'] = ['value' => 'ACME co', 'label' => ctrans('texts.company_name')];
$data['$company.company_name'] = &$data['$company.name'];
$data['$company.address1'] = ['value' => '5 Jimbuckeroo Way', 'label' => ctrans('texts.address1')];
@ -218,6 +221,7 @@ trait MakesTemplateData
$data['$vendor.phone'] = &$data['$phone'];
$data['$vendor.city_state_postal'] = &$data['$city_state_postal'];
$data['$vendor.postal_city_state'] = &$data['$postal_city_state'];
$data['$vendor.postal_city'] = &$data['$postal_city'];
$data['$vendor.country'] = &$data['$country'];
$data['$vendor.email'] = &$data['$email'];

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

@ -275,6 +275,8 @@ class VendorHtmlEngine
$data['$vendor.city_state_postal'] = &$data['$city_state_postal'];
$data['$postal_city_state'] = ['value' => $this->vendor->present()->cityStateZip($this->vendor->city, $this->vendor->state, $this->vendor->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$vendor.postal_city_state'] = &$data['$postal_city_state'];
$data['$postal_city'] = ['value' => $this->vendor->present()->cityStateZip($this->vendor->city, null, $this->vendor->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city')];
$data['$vendor.postal_city'] = &$data['$postal_city'];
$data['$vendor.country'] = &$data['$country'];
$data['$vendor.email'] = &$data['$email'];
@ -309,6 +311,7 @@ class VendorHtmlEngine
$data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: '&nbsp;', 'label' => ctrans('texts.city_state_postal')];
$data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$company.postal_city'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, null, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city')];
$data['$company.name'] = ['value' => $this->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')];
$data['$account'] = &$data['$company.name'];
@ -453,6 +456,8 @@ class VendorHtmlEngine
$data['$client.city_state_postal'] = &$data['$city_state_postal'];
$data['$postal_city_state'] = ['value' => $this->entity->client->present()->cityStateZip($this->entity->client->city, $this->entity->client->state, $this->entity->client->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
$data['$postal_city'] = ['value' => $this->entity->client->present()->cityStateZip($this->entity->client->city, null, $this->entity->client->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city')];
$data['$client.postal_city'] = &$data['$postal_city'];
$data['$client.country'] = &$data['$country'];
$data['$client.email'] = &$data['$email'];

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",

926
composer.lock generated

File diff suppressed because it is too large Load diff

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,67 +118,23 @@ 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,
]);
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::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,
'company_id' => $company->id,
'is_primary' => 1,
'name' => 'cypress'
]);
ClientContact::factory()->count(5)->create([
'user_id' => $user->id,
'client_id' => $c->id,
'company_id' => $company->id,
]);
});
$client->number = $client->getNextClientNumber($client);
$client->save();
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'),
]);
/* 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,

View file

@ -1902,6 +1902,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'task' => 'Aufgabe',
'contact_name' => 'Name des Kontakts',
'city_state_postal' => 'Stadt/Bundesland/PLZ',
'postal_city' => 'PLZ/Stadt',
'custom_field' => 'Benutzerdefinierte Felder',
'account_fields' => 'Unternehmensfelder',
'facebook_and_twitter' => 'Facebook und Twitter',

View file

@ -1901,6 +1901,7 @@ $LANG = array(
'task' => 'Task',
'contact_name' => 'Contact Name',
'city_state_postal' => 'City/State/Postal',
'postal_city' => 'Postal/City',
'custom_field' => 'Custom Field',
'account_fields' => 'Company Fields',
'facebook_and_twitter' => 'Facebook and Twitter',

View file

@ -1901,6 +1901,7 @@ $LANG = array(
'task' => 'Task',
'contact_name' => 'Contact Name',
'city_state_postal' => 'City/State/Postal',
'postal_city' => 'Postal/City',
'custom_field' => 'Custom Field',
'account_fields' => 'Company Fields',
'facebook_and_twitter' => 'Facebook and Twitter',

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

@ -4,8 +4,8 @@ const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"flutter.js": "f85e6fb278b0fd20c349186fb46ae36d",
"/": "81db08b231c2ca12a2c6c27abc6546dc",
"main.dart.js": "80df879ac94d14fd27d03749428a2ccc",
"/": "0626f533f39f1481608bb9467f56daaf",
"main.dart.js": "16a58ed772159e59430f6c95e14416da",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",

237468
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

240386
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

@ -17,6 +17,7 @@ use App\Factory\RecurringInvoiceToInvoiceFactory;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\RecurringInvoice;
use App\Utils\Helpers;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
@ -304,6 +305,35 @@ class RecurringInvoiceTest extends TestCase
$this->assertEquals(10, $invoice->subscription_id);
}
public function testRecurringDatePassesToInvoice()
{
$noteText = "Hello this is for :MONTH_AFTER";
$recurringDate = \Carbon\Carbon::now()->subDays(10);
$item = InvoiceItemFactory::create();
$item->cost = 10;
$item->notes = $noteText;
$recurring_invoice = InvoiceToRecurringInvoiceFactory::create($this->invoice);
$recurring_invoice->user_id = $this->user->id;
$recurring_invoice->next_send_date = $recurringDate;
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
$recurring_invoice->remaining_cycles = 2;
$recurring_invoice->next_send_date = $recurringDate;
$recurring_invoice->line_items = [$item];
$recurring_invoice->save();
$recurring_invoice->number = $this->getNextRecurringInvoiceNumber($this->invoice->client, $this->invoice);
$recurring_invoice->subscription_id = 10;
$recurring_invoice->save();
$invoice = RecurringInvoiceToInvoiceFactory::create($recurring_invoice, $this->invoice->client);
$expectedNote = Helpers::processReservedKeywords($noteText, $this->invoice->client, $recurringDate);
$this->assertEquals($expectedNote, $invoice->line_items[0]->notes);
}
public function testSubscriptionIdPassesToInvoiceIfNull()
{
$recurring_invoice = InvoiceToRecurringInvoiceFactory::create($this->invoice);

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.