Merge branch 'v2'

This commit is contained in:
Benjamin Beganović 2020-05-28 14:41:18 +02:00
commit 7d1eeafebe
175 changed files with 114685 additions and 105485 deletions

View File

@ -82,7 +82,7 @@ jobs:
- name: Migrate Database
run: |
php artisan migrate:fresh --seed --force && php artisan db:seed --class=RandomDataSeeder --force
php artisan migrate:fresh --seed --force && php artisan db:seed --force
- name: Prepare JS/CSS assets
run: |

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}

View File

@ -2,6 +2,7 @@
<img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/>
</p>
![phpunit](https://github.com/turbo124/invoiceninja/workflows/phpunit/badge.svg?branch=v2)
[![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=v2)](https://travis-ci.org/invoiceninja/invoiceninja)
[![codecov](https://codecov.io/gh/invoiceninja/invoiceninja/branch/v2/graph/badge.svg)](https://codecov.io/gh/invoiceninja/invoiceninja)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d39acb4bf0f74a0698dc77f382769ba5)](https://www.codacy.com/app/turbo124/invoiceninja?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=invoiceninja/invoiceninja&amp;utm_campaign=Badge_Grade)

View File

@ -5,7 +5,9 @@ namespace App\Console\Commands;
use App;
use App\Libraries\CurlUtils;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\CompanyLedger;
use App\Models\Contact;
use App\Models\Invitation;
use App\Models\Invoice;
@ -287,56 +289,59 @@ class CheckData extends Command
private function checkInvoiceBalances()
{
// $invoices = DB::table('invoices')
// ->leftJoin('payments', function($join) {
// $join->on('payments.invoice_id', '=', 'invoices.id')
// ->where('payments.payment_status_id', '!=', 2)
// ->where('payments.payment_status_id', '!=', 3)
// ->where('payments.is_deleted', '=', 0);
// })
// ->where('invoices.updated_at', '>', '2017-10-01')
// ->groupBy('invoices.id')
// ->havingRaw('(invoices.amount - invoices.balance) != coalesce(sum(payments.amount - payments.refunded), 0)')
// ->get(['invoices.id', 'invoices.amount', 'invoices.balance', DB::raw('coalesce(sum(payments.amount - payments.refunded), 0)')]);
// $this->logMessage($invoices->count() . ' invoices with incorrect balances');
$wrong_balances = 0;
$wrong_paid_to_dates = 0;
foreach(Client::cursor() as $client)
{
$invoice_balance = $client->invoices->where('is_deleted', false)->sum('balance');
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
if($ledger && $invoice_balance != $client->balance)
{
$wrong_balances++;
$this->logMessage($client->present()->name . " - " . $client->id . " - balances do not match {$invoice_balance} - {$client->balance} - {$ledger->balance}");
$this->isValid = false;
}
}
$this->logMessage("{$wrong_balances} clients with incorrect balances");
// if ($invoices->count() > 0) {
// $this->isValid = false;
// }
}
private function checkClientBalances()
{
// find all clients where the balance doesn't equal the sum of the outstanding invoices
// $clients = DB::table('clients')
// ->join('invoices', 'invoices.client_id', '=', 'clients.id')
// ->join('accounts', 'accounts.id', '=', 'clients.company_id')
// ->where('accounts.id', '!=', 20432)
// ->where('clients.is_deleted', '=', 0)
// ->where('invoices.is_deleted', '=', 0)
// ->where('invoices.is_public', '=', 1)
// ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD)
// ->where('invoices.is_recurring', '=', 0)
// ->havingRaw('abs(clients.balance - sum(invoices.balance)) > .01 and clients.balance != 999999999.9999');
// if ($this->option('client_id')) {
// $clients->where('clients.id', '=', $this->option('client_id'));
// }
$wrong_balances = 0;
$wrong_paid_to_dates = 0;
// $clients = $clients->groupBy('clients.id', 'clients.balance')
// ->orderBy('accounts.company_id', 'DESC')
// ->get(['accounts.company_id', 'clients.company_id', 'clients.id', 'clients.balance', 'clients.paid_to_date', DB::raw('sum(invoices.balance) actual_balance')]);
// $this->logMessage($clients->count() . ' clients with incorrect balance/activities');
foreach(Client::cursor() as $client)
{
$invoice_balance = $client->invoices->where('is_deleted', false)->sum('balance');
$invoice_amounts = $client->invoices->where('is_deleted', false)->sum('amount') - $invoice_balance;
// if ($clients->count() > 0) {
// $this->isValid = false;
// }
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
// foreach ($clients as $client) {
// $this->logMessage("=== Company: {$client->company_id} Account:{$client->company_id} Client:{$client->id} Balance:{$client->balance} Actual Balance:{$client->actual_balance} ===");
if($ledger && (string)$invoice_amounts != rtrim($client->paid_to_date, "0"))
{
$wrong_paid_to_dates++;
$this->logMessage($client->present()->name . " - " . $client->id . " - client paid to dates do not match {$invoice_amounts} - " .rtrim($client->paid_to_date, "0"));
$this->isValid = false;
}
}
$this->logMessage("{$wrong_paid_to_dates} clients with incorrect paid_to_dates");
//}
}
private function checkLogoFiles()

View File

@ -135,28 +135,44 @@ class CreateTestData extends Command
$this->createClient($company, $user);
}
foreach ($company->clients as $client) {
for($x=0; $x<$this->count; $x++)
{
$client = $company->clients->random();
$this->info('creating invoice for client #'.$client->id);
$this->createInvoice($client);
$client = $company->clients->random();
$this->info('creating credit for client #'.$client->id);
$this->createCredit($client);
$client = $company->clients->random();
$this->info('creating quote for client #'.$client->id);
$this->createQuote($client);
$client = $company->clients->random();
$this->info('creating expense for client #'.$client->id);
$this->createExpense($client);
$client = $company->clients->random();
$this->info('creating vendor for client #'.$client->id);
$this->createVendor($client);
$client = $company->clients->random();
$this->info('creating task for client #'.$client->id);
$this->createTask($client);
$client = $company->clients->random();
$this->info('creating project for client #'.$client->id);
$this->createProject($client);
}
}
private function createMediumAccount()
@ -217,49 +233,42 @@ class CreateTestData extends Command
$this->createClient($company, $user);
}
foreach ($company->clients as $client) {
$this->info('creating invoice for client #'.$client->id);
for($x=0; $x<$this->count; $x++)
{
$client = $company->clients->random();
for ($i=0; $i<$this->count; $i++) {
$this->createInvoice($client);
}
$this->info('creating invoice for client #'.$client->id);
$this->createInvoice($client);
$client = $company->clients->random();
$this->info('creating credit for client #'.$client->id);
$this->createCredit($client);
for ($i=0; $i<$this->count; $i++) {
$this->createCredit($client);
}
$client = $company->clients->random();
$this->info('creating quote for client #'.$client->id);
$this->createQuote($client);
for ($i=0; $i<$this->count; $i++) {
$this->createQuote($client);
}
$client = $company->clients->random();
$this->info('creating expense for client #'.$client->id);
for ($i=0; $i<$this->count; $i++) {
$this->createExpense($client);
}
$this->createExpense($client);
$client = $company->clients->random();
$this->info('creating vendor for client #'.$client->id);
for ($i=0; $i<$this->count; $i++) {
$this->createVendor($client);
}
$this->createVendor($client);
$client = $company->clients->random();
$this->info('creating task for client #'.$client->id);
for ($i=0; $i<$this->count; $i++) {
$this->createTask($client);
}
$this->createTask($client);
$client = $company->clients->random();
$this->info('creating project for client #'.$client->id);
for ($i=0; $i<$this->count; $i++) {
$this->createProject($client);
}
$this->createProject($client);
}
}
@ -322,25 +331,40 @@ class CreateTestData extends Command
$this->createClient($company, $user);
}
foreach ($company->clients as $client) {
for($x=0; $x<$this->count; $x++)
{
$client = $company->clients->random();
$this->info('creating invoice for client #'.$client->id);
$this->createInvoice($client);
$client = $company->clients->random();
$this->info('creating credit for client #'.$client->id);
$this->createCredit($client);
$client = $company->clients->random();
$this->info('creating quote for client #'.$client->id);
$this->createQuote($client);
$client = $company->clients->random();
$this->info('creating expense for client #'.$client->id);
$this->createExpense($client);
$client = $company->clients->random();
$this->info('creating vendor for client #'.$client->id);
$this->createVendor($client);
$client = $company->clients->random();
$this->info('creating task for client #'.$client->id);
$this->createTask($client);
$client = $company->clients->random();
$this->info('creating project for client #'.$client->id);
$this->createProject($client);
}

View File

@ -38,6 +38,8 @@ class PostUpdate extends Command
{
set_time_limit(0);
info("running post update");
try {
Artisan::call('migrate');
} catch (Exception $e) {
@ -85,12 +87,14 @@ class PostUpdate extends Command
$output = $factory->createOutput();
$input = new \Symfony\Component\Console\Input\ArrayInput(array(
'command' => 'update',
'command' => 'install',
));
$input->setInteractive(false);
echo "<pre>";
$cmdret = $app->doRun($input,$output);
echo "end!";
\Log::error(print_r($cmdret,1));
}
}

View File

@ -29,8 +29,8 @@ class CompanySettings extends BaseSettings
public $enable_client_portal_tasks = false;
public $enable_client_portal_password = false;
public $enable_client_portal = true;//implemented
public $enable_client_portal_dashboard = true;//implemented
public $enable_client_portal = true; //implemented
public $enable_client_portal_dashboard = true; //implemented
public $signature_on_pdf = false;
public $document_email_attachment = false;
public $send_portal_password = false;
@ -55,7 +55,7 @@ class CompanySettings extends BaseSettings
public $default_task_rate = 0;
public $payment_terms = -1;
public $payment_terms = "";
public $send_reminders = false;
public $custom_message_dashboard = '';
@ -231,7 +231,10 @@ class CompanySettings extends BaseSettings
public $portal_custom_footer = '';
public $portal_custom_js = '';
public $client_can_register = false;
public static $casts = [
'client_can_register' => 'bool',
'portal_design_id' => 'string',
'late_fee_endless_percent' => 'float',
'late_fee_endless_amount' => 'float',
@ -348,7 +351,7 @@ class CompanySettings extends BaseSettings
'credit_footer' => 'string',
'credit_terms' => 'string',
'name' => 'string',
'payment_terms' => 'integer',
'payment_terms' => 'string',
'payment_type_id' => 'string',
'phone' => 'string',
'postal_code' => 'string',
@ -411,7 +414,7 @@ class CompanySettings extends BaseSettings
'custom_value4' => 'string',
'inclusive_taxes' => 'bool',
'name' => 'string',
'payment_terms' => 'integer',
'payment_terms' => 'string',
'payment_type_id' => 'string',
'phone' => 'string',
'postal_code' => 'string',
@ -479,7 +482,7 @@ class CompanySettings extends BaseSettings
$data->timezone_id = (string) config('ninja.i18n.timezone_id');
$data->currency_id = (string) config('ninja.i18n.currency_id');
$data->language_id = (string) config('ninja.i18n.language_id');
$data->payment_terms = (int) config('ninja.i18n.payment_terms');
$data->payment_terms = (string) config('ninja.i18n.payment_terms');
$data->military_time = (bool) config('ninja.i18n.military_time');
$data->date_format_id = (string) config('ninja.i18n.date_format_id');
$data->country_id = (string) config('ninja.i18n.country_id');

View File

@ -15,15 +15,16 @@ use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
use Illuminate\Database\Eloquent\RelationNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\ValidationException;
use Sentry\State\Scope;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Database\Eloquent\RelationNotFoundException;
use Sentry\State\Scope;
use function Sentry\configureScope;
class Handler extends ExceptionHandler
@ -55,9 +56,12 @@ class Handler extends ExceptionHandler
*/
public function report(Exception $exception)
{
if(!Schema::hasTable('accounts'))
return;
if (app()->bound('sentry') && $this->shouldReport($exception)) {
app('sentry')->configureScope(function (Scope $scope): void {
if (auth()->guard('contact')->user() && auth()->guard('contact')->user()->company->account->report_errors) {
if (auth()->guard('contact') && auth()->guard('contact')->user() && auth()->guard('contact')->user()->company->account->report_errors) {
$scope->setUser([
'id' => auth()->guard('contact')->user()->company->account->key,
'email' => "anonymous@example.com",

View File

@ -26,15 +26,16 @@ class CloneQuoteToInvoiceFactory
unset($quote_array['client']);
unset($quote_array['company']);
unset($quote_array['hashed_id']);
unset($quote_array['invoice_id']);
unset($quote_array['id']);
foreach($quote_array as $key => $value)
$invoice->{$key} = $value;
$invoice->status_id = Invoice::STATUS_DRAFT;
$invoice->due_date = null;
$invoice->partial_due_date = null;
$invoice->number = null;
$invoice->status_id = null;
return $invoice;

View File

@ -0,0 +1,26 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Factory;
use App\Models\PaymentTerm;
class PaymentTermFactory
{
public static function create(int $company_id, int $user_id) :PaymentTerm
{
$payment_term = new PaymentTerm;
$payment_term->user_id = $user_id;
$payment_term->company_id = $company_id;
return $payment_term;
}
}

View File

@ -82,6 +82,11 @@ class QuoteFilters extends QueryFilters
});
}
public function number($number = '')
{
return $this->builder->where('number', 'like', '%'.$number.'%');
}
/**
* Sorts the list based on $sort
*

View File

@ -62,8 +62,14 @@ class GmailTransport extends Transport
$this->gmail->cc($message->getCc());
$this->gmail->bcc($message->getBcc());
\Log::error(print_r($message->getChildren(),1));
foreach($message->getChildren() as $child)
$this->gmail->attach($child); //todo this should 'just work'
$this->gmail->send();
$this->sendPerformed($message);
return $this->numberOfRecipients($message);

View File

@ -23,6 +23,7 @@ use App\Utils\Traits\AppSetup;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request as Input;
use Illuminate\Support\Facades\Schema;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection;
@ -265,6 +266,7 @@ class BaseController extends Controller
'company.quotes.invitations.contact',
'company.quotes.invitations.company',
'company.credits',
'company.payment_terms',
//'company.credits.invitations.contact',
//'company.credits.invitations.company',
'company.vendors.contacts',
@ -282,6 +284,7 @@ class BaseController extends Controller
'company.users.company_user',
'company.tax_rates',
'company.groups',
'company.payment_terms',
];
/**
@ -311,10 +314,16 @@ class BaseController extends Controller
public function flutterRoute()
{
if ((bool)$this->checkAppSetup() !== false) {
// // Ensure all request are over HTTPS in production
// if (! request()->secure()) {
// return redirect()->secure(request()->path());
// }
if ((bool)$this->checkAppSetup() !== false && Schema::hasTable('accounts') && $account = Account::all()->first()) {
$data = [];
if (Ninja::isSelfHost() && $account = Account::all()->first()) {
if (Ninja::isSelfHost()) {
$data['report_errors'] = $account->report_errors;
} else {
$data['report_errors'] = true;

View File

@ -41,7 +41,7 @@ class InvitationController extends Controller
if ((bool)$invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
$this->middleware('auth:contact');
} else {
auth()->guard('contact')->login($invitation->contact, false);
auth()->guard('contact')->login($invitation->contact, true);
}
if (!request()->has('silent')) {

View File

@ -23,13 +23,13 @@ class SwitchCompanyController extends Controller
public function __invoke(string $contact)
{
$client_contact = ClientContact::query()
->where('user_id', auth()->user()->id)
$client_contact = ClientContact::where('email', auth()->user()->email)
->where('id', $this->transformKeys($contact))
->first();
Auth::guard('contact')->login($client_contact, true);
return back();
return redirect('/client/dashboard');
}
}

View File

@ -20,6 +20,7 @@ use App\Http\Requests\Company\StoreCompanyRequest;
use App\Http\Requests\Company\UpdateCompanyRequest;
use App\Http\Requests\SignupRequest;
use App\Jobs\Company\CreateCompany;
use App\Jobs\Company\CreateCompanyPaymentTerms;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Ninja\RefundCancelledAccount;
use App\Jobs\RegisterNewAccount;
@ -204,6 +205,8 @@ class CompanyController extends BaseController
$company = CreateCompany::dispatchNow($request->all(), auth()->user()->company()->account);
CreateCompanyPaymentTerms::dispatchNow($company, auth()->user());
$company = $this->company_repo->save($request->all(), $company);
$this->uploadLogo($request->file('company_logo'), $company, $company);

View File

@ -14,7 +14,7 @@ namespace App\Http\Controllers;
use App\Helpers\Email\InvoiceEmail;
use App\Http\Requests\Email\SendEmailRequest;
use App\Jobs\Invoice\EmailInvoice;
use App\Jobs\Mail\EntitySentEmail;
use App\Jobs\Mail\EntitySentMailer;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Quote;
@ -115,14 +115,17 @@ class EmailController extends BaseController
$entity_string = strtolower(class_basename($entity_obj));
$entity_obj->invitations->each(function ($invitation) use ($subject, $body, $entity_string, $entity_obj) {
if ($invitation->contact->send_email && $invitation->contact->email) {
$when = now()->addSeconds(1);
$invitation->contact->notify((new SendGenericNotification($invitation, $entity_string, $subject, $body))->delay($when));
EntitySentEmail::dispatch($invitation, $entity_string, $entity_obj->user, $invitation->company);
EntitySentMailer::dispatch($invitation, $entity_string, $entity_obj->user, $invitation->company);
}
});
if ($this instanceof Invoice) {

View File

@ -670,6 +670,9 @@ class InvoiceController extends BaseController
}
break;
case 'delete':
//need to make sure the invoice is cancelled first!!
$invoice->service()->handleCancellation()->save();
$this->invoice_repo->delete($invoice);
if (!$bulk) {

View File

@ -366,7 +366,7 @@ class MigrationController extends BaseController
return;
}
StartMigration::dispatch(base_path("storage/app/public/$migration_file"), $user, $company);
StartMigration::dispatch(base_path("storage/app/public/$migration_file"), $user, $company)->delay(now()->addSeconds(60));
return response()->json([
'_id' => Str::uuid(),

View File

@ -0,0 +1,12 @@
<?php
/**
* @OA\Schema(
* schema="PaymentTerm",
* type="object",
* @OA\Property(property="num_days", type="integer", example="1", description="The payment term length in days"),
* @OA\Property(property="name", type="string", example="NET 1", description="The payment term length in string format"),
* @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"),
* @OA\Property(property="updated_at", type="number", format="integer", example="134341234234", description="Timestamp"),
* @OA\Property(property="archived_at", type="number", format="integer", example="134341234234", description="Timestamp"),
* )
*/

View File

@ -0,0 +1,477 @@
<?php
namespace App\Http\Controllers;
use App\Factory\PaymentTermFactory;
use App\Http\Requests\PaymentTerm\CreatePaymentTermRequest;
use App\Http\Requests\PaymentTerm\DestroyPaymentTermRequest;
use App\Http\Requests\PaymentTerm\ShowPaymentTermRequest;
use App\Http\Requests\PaymentTerm\StorePaymentTermRequest;
use App\Http\Requests\PaymentTerm\UpdatePaymentTermRequest;
use App\Models\PaymentTerm;
use App\Repositories\PaymentTermRepository;
use App\Transformers\PaymentTermTransformer;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
class PaymentTermController extends BaseController
{
use MakesHash;
protected $entity_type = PaymentTerm::class;
protected $entity_transformer = PaymentTermTransformer::class;
/**
* @var PaymentRepository
*/
protected $payment_term_repo;
/**
* PaymentTermController constructor.
*
* @param \App\Repositories\PaymentTermRepository $payment_term_repo The payment term repo
*/
public function __construct(PaymentTermRepository $payment_term_repo)
{
parent::__construct();
$this->payment_term_repo = $payment_term_repo;
}
/**
* @OA\Get(
* path="/api/v1/payment_terms",
* operationId="getPaymentTerms",
* tags={"payment_terms"},
* summary="Gets a list of payment terms",
* description="Lists payment terms",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Response(
* response=200,
* description="A list of payment terms",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/PaymentTerm"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function index()
{
$payment_terms = PaymentTerm::whereCompanyId(auth()->user()->company()->id)->orWhere('company_id', null);
return $this->listResponse($payment_terms);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Http\Requests\Payment\CreatePaymentTermRequest $request The request
*
* @return \Illuminate\Http\Response
*
*
*
* @OA\Get(
* path="/api/v1/payment_terms/create",
* operationId="getPaymentTermsCreate",
* tags={"payment_terms"},
* summary="Gets a new blank PaymentTerm object",
* description="Returns a blank object with default values",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="A blank PaymentTerm object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Payment"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function create(CreatePaymentTermRequest $request)
{
$payment_term = PaymentTermFactory::create(auth()->user()->company()->id, auth()->user()->id);
return $this->itemResponse($payment_term);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Payment\StorePaymentRequest $request The request
*
* @return \Illuminate\Http\Response
*
*
*
* @OA\Post(
* path="/api/v1/payment_terms",
* operationId="storePaymentTerm",
* tags={"payment_terms"},
* summary="Adds a Payment",
* description="Adds a Payment Term to the system",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\RequestBody(
* description="The payment_terms request",
* required=true,
* @OA\JsonContent(ref="#/components/schemas/PaymentTerm"),
* ),
* @OA\Response(
* response=200,
* description="Returns the saved Payment object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/PaymentTerm"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function store(StorePaymentTermRequest $request)
{
$payment_term = PaymentTermFactory::create(auth()->user()->company()->id, auth()->user()->id);
$payment_term->fill($request->all());
$payment_term->save();
return $this->itemResponse($payment_term->fresh());
}
/**
* @OA\Get(
* path="/api/v1/payment_terms/{id}",
* operationId="showPaymentTerm",
* tags={"payment_terms"},
* summary="Shows a Payment Term",
* description="Displays an Payment Term by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Payment Term Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Payment Term object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/PaymentTerm"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function show(ShowPaymentTermRequest $request, PaymentTerm $payment_term)
{
return $this->itemResponse($payment_term);
}
/**
* @OA\Get(
* path="/api/v1/payment_terms/{id}/edit",
* operationId="editPaymentTerms",
* tags={"payment_terms"},
* summary="Shows an Payment Term for editting",
* description="Displays an Payment Term by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Payment Term Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Payment object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/PaymentTerm"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function edit(EditPaymentRequest $request, Payment $payment)
{
return $this->itemResponse($payment);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\PaymentTerm\UpdatePaymentTermRequest $request The request
* @param \App\Models\PaymentTerm $payment_term The payment term
*
* @return \Illuminate\Http\Response
*
*
* @OA\Put(
* path="/api/v1/payment_terms/{id}",
* operationId="updatePaymentTerm",
* tags={"payment_terms"},
* summary="Updates a Payment Term",
* description="Handles the updating of an Payment Termby id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Payment Term Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Payment Term object",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/PaymentTerm"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function update(UpdatePaymentTermRequest $request, PaymentTerm $payment_term)
{
$payment_term->fill($request->all());
$payment_term->save();
return $this->itemResponse($payment_term->fresh());
}
/**
* Remove the specified resource from storage.
*
* @param \App\Http\Requests\PaymentTerm\DestroyPaymentTermRequest $request
* @param \App\Models\PaymentTerm $payment_term
*
* @return \Illuminate\Http\Response
*
*
* @OA\Delete(
* path="/api/v1/payment_terms/{id}",
* operationId="deletePaymentTerm",
* tags={"payment_termss"},
* summary="Deletes a Payment Term",
* description="Handles the deletion of an PaymentTerm by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Payment Term Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns a HTTP status",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function destroy(DestroyPaymentTermRequest $request, PaymentTerm $payment_term)
{
$payment_term->delete();
return response()->json([], 200);
}
/**
* Perform bulk actions on the list view
*
* @return Collection
*
*
* @OA\Post(
* path="/api/v1/payment_terms/bulk",
* operationId="bulkPaymentTerms",
* tags={"payment_terms"},
* summary="Performs bulk actions on an array of payment terms",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\RequestBody(
* description="Payment Ter,s",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="integer",
* description="Array of hashed IDs to be bulk 'actioned",
* example="[0,1,2,3]",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="The Payment Terms response",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/PaymentTerm"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
*/
public function bulk()
{
$action = request()->input('action');
$ids = request()->input('ids');
$payment_terms = PaymentTerm::withTrashed()->company()->find($this->transformKeys($ids));
$payment_terms->each(function ($payment_term, $key) use ($action) {
if (auth()->user()->can('edit', $payment_term)) {
$this->payment_term_repo->{$action}($payment_term);
}
});
return $this->listResponse(PaymentTerm::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
}

View File

@ -528,6 +528,20 @@ class QuoteController extends BaseController
return response()->json(['message' => 'Email Sent!'], 200);
}
if($action == 'convert') {
$this->entity_type = Quote::class;
$this->entity_transformer = QuoteTransformer::class;
$quotes->each(function ($quote, $key) use ($action) {
if (auth()->user()->can('edit', $quote) && $quote->service()->isConvertable()) {
$quote->service()->convertToInvoice();
}
});
return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
}
/*
* Send the other actions to the switch
*/
@ -642,14 +656,6 @@ class QuoteController extends BaseController
}
return $this->itemResponse($quote->service()->approve()->save());
break;
case 'convert':
$this->entity_type = Invoice::class;
$this->entity_transformer = InvoiceTransformer::class;
return $this->itemResponse($quote->service()->convertToInvoice());
break;
case 'history':
# code...

View File

@ -16,6 +16,7 @@ use Codedge\Updater\UpdaterManager;
use Composer\Factory;
use Composer\IO\NullIO;
use Composer\Installer;
use Cz\Git\GitRepository;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Storage;
@ -66,22 +67,16 @@ class SelfUpdateController extends BaseController
return response()->json(['message' => 'Self update not available on this system.'], 403);
}
info("is new version available = ". $updater->source()->isNewVersionAvailable());
/* .git MUST be owned/writable by the webserver user */
$repo = new GitRepository(base_path());
// Get the new version available
$versionAvailable = $updater->source()->getVersionAvailable();
info("Are there changes to pull? " . $repo->hasChanges());
info($versionAvailable);
$res = $repo->pull();
info("Are there any changes to pull? " . $repo->hasChanges());
// Create a release
$release = $updater->source()->fetch($versionAvailable);
info(print_r($release,1));
// Run the update process
$res = $updater->source()->update($release);
info(print_r($res,1));
Artisan::call('ninja:post-update');
return response()->json(['message'=>$res], 200);
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
@ -34,9 +35,15 @@ trait VerifiesUserEmail
$user->confirmation_code = null;
$user->save();
return response()->json(['message' => ctrans('texts.security_confirmation')]);
return $this->render('auth.confirmed', [
'root' => 'themes',
'message' => ctrans('texts.security_confirmation'),
]);
}
return response()->json(['message' => ctrans('texts.wrong_confirmation')]);
return $this->render('auth.confirmed', [
'root' => 'themes',
'message' => ctrans('texts.wrong_confirmation'),
]);
}
}

View File

@ -45,6 +45,8 @@ class StartupCheck
Session::flash('message', 'Cache cleared');
}
/* Make sure our cache is built */
$cached_tables = config('ninja.cached_tables');

View File

@ -36,8 +36,8 @@ class CreateAccountRequest extends Request
{
return [
//'email' => 'required|string|email|max:100',
'first_name' => 'required|string|max:100',
'last_name' => 'required|string:max:100',
'first_name' => 'string|max:100',
'last_name' => 'string:max:100',
'password' => 'required|string|min:6',
'email' => 'bail|required|email',
'email' => new NewUniqueUserRule(),

View File

@ -49,6 +49,9 @@ class StoreInvoiceRequest extends Request
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['invitations.*.client_contact_id'] = 'distinct';
return $rules;
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\PaymentTerm;
use App\Http\Requests\Request;
use App\Models\Payment;
class ActionPaymentTermRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\PaymentTerm;
use App\Http\Requests\Request;
use App\Models\PaymentTerm;
class CreatePaymentTermRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\PaymentTerm;
use App\Http\Requests\Request;
use App\Models\PaymentTerm;
class DestroyPaymentTermRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\PaymentTerm;
use App\Http\Requests\Request;
use App\Models\PaymentTerm;
class EditPaymentTermRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->user()->isAdmin();
}
public function rules()
{
$rules = [];
return $rules;
}
protected function prepareForValidation()
{
$input = $this->all();
//$input['id'] = $this->encodePrimaryKey($input['id']);
$this->replace($input);
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\PaymentTerm;
use App\Http\Requests\Request;
use App\Models\PaymentTerm;
class ShowPaymentTermRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\PaymentTerm;
use App\Http\Requests\Request;
use App\Models\PaymentTerm;
use App\Utils\Traits\MakesHash;
class StorePaymentTermRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
protected function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
public function rules()
{
$rules = [
];
return $rules;
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\PaymentTerm;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class UpdatePaymentTermRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'num_days' => 'required',
];
}
protected function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -49,7 +49,8 @@ class PortalComposer
$data['company'] = auth()->user()->company;
$data['client'] = auth()->user()->client;
$data['settings'] = auth()->user()->client->getMergedSettings();
$data['multiple_contacts'] = ClientContact::where('email', auth('contact')->user()->email)->get();
$data['multiple_contacts'] = ClientContact::where('email', auth('contact')->user()->email)->whereNotNull('email')->distinct('company_id')->get();
return $data;
}

View File

@ -3,6 +3,7 @@ namespace App\Jobs\Account;
use App\Events\Account\AccountCreated;
use App\Jobs\Company\CreateCompany;
use App\Jobs\Company\CreateCompanyPaymentTerms;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\User\CreateUser;
use App\Models\Account;
@ -60,6 +61,8 @@ class CreateAccount
$spaa9f78 = CreateUser::dispatchNow($this->request, $sp794f3f, $sp035a66, true);
CreateCompanyPaymentTerms::dispatchNow($sp035a66, $spaa9f78);
if ($spaa9f78) {
auth()->login($spaa9f78, false);
}

View File

@ -57,10 +57,10 @@ class CreateCompany
$company->ip = request()->ip();
$company->settings = $settings;
$company->db = config('database.default');
$company->enabled_modules = config('ninja.enabled_modules');
$company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : '';
$company->save();
return $company;
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Company;
use App\DataMapper\CompanySettings;
use App\Events\UserSignedUp;
use App\Models\Company;
use App\Models\PaymentTerm;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Request;
class CreateCompanyPaymentTerms
{
use MakesHash;
use Dispatchable;
protected $company;
protected $user;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($company, $user)
{
$this->company = $company;
$this->user = $user;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$paymentTerms = [
['num_days' => 0, 'name' => 'Net 0', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 7, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 10, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 14, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 15, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 30, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 60, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 90, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
];
PaymentTerm::insert($paymentTerms);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Jobs\Mail;
use App\Jobs\Util\SystemLogger;
use App\Libraries\Google\Google;
use App\Libraries\MultiDB;
use App\Mail\Admin\EntityNotificationMailer;
use App\Mail\Admin\EntityPaidObject;
use App\Mail\Admin\EntitySentObject;
use App\Models\SystemLog;
use App\Models\User;
use App\Providers\MailServiceProvider;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
class EntityPaidMailer extends BaseMailerJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $company;
public $user;
public $payment;
public $entity_type;
public $entity;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($payment, $user, $company)
{
$this->company = $company;
$this->user = $user;
$this->payment = $payment;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
info("entity paid mailer");
//Set DB
//
MultiDB::setDb($this->company->db);
if($this->company->company_users->first()->is_migrating)
return true;
//if we need to set an email driver do it now
$this->setMailDriver($this->payment->client->getSetting('email_sending_method'));
$mail_obj = (new EntityPaidObject($this->payment))->build();
$mail_obj->from = [$this->payment->user->email, $this->payment->user->present()->name()];
//send email
Mail::to($this->user->email)
->send(new EntityNotificationMailer($mail_obj));
//catch errors
if (count(Mail::failures()) > 0) {
$this->logMailError(Mail::failures());
}
}
private function logMailError($errors)
{
SystemLogger::dispatch(
$errors,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND,
SystemLog::TYPE_FAILURE,
$this->payment->client
);
}
}

View File

@ -5,7 +5,7 @@ namespace App\Jobs\Mail;
use App\Jobs\Util\SystemLogger;
use App\Libraries\Google\Google;
use App\Libraries\MultiDB;
use App\Mail\Admin\EntitySent;
use App\Mail\Admin\EntityNotificationMailer;
use App\Mail\Admin\EntitySentObject;
use App\Models\SystemLog;
use App\Models\User;
@ -18,7 +18,7 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
class EntitySentEmail extends BaseMailerJob implements ShouldQueue
class EntitySentMailer extends BaseMailerJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@ -56,19 +56,19 @@ class EntitySentEmail extends BaseMailerJob implements ShouldQueue
*/
public function handle()
{
//set DB
info("entity sent mailer");
//Set DB
MultiDB::setDb($this->company->db);
//if we need to set an email driver do it now
$this->setMailDriver($this->entity->client->getSetting('email_sending_method'));
$mail_obj = (new EntitySentObject($this->invitation, $this->entity_type))->build();
$mail_obj->from = $this->entity->user->present()->name();
$mail_obj->from = [$this->entity->user->email, $this->entity->user->present()->name()];
//send email
// Mail::to($this->user->email)
Mail::to('turbo124@gmail.com') //@todo
->send(new EntitySent($mail_obj));
Mail::to($this->user->email)
->send(new EntityNotificationMailer($mail_obj));
//catch errors
if (count(Mail::failures()) > 0) {
@ -84,7 +84,7 @@ class EntitySentEmail extends BaseMailerJob implements ShouldQueue
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND,
SystemLog::TYPE_FAILURE,
$this->invoice->client
$this->entity->client
);
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Jobs\Mail;
use App\Jobs\Util\SystemLogger;
use App\Libraries\Google\Google;
use App\Libraries\MultiDB;
use App\Mail\Admin\EntityNotificationMailer;
use App\Mail\Admin\EntityViewedObject;
use App\Models\SystemLog;
use App\Models\User;
use App\Providers\MailServiceProvider;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
class EntityViewedMailer extends BaseMailerJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $company;
public $user;
public $invitation;
public $entity_type;
public $entity;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($invitation, $entity_type, $user, $company)
{
$this->company = $company;
$this->user = $user;
$this->invitation = $invitation;
$this->entity = $invitation->{$entity_type};
$this->entity_type = $entity_type;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
info("entity viewed mailer");
//Set DB
MultiDB::setDb($this->company->db);
//if we need to set an email driver do it now
$this->setMailDriver($this->entity->client->getSetting('email_sending_method'));
$mail_obj = (new EntityViewedObject($this->invitation, $this->entity_type))->build();
$mail_obj->from = [$this->entity->user->email, $this->entity->user->present()->name()];
//send email
Mail::to($this->user->email)
->send(new EntityNotificationMailer($mail_obj));
//catch errors
if (count(Mail::failures()) > 0) {
$this->logMailError(Mail::failures());
}
}
private function logMailError($errors)
{
SystemLogger::dispatch(
$errors,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND,
SystemLog::TYPE_FAILURE,
$this->invoice->client
);
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace App\Jobs\Mail;
use App\Jobs\Util\SystemLogger;
use App\Libraries\Google\Google;
use App\Libraries\MultiDB;
use App\Mail\Admin\EntityNotificationMailer;
use App\Mail\Admin\EntityPaidObject;
use App\Mail\Admin\EntitySentObject;
use App\Mail\Admin\PaymentFailureObject;
use App\Models\SystemLog;
use App\Models\User;
use App\Providers\MailServiceProvider;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies;
public $client;
public $message;
public $company;
public $amount;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($client, $message, $company, $amount)
{
$this->company = $company;
$this->message = $message;
$this->client = $client;
$this->amount = $amount;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
info("entity payment failure mailer");
//Set DB
MultiDB::setDb($this->company->db);
//if we need to set an email driver do it now
$this->setMailDriver($this->client->getSetting('email_sending_method'));
//iterate through company_users
$this->company->company_users->each(function ($company_user){
//determine if this user has the right permissions
$methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure']);
//if mail is a method type -fire mail!!
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
$mail_obj = (new PaymentFailureObject($this->client, $this->message, $this->amount, $this->company))->build();
$mail_obj->from = [$this->company->owner()->email, $this->company->owner()->present()->name()];
//send email
Mail::to($company_user->user->email)
->send(new EntityNotificationMailer($mail_obj));
//catch errors
if (count(Mail::failures()) > 0) {
$this->logMailError(Mail::failures());
}
}
});
}
private function logMailError($errors)
{
SystemLogger::dispatch(
$errors,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND,
SystemLog::TYPE_FAILURE,
$this->client
);
}
}

View File

@ -31,6 +31,7 @@ use App\Libraries\MultiDB;
use App\Mail\MigrationCompleted;
use App\Mail\MigrationFailed;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
@ -38,6 +39,7 @@ use App\Models\Credit;
use App\Models\Document;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Quote;
use App\Models\TaxRate;
@ -86,6 +88,7 @@ class Import implements ShouldQueue
private $available_imports = [
'company',
'users',
'payment_terms',
'tax_rates',
'clients',
'products',
@ -145,7 +148,7 @@ class Import implements ShouldQueue
* @return void
* @throws \Exception
*/
public function handle()
public function handle() :bool
{
set_time_limit(0);
@ -154,7 +157,6 @@ class Import implements ShouldQueue
if (! in_array($key, $this->available_imports)) {
//throw new ResourceNotAvailableForMigration("Resource {$key} is not available for migration.");
info("Resource {$key} is not available for migration.");
continue;
}
@ -168,6 +170,8 @@ class Import implements ShouldQueue
Mail::to($this->user)->send(new MigrationCompleted());
info('Completed🚀🚀🚀🚀🚀 at '.now());
return true;
}
/**
@ -366,7 +370,9 @@ class Import implements ShouldQueue
unset($modified_contacts[$key]['id']);
}
$contact_repository->save($modified_contacts, $client);
$saveable_contacts['contacts'] = $modified_contacts;
$contact_repository->save($saveable_contacts, $client);
}
$key = "clients_{$resource['id']}";
@ -695,6 +701,28 @@ class Import implements ShouldQueue
$data = null;
}
private function processPaymentTerms(array $data) :void
{
PaymentTerm::unguard();
$modified = collect($data)->map(function ($item){
$item['user_id'] = $this->user->id;
$item['company_id'] = $this->company->id;
return $item;
})->toArray();
PaymentTerm::insert($modified);
PaymentTerm::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
}
private function processCompanyGateways(array $data) :void
{
CompanyGateway::unguard();

View File

@ -74,6 +74,9 @@ class StartMigration implements ShouldQueue
*/
public function handle()
{
set_time_limit(0);
MultiDB::setDb($this->company->db);
auth()->login($this->user, false);
@ -96,7 +99,7 @@ class StartMigration implements ShouldQueue
$zip->close();
if (app()->environment() == 'testing') {
return;
return true;
}
$this->company->setMigration(true);
@ -110,6 +113,9 @@ class StartMigration implements ShouldQueue
$data = json_decode(file_get_contents($file), 1);
Import::dispatchNow($data, $this->company, $this->user);
$this->company->setMigration(false);
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
$this->company->setMigration(false);
@ -119,6 +125,11 @@ class StartMigration implements ShouldQueue
info($e->getMessage());
}
}
//always make sure we unset the migration as running
return true;
}
public function failed($exception = null)

View File

@ -37,18 +37,27 @@ class SubscriptionHandler implements ShouldQueue
*
* @return void
*/
public function handle()
public function handle() :bool
{
if(!$this->entity->company || $this->entity->company->company_users->first()->is_migrating)
return true;
info("i got past the check");
$subscriptions = Subscription::where('company_id', $this->entity->company_id)
->where('event_id', $this->event_id)
->get();
if(!$subscriptions || $subscriptions->count() == 0)
return;
return true;
$subscriptions->each(function($subscription) {
$this->process($subscription);
});
return true;
}
private function process($subscription)

View File

@ -13,6 +13,7 @@ namespace App\Listeners\Credit;
use App\Factory\CreditInvitationFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -38,7 +39,7 @@ class CreateCreditInvitation implements ShouldQueue
$contacts = $credit->client->contacts;
$contacts->each(function ($contact) use ($credit) {
$invitation = InvoiceInvitation::whereCompanyId($credit->company_id)
$invitation = CreditInvitation::whereCompanyId($credit->company_id)
->whereClientContactId($contact->id)
->whereCreditId($credit->id)
->first();

View File

@ -11,6 +11,7 @@
namespace App\Listeners\Invoice;
use App\Jobs\Mail\EntitySentMailer;
use App\Models\Activity;
use App\Models\ClientContact;
use App\Models\InvoiceInvitation;
@ -57,7 +58,7 @@ class InvoiceEmailedNotification implements ShouldQueue
//This allows us better control of how we
//handle the mailer
EntitySentEmail::dispatch($invitation, 'invoice', $user, $invitation->company);
EntitySentMailer::dispatch($invitation, 'invoice', $user, $invitation->company);
}
$notification->method = $methods;

View File

@ -11,6 +11,7 @@
namespace App\Listeners\Misc;
use App\Jobs\Mail\EntityViewedMailer;
use App\Notifications\Admin\EntityViewedNotification;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -44,11 +45,22 @@ class InvitationViewedListener implements ShouldQueue
$notification = new EntityViewedNotification($invitation, $entity_name);
foreach ($invitation->company->company_users as $company_user) {
$entity_viewed = "{$entity_name}_viewed";
$notification->method = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed]);
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed]);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
EntityViewedMailer::dispatch($invitation, $entity_name, $company_user->user, $invitation->company);
}
$notification->method = $methods;
$company_user->user->notify($notification);
}
if (isset($invitation->company->slack_webhook_url)) {
@ -57,14 +69,9 @@ class InvitationViewedListener implements ShouldQueue
Notification::route('slack', $invitation->company->slack_webhook_url)
->notify($notification);
}
}
private function userNotificationArray($notifications)
{
$via_array = [];
if (stripos($this->company_user->permissions, ) !== false);
}
}

View File

@ -11,6 +11,7 @@
namespace App\Listeners\Payment;
use App\Jobs\Mail\EntityPaidMailer;
use App\Models\Activity;
use App\Models\Invoice;
use App\Models\Payment;
@ -46,10 +47,26 @@ class PaymentNotification implements ShouldQueue
/*User notifications*/
foreach ($payment->company->company_users as $company_user) {
if($company_user->is_migrating)
return true;
$user = $company_user->user;
$methods = $this->findUserEntityNotificationType($payment, $company_user, ['all_notifications']);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
//Fire mail notification here!!!
//This allows us better control of how we
//handle the mailer
EntityPaidMailer::dispatch($payment, $user, $payment->company);
}
$notification = new NewPaymentNotification($payment, $payment->company);
$notification->method = $this->findUserEntityNotificationType($payment, $company_user, ['all_notifications']);
$notification->method = $methods;
if ($user) {
$user->notify($notification);

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://creditninja.com)
*
* @link https://github.com/creditninja/creditninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://creditninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Listeners\Quote;
use App\Factory\CreditInvitationFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Factory\QuoteInvitationFactory;
use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot;
use Symfony\Component\Debug\Exception\FatalThrowableError;
class CreateQuoteInvitation implements ShouldQueue
{
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
$quote = $event->credit;
$contacts = $quote->client->contacts;
$contacts->each(function ($contact) use ($quote) {
$invitation = QuoteInvitation::whereCompanyId($quote->company_id)
->whereClientContactId($contact->id)
->whereQuoteId($quote->id)
->first();
if (!$invitation && $contact->send_credit) {
$ii = QuoteInvitationFactory::create($quote->company_id, $quote->user_id);
$ii->quote_id = $quote->id;
$ii->client_contact_id = $contact->id;
$ii->save();
} elseif ($invitation && !$contact->send_credit) {
$invitation->delete();
}
});
}
}

View File

@ -14,10 +14,11 @@ namespace App\Mail\Admin;
use App\Models\User;
use Illuminate\Mail\Mailable;
class EntitySent extends Mailable
class EntityNotificationMailer extends Mailable
{
public $mail_obj;
/**
* Create a new message instance.
*
@ -35,9 +36,9 @@ class EntitySent extends Mailable
*/
public function build()
{
return $this->from($this->mail_obj->from) //todo
return $this->from($this->mail_obj->from[0], $this->mail_obj->from[1]) //todo
->subject($this->mail_obj->subject)
->markdown($this->mail_obj->markdown, ['data' => $this->mail_obj->data])
->markdown($this->mail_obj->markdown, $this->mail_obj->data)
->withSwiftMessage(function ($message) {
$message->getHeaders()->addTextHeader('Tag', $this->mail_obj->tag);
});

View File

@ -0,0 +1,96 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Mail\Admin;
use App\Models\User;
use App\Utils\Number;
class EntityPaidObject
{
public $invitation;
public $entity;
public $contact;
public $company;
public $settings;
public function __construct($payment)
{
$this->payment = $payment;
$this->company = $payment->company;
}
public function build()
{
$mail_obj = new \stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
return $mail_obj;
}
private function getAmount()
{
return Number::formatMoney($this->payment->amount, $this->payment->client);
}
private function getSubject()
{
return
ctrans(
'texts.notification_payment_paid_subject',
['client' => $this->payment->client->present()->name()]
);
}
private function getData()
{
$settings = $this->payment->client->getMergedSettings();
$amount = Number::formatMoney($this->payment->amount, $this->payment->client);
$invoice_texts = ctrans('texts.invoice_number_short');
foreach ($this->payment->invoices as $invoice) {
$invoice_texts .= $invoice->number . ',';
}
$invoice_texts = substr($invoice_texts, 0, -1);
$data = [
'title' => ctrans(
'texts.notification_payment_paid_subject',
['client' => $this->payment->client->present()->name()]
),
'message' => ctrans(
'texts.notification_payment_paid',
['amount' => $amount,
'client' => $this->payment->client->present()->name(),
'invoice' => $invoice_texts,
]
),
'url' => config('ninja.app_url') . '/payments/' . $this->payment->hashed_id,
'button' => ctrans('texts.view_payment'),
'signature' => $settings->email_signature,
'logo' => $this->company->present()->logo(),
];
return $data;
}
}

View File

@ -71,13 +71,12 @@ class EntitySentObject
$settings = $this->entity->client->getMergedSettings();
$data = [
return [
'title' => $this->getSubject(),
'message' => ctrans(
"texts.notification_{$this->entity_type}_sent",
[
'amount' => $this->getAmount(),
'client' => $this->contact->present()->name(),
'invoice' => $this->entity->number,
]
@ -88,6 +87,5 @@ class EntitySentObject
'logo' => $this->company->present()->logo(),
];
return $data;
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Mail\Admin;
use App\Models\User;
use App\Utils\Number;
class EntityViewedObject
{
public $invitation;
public $entity_type;
public $entity;
public $contact;
public $company;
public $settings;
public function __construct($invitation, $entity_type)
{
$this->invitation = $invitation;
$this->entity_type = $entity_type;
$this->entity = $invitation->{$entity_type};
$this->contact = $invitation->contact;
$this->company = $invitation->company;
}
public function build()
{
$mail_obj = new \stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
return $mail_obj;
}
private function getAmount()
{
return Number::formatMoney($this->entity->amount, $this->entity->client);
}
private function getSubject()
{
return
ctrans(
"texts.notification_{$this->entity_type}_viewed_subject",
[
'client' => $this->contact->present()->name(),
'invoice' => $this->entity->number,
]
);
}
private function getData()
{
$settings = $this->entity->client->getMergedSettings();
$data = [
'title' => $this->getSubject(),
'message' => ctrans(
"texts.notification_{$this->entity_type}_viewed",
[
'amount' => $this->getAmount(),
'client' => $this->contact->present()->name(),
'invoice' => $this->entity->number,
]
),
'url' => $this->invitation->getAdminLink(),
'button' => ctrans("texts.view_{$this->entity_type}"),
'signature' => $settings->email_signature,
'logo' => $this->company->present()->logo(),
];
return $data;
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Mail\Admin;
use App\Models\User;
use App\Utils\Number;
class PaymentFailureObject
{
public $client;
public $message;
public $company;
public $amount;
public function __construct($client, $message, $amount, $company)
{
$this->client = $client;
$this->message = $message;
$this->amount = $amount;
$this->company = $company;
}
public function build()
{
$mail_obj = new \stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
return $mail_obj;
}
private function getAmount()
{
return Number::formatMoney($this->amount, $this->client);
}
private function getSubject()
{
return
ctrans(
'texts.payment_failed_subject',
['client' => $this->payment->client->present()->name()]
);
}
private function getData()
{
$signature = $this->client->getSetting('email_signature');
$data = [
'title' => ctrans(
'texts.payment_failed_subject',
['client' => $this->client->present()->name()]
),
'message' => ctrans(
'texts.notification_payment_paid',
['amount' => $this->getAmount(),
'client' => $this->client->present()->name(),
'message' => $this->message,
]
),
'signature' => $signature,
'logo' => $this->company->present()->logo(),
];
return $data;
}
}

View File

@ -182,6 +182,11 @@ class Client extends BaseModel implements HasLocalePreference
return $this->belongsTo(Country::class);
}
public function invoices()
{
return $this->hasMany(Invoice::class);
}
public function shipping_country()
{
return $this->belongsTo(Country::class, 'shipping_country_id', 'id');

View File

@ -284,6 +284,11 @@ class Company extends BaseModel
return $this->hasMany(Design::class)->whereCompanyId($this->id)->orWhere('company_id', null);
}
public function payment_terms()
{
return $this->hasMany(PaymentTerm::class)->whereCompanyId($this->id)->orWhere('company_id', null);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
@ -401,9 +406,13 @@ class Company extends BaseModel
public function setMigration($status)
{
$this->company_users->each(function ($cu) use ($status) {
$company_users = CompanyUser::where('company_id', $this->id)->get();
foreach($company_users as $cu)
{
$cu->is_migrating=$status;
$cu->save();
});
}
}
}

View File

@ -94,6 +94,11 @@ class Payment extends BaseModel
return $this->belongsTo(Company::class);
}
public function contact()
{
return $this->belongsTo(ClientContact::class);
}
public function user()
{
return $this->belongsTo(User::class)->withTrashed();

View File

@ -30,6 +30,8 @@ class PaymentTerm extends BaseModel
*/
protected $dates = ['deleted_at'];
protected $fillable = ['num_days'];
public function getNumDays()
{
return $this->num_days == -1 ? 0 : $this->num_days;
@ -39,7 +41,7 @@ class PaymentTerm extends BaseModel
{
$default_terms = collect(config('ninja.payment_terms'));
$terms = self::scope()->get();
$terms = self::whereCompanyId(auth()->user()->company()->id)->orWhere('company_id', null)->get();
$terms->map(function ($term) {
return $term['num_days'];

View File

@ -21,28 +21,28 @@ class PaymentType extends StaticModel
public $timestamps = false;
const CREDIT = 1;
const ACH = 5;
const VISA = 6;
const MASTERCARD = 7;
const AMERICAN_EXPRESS = 8;
const DISCOVER = 9;
const DINERS = 10;
const EUROCARD = 11;
const NOVA = 12;
const CREDIT_CARD_OTHER = 13;
const PAYPAL = 14;
const CARTE_BLANCHE = 17;
const UNIONPAY = 18;
const JCB = 19;
const LASER = 20;
const MAESTRO = 21;
const SOLO = 22;
const SWITCH = 23;
const ALIPAY = 28;
const SOFORT = 29;
const SEPA = 30;
const GOCARDLESS = 31;
const CRYPTO = 32;
const ACH = 4;
const VISA = 5;
const MASTERCARD = 6;
const AMERICAN_EXPRESS = 7;
const DISCOVER = 8;
const DINERS = 9;
const EUROCARD = 10;
const NOVA = 11;
const CREDIT_CARD_OTHER = 12;
const PAYPAL = 13;
const CARTE_BLANCHE = 16;
const UNIONPAY = 17;
const JCB = 18;
const LASER = 19;
const MAESTRO = 20;
const SOLO = 21;
const SWITCH = 22;
const ALIPAY = 27;
const SOFORT = 28;
const SEPA = 29;
const GOCARDLESS = 30;
const CRYPTO = 31;
public static function parseCardType($cardName)
{

View File

@ -69,6 +69,7 @@ class EntitySentNotification extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
//@TODO THESE ARE @DEPRECATED NOW we are now using app/Mail/Admin/*
$amount = Number::formatMoney($this->entity->amount, $this->entity->client);
$subject = ctrans(
"texts.notification_{$this->entity_name}_sent_subject",

View File

@ -68,6 +68,8 @@ class EntityViewedNotification extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
//@TODO THESE ARE @DEPRECATED NOW we are now using app/Mail/Admin/*
$data = $this->buildDataArray();
$subject = $this->buildSubject();

View File

@ -63,6 +63,9 @@ class InvoiceSentNotification extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
//@TODO THESE ARE @DEPRECATED NOW we are now using app/Mail/Admin/*
$amount = Number::formatMoney($this->invoice->amount, $this->invoice->client);
$subject = ctrans(
'texts.notification_invoice_sent_subject',

View File

@ -62,6 +62,10 @@ class InvoiceViewedNotification extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
//@TODO THESE ARE @DEPRECATED NOW we are now using app/Mail/Admin/*
$amount = Number::formatMoney($this->invoice->amount, $this->invoice->client);
$subject = ctrans(
'texts.notification_invoice_viewed_subject',

View File

@ -58,6 +58,9 @@ class NewPartialPaymentNotification extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
//@TODO THESE ARE @DEPRECATED NOW we are now using app/Mail/Admin/*
$amount = Number::formatMoney($this->payment->amount, $this->payment->client);
$invoice_texts = ctrans('texts.invoice_number_short');

View File

@ -61,6 +61,9 @@ class NewPaymentNotification extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
//@TODO THESE ARE @DEPRECATED NOW we are now using app/Mail/Admin/*
$amount = Number::formatMoney($this->payment->amount, $this->payment->client);
$invoice_texts = ctrans('texts.invoice_number_short');

View File

@ -13,7 +13,7 @@
namespace App\PaymentDrivers;
use App\Events\Payment\PaymentWasCreated;
//use App\Jobs\Invoice\UpdateInvoicePayment;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
@ -140,6 +140,9 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
$this->client
);
} elseif (!$response->isSuccessful()) {
PaymentFailureMailer::dispatch($this->client, $response->getMessage, $this->client->company, $response['PAYMENTINFO_0_AMT']);
SystemLogger::dispatch(
[
'data' => $request->all(),
@ -271,12 +274,12 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
return $payment;
}
public function refund(Payment $payment, $amount = null)
public function refund(Payment $payment, $amount)
{
$this->gateway();
$response = $this->gateway
->refund(['transactionReference' => $payment->transaction_reference, 'amount' => $amount ?? $payment->amount])
->refund(['transactionReference' => $payment->transaction_reference, 'amount' => $amount])
->send();
if ($response->isSuccessful()) {
@ -305,6 +308,7 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
$this->client
);
return false;
}
}

View File

@ -13,6 +13,7 @@ namespace App\PaymentDrivers;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\PaymentFactory;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
@ -364,6 +365,9 @@ class StripePaymentDriver extends BasePaymentDriver
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
} else {
PaymentFailureMailer::dispatch($this->client, $server_response->cancellation_reason, $this->client->company, $server_response->amount);
/*Fail and log*/
SystemLogger::dispatch(
[

View File

@ -0,0 +1,33 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Policies;
use App\Models\Payment;
use App\Models\User;
/**
* Class PaymentTermPolicy
* @package App\Policies
*/
class PaymentTermPolicy extends EntityPolicy
{
/**
* Checks if the user has create permissions
*
* @param User $user
* @return bool
*/
public function create(User $user) : bool
{
return $user->isAdmin() || $user->hasPermission('create_all');
}
}

View File

@ -22,6 +22,7 @@ use App\Models\Expense;
use App\Models\GroupSetting;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Quote;
use App\Models\RecurringInvoice;
@ -41,6 +42,7 @@ use App\Policies\ExpensePolicy;
use App\Policies\GroupSettingPolicy;
use App\Policies\InvoicePolicy;
use App\Policies\PaymentPolicy;
use App\Policies\PaymentTermPolicy;
use App\Policies\ProductPolicy;
use App\Policies\QuotePolicy;
use App\Policies\RecurringInvoicePolicy;
@ -72,6 +74,7 @@ class AuthServiceProvider extends ServiceProvider
GroupSetting::class => GroupSettingPolicy::class,
Invoice::class => InvoicePolicy::class,
Payment::class => PaymentPolicy::class,
PaymentTerm::class => PaymentTermPolicy::class,
Product::class => ProductPolicy::class,
Quote::class => QuotePolicy::class,
RecurringInvoice::class => RecurringInvoicePolicy::class,

View File

@ -24,6 +24,7 @@ class ClientContactRepository extends BaseRepository
{
public function save(array $data, Client $client) : void
{
if (isset($data['contacts'])) {
$contacts = collect($data['contacts']);
} else {
@ -66,17 +67,20 @@ class ClientContactRepository extends BaseRepository
}
$update_contact->save();
});
//always made sure we have one blank contact to maintain state
if ($contacts->count() == 0) {
if ($client->contacts->count() == 0) {
info("no contacts found");
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
$new_contact->client_id = $client->id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Repositories;
/**
* Class for payment term repository.
*/
class PaymentTermRepository extends BaseRepository
{
}

View File

@ -29,7 +29,11 @@ class MarkSent
event(new CreditWasMarkedSent($this->credit, $this->credit->company));
$this->credit->service()->setStatus(Credit::STATUS_SENT)->applyNumber()->save();
$this->credit
->service()
->setStatus(Credit::STATUS_SENT)
->applyNumber()
->save();
return $this->credit;
}

View File

@ -62,6 +62,8 @@ class ApplyPayment extends AbstractService
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($this->payment_amount*-1);
}
$this->invoice->service()->applyNumber()->save();
return $this->invoice;
}
}

View File

@ -139,8 +139,13 @@ class InvoiceService
/* One liners */
public function setDueDate()
{
$this->invoice->due_date = Carbon::now()->addDays($this->invoice->client->getSetting('payment_terms'));
if($this->invoice->due_date != '')
return $this;
//$this->invoice->due_date = Carbon::now()->addDays($this->invoice->client->getSetting('payment_terms'));
$this->invoice->due_date = Carbon::parse($this->invoice->date)->addDays($this->invoice->client->getSetting('payment_terms'));
return $this;
}

View File

@ -61,6 +61,7 @@ class MarkPaid extends AbstractService
$this->invoice->service()
->updateBalance($payment->amount*-1)
->setStatus(Invoice::STATUS_PAID)
->applyNumber()
->save();
/* Update Invoice balance */

View File

@ -45,6 +45,7 @@ class MarkSent extends AbstractService
->service()
->setStatus(Invoice::STATUS_SENT)
->applyNumber()
->setDueDate()
->save();
$this->client->service()->updateBalance($this->invoice->balance)->save();

View File

@ -10,10 +10,10 @@ class ConvertQuote
private $client;
private $invoice_repo;
public function __construct($client, InvoiceRepository $invoice_repo)
public function __construct($client)
{
$this->client = $client;
$this->invoice_repo = $invoice_repo;
$this->invoice_repo = new InvoiceRepository();
}
/**
@ -23,9 +23,19 @@ class ConvertQuote
public function run($quote)
{
$invoice = CloneQuoteToInvoiceFactory::create($quote, $quote->user_id, $quote->company_id);
$this->invoice_repo->save([], $invoice);
$invoice = $this->invoice_repo->save([], $invoice);
$invoice->fresh();
$invoice->service()
->markSent()
->createInvitations()
->save();
$quote->invoice_id = $invoice->id;
$quote->save();
// maybe should return invoice here
return $quote;
return $invoice;
}
}

View File

@ -29,7 +29,11 @@ class MarkSent
event(new QuoteWasMarkedSent($this->quote, $this->quote->company));
$this->quote->service()->setStatus(Quote::STATUS_SENT)->applyNumber()->save();
$this->quote
->service()
->setStatus(Quote::STATUS_SENT)
->applyNumber()
->save();
return $this->quote;
}

View File

@ -21,6 +21,8 @@ class QuoteService
{
protected $quote;
public $invoice;
public function __construct($quote)
{
$this->quote = $quote;
@ -38,17 +40,29 @@ class QuoteService
public function markApproved()
{
$mark_approved = new MarkApproved($this->quote->client);
$this->quote = $mark_approved->run($this->quote);
if ($this->quote->client->getSetting('auto_convert_quote') === true) {
$convert_quote = new ConvertQuote($this->quote->client);
$this->quote = $convert_quote->run($this->quote);
$this->convert();
}
return $this;
}
public function convert() :QuoteService
{
if($this->quote->invoice_id)
return $this;
$convert_quote = new ConvertQuote($this->quote->client);
$this->invoice = $convert_quote->run($this->quote);
$this->quote->fresh();
return $this;
}
public function getQuotePdf($contact = null)
{
$get_invoice_pdf = new GetQuotePdf();
@ -101,8 +115,7 @@ class QuoteService
$invoice = null;
if ($this->quote->client->getSetting('auto_convert_quote')) {
$invoice = $this->convertToInvoice();
$this->linkInvoiceToQuote($invoice)->save();
$this->convert();
}
if ($this->quote->client->getSetting('auto_archive_quote')) {
@ -113,33 +126,27 @@ class QuoteService
return $this;
}
/**
* Where we convert a quote to an invoice we link the two entities via the invoice_id parameter on the quote table
* @param object $invoice The Invoice object
* @return object QuoteService
*/
public function linkInvoiceToQuote($invoice) :QuoteService
public function convertToInvoice()
{
$this->quote->invoice_id = $invoice->id;
return $this;
//to prevent circular references we need to explicit call this here.
$mark_approved = new MarkApproved($this->quote->client);
$this->quote = $mark_approved->run($this->quote);
$this->convert();
return $this->invoice;
}
public function convertToInvoice() :Invoice
public function isConvertable() :bool
{
if($this->quote->invoice_id)
return false;
$invoice = CloneQuoteToInvoiceFactory::create($this->quote, $this->quote->user_id);
$invoice->status_id = Invoice::STATUS_SENT;
$invoice->due_date = null;
$invoice->number = null;
$invoice->save();
if($this->quote->status_id == Quote::STATUS_EXPIRED)
return false;
$invoice->service()
->markSent()
->createInvitations()
->save();
return $invoice;
return true;
}
/**
@ -149,6 +156,7 @@ class QuoteService
public function save() : ?Quote
{
$this->quote->save();
return $this->quote;
}
}

View File

@ -23,6 +23,7 @@ use App\Models\Design;
use App\Models\Expense;
use App\Models\GroupSetting;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
@ -31,6 +32,7 @@ use App\Models\TaxRate;
use App\Models\User;
use App\Transformers\CompanyLedgerTransformer;
use App\Transformers\CreditTransformer;
use App\Transformers\PaymentTermTransformer;
use App\Transformers\TaskTransformer;
use App\Utils\Traits\MakesHash;
@ -65,6 +67,7 @@ class CompanyTransformer extends EntityTransformer
'expenses',
'vendors',
'payments',
'payment_terms',
'company_user',
'groups',
'company_gateways',
@ -253,4 +256,11 @@ class CompanyTransformer extends EntityTransformer
return $this->includeCollection($company->ledger, $transformer, CompanyLedger::class);
}
public function includePaymentTerms(Company $company)
{
$transformer = new PaymentTermTransformer($this->serializer);
return $this->includeCollection($company->payment_terms()->get(), $transformer, PaymentTerm::class);
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Transformers;
use App\Models\PaymentTerm;
use App\Utils\Traits\MakesHash;
class PaymentTermTransformer extends EntityTransformer
{
use MakesHash;
public function transform(PaymentTerm $payment_term)
{
return [
'id' => (string) $this->encodePrimaryKey($payment_term->id),
'num_days' => (int) $payment_term->num_days,
'name' => (string) ctrans('texts.payment_terms_net') . ' ' . $payment_term->getNumDays(),
'is_deleted' => (bool) $payment_term->is_deleted,
'created_at' => (int) $payment_term->created_at,
'updated_at' => (int) $payment_term->updated_at,
'archived_at' => (int) $payment_term->deleted_at,
];
}
}

View File

@ -81,7 +81,7 @@ class QuoteTransformer extends EntityTransformer
'client_id' => (string) $this->encodePrimaryKey($quote->client_id),
'status_id' => (string)$quote->status_id,
'design_id' => (string) $this->encodePrimaryKey($quote->design_id),
'invoice_id' => (string)$quote->invoice_id,
'invoice_id' => (string)$this->encodePrimaryKey($quote->invoice_id),
'updated_at' => (int)$quote->updated_at,
'archived_at' => (int)$quote->deleted_at,
'created_at' => (int)$quote->created_at,

View File

@ -64,6 +64,7 @@ class UserTransformer extends EntityTransformer
'custom_value2' => $user->custom_value2 ?: '',
'custom_value3' => $user->custom_value3 ?: '',
'custom_value4' => $user->custom_value4 ?: '',
'oauth_provider_id' => (string)$user->oauth_provider_id,
];
}

View File

@ -17,7 +17,7 @@ trait ActionsInvoice
{
public function invoiceDeletable($invoice) :bool
{
if ($invoice->status_id <= Invoice::STATUS_SENT && $invoice->is_deleted == false && $invoice->deleted_at == null) {
if ($invoice->status_id <= Invoice::STATUS_SENT && $invoice->is_deleted == false && $invoice->deleted_at == null && $invoice->balance == 0) {
return true;
}

View File

@ -30,7 +30,7 @@ trait UserNotifies
array_push($required_permissions, "all_user_notifications");
}
if (count(array_intersect($required_permissions, $notifications->email)) >=1) {
if (count(array_intersect($required_permissions, $notifications->email)) >=1 || count(array_intersect($required_permissions, "all_user_notifications")) >=1 || count(array_intersect($required_permissions, "all_notifications")) >=1) {
array_push($notifiable_methods, 'mail');
}
@ -54,13 +54,30 @@ trait UserNotifies
array_push($required_permissions, "all_user_notifications");
}
if (count(array_intersect($required_permissions, $notifications->email)) >=1) {
if (count(array_intersect($required_permissions, $notifications->email)) >=1 || count(array_intersect($required_permissions, "all_user_notifications")) >=1 || count(array_intersect($required_permissions, "all_notifications")) >=1) {
array_push($notifiable_methods, 'mail');
}
return $notifiable_methods;
}
public function findCompanyUserNotificationType($company_user, $required_permissions) :array
{
if ($this->migrationRunning($company_user)) {
return [];
}
$notifiable_methods = [];
$notifications = $company_user->notifications;
if (count(array_intersect($required_permissions, $notifications->email)) >=1 || count(array_intersect($required_permissions, "all_user_notifications")) >=1 || count(array_intersect($required_permissions, "all_notifications")) >=1) {
array_push($notifiable_methods, 'mail');
}
return $notifiable_methods;
}
private function migrationRunning($company_user)
{
return $company_user->is_migrating;

View File

@ -23,10 +23,11 @@
"asgrim/ofxparser": "^1.2",
"beganovich/omnipay-checkout": "dev-master",
"cleverit/ubl_invoice": "^1.3",
"codedge/laravel-selfupdater": "~3.0",
"composer/composer": "^1.10",
"czproject/git-php": "^3.17",
"dacastro4/laravel-gmail": "^3.2",
"doctrine/dbal": "^2.10",
"fedeisas/laravel-mail-css-inliner": "2.3",
"fideloper/proxy": "^4.0",
"fzaninotto/faker": "^1.4",
"google/apiclient": "^2.0",

View File

@ -178,7 +178,6 @@ return [
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\ComposerServiceProvider::class,
Codedge\Updater\UpdaterServiceProvider::class,
App\Providers\MultiDBProvider::class,
App\Providers\ClientPortalServiceProvider::class,
],

20
config/css-inliner.php Normal file
View File

@ -0,0 +1,20 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Css Files
|--------------------------------------------------------------------------
|
| Css file of your style for your emails
| The content of these files will be added directly into the inliner
| Use absolute paths, ie. public_path('css/main.css')
|
*/
'css-files' => [
public_path('css/app.css'),
],
];

View File

@ -25,6 +25,7 @@ return [
'company_id' => 0,
'hash_salt' => env('HASH_SALT', ''),
'currency_converter_api_key' => env('OPENEXCHANGE_APP_ID',''),
'enabled_modules' => 4095,
'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller'
@ -54,7 +55,7 @@ return [
'datetime_format_id' => env('DEFAULT_DATETIME_FORMAT_ID', '1'),
'locale' => env('DEFAULT_LOCALE', 'en'),
'map_zoom' => env('DEFAULT_MAP_ZOOM', 10),
'payment_terms' => env('DEFAULT_PAYMENT_TERMS', -1),
'payment_terms' => env('DEFAULT_PAYMENT_TERMS', "-1"),
'military_time' => env('MILITARY_TIME', 0),
'first_day_of_week' => env('FIRST_DATE_OF_WEEK', 0),
'first_month_of_year' => env('FIRST_MONTH_OF_YEAR', '2000-01-01')
@ -97,41 +98,6 @@ return [
'slack' => env('SLACK_WEBHOOK_URL', ''),
'mail' => env('HOSTED_EMAIL', ''),
],
'payment_terms' => [
[
'num_days' => 0,
'name' => '',
],
[
'num_days' => 7,
'name' => '',
],
[
'num_days' => 10,
'name' => '',
],
[
'num_days' => 14,
'name' => '',
],
[
'num_days' => 15,
'name' => '',
],
[
'num_days' => 30,
'name' => '',
],
[
'num_days' => 60,
'name' => '',
],
[
'num_days' => 90,
'name' => '',
]
],
'themes' => [
'global' => 'ninja2020',
'portal' => 'ninja2020',

View File

@ -1,152 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default source repository type
|--------------------------------------------------------------------------
|
| The default source repository type you want to pull your updates from.
|
*/
'default' => env('SELF_UPDATER_SOURCE', 'github'),
/*
|--------------------------------------------------------------------------
| Version installed
|--------------------------------------------------------------------------
|
| Set this to the version of your software installed on your system.
|
*/
'version_installed' => env('SELF_UPDATER_VERSION_INSTALLED', ''),
/*
|--------------------------------------------------------------------------
| Repository types
|--------------------------------------------------------------------------
|
| A repository can be of different types, which can be specified here.
| Current options:
| - github
| - http
|
*/
'repository_types' => [
'github' => [
'type' => 'github',
'repository_vendor' => env('SELF_UPDATER_REPO_VENDOR', ''),
'repository_name' => env('SELF_UPDATER_REPO_NAME', ''),
'repository_url' => '',
'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'),
'private_access_token' => env('SELF_UPDATER_GITHUB_PRIVATE_ACCESS_TOKEN', ''),
'use_branch' => env('SELF_UPDATER_BRANCH_NAME', 'v2'),
],
'http' => [
'type' => 'http',
'repository_url' => env('SELF_UPDATER_REPO_URL', ''),
'pkg_filename_format' => env('SELF_UPDATER_PKG_FILENAME_FORMAT', 'v_VERSION_'),
'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'),
'private_access_token' => env('SELF_UPDATER_HTTP_PRIVATE_ACCESS_TOKEN', ''),
],
],
/*
|--------------------------------------------------------------------------
| Exclude folders from update
|--------------------------------------------------------------------------
|
| Specifiy folders which should not be updated and will be skipped during the
| update process.
|
| Here's already a list of good examples to skip. You may want to keep those.
|
*/
'exclude_folders' => [
'node_modules',
'bootstrap/cache',
'bower',
'storage/app',
'storage/framework',
'storage/logs',
'storage/self-update',
'vendor',
],
/*
|--------------------------------------------------------------------------
| Event Logging
|--------------------------------------------------------------------------
|
| Configure if fired events should be logged
|
*/
'log_events' => env('SELF_UPDATER_LOG_EVENTS', true),
/*
|--------------------------------------------------------------------------
| Mail To Settings
|--------------------------------------------------------------------------
|
| Configure if fired events should be logged
|
*/
'notifications' => [
'notifications' => [
\Codedge\Updater\Notifications\Notifications\UpdateSucceeded::class => ['mail'],
\Codedge\Updater\Notifications\Notifications\UpdateFailed::class => ['mail'],
\Codedge\Updater\Notifications\Notifications\UpdateAvailable::class => ['mail'],
],
/*
* Here you can specify the notifiable to which the notifications should be sent. The default
* notifiable will use the variables specified in this config file.
*/
'notifiable' => \Codedge\Updater\Notifications\Notifiable::class,
'mail' => [
'to' => [
'address' => env('SELF_UPDATER_MAILTO_ADDRESS', 'notifications@example.com'),
'name' => env('SELF_UPDATER_MAILTO_NAME', ''),
],
'from' => [
'address' => env('SELF_UPDATER_MAIL_FROM_ADDRESS', 'updater@example.com'),
'name' => env('SELF_UPDATER_MAIL_FROM_NAME', 'Update'),
],
],
],
/*
|---------------------------------------------------------------------------
| Register custom artisan commands
|---------------------------------------------------------------------------
*/
'artisan_commands' => [
'pre_update' => [
//'command:signature' => [
// 'class' => Command class
// 'params' => []
//]
],
'post_update' => [
'ninja:post-update' => [
'class' => \App\Console\Commands\PostUpdate::class,
'params' => [
'log' => 1,
'reset' => false,
// etc.
]
],
],
],
];

4
cypress.json Normal file
View File

@ -0,0 +1,4 @@
{
"video": false,
"baseUrl": "http://ninja.test:8000/"
}

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -0,0 +1,37 @@
describe('Credits', () => {
beforeEach(() => {
cy.clientLogin();
});
it('should show credits page', () => {
cy.visit('/client/credits');
cy.location().should(location => {
expect(location.pathname).to.eq('/client/credits');
});
});
it('should show credits text', () => {
cy.visit('/client/credits');
cy.get('body')
.find('h3')
.first()
.should('contain.text', 'Credits');
});
it('should have required table elements', () => {
cy.visit('/client/credits');
cy.get('body')
.find('table.credits-table > tbody > tr')
.first()
.find('a')
.first()
.should('contain.text', 'View')
.click()
.location()
.should(location => {
expect(location.pathname).to.eq('/client/credits/VolejRejNm');
});
});
});

View File

@ -0,0 +1,73 @@
context('Invoices', () => {
beforeEach(() => {
cy.clientLogin();
});
it('should show invoices page', () => {
cy.visit('/client/invoices');
cy.location().should(location => {
expect(location.pathname).to.eq('/client/invoices');
});
});
it('should show invoices text', () => {
cy.visit('/client/invoices');
cy.get('body')
.find('h3')
.first()
.should('contain.text', 'Invoices');
});
it('should show download and pay now buttons', () => {
cy.visit('/client/invoices');
cy.get('body')
.find('button[value="download"]')
.first()
.should('contain.text', 'Download');
cy.get('body')
.find('button[value="payment"]')
.first()
.should('contain.text', 'Pay Now');
});
it('should have per page options dropdown', () => {
cy.visit('/client/invoices');
cy.get('body')
.find('select')
.first()
.should('have.value', '10');
});
it('should have required table elements', () => {
cy.visit('/client/invoices');
cy.get('body')
.find('table.invoices-table > tbody > tr')
.first()
.find('.button-link')
.first()
.should('contain.text', 'View')
.click()
.location()
.should(location => {
expect(location.pathname).to.eq('/client/invoices/VolejRejNm');
});
});
it('should filter table content', () => {
cy.visit('/client/invoices');
cy.get('body')
.find('#paid-checkbox')
.check();
cy.get('body')
.find('table.invoices-table > tbody > tr')
.first()
.should('not.contain', 'Overdue');
});
});

View File

@ -0,0 +1,48 @@
context('Login', () => {
beforeEach(() => {
cy.visit('/client/login');
});
it('should type into login form elements', () => {
cy.get('#test_email')
.invoke('val')
.then(emailValue => {
cy.get('#email')
.type(emailValue)
.should('have.value', emailValue);
});
cy.get('#test_password')
.invoke('val')
.then(passwordValue => {
cy.get('#password')
.type(passwordValue)
.should('have.value', passwordValue);
});
});
it('should login into client portal', () => {
cy.get('#test_email')
.invoke('val')
.then(emailValue => {
cy.get('#test_password')
.invoke('val')
.then(passwordValue => {
cy.get('#email')
.type(emailValue)
.should('have.value', emailValue);
cy.get('#password')
.type(passwordValue)
.should('have.value', passwordValue);
cy.get('#loginBtn')
.contains('Login')
.click();
cy.location().should(location => {
expect(location.pathname).to.eq(
'/client/dashboard'
);
});
});
});
});
});

View File

@ -0,0 +1,39 @@
context('Payment methods', () => {
beforeEach(() => {
cy.clientLogin();
});
it('should show payment methods page', () => {
cy.visit('/client/payment_methods');
cy.location().should(location => {
expect(location.pathname).to.eq('/client/payment_methods');
});
});
it('should show payment methods text', () => {
cy.visit('/client/payment_methods');
cy.get('body')
.find('h3')
.first()
.should('contain.text', 'Payment Method');
});
it('should show add payment method button', () => {
cy.visit('/client/payment_methods');
cy.get('body')
.find('a.button.button-primary')
.first()
.should('contain.text', 'Add Payment Method');
});
it('should have per page options dropdown', () => {
cy.visit('/client/payment_methods');
cy.get('body')
.find('select')
.first()
.should('have.value', '10');
});
});

View File

@ -0,0 +1,46 @@
context('Payments', () => {
beforeEach(() => {
cy.clientLogin();
});
it('should show payments page', () => {
cy.visit('/client/payments');
cy.location().should(location => {
expect(location.pathname).to.eq('/client/payments');
});
});
it('should show payments text', () => {
cy.visit('/client/payments');
cy.get('body')
.find('h3')
.first()
.should('contain.text', 'Payments');
});
it('should have per page options dropdown', () => {
cy.visit('/client/payments');
cy.get('body')
.find('select')
.first()
.should('have.value', '10');
});
it('should have required table elements', () => {
cy.visit('/client/payments');
cy.get('body')
.find('table.payments-table > tbody > tr')
.first()
.find('a')
.first()
.should('contain.text', 'View')
.click()
.location()
.should(location => {
expect(location.pathname).to.eq('/client/payments/VolejRejNm');
});
});
})

View File

@ -0,0 +1,73 @@
describe('Quotes', () => {
beforeEach(() => {
cy.clientLogin();
});
it('should show quotes page', () => {
cy.visit('/client/quotes');
cy.location().should(location => {
expect(location.pathname).to.eq('/client/quotes');
});
});
it('should show quotes text', () => {
cy.visit('/client/quotes');
cy.get('body')
.find('h3')
.first()
.should('contain.text', 'Quotes');
});
it('should show download and approve buttons', () => {
cy.visit('/client/quotes');
cy.get('body')
.find('button[value="download"]')
.first()
.should('contain.text', 'Download');
cy.get('body')
.find('button[value="approve"]')
.first()
.should('contain.text', 'Approve');
});
it('should have per page options dropdown', () => {
cy.visit('/client/quotes');
cy.get('body')
.find('select')
.first()
.should('have.value', '10');
});
it('should have required table elements', () => {
cy.visit('/client/quotes');
cy.get('body')
.find('table.quotes-table > tbody > tr')
.first()
.find('.button-link')
.first()
.should('contain.text', 'View')
.click()
.location()
.should(location => {
expect(location.pathname).to.eq('/client/quotes/VolejRejNm');
});
});
it('should filter table content', () => {
cy.visit('/client/quotes');
cy.get('body')
.find('#draft-checkbox')
.check();
cy.get('body')
.find('table.quotes-table > tbody > tr')
.first()
.should('not.contain', 'Sent');
});
});

View File

@ -0,0 +1,48 @@
context('Recurring invoices', () => {
beforeEach(() => {
cy.clientLogin();
});
// test url
it('should show recurring invoices page', () => {
cy.visit('/client/recurring_invoices');
cy.location().should(location => {
expect(location.pathname).to.eq('/client/recurring_invoices');
});
});
it('should show reucrring invoices text', () => {
cy.visit('/client/recurring_invoices');
cy.get('h3')
.first()
.should('contain.text', 'Recurring Invoices');
});
it('should have per page options dropdown', () => {
cy.visit('/client/recurring_invoices');
cy.get('body')
.find('select')
.first()
.should('have.value', '10');
});
it('should have required table elements', () => {
cy.visit('/client/recurring_invoices');
cy.get('body')
.find('table.recurring-invoices-table > tbody > tr')
.first()
.find('a')
.first()
.should('contain.text', 'View')
.click()
.location()
.should(location => {
expect(location.pathname).to.eq('/client/recurring_invoices/VolejRejNm');
});
});
});

Some files were not shown because too many files have changed in this diff Show More